Resolve Tech-Debt Ledger Entries
Operator-facing surface for the knowledge-base/project/learnings/technical-debt/ ledger. The ledger is populated reactively by /soleur:compound; this skill is the read + close half of the loop.
Complement: [/soleur:harvest-debt](../harvest-debt/SKILL.md) surfaces inline SOLEUR-DEBT: markers in source code (the deferrals that have not yet been promoted into this ledger). harvest surfaces; compound promotes; this skill closes.
Four modes:
--list— print a deterministic markdown table of open entries (severity desc, then date asc). Stdout only, no prompts. Pair with--jsonfor agent //loopcomposition.--close N --status S [--linked-issue N]— non-interactive close. Bypasses prompts. Reuses the same validation surface as interactive mode (status enum, linked-issue range, fixture-path refusal,gh issue view, atomic mutation, body MD5 check). IndexNfollows the--listordering.- interactive (default) — list, prompt for entry selection, prompt for new status +
linked_issue, mutate the entry's frontmatter atomically, print the diff. Does NOT auto-commit; the operator commits on whatever branch they are on. --no-verify— modifier on interactive /--close: skip thegh issue view <N>round-trip. Use offline or whenghis unavailable.
The skill never calls git commit or git push. Recovery from a wrong close is git checkout -- <file> (pre-commit) or git revert (post-commit).
Commands
list
python3 plugins/soleur/skills/resolve-debt/scripts/resolve-debt.py --list
Walks knowledge-base/project/learnings/technical-debt/ (skips archive/). For each .md file, parses frontmatter via the parse_frontmatter helper from the repo-root scripts/ directory (file: frontmatter_lib.py). Files with parse failures emit a stderr warning naming the file and are skipped — --list does not crash on malformed frontmatter.
Filters to status == open. Sorts by severity desc (high > medium > low > unset), then date asc (oldest first).
Output columns: idx | file | date | severity | component-or-category | title.
Empty state: No open debt entries. exit 0.
close (interactive default)
python3 plugins/soleur/skills/resolve-debt/scripts/resolve-debt.py
- Display the same table as
--list. - Prompt:
Select entry (1..N) or q to quit:. Out-of-range or non-numeric → re-prompt up to 3 attempts, then exit 2. - Prompt:
Status (resolved | wont-fix):. Enum reject → re-prompt. - If
resolved: promptlinked_issue (integer, e.g., 2723):. Validated byint()parse (rejects strings, floats, shell metachars by construction) + range-check1 <= n <= 9_999_999. The bounds reject shell-metachar injection; the digit-count cap is incidental, not load-bearing. - Unless
--no-verify:gh issue view <N> --json state,titlewith 5-second timeout. Non-zero exit printsgh issue view failed (<reason>). Re-invoke with --no-verify to skip validation.to stderr and exits 1. There is no closed-state warning branch — if the operator typed the number, they meant it. - Mutate atomically: serialize the new frontmatter to a tempfile in the same directory, then
os.replace. SIGINT beforeos.replaceleaves the original file untouched. - Print
git diff -- <file>to stdout. PrintDiff above. Review and commit when ready. To undo: git checkout -- <file>. No auto-commit by design.to stderr. Exit 0.
help
python3 plugins/soleur/skills/resolve-debt/scripts/resolve-debt.py --help
Prints a usage block enumerating the three modes; exit 0.
Frontmatter Contract
status: required, enumopen | resolved | wont-fix. Defaultopenfor new entries (set by/soleur:compound'sresolution-template.md).linked_issue: required whenstatus: resolved; optional whenstatus: wont-fix; forbidden whenstatus: open. Stored as a YAML integer, no#prefix.
Why status instead of "absence-of-linked_issue": wont-fix is the load-bearing discriminator of record. Without status, there is no way to express "we know about this debt and have decided not to fix it." Future schema simplification must preserve status for this reason.
See [knowledge-base/project/learnings/technical-debt/README.md](../../../../knowledge-base/project/learnings/technical-debt/README.md) for the full contract.
Sharp Edges
- Frontmatter parsing goes through the shared
parse_frontmatterhelper in the repo-rootscripts/directory (file:frontmatter_lib.py), imported viasys.path.insert. Thebackfill-frontmatter.pyone-shot migration in the same directory imports the same module — single source of truth. Do not re-implement YAML mutation in shell —sedrange patterns match all---blocks and ledger bodies may contain horizontal-rule---. - No auto-commit by design. The skill prints the diff and stops. Ledger commits land on whatever branch the operator is on; auto-commit would risk wrong-branch writes.
- Archive is frozen. The walker skips
knowledge-base/project/learnings/technical-debt/archive/. Closed-out historical entries live there and are not re-surfaced. gh issue viewfailure is a hard stop. No silent fallback to skip validation. The error message names the failure mode and points at--no-verifyfor the operator to opt in explicitly.- Two schema shapes coexist. Legacy entries use
module / problem_type / component / tags / severity; current entries usetitle / category / tags / severity. The skill preserves whichever shape it finds. Schema unification is a separate follow-up.
Non-Goals
--undo-closeflag — recovery isgit checkout -- <file>(pre-commit) orgit revert(post-commit).- Bulk-resolve.
- Severity filter (
--severity high). - Scheduled scanner / re-surfacing of stale open entries — deferred to issue #3650 (Spec B).