From 9278722de22747f6daa2aba304505f5573a3fe97 Mon Sep 17 00:00:00 2001 From: sameera Date: Sun, 2 Aug 2026 08:52:25 -0400 Subject: [PATCH 1/2] docs(lessons): the epic #185 close lesson lands on the trunk The close wrote this lesson but the feature PR merged without it, leaving the close record pointing at a path that was never committed. Co-Authored-By: Claude Opus 5 --- .../2026-08-02-backlog-stubs-as-issues.md | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/delivery/lessons/2026-08-02-backlog-stubs-as-issues.md diff --git a/docs/delivery/lessons/2026-08-02-backlog-stubs-as-issues.md b/docs/delivery/lessons/2026-08-02-backlog-stubs-as-issues.md new file mode 100644 index 0000000..6eecf38 --- /dev/null +++ b/docs/delivery/lessons/2026-08-02-backlog-stubs-as-issues.md @@ -0,0 +1,63 @@ +--- +date: 2026-08-02 +epic: "Backlog Stubs Become GitHub Issues" +source: "#185" +--- + +# Lesson: a record that edits story scope leaves the story issues behind + +## The record superseded three acceptance criteria, and nothing made the issues follow + +Decision record #192 was approved with five explicit scope edits: promotion populates the stub in +place rather than closing it, slug lookup is retired, and the backlog surfaces once rather than per +feature. Those edits invalidated four acceptance criteria already written onto story issues #188 and +#190 — including one, "the source stub issue is closed as completed", that the record's own invariant +4 forbids outright. + +The code followed the record. The issues did not. Nothing in the pipeline noticed until +`/nxs.analyze` ran at the end and reported it as a HIGH finding, at which point the lead hand-amended +both issue bodies and the epic body before close would run against criteria the build contradicted. + +**What the next epic in this area should do differently:** treat "which story ACs does this record +supersede, and have they been re-filed" as a step of `/nxs.decision-record`, not a discovery for +`/nxs.analyze`. The record already writes a per-story Story Coverage section naming its scope edits — +that section is the exact worklist, and it is produced at the moment the edits are decided. Amending +the issues then costs minutes; amending them at analyze time costs a re-analysis and a waived +conformance gate, which is what this epic actually paid. + +## An absolute invariant met two file classes nobody had in mind + +Invariant 17 read: the file deletion lands only after **every** committed reference has been +repointed. Written that way it was clean and checkable. In this repository it met two kinds of file it +could not have meant — a derived anchor the distillation PR regenerates, and a frozen decision archive +whose whole value is recording what was true at the time. Satisfying the invariant literally would +have hand-edited a generated file and falsified an audit trail. + +Both were resolved correctly at implementation time, with a stub each, and both surfaced at close as +deviations. That is the system working — but the cost was two deviations on a close record for +something the record could have said in one clause. **Next time:** when an invariant quantifies over +"every committed X", state its exclusions where it is written. Generated surfaces and frozen archives +are the two that recur here. + +## Estimates were sized against a moving inventory + +The epic was planned against 22 proposed and 12 promoted backlog blocks. The record corrected 12 → 13 +at approval; the migration ran against 24 proposed. Two blocks were appended to the committed +backlogs between planning and implementation — by the ordinary act of deferring scope, which is what +those files were for. + +Nothing broke: the migration is batch-driven and counted what it found. But the record's risk section +named "twenty-two irreversible creations in one run" as the thing to rehearse, and the rehearsal +figure was wrong by two before it was run. **For any future migration of a live surface, take the +count at the migration commit, not at planning**, and phrase the risk as "every block present at +migration time" rather than a number. + +## Sizing held, and the record predicted where it wouldn't + +Four S stories and one M, complexity M overall. The four S stories landed as S. Story #188 was the one +the record flagged in advance — "this is the change that most pressures that story's sizing" — because +in-place promotion needed an update path the epic filing surface did not have. It did absorb the most +work. + +Worth keeping: the record's ADDRESS risks named the sizing pressure on a specific story rather than on +the epic. That is a more useful signal than a complexity rollup, and it was accurate here. From ad4dcce87bc299326e7fc0a458bc0f3236860742 Mon Sep 17 00:00:00 2001 From: sameera Date: Sun, 2 Aug 2026 08:55:28 -0400 Subject: [PATCH 2/2] =?UTF-8?q?distill:=20epic=20#185=20=E2=80=94=20backlo?= =?UTF-8?q?g=20stubs=20become=20GitHub=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates the backlog-stub concept and updates epic-approval-gate, which sheds the stub detail it no longer owns. Refreshes the anchors for both plus the stale stub-promotion glob line on workspace-resolution, and removes the consumed scratch dir. Co-Authored-By: Claude Opus 5 --- .nexus/anchors/backlog-stub.md | 26 +++++++ .nexus/anchors/epic-approval-gate.md | 10 +-- .nexus/anchors/workspace-resolution.md | 8 +-- .nexus/concepts/backlog-stub.md | 36 ++++++++++ .nexus/concepts/epic-approval-gate.md | 15 ++-- .../decisions-feat-backlog-stubs-as-issues.md | 71 ------------------- docs/concepts.md | 5 +- 7 files changed, 84 insertions(+), 87 deletions(-) create mode 100644 .nexus/anchors/backlog-stub.md create mode 100644 .nexus/concepts/backlog-stub.md delete mode 100644 .nexus/queue/epic-185/sameera/decisions-feat-backlog-stubs-as-issues.md diff --git a/.nexus/anchors/backlog-stub.md b/.nexus/anchors/backlog-stub.md new file mode 100644 index 0000000..f354e72 --- /dev/null +++ b/.nexus/anchors/backlog-stub.md @@ -0,0 +1,26 @@ +--- +concept: backlog-stub +source_sha: 9d42385a3585858bf599987598a214d4daa37968 +generated: 2026-08-02 +--- + + + +# Code Anchors: Backlog Stub + +- `.claude/skills/nxs-gh-shared/delivery_config.py` — resolves the unplanned label through the shared precedence chain, exposes the cross-feature backlog as one query in its three forms (listing, search fragment, negated filter), and carries the label preflight that upserts every label a filing run will apply and reports the ones it can neither create nor find. +- `.claude/skills/nxs-gh-create-story/scripts/create_gh_issues.py` — the one batch filing path: takes its canonical classification from the caller instead of stamping every issue a story, upserts every declared label before creating anything, refuses a work-item that is both a stub and a sub-issue, and resolves a literal issue-number dependency edge that points outside the batch. +- `.claude/skills/nxs-gh-create-epic/scripts/nxs_gh_create_epic.py` — the promotion path: reads the target's labels, refuses to promote an issue that is not an unplanned epic, and populates that same issue in place while clearing the unplanned label, so no second issue is created and nothing is closed. +- `libs/epic-resolve/src/classify.ts` — resolves the unplanned label across the process seam and answers whether an epic issue still carries it. +- `libs/epic-resolve/src/resolve.ts` — refuses to materialize an unplanned epic by name rather than emitting one with an empty story set. +- `libs/epic-resolve/src/resolve.spec.ts` — pins the refusal and the label resolution against the resolver's fixtures. +- `.claude/commands/nxs.epic.md` — the decomposition writer: files one stub issue per functional goal behind the right-size gate's consent, and promotes by issue number with no slug lookup. +- `.claude/commands/nxs.close.md` — the deferred-scope writer: files stub issues as the first step after the closure checkpoint, before the close record naming their numbers is committed. +- `.claude/skills/nxs-gh-shared/test_backlog_stubs.py` — covers stub filing through the batch path: classification from the caller, the unplanned label, and the no-parent refusal. +- `.claude/skills/nxs-gh-shared/test_backlog_query.py` — covers the three query forms and the quoting of a multi-word label. +- `.claude/skills/nxs-gh-shared/test_stub_promotion.py` — covers promotion legality and populating the issue in place. +- `.claude/skills/nxs-gh-shared/test_deferred_scope_stubs.py` — covers the close-side writer and the record naming the filed numbers. +- `.claude/skills/nxs-gh-shared/test_literal_dependency_refs.py` — covers the literal issue-number dependency edge. +- `.claude/skills/nxs-gh-shared/test_migration_dry_run.py` — covers the one-batch migration of the retired committed backlogs. +- `docs/features/README.md` — the features index carrying the single link to the cross-feature backlog query. diff --git a/.nexus/anchors/epic-approval-gate.md b/.nexus/anchors/epic-approval-gate.md index f957c11..9c9fe8a 100644 --- a/.nexus/anchors/epic-approval-gate.md +++ b/.nexus/anchors/epic-approval-gate.md @@ -1,7 +1,7 @@ --- concept: epic-approval-gate -source_sha: 1e0c90b776f0bd12a70388cb4643cd2bd7831766 -generated: 2026-07-26 +source_sha: 9d42385a3585858bf599987598a214d4daa37968 +generated: 2026-08-02 ---