Skip to content

fix(aidd-pm): give a spec gap one home, and refuse only one written elsewhere - #969

Merged
blafourcade merged 17 commits into
nextfrom
fix/spec-open-questions
Oct 7, 2026
Merged

blafourcade merged 17 commits into
nextfrom
fix/spec-open-questions

Conversation

@blafourcade

@blafourcade blafourcade commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

🎯 What & why

The TBD: marker had a form and no home, so two runs on the same feature put the same gap in different sections. A spec now has one place for its open questions.

🛠️ How it works

spec-template.md:29 owns the section and is the only file that names its heading. tbd-marker.md:5 defines what belongs there, and both actions point at it rather than restating it.

A gap is an unresolved decision a required section depends on. spec-validator.yml:24 repeats that definition, the one deliberate exception to one fact, one home: its evaluator reads the yml alone, and without the definition there, one supplied its own and refused a spec whose content was complete.

What invalidates a spec is a gap written outside the section, not one still open. The first version refused any open gap. Measured through the installed tree, a request answering every required field still lists two or three entries — the CSV column set, when the seven days start — so that rule refused complete work. Before this branch an extra question cost nothing; open_questions_unresolved would have made it a refusal.

SKILL.md is untouched. Its transversal rule names no placement, so there is nothing to retarget. #626 lists the file as affected; it is not.

Past the issue, on the author asking for the existing text to be cut too: the skill goes from 1379 to 1050 words. The backlog declaration left step 6 for backlog-link.md, its shape became an asset, and the guard that pins the taught field names follows it.

🧪 How to verify

  • aidd translate . --to claude --out <dir>, then draft a spec in that tree from a request that leaves a decision open: the gap lands under the open questions section and nowhere else.
  • 40 runs through the rebuilt tree, counted mechanically in journey.md: placement 8/8 then 4/4, from a PRD 3/3, on a second model 3/3, refine 3/3, the backlog declaration 3/3, and each validator case 2/2.
  • pnpm exec lefthook run pre-commit

⚠️ Heads-up

🔗 Linked issue

Closes #626

✅ I certify

  • I DO CERTIFY I READ EACH LINE OF THE PULL REQUEST BECAUSE I AM A SOFTWARE ENGINEER, NOT A AI PUPPY.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

blafourcade and others added 15 commits October 7, 2026 13:26
The TBD marker had a form but no place, so two runs on the same feature
could put the same gap in different sections. Gaps are now listed once under
a required Open Questions section, and a residual question makes the spec
invalid. The new criterion weighs nothing, so the pass threshold is unchanged.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The Deliver zone wrote this plan and its phase, so it is committed with the
work it describes. It carries the backlog link to #626
and the five decisions the implementation was not free to re-make, including
the two the ticket got wrong: SKILL.md needs no change, and the validator
gains no weighted criterion.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…et refine clear entries

Review of a4d185a found two gaps.

First, the rule and both test rows said "no required section carries a
marker". Context and Stakeholders are optional, so a gap listed under Open
Questions and copied into Context passed every check while reproducing the
second run measured in #626. The rule now reads "every other section", in
tbd-marker.md and in both test rows.

Second, refine only listed what was still unanswered. With the marker no
longer inside its field, rewriting a field no longer deleted it, so one stale
entry could invalidate a spec whose content was complete. Step 4 now removes
answered entries first and writes None when none remain, with a test row.

Also drops the placeholder bullet from the spec template and the
criterion's self-description from the validator. Weights and threshold are
unchanged.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
A checker found the plan carrying a false reason: check-doc-duplication.js
scans only docs, the two memory banks and the READMEs, so it could never have
failed a sentence shared inside a skill. The decision stands on one fact, one
home; the reason did not.

The same round found one noun written four ways across the skill, the broadest
of which now blocks validation. The plan fixes the unit: a gap is an unresolved
decision a required section depends on, and what no required section depends on
is not a gap.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
… spec-wide

Finding 1: the spec skill named one unit four ways (unresolved decision,
gap, missing required field, field still unanswered). tbd-marker.md now
defines a gap once, as an unresolved decision a required section depends
on, excluding what no required section depends on and implementation
detail. 01-build, 02-refine, the template and the validator point at it.

Finding 2: the open_questions criterion now states that no TBD marker
appears outside Open Questions anywhere in the spec, Context included, so
an evaluator reading the yml alone has one reading. Weights and
pass_threshold are unchanged.

Also: 02-refine removes an entry only once its decision is fully
answered, and rewrites it to the remaining question otherwise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The validator now refuses both cases phase-1 named and never ran: a residual
entry, and a None section with a marker under Context. Placement and the
measured case hold.

The thin-request runs failed their criterion. Two of three still ask which
fields the CSV must carry, which no required section depends on. The unit
bounds where a gap is written, not which questions are raised.

Also repairs a sentence in tbd-marker.md that read "nowhere else, anywhere in
the spec".

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The heading was hardcoded in five files. The template owns it now, and nothing
else names it.

Cutting the text also closed the over-ask the previous evidence caught: on the
same thin request, drafts went from 5, 4 and 6 entries with CSV field lists and
refusal wording to 2, 4 and 2 with none. The rule did not change, only its
length.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
It said answered twice and rewrite twice. Three clauses, three behaviours, no
word spent on a second pass.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…draft

