T-1KPE-add-backlog-capture
Status: closed/done · Impact: medium · Complexity: large
Implements standard S0004 and the ADR at
docs/planning/decisions/sdlc-cli/. This task does two things at once: stands up the unifiedsdlc <noun> <verb>CLI, and shipsbacklogas its first user-facing capability. It is deliberately large; the Approach is phased and may be split into a foundation task + a backlog task if preferred.
Stand up the single sdlc <noun> <verb> CLI this repo is adopting (S0004) and
ship the first capability on it — backlog capture. Today, capturing a stray
idea means the full /sdlc:task-new interview or hand-rolling a backlog file
plus a branch and PR; and the only sdlc-style CLI is the standalone
sdlc_lease.py. After this, sdlc backlog capture "<freeform>" (or the
/sdlc:backlog-capture skill) turns freeform text into a backlog document and
lands it on a rolling capture PR, and the lease CLI is reachable as a hidden
sdlc lease … noun under one dispatcher.
| Location | Role today |
|---|---|
plugin/scripts/sdlc_lease.py | The only sdlc-style CLI entry point: a standalone sdlc lease … dispatcher. There is no unified sdlc dispatcher (the plugin/cli/ this task creates does not exist yet). |
plugin/scripts/lease_cli/ | Per-subcommand lease handlers behind sdlc_lease.py (_common.py, task_*, op_claim, heartbeat, …). |
plugin/skills/task-new/ | Full interactive task intake; no lightweight backlog equivalent. |
plugin/skills/backlog-triage/ | Triages existing backlog files; assumes they exist. |
docs/planning/backlog/ | Freeform <slug>.md backlog docs (no date prefix); created by hand. |
plugin/entities/backlog/schema.json | Backlog schema — type, schema_version, status, tags, last_reviewed, result, all optional (required: []). |
Proposed
Section titled “Proposed”Per the ADR (docs/planning/decisions/sdlc-cli/), deliver:
- A unified
sdlc <noun> <verb>CLI underplugin/cli/— one dispatcher, one--help, a shared_common.py(exit codes, exception→exit mapping, formatters), and--advanced/-adto reveal hidden nouns. - Lease folded in as a hidden
leasenoun — migrated fromplugin/scripts/lease_cli/; same subcommands/exit-codes/markers; authority resolution + namespace-conflict guard scoped to this noun (not global). - The
backlognoun.sdlc backlog create= deterministic tail (structured args, no LLM): writedocs/planning/backlog/<slug>.md, validate, manage the rollingbacklog-capturebranch (open PR by head → append; else create/reset offmain), commit/push, PR create-or-update, printPR: <url>.sdlc backlog capture= LLM head and superset ofcreate: freeform text + the same structured flags, fills gaps viaclaude -p, explicit flags win,--no-llmskips the LLM, then callscreate. Only placeclaude -pruns. - The
/sdlc:backlog-captureskill — the ambient-Claude head: interprets freeform text in-session (inferring tags) and callssdlc backlog createdirectly; nevercapture, neverclaude -p(no nested Claude).
Approach
Section titled “Approach”Phased; each phase is independently committable (and could be split out as its own task).
- Dispatcher foundation. Create
plugin/cli/with thesdlcentry point (noun/verb argparse tree mirroring today’ssdlc_lease.pystructure), a shared_common.pylifted fromlease_cli/_common, and--advanced/-adsupport that hides nouns registered withhelp=argparse.SUPPRESSfromsdlc --help. Addplugin/cli/README.md. - Lease migration.
git mv plugin/scripts/lease_cli → plugin/cli/lease_cli(history follows); mount it as a hiddenleasenoun; movelease_authorityresolution and the namespace-conflict guard into the lease noun’s dispatch so other nouns don’t require lease config. Reduceplugin/scripts/sdlc_lease.pyto a thin back-compat shim that forwards its argv tosdlc lease …for a transition window (rather than a hard-cut delete), so existing call sites keep working; grep-update skill/script references to point at the newsdlc lease …surface where practical. - Backlog tail.
plugin/cli/backlog_cli/create.py— structured args, file write + schema validation, rolling-branch management, commit/push, PR create-or-update,PR: <url>marker,--dry-run, no Claude reference. Addbacklog_cli/tests/run_evals.py(mock thegh/gitboundary; lean on--dry-run); wire intosdlc.yamlquality_checks. - Backlog head.
plugin/cli/backlog_cli/capture.py— freeform positional text plus thecreateflags; derive missing fields viaclaude -p(headless,--output-format json); explicit flags win;--no-llm/ a complete structured invocation skips the LLM; then callcreate. Define the JSON contract and the failure mode whenclaudeis absent/unparseable. - Skill head.
plugin/skills/backlog-capture/SKILL.md— ambient Claude interprets freeform → structured args and shells tosdlc backlog create(nevercapture, neverclaude -p). Add the companiondocs/skills/backlog-capture.md(Mermaid flowchart); confirmcheck_skill_docs.pypasses.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/cli/sdlc | new | Unified dispatcher entry point (uv-inline shebang); noun/verb tree; --advanced/-ad. |
plugin/cli/README.md | new | CLI convention: how to add a noun, visible vs hidden, markers/exit codes. |
plugin/cli/_common.py | new | Shared exit codes, exception→exit mapping, formatters (lifted from lease_cli/_common). |
plugin/cli/lease_cli/ | new | Lease CLI migrated here from plugin/scripts/lease_cli/ via git mv (history follows), mounted as hidden lease noun. |
plugin/scripts/lease_cli/ | delete | Moved under plugin/cli/ (the git mv source side). |
plugin/scripts/sdlc_lease.py | modify | Reduced to a thin back-compat shim that forwards to sdlc lease … for a transition window; grep-update skill/script references to the old path. |
plugin/cli/backlog_cli/create.py | new | Deterministic tail. |
plugin/cli/backlog_cli/capture.py | new | LLM head (claude -p → create). |
plugin/cli/backlog_cli/tests/run_evals.py | new | Deterministic tests (mocked gh/git, --dry-run). |
plugin/skills/backlog-capture/SKILL.md | new | Skill head → sdlc backlog create. |
docs/skills/backlog-capture.md | new | Companion per-skill doc (Mermaid). |
plugin/conventions/branch-naming.md | modify | Document the rolling backlog-capture branch. |
sdlc.yaml | modify | Add backlog_cli tests to quality_checks; fix any lease-CLI path references. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc --helplistsbacklogbut notlease;sdlc --help --advanced(or-ad) also listslease;sdlc lease --helpworks regardless of the flag. - AC-2: Existing lease behaviour is preserved via
sdlc lease …(e.g.sdlc lease list) with the same exit codes/markers;sdlc backlog …runs withoutlease_authorityconfigured. - AC-3:
sdlc backlog create --headline "X" --body "…" [--tag t](no Claude) createsdocs/planning/backlog/<slug>.md(validates against the backlog schema), manages the branch, opens-or-updates the PR, printsPR: <url>; thecreatepath contains no LLM reference. - AC-4:
sdlc backlog capture "<freeform>"fills missing fields viaclaude -pthen callscreate; explicit flags override LLM-derived values;--no-llmor a complete structured invocation skips the LLM; it is the only path invokingclaude -p. - AC-5:
/sdlc:backlog-captureinterprets in-session and callssdlc backlog createdirectly — nevercapture, neverclaude -p; one invocation, no further prompts. - AC-6: Rolling branch — no open capture PR → create/reset
backlog-captureoffmain+ open PR; a second run while open appends (no second PR); after merge the next run starts a fresh branch offmain(resetting a stalebacklog-captureif not auto-deleted). - AC-7:
--dry-runperforms no git/gh side effects;backlog_clitests pass;docs/skills/backlog-capture.mdpassescheck_skill_docs.py.
Out of scope
Section titled “Out of scope”- Triage, promotion, or dedup of captured items — stays with
/sdlc:backlog-triage. - Auto-merging or auto-closing the capture PR.
- Rich frontmatter beyond an optional
--tag(related,impact,result) at capture time — enrich during triage. - PATH-on-enter ergonomics so
sdlcresolves by name inside a project — separate task. - Folding other existing helpers (entity scaffolds, etc.) into
sdlcnouns — future.
Dependencies
Section titled “Dependencies”- Implements the design in the ADR
docs/planning/decisions/sdlc-cli/and standard S0004 (PR #146); those should merge first. Soft dependency — this task realises them, so they are not blocking in thedepends_onsense.
Discovery context
Section titled “Discovery context”- Grew out of a 2026-05-27 design discussion while building
/sdlc:status-dashboard: a desire for quick capture “in a clean way” that converged on a unifiedsdlc <noun> <verb>CLI with an LLM-head/deterministic-tail split (S0004).
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-27. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto — migrated lease test suite (62 passing) plus agent-manual checks of
sdlc --help,sdlc --help -ad,sdlc lease --help(lease hidden in plain help, shown under-ad, noun-help works regardless). - AC-2: auto — 62 lease-CLI tests pass via the
sdlc lease …surface; agent-manual parity check of theplugin/scripts/sdlc_lease.pyshim vssdlc lease list(identical output/exit code);sdlc backlog …runs with nolease_authorityconfigured. - AC-3: auto —
backlog_clieval suite (file written + schema-valid viavalidate_frontmatter.py, rolling-branch + PR flow over a mocked git/gh seam,PR: <url>marker) plus a grep guard asserting thecreatetail has no model reference. - AC-4: auto — capture eval suite (LLM fills missing fields over a mocked model seam, explicit flags
override,
--no-llm/complete-structured skips the model,claude-absent and unparseable-JSON failure modes, capture-only-invokes-claude invariant). - AC-5: deferred-user — the skill head is ambient-Claude-instructed;
check_skill_docs.pypasses (OK skills/backlog-capture) and the SKILL.md guardrails forbidcapture/claude -p, but the in-session interpretation behaviour should be spot-checked by actually running/sdlc:backlog-capture "<freeform>"once. - AC-6: auto — three rolling-branch eval cases (no open PR → create+open; open PR exists → append,
no 2nd PR; merged/stale → reset off
main). - AC-7: auto —
--dry-runno-side-effect eval cases; fullbacklog_clisuite (30 cases) green;docs/skills/backlog-capture.mdpassescheck_skill_docs.py.
What worked
Section titled “What worked”- The head/deterministic-tail split (S0004) made the work cleanly wave-able:
create(offline, mock-the-git/gh-boundary) was fully unit-testable, and bothcapture(CLI head) and the skill (ambient head) reduced to thin wrappers over it. - The existing
sdlc_lease.py+lease_cli/structure was a precise template for the new dispatcher and thebacklognoun, so the migration and the new noun mirrored a known-good shape. git mvpreserved lease-CLI history; the 62 lease tests + 175 lease-lib tests gave a tight regression signal that the move and authority-scoping didn’t break behaviour.
Friction and automation gaps
Section titled “Friction and automation gaps”- The merged task passed authoring/merge but FAILED the v3 implementation-readiness gate at pickup
(invalid Files-to-touch kinds
new (git mv)/modify/delete, and a## Todayrow citing the not-yet-existentplugin/cli/) — task-new/task-define should run the sameparse_touchpoints.pyresolution + kind validation at authoring time so these are caught before merge, not mid-task-work. → T-24ZN-task-authoring-validates-touchpoint-kinds start_task.py’s rebase conflicted on the frontmatter even though its stamp-lift handlesreadiness_verified_at:— because the start-commit also addslast_reviewed:adjacent to it and the verify-stamp commit (made beforelast_reviewedexisted) collides; the stamp-lift should also alignlast_reviewed:(or the verify-stamp commit should carry it) so the rebase is conflict-free. → T-2QXZ-start-task-handles-frontmatter-rebase-cleanly- The Step 3a quality baseline was captured in the main checkout (which carried 3 untracked backlog
files and had drifted ahead of the worktree’s committed corpus), so the Step 7 baseline-diff
false-positived on
audit_entitiescorpus-count/summary lines — baseline capture should run in an environment matching the gate (worktree, committed-only), and/or the gate should ignore audit summary/corpus-count lines. → T-BCNP-quality-gate-ignores-summary-and-corpus-lines - The task was authored at
schema_version: '3'and missed the v4 bulk-stamp, forcing a mid-task bump to clear anaudit_entitiesdrift — the v4 migration should sweep tasks created/merged concurrently with the bump. → T-YGJ1-schema-migration-sweeps-concurrent-tasks git commit -mwith()in the message body silently broke under the worktree’s fish shell (command substitution) — thegit commit -F <tempfile>convention works, but the failure mode is a confusing git-usage dump; a commit helper that always routes through a tempfile would remove the foot-gun. → T-SPBO-commit-helper-routes-through-tempfile
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-24ZN-task-authoring-validates-touchpoint-kinds — run the gate’s
parse_touchpoints.pykind/resolution check at task-new/task-define authoring time (created) - T-2QXZ-start-task-handles-frontmatter-rebase-cleanly — auto-resolve start_task.py’s
frontmatter rebase conflict (incl. the
last_reviewed/verify-stamp adjacency) (linked) - T-BCNP-quality-gate-ignores-summary-and-corpus-lines — make the baseline diff ignore audit summary/corpus-count lines (and fold in the committed-only baseline-environment angle) (linked)
- T-YGJ1-schema-migration-sweeps-concurrent-tasks — re-sweep tasks merged concurrently with a schema bump so stragglers don’t drift (created)
- T-SPBO-commit-helper-routes-through-tempfile — ship an executable commit helper that always routes the message through a tempfile (created)