Skip to content

Report Tech Debt Workflow Design

Workflow and domain design for the tech-debt loop.

Layer Document
Platform Multi-Branch Loops Design
Caller shell Loop Caller Workflows Design
Invariants Loop Engineering Design

Artifacts: on-loop-tech-debt.yaml · skill tech-debt · tech-debt/scripts/detect_tech_debt.sh

Shared caller keys: Loop Caller Inputs Reference.

Purpose

Run a full-repository mechanical technical-debt scan, classify findings via the skill taxonomy, and open an L2 merge-gated PR that writes a dated report under docs/report/tech-debt/.

Supported use cases

  • Weekly cron scan of main for mechanical debt signals (shell, Go, Terraform, workflow, dependency sensors)
  • Classify signals[] and hotspots[] into prioritized findings with evidence
  • Write docs/report/tech-debt/YYYY-MM-DD.md via L2 review PR
  • Compare against previous_report for resolved, recurring, and regression items

Out of scope

  • Code fixes, refactors, or dependency upgrades (report-only loop)
  • GitHub Issue creation at any level (log + state + report PR only)
  • L1 log-only observation phase (skipped — L2 from start)
  • CVE database lookups or duplicate lint enforcement already covered by CI
  • Hook-triggered or user-invoked debt scans
  • Loop state and detect script management

Report loop family

Report loops emit structured artifacts under docs/report/<domain>/ via merge-gated PRs (dogfood: loop_name: tech-debt, skill tech-debt).

Action loops (docs-updater, ci-sweeper, refactor) modify application or documentation source to fix drift or failures. Report loops classify mechanical signals and publish reports only — they do not edit source outside the report allowlist.

Loop Skill Role Trigger
tech-debt tech-debt Cron loop: detect signals + skill classify/report on-loop-tech-debt.yaml
docs-updater docs-updater Action loop: doc drift detect + fix PR on-loop-docs-updater.yaml
ci-sweeper ci-sweeper Action loop: CI failure detect + fix PR on-loop-ci-sweeper.yaml
refactor refactor Action loop: H1 structural refactor fix PR on-loop-refactor.yaml

Detect script path: tech-debt/scripts/detect_tech_debt.sh (under common package).

Skill execution boundaries: tech-debt SKILL.md (USE FOR / DO NOT USE FOR).

Modes

Mode Default Behavior
integration on Detect on watch branch → report PR to same branch
pull_request off not supported for this loop

Caller inputs

Keys are passed in on-loop-tech-debt.yaml via with: on ci-loop-caller.yaml (alphabetically ordered). Multiline values (agent_checker_instructions, agent_maker_instructions) are defined inline in the caller workflow.

Shared semantics: Loop Caller Inputs Reference. Platform branch/finalize caps: canonical table.

Schedule: 0 8 * * 1 (Monday 08:00 UTC, weekly).