The gap rules were lean and the text around them was not, so the skill still
read as mostly prose. 01-build loses a quarter of its words, 02-refine and the
template guidance the rest: the restated sub-bullet, the parenthetical listing
the optional sections the template already marks, the three example verbs, and
the two paragraphs explaining the backlog declaration.

Every rule is kept. A fresh context given the shortened step 6 still writes
backlog-link.json for a named ticket, writes none for no ticket, and leaves an
existing file untouched for a different one. A fourth draft of the thin request
listed three gaps, none of them presentation detail.

This goes past the issue, on the user asking for the existing text to be cut
too.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
A checker measured what the last cut cost, and two of the three were real.

Refine lost "None when none remains", so resolving the last gap left an empty
section 3 times out of 3, and the validator refuses that. The validator lost
the definition of a gap, so an evaluator reading the yml alone supplied its own
and refused a spec whose content was complete. Refine never linked the
template, so on a spec carrying no such section it invented the heading in
lower case 3 times out of 3. All three now pass 3 of 3.

01-build gets back what written_by holds, which its own test row requires.

The journey no longer claims the cut caused the shorter drafts: that cut
changed several clauses at once, so length was never isolated. It reports what
is measured instead, including 0 markers outside the section across 22 drafts
and None on a fully answered request 3 times out of 3.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
Eight sessions reading SKILL.md from a translated sandbox and following it.
Placement holds: the section exists 8 of 8, no marker outside it 8 of 8. Zero
does not: a request answering every required field still lists entries 3 of 3,
and the new hard threshold then refuses that spec.

The pasted-text runs were optimistic. They measured None 3 of 3 where the
router measures 0 of 3, because the router reads more files and asks more.

Also records why the first installed-skill attempt measured nothing: the Skill
tool resolves to the user-scope install whatever a project declares, and a
project marketplace does not register in a headless session.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…pen does not

Measured through the installed tree: a request answering every required field
still lists 3, 2 and 2 entries, always the column set and when the seven days
start. Neither is a decision a required section depends on. So the threshold
added here refused specs where nothing was missing, and before this branch an
extra question cost nothing.

open_questions_unresolved becomes open_questions_outside_their_section. A
listed gap is valid, a gap written in any other section is not, which is the
defect the issue measured. Driving to zero moves to the template as the
intention it is, and to #625 as the only thing that will enforce it.

Also closes four record findings: the plan claimed one home for a definition
the validator must repeat, phase-1 still framed the rule on required sections,
01-build had lost what a backlog item is and the one-support rule, and two
files named two authorities for one check.

Measured, three runs each: a listed gap valid 3/3, a gap under Context invalid
3/3, None valid 3/3.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…draft

The backlog declaration was a contract inside a step, so it moves to its own
reference and the step becomes one line. Four duplications go with it: the
template already gives headers, bullets and a non-goals section, so the
transversal rule saying to was redundant; the drop-implementation-detail clause
is already transversal; the validator repeated the plan boundary its own hard
threshold carries; and two files named two authorities for one section check.

The assets lose their padding. The template keeps one placeholder bullet per
list instead of two and states each rule once. The validator reads as a
checklist again, every criterion one line, arithmetic untouched: required 90,
total 100, pass_threshold 90, five hard thresholds.

1379 words to 1050, with a section and a reference added since.

Measured on the rebuilt tree: every required section present 4 of 4, the
section exists 4 of 4, no marker outside it 4 of 4, and the three validator
cases unchanged at 2 of 2 each.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The JSON was a scaffold inside a reference, so it moves to
assets/backlog-link-template.json beside the spec template, and the reference
keeps only the rules. The guard that pins the taught field names follows it.

Tested through the tree, which the declaration never had been and refine never
had at all: a named ticket writes the file with all three fields, no ticket
writes none, an existing file for another item is left untouched, and refine
drops a resolved entry then reads None on the last.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…ts heading

The template shipped `## Stakeholders (optional)`, and a drafted spec carried
the parenthesis into its own heading. Optionality belongs to the template that
teaches it, not to the artefact.

Tested through the tree: no drafted spec carries it, 6 runs out of 6.

Two paths nothing had exercised, both green. From a PRD, 3 runs out of 3 carry
every required section and lift the PRD own open question as the one gap. On a
second model, 3 runs out of 3 place every gap in the section, which settles
that the over-asking is not one model habit: 6, 7 and 7 entries against 6 to 8.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
@blafourcade
blafourcade marked this pull request as ready for review October 7, 2026 13:36
@blafourcade
blafourcade requested a review from a team as a code owner October 7, 2026 13:36
blafourcade and others added 2 commits October 7, 2026 15:50
…ced block

CI caught what no local gate could: the spec skill ships its backlog-link shape
as an asset now, and this guard extracted the first fenced json block out of
the action, which no longer has one. It read null and failed, exactly as a
guard should.

The loader accepts both forms, the asset bytes or the fenced block, so each
skill still feeds its own example through the real adapter.

Worth noting: the pre-push gate globs cli/**, so a plugins-only change runs no
cli test locally, while cli-ci.yml runs them. That is the gap this slipped
through.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The loader docblock restated its own body, and the header docblock explained
which skill ships which form, which the two paths say. One line stays: why the
real adapter and not a stand-in parser, the only thing the code cannot show.

Refs #626

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
@blafourcade
blafourcade merged commit 22c541a into next Oct 7, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant