Skip to content

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 unified sdlc <noun> <verb> CLI, and ships backlog as 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.

LocationRole today
plugin/scripts/sdlc_lease.pyThe 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.jsonBacklog schema — type, schema_version, status, tags, last_reviewed, result, all optional (required: []).

Per the ADR (docs/planning/decisions/sdlc-cli/), deliver:

  1. A unified sdlc <noun> <verb> CLI under plugin/cli/ — one dispatcher, one --help, a shared _common.py (exit codes, exception→exit mapping, formatters), and --advanced/-ad to reveal hidden nouns.
  2. Lease folded in as a hidden lease noun — migrated from plugin/scripts/lease_cli/; same subcommands/exit-codes/markers; authority resolution + namespace-conflict guard scoped to this noun (not global).
  3. The backlog noun. sdlc backlog create = deterministic tail (structured args, no LLM): write docs/planning/backlog/<slug>.md, validate, manage the rolling backlog-capture branch (open PR by head → append; else create/reset off main), commit/push, PR create-or-update, print PR: <url>. sdlc backlog capture = LLM head and superset of create: freeform text + the same structured flags, fills gaps via claude -p, explicit flags win, --no-llm skips the LLM, then calls create. Only place claude -p runs.
  4. The /sdlc:backlog-capture skill — the ambient-Claude head: interprets freeform text in-session (inferring tags) and calls sdlc backlog create directly; never capture, never claude -p (no nested Claude).

Phased; each phase is independently committable (and could be split out as its own task).

  1. Dispatcher foundation. Create plugin/cli/ with the sdlc entry point (noun/verb argparse tree mirroring today’s sdlc_lease.py structure), a shared _common.py lifted from lease_cli/_common, and --advanced/-ad support that hides nouns registered with help=argparse.SUPPRESS from sdlc --help. Add plugin/cli/README.md.
  2. Lease migration. git mv plugin/scripts/lease_cli → plugin/cli/lease_cli (history follows); mount it as a hidden lease noun; move lease_authority resolution and the namespace-conflict guard into the lease noun’s dispatch so other nouns don’t require lease config. Reduce plugin/scripts/sdlc_lease.py to a thin back-compat shim that forwards its argv to sdlc 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 new sdlc lease … surface where practical.
  3. 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. Add backlog_cli/tests/run_evals.py (mock the gh/git boundary; lean on --dry-run); wire into sdlc.yaml quality_checks.
  4. Backlog head. plugin/cli/backlog_cli/capture.py — freeform positional text plus the create flags; derive missing fields via claude -p (headless, --output-format json); explicit flags win; --no-llm / a complete structured invocation skips the LLM; then call create. Define the JSON contract and the failure mode when claude is absent/unparseable.
  5. Skill head. plugin/skills/backlog-capture/SKILL.md — ambient Claude interprets freeform → structured args and shells to sdlc backlog create (never capture, never claude -p). Add the companion docs/skills/backlog-capture.md (Mermaid flowchart); confirm check_skill_docs.py passes.
LocationKindChange
plugin/cli/sdlcnewUnified dispatcher entry point (uv-inline shebang); noun/verb tree; --advanced/-ad.
plugin/cli/README.mdnewCLI convention: how to add a noun, visible vs hidden, markers/exit codes.
plugin/cli/_common.pynewShared exit codes, exception→exit mapping, formatters (lifted from lease_cli/_common).
plugin/cli/lease_cli/newLease CLI migrated here from plugin/scripts/lease_cli/ via git mv (history follows), mounted as hidden lease noun.
plugin/scripts/lease_cli/deleteMoved under plugin/cli/ (the git mv source side).
plugin/scripts/sdlc_lease.pymodifyReduced 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.pynewDeterministic tail.
plugin/cli/backlog_cli/capture.pynewLLM head (claude -pcreate).
plugin/cli/backlog_cli/tests/run_evals.pynewDeterministic tests (mocked gh/git, --dry-run).
plugin/skills/backlog-capture/SKILL.mdnewSkill head → sdlc backlog create.
docs/skills/backlog-capture.mdnewCompanion per-skill doc (Mermaid).
plugin/conventions/branch-naming.mdmodifyDocument the rolling backlog-capture branch.
sdlc.yamlmodifyAdd backlog_cli tests to quality_checks; fix any lease-CLI path references.
  • AC-1: sdlc --help lists backlog but not lease; sdlc --help --advanced (or -ad) also lists lease; sdlc lease --help works 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 without lease_authority configured.
  • AC-3: sdlc backlog create --headline "X" --body "…" [--tag t] (no Claude) creates docs/planning/backlog/<slug>.md (validates against the backlog schema), manages the branch, opens-or-updates the PR, prints PR: <url>; the create path contains no LLM reference.
  • AC-4: sdlc backlog capture "<freeform>" fills missing fields via claude -p then calls create; explicit flags override LLM-derived values; --no-llm or a complete structured invocation skips the LLM; it is the only path invoking claude -p.
  • AC-5: /sdlc:backlog-capture interprets in-session and calls sdlc backlog create directly — never capture, never claude -p; one invocation, no further prompts.
  • AC-6: Rolling branch — no open capture PR → create/reset backlog-capture off main + open PR; a second run while open appends (no second PR); after merge the next run starts a fresh branch off main (resetting a stale backlog-capture if not auto-deleted).
  • AC-7: --dry-run performs no git/gh side effects; backlog_cli tests pass; docs/skills/backlog-capture.md passes check_skill_docs.py.
  • 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 sdlc resolves by name inside a project — separate task.
  • Folding other existing helpers (entity scaffolds, etc.) into sdlc nouns — future.
  • 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 the depends_on sense.
  • 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 unified sdlc <noun> <verb> CLI with an LLM-head/deterministic-tail split (S0004).

Captured by /sdlc:task-work on 2026-05-27. PR: pending.

  • 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 the plugin/scripts/sdlc_lease.py shim vs sdlc lease list (identical output/exit code); sdlc backlog … runs with no lease_authority configured.
  • AC-3: auto — backlog_cli eval suite (file written + schema-valid via validate_frontmatter.py, rolling-branch + PR flow over a mocked git/gh seam, PR: <url> marker) plus a grep guard asserting the create tail 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.py passes (OK skills/backlog-capture) and the SKILL.md guardrails forbid capture/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-run no-side-effect eval cases; full backlog_cli suite (30 cases) green; docs/skills/backlog-capture.md passes check_skill_docs.py.
  • The head/deterministic-tail split (S0004) made the work cleanly wave-able: create (offline, mock-the-git/gh-boundary) was fully unit-testable, and both capture (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 the backlog noun, so the migration and the new noun mirrored a known-good shape.
  • git mv preserved 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.
  • 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 ## Today row citing the not-yet-existent plugin/cli/) — task-new/task-define should run the same parse_touchpoints.py resolution + 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 handles readiness_verified_at: — because the start-commit also adds last_reviewed: adjacent to it and the verify-stamp commit (made before last_reviewed existed) collides; the stamp-lift should also align last_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_entities corpus-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 an audit_entities drift — the v4 migration should sweep tasks created/merged concurrently with the bump. → T-YGJ1-schema-migration-sweeps-concurrent-tasks
  • git commit -m with ( ) in the message body silently broke under the worktree’s fish shell (command substitution) — the git 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

← Back to Tasks