Input / JSON key Description Dogfood value
agent_maker_max_turns Max maker agent turns per loop attempt (one Agent→Verify cycle). 100
agent_maker_model Maker model ID. Cursor: agent --list-models. claude-sonnet-5
agent_maker_effort Maker reasoning effort. Only engines with an effort flag (claude) consume it medium
agent_loop_max_attempts Max Agent→Verify retry cycles before finalize records failure. 3
agent_checker_instructions Checker APPROVE/REJECT rubric. Allowlisted paths only; closed-set fixes (broken_doc_ref, stale_doc, pin_drift) on docs/manifests; no invented paths; cap Critical+High at 25. Inline in caller workflow
agent_checker_max_turns Max checker agent turns per verification. 100
agent_checker_model Checker model ID. Cursor: agent --list-models. claude-opus-5
agent_checker_effort Checker reasoning effort. Only engines with an effort flag (claude) consume it medium
allowlist Comma-separated globs the maker may modify. Report path plus optional secondary closed-set fix paths (docs/**/*.md, package.json, go.mod). docs/report/tech-debt/**/*.md,docs/**/*.md,package.json,go.mod
branch_match Comma-separated integration branch patterns to watch. main
branch_state Branch for .loop/* persistence, state migration, and watch fallback. main
budget_max_runs_per_day Daily run cap keyed by loop_name. Caller input; .loop/loop-budget.json overrides when present. 2
budget_max_tokens_per_day Daily aggregated token cap across loops. 1000000
denylist Comma-separated globs the maker must not touch. **/.env,**/credentials*,**/secrets*,src/**,.github/**
detect_script Domain detect script path. .agents/skills/tech-debt/scripts/detect_tech_debt.sh
engine AI engine (claude, copilot, codex, cursor). Maps AGENT_TOKEN to engine env. claude
environment GitHub Environment for env-scoped secrets inside the reusable workflow. Leave default when repository secrets suffice. default
delivery Platform delivery after APPROVE (open_pr for dogfood). open_pr
infer_files_pattern Extended regex to infer file paths from checker text. See caller workflow
level Autonomy level (L1, L2, L3). L2 from start — no L1 phase. L2
loop_name Loop identifier; state file .loop/state-tech-debt.json. tech-debt
max_targets_per_schedule Max targets per cron tick after priority filters. 1
may_edit Agent worktree edit gate (true for dogfood). true
no_changes_verdict APPROVE or REJECT when maker produces no file diff. REJECT when signals present but no report file written. REJECT
pr_body Optional static prefix (dogfood: ""). loop-finalize composes agent Overview/Summary + mechanical sections. See Loop PR Body Readable Design. ""
pr_enabled Enumerate open PR heads. tech-debt uses integration branches only. false
pr_title PR title when finalize strategy is open_pr. docs(tech-debt): technical debt report (loop-tech-debt)
agent_maker_instructions Domain instructions: classify signals; write dated report; compare previous report. Inline in caller workflow
agent_maker_skill_name Skill package to invoke. tech-debt
write_target Agent artifact when may_edit is true (report for this loop). report

Domain detect environment (detect_domain_env_json)

Dogfood passes {} — defaults match the layout below. Override paths when changing report location (align allowlist / infer_files_pattern).

detect_domain_env_json: >-
  {"TECH_DEBT_DIR":"docs/report/tech-debt"}
JSON key / env var Description Dogfood value
TECH_DEBT_DIR Report output directory docs/report/tech-debt
TECH_DEBT_LEGACY_SEARCH_DIRS Comma-separated prior-report search roots docs/report/tech-debt
TECH_DEBT_DATE_FORMAT UTC strftime for report basename %Y-%m-%d
TECH_DEBT_FILE_EXTENSION Report filename extension (include dot) .md
TECH_DEBT_PREVIOUS_GLOB Glob for prior reports under search dirs ????-??-??.md
REPO_PATHS_EXTRA_PRUNES Optional extra detect prune roots (default: report parent) unset

Detect

Integration mode only

Per watch branch, loop-detect checks out the branch and invokes detect_tech_debt.sh with targets["integration:<branch>"].last_sha.

Detect script outputs mechanical signals (not semantic findings):

Field Role
signals[] Mechanical debt indicators from repo-wide sensors
hotspots[] Aggregated high-density paths or categories
warnings[] Non-blocking sensor or scope warnings
skip true when no debt signals warrant a run
report_file Target path: <TECH_DEBT_DIR>/<UTC-date>.md (defaults under docs/report/tech-debt/)
previous_report Latest dated report under TECH_DEBT_DIR plus optional TECH_DEBT_LEGACY_SEARCH_DIRS; empty string if none

Default scope: full repository (sensors read source for evidence; docs/report/** excluded from sensors per skill references).

Skill (tech-debt) classifies signals into prioritized findings and writes report_file at L2.

loop-detect emits per-branch target_json:

  • from.ref = HEAD on watch branch
  • to.branch = watch branch
  • finalize = open_pr

Stable filters (detect only)

  • Circuit breaker on targets[key].consecutive_failures
  • Budget (platform)

No infra/env classification — not applicable.

State fields (per target key)

Field Role
last_sha Scan cursor; advances when report PR merges (on-loop-state-promote)
pending Written at finalize on open_pr; promoted to last_sha on merge
outcome pr-created, rejected, no-op, …
consecutive_failures Circuit breaker

No workflow_run_id / ci ledger.

Execute

  • Worktree from target.from on integration branch
  • Checker diff baseline: to.branch
  • verifier_context: detect signal summary (counts, hotspots, warnings). Platform always wires; may be brief

Finalize

PR body is composed by loop-finalize from agent ## Overview / ## Summary (skill-owned) plus mechanical sections. Dogfood sets pr_body: "". See Loop PR Body Skill Contract.

Always open_pr to to.branch at L2. L3 push rarely appropriate for report loops; if enabled, requires explicit promotion review.

No domain_persistence_script.

Merge-gated cursor: Same platform rule as all L2 open_pr loops — pending at finalize, last_sha on report PR merge via on-loop-state-promote.yaml. See State delivery philosophy.

Skill

  • Read signals[], hotspots[], warnings[], report_file, previous_report from detect JSON
  • Classify per skill category-debt-taxonomy.md and common-checklist.md
  • At L2, write full report to report_file within allowlist
  • Emit session summary always (per common-output-format.md)
  • Cap Critical + High findings at 25 per report
  • Do not create GitHub Issues

Checker rubric outline

Inline in caller agent_checker_instructions (must match on-loop-tech-debt.yaml):

  • APPROVE when all of the following hold:
    1. Changes stay under the allowlist (docs/report/tech-debt/**/*.md, docs/**/*.md, package.json, go.mod)
    2. Report content matches the provided detect signals and hotspots
    3. File paths cited in the report exist in the repository
    4. Critical + High findings count is at most 25
    5. No denylist paths were modified
    6. Closed-set fixes (broken_doc_ref, stale_doc, simple pin_drift) only on allowlisted documentation or manifest paths
  • REJECT when any of the following hold: no report file written despite non-empty signals; invented or non-existent paths in the report; changes outside the allowlist or denylist modifications; semantic claims unsupported by cited evidence; structural or security debt edited instead of reported

State delivery

See State delivery philosophy for platform rules.

Target (dogfood): merge-gated pending + on-loop-state-promote — same as changelog and docs-updater.

Persistence: state-tech-debt.json on branch_state via finalize inside ci-loop-agent.

refactor is an action loop that applies O1/O2 structural refactors via fix PRs — not a report-only loop. It may consume report findings as input context but belongs alongside docs-updater and ci-sweeper, not tech-debt. See Refactor Workflow Design.

Implementation Checklist

Shared platform: Multi-Branch — Shared platform checklist.

Loop-specific

  • [x] tech-debt/scripts/detect_tech_debt.sh (facts output)
  • [x] on-loop-tech-debt.yaml dogfood caller via ci-loop-caller
  • [x] verifier_context on execute path (build_verifier_context_from_result .signals branch)
  • [x] Skill consolidated into common: tech-debt (was loop-tech-debt / loop-report-tech-debt)
  • [x] .loop/loop-budget.json entry for tech-debt

References