debt-ops
June 3, 2026 · View on GitHub
Brings debt-ops' write-time feedback loop to GitHub Copilot CLI via Copilot's
hooks. This is the differentiator the portable skills can't carry: a
skill is model-invoked, but Copilot's postToolUse hook fires on every edit, so
quality checks run deterministically — same posture as the Claude Code and Codex
adapters.
Status: prototype (packaging plan Phase C). The hook scripts are validated standalone against Copilot's documented JSON contract, and the adapter is now packaged as an installable Copilot plugin (
plugin.json+ marketplace). The remaining validation step is confirming${PLUGIN_ROOT}expansion and marketplace discovery on a realcopilot plugin install.
What ports, and what doesn't
Copilot's hook contract differs from Claude Code's in three ways that shape this adapter:
| Capability | Claude Code | Copilot | Here |
|---|---|---|---|
| Write-time quality checks | PostToolUse (matched) | postToolUse (no matcher) | ✅ ported — feedback.py self-filters to edit tools by toolName/toolArgs. ⚠️ delivery caveat below |
| Disciplines at session start | SessionStart inject | command hooks can't inject | ➡️ moved to the charter (debt-ops-init); the sessionStart command hook only warms path caches + logs a metric |
| Stop-time safety net | Stop decision:block | agentStop decision:block + reason | ✅ ported — stop.py on agentStop; same two-stage marker-vs-registry block + per-session cap as Claude/Codex |
drop A intercept + confirm | UserPromptSubmit block | userPromptSubmitted output not processed | ⚠️ degraded — use the debt-ops-add skill's manual drop (read current-turn.txt, rm the entry) |
So on Copilot you get the full write-time loop, the Stop-time safety net, and capture/review/metrics via the portable skills; the disciplines come from the charter instead of a per-session inject. Drops are manual.
⚠️ Write-time feedback delivery — upstream bug. copilot-cli#2980 (open): a
postToolUsehook'sadditionalContextis captured but not injected into the agent. Sofeedback.pyprefersmodifiedResult(which is forwarded) — it appends the pass/fail summary to the tool's own result, falling back toadditionalContextwhen it can't do so safely. Until that fix lands, on edits where the fallback is taken the agent may not see the feedback, even though the hook ran (the debug log
- metrics still record it). The
agentStopsafety net is unaffected —decision/reasonis a separate, working channel.
Install
Needs a git repo and Python 3.10+.
Option A — plugin install (recommended)
This adapter is a packaged Copilot CLI plugin (plugin.json bundling the hooks and
the four debt-ops-* skills). Install it from the repo's marketplace:
copilot plugin marketplace add bcanfield/agentic-tech-debt
copilot plugin install debt-ops@agentic-tech-debt
The marketplace lives at .github/plugin/marketplace.json and points at ./copilot.
We go through a marketplace rather than a bare copilot plugin install owner/repo
because the bare form can't discover a plugin in a subdirectory yet
(copilot-cli#2390); marketplace
installs flatten the source dir so plugin.json is found. The plugin's hooks resolve
their scripts via ${PLUGIN_ROOT} (ADR 0015).
Then run the charter step below (still required — it's what gives the loop commands).
Option B — manual copy (no plugin)
-
Hooks — copy this adapter's hook files into your repo's
.github/hooks/:mkdir -p .github/hooks cp copilot/hooks/debt-ops.json copilot/hooks/feedback.py copilot/hooks/session-start.py copilot/hooks/stop.py .github/hooks/Copilot discovers
.github/hooks/*.json; the.pyfiles sit alongside and are referenced by the config (run with the repo root as cwd). User-level install works too — drop the same files under~/.copilot/hooks/and adjust the paths indebt-ops.json. (hooks/debt-ops.jsonis the manual-copy config;hooks/hooks.jsonis the${PLUGIN_ROOT}variant Option A uses.) -
Skills — install the portable skills into Copilot (
.github/skills/in the repo, or~/.copilot/skills/personal).
Charter (both options)
Run debt-ops-init once. It writes the disciplines and a
<!-- debt-ops:feedback v1 --> quality-commands block into
.github/copilot-instructions.md (or AGENTS.md). feedback.py reads that block to
know what to run on each edit — without it, the write-time loop has no commands to
run.
How it works
sessionStart→session-start.py— probes + caches the repo's ADR and registry directories and logs onesessionmetric. Silent; injects nothing (Copilot command hooks can't).postToolUse→feedback.py— fires after every tool. Idles unless the tool was a file edit (matched viatoolName/toolArgs). On an edit: reads the charter's quality-commands block, runs each command in parallel under a 3 s budget, warns if the edit dropped the repo's test-file count, and returns the pass/fail summary (viamodifiedResult; see the delivery caveat above).agentStop→stop.py— fires at end of turn. Counts new TODO/FIXME/HACK/XXX markers vs new registry entries; if markers outpace registrations it returnsdecision: "block"with a one-line nudge, capped once per session so it can't loop.
Cache and metrics live under ~/.cache/debt-ops/ (override with DEBT_OPS_CACHE) —
the same base the skills and other adapters use, so debt-ops-review and
debt-ops-metrics see this adapter's data.
Note on script duplication
feedback.py and session-start.py reuse the Claude adapter's logic almost
verbatim, on purpose — the core functions are kept identical so the planned
vendored _common.py (ADR 0011 / packaging plan Phase E) can replace all copies in
one lift. Don't "clean up" the duplication here; it's tracked debt with a scheduled
payoff.