Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a4d185a
fix(aidd-pm): give spec gaps one home under open questions
blafourcade Oct 7, 2026
3f78534
docs(aidd-pm): record the plan behind the spec open questions fix
blafourcade Oct 7, 2026
50d023f
fix(aidd-pm): scope the spec marker rule to every other section and l…
blafourcade Oct 7, 2026
94e0bc5
docs(aidd-pm): name the gap once in the spec open questions plan
blafourcade Oct 7, 2026
2731f89
fix(aidd-pm): define a spec gap once and make the validator criterion…
blafourcade Oct 7, 2026
5592698
docs(aidd-pm): record the spec open questions journey and review
blafourcade Oct 7, 2026
717558e
fix(aidd-pm): cut the spec gap rules to the words that change the draft
blafourcade Oct 7, 2026
e7959a0
fix(aidd-pm): say the refine gap step once
blafourcade Oct 7, 2026
7a40f59
refactor(aidd-pm): cut the spec actions to the words that change the …
blafourcade Oct 7, 2026
83ba310
fix(aidd-pm): put back the three clauses the cut should have kept
blafourcade Oct 7, 2026
03e1494
docs(aidd-pm): record what the installed tree does, and what it refuses
blafourcade Oct 7, 2026
6670187
fix(aidd-pm): a gap written elsewhere refuses the spec, a gap still o…
blafourcade Oct 7, 2026
3cb10ee
refactor(aidd-pm): the spec skill keeps only the words that change a …
blafourcade Oct 7, 2026
4771989
refactor(aidd-pm): the backlog declaration shape becomes an asset
blafourcade Oct 7, 2026
19ea466
fix(aidd-pm): a section says it is optional in its guidance, not in i…
blafourcade Oct 7, 2026
1da8138
test(cli): the taught backlog example can be an asset, not only a fen…
blafourcade Oct 7, 2026
2a93e71
test(cli): drop the comments the code already says
blafourcade Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"backlog": "ai-driven-dev/framework#626",
"written_at": "2026-10-07T12:00:00Z",
"written_by": "aidd-dev:01-plan"
}
138 changes: 138 additions & 0 deletions aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Journey evidence

Nothing here evaluates the validator, and no gate reads prose, so each case is run as fresh
`claude -p` contexts given the skill text and the task, outside the repository. Criteria
fixed before running, no run repeated, every count below mechanical.

## Placement - pass

22 drafts, three requests, both text versions. Markers outside the open questions section:
**0**. Heading exact: **22/22**, though only the template names it.

## Driving to zero - not reached, and nothing now refuses a spec for it

With the text pasted into one prompt, a request answering every required field produced 0, 0,
0 entries against 3, 2, 0 before the cut. Through the installed tree it produces 3, 2, 2. The
installed figure is the one to trust: it is the route a user runs.

**No causal claim is made about either pair.** The cut changed several clauses at once, so
length is isolated in neither table, and the thin-request ranges overlap (2, 3, 2, 2, 4
against 3, 4, 3, 4, 5).

Because zero is not reached, no refusal rides on it. `open_questions_unresolved` is gone.
What invalidates a spec is a gap written outside the section, which is what #626 measured.

## The measured case - pass

Retention and the row cap left open, the two #626 measured: **2, 2, 2** entries, both versions.

## Refine - pass

| Finding | Result |
| --- | --- |
| answers one of two | the entry is gone, the other stays, 3/3 |
| answers one in part | narrowed to what remains, 3/3 |
| answers the last | `None`, 3/3 |
| spec carries no such section | the section is added, heading exact, 3/3 |

The third row failed before this round: cutting `None` from step 4 produced an empty section
3/3. The fourth is an observation, not a mechanism. Its before-and-after prompts differ by
more than the text, since the later one inlines the template, so the link `02-refine.md`
gained was never exercised as a link.

## The validator - pass

Fresh context, the yml and one spec. Stands in for the gate #625 brings.

| Spec | Verdict |
| --- | --- |
| one gap listed in its own section | valid 3/3 |
| `None`, with a gap written under `## Context` | invalid 3/3 |
| no open questions section at all | invalid 3/3 |
| `None`, every required section resolved | valid 3/3, and 3/3 again on a fuller spec |

The first row read invalid 3/3 while a hard threshold refused an open gap. It refused
complete work, so it is gone.

The fourth row failed while the yml carried no definition of a gap: an evaluator reading it
alone supplied its own, and refused a spec whose content was complete.

## The installed tree, read from disk - placement passes, zero does not

Eight sessions in eight sandboxes, each built by `aidd translate . --to claude`, each reading
`SKILL.md` and following it to the action and to whatever the action links. Real reads, real
routing, the branch's own files.

| Measure | Result |
| --- | --- |
| the section exists | 8/8 |
| markers outside it | 0/8 |
| entries, thin request | 7, 7, 5, 5, 6 |
| entries, request fully answered | 3, 2, 2 |
| `None` on a fully answered request | **0/3** |

Placement holds where it counts. Zero does not: a request answering every required field
still produces entries, and `open_questions_unresolved: invalid` then refuses the spec. Two
questions recur, the column set 3/3 and when the seven days start 3/3, and the first is
presentation detail no required section depends on.

The pasted-text runs above measured 2, 3, 2, 2, 4 entries and `None` 3/3 on the same
requests. They were optimistic. The router reads more files, and asks more.

### Why the earlier installed-skill attempt measured nothing

`aidd-pm:04-spec` resolves to the user-scope install, `~/.claude/plugins/cache/aidd-framework/aidd-pm/2.5.0`,
whatever a project declares. Those runs drafted against the published skill and reproduced
#626 exactly: no section, seven to ten markers inline. A project marketplace does not
register in a headless session either, which `Unknown skill: spec-sandbox:04-spec` says
outright. So the Skill tool's own registration is still unmeasured, and only it is.

## Not measured

The Skill tool's registration, which no sandbox can exercise while a user-scope install
shadows it. One model. And no judge classified the entries, so which gaps a draft raises
rests on reading them.

## After the last cut - pass

The whole skill, cut to what changes a draft: 1379 words to 1050, with a section and a
reference added. Four sessions through the installed tree and six validator verdicts:

| Measure | Result |
| --- | --- |
| every required section present | 4/4 |
| the open questions section exists | 4/4 |
| markers outside it | 0/4 |
| a gap listed in its own section | valid 2/2 |
| a gap written under `## Context` | invalid 2/2 |
| `None` | valid 2/2 |

Entries stay high on a thin request, 8, 7 and 6, and 3 on a fully answered one. The cut
neither fixed nor worsened that; nothing refuses a spec for it.

## The two paths nothing had exercised - pass

Through the tree, after the declaration moved to a reference and its shape to an asset.

| Case | Result |
| --- | --- |
| the request names a ticket | `backlog-link.json` written, all three fields, `written_by` the skill |
| it names none | no file |
| the file is already there, for another item | left untouched, the hand-written one survives |
| refine resolves one gap of two | the entry is gone, the other stays, 2/2 |
| refine resolves the last | `None` |

The declaration had only ever been checked against the text pasted in one prompt, before it
moved. Refine had never been run through the tree at all.

## The PRD path, the optional headings, a second model - pass

| Case | Runs | Result |
| --- | --- | --- |
| drafted from a PRD | 3 | every required section present, the PRD's own open question carried as the one gap, nothing outside |
| `(optional)` reaching a drafted spec | 6 | 0, since the template now says it in the guidance and not in the heading |
| a second model | 3 | every required section present, the section exists, nothing outside |

The second model lists 6, 7 and 7 entries on the thin request where the first lists 6 to 8.
So the over-asking is not one model's habit. Nothing refuses a spec for it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
status: implemented
---

# Instruction: the marker has a home
## Architecture projection
> Tree of the final files. ✅ create · ✏️ modify · ❌ delete

```txt
.
└── plugins/aidd-pm/skills/04-spec/
├── references/tbd-marker.md ✏️ the marker, its home, one entry per gap
├── assets/spec-template.md ✏️ an `## Open Questions` section
├── assets/spec-validator.yml ✏️ a residual question is invalid
└── actions/
├── 01-build.md ✏️ gaps are listed, never inlined; one test row
└── 02-refine.md ✏️ the same for a field still unanswered; one test row
```
## User Journey
```mermaid
flowchart TD
A[a request leaves a decision pending] --> B[04-spec drafts the spec]
B --> C[the gap is listed under Open Questions, once]
C --> D{any question left?}
D -- yes --> E[the spec is invalid: the decision comes back to the user]
D -- no --> F[Open Questions reads None and the spec validates]
```
## Test Scope
```mermaid
---
title: Test scope
---
journey
section Setup
draft a spec from a request that leaves one decision pending => a spec carrying one gap: 5: system
section Happy path
read the drafted spec => the gap appears once, under Open Questions: 5: system
read every other section => each carries resolved content only, Context and Stakeholders included: 5: system
draft the same spec twice => the gap lands in the same section both times: 5: system
section Edge case - no gap at all
every field is answered => draft the spec => Open Questions reads None and the spec validates: 1: system
section Edge case - a question survives validation
a gap is still listed => validate the spec => the validator calls it invalid: 1: system
```
## Tasks to do
### `1)` state the marker's home
> `tbd-marker.md` carries the form, the home, and the one-entry rule.

1. Keep the form `TBD: <precise question>`.
2. Say the home is the template's open questions section, without naming the heading.
3. Define the unit: a gap is an unresolved decision a required section depends on.
4. Say each gap is one entry there, and that no other section carries a marker.

### `2)` give the template the section
> A spec has a place to put a pending decision.

1. Add `## Open Questions` to `spec-template.md`, after `Done-when`.
2. Guide it: one entry per unresolved decision, the marker's form, `None` when there is nothing left.
3. Say it drives to zero before the spec validates.

### `3)` make a residual question invalid
> The validator refuses a spec that still asks something.

1. Add `open_questions_unresolved: invalid` to `hard_thresholds`.
2. Leave every weight and `pass_threshold` untouched.
3. Add the `open_questions` criterion as `required: true`, `weight: 0`, describing the list's shape: required makes an absent or malformed section invalid, and a zero weight keeps the weighted total at 100.

### `4)` route both actions to the home
> Neither action places a marker by judgement any more.

1. `01-build.md` step 3: list every gap per `tbd-marker.md`.
2. `02-refine.md` step 4: the same, and remove an entry only once its decision is fully answered.
3. Add one test row to each: a gap is listed once, in the open questions section only.

### `5)` prove it
> The change holds under the repository's own gates.

1. Read every edited file back.
2. Run `pnpm exec lefthook run pre-commit`.
3. Commit through the commit skill.

## Test acceptance criteria
| Task | Acceptance criteria |
| --- | --- |
| 1 | `tbd-marker.md` names the template's open questions section as the home, and allows one entry per gap |
| 2 | `spec-template.md` carries the section with its guidance, and `None` is the empty form |
| 3 | `spec-validator.yml` calls a spec with an unresolved question invalid, and no weight or threshold moved |
| 4 | each action's gap step points at the home, and each `## Test` table has the row for it |
| 5 | pre-commit is green and the work is committed |
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
status: implemented
---

# Instruction: the rules are cut to what changes the draft
## Architecture projection
> Tree of the final files. ✅ create · ✏️ modify · ❌ delete

```txt
.
└── plugins/aidd-pm/skills/04-spec/
├── references/tbd-marker.md ✏️ the unit and the home, nothing else
├── assets/spec-template.md ✏️ the section owns its own heading
├── assets/spec-validator.yml ✏️ placement refuses, an open gap does not
└── actions/
├── 01-build.md ✏️ the steps lose their restated prose
└── 02-refine.md ✏️ the gap step says each thing once
```
## User Journey
```mermaid
flowchart TD
A[a rule is written in five files] --> B[the template owns the heading]
B --> C[the actions point at the reference]
C --> D{does a rule still bind?}
D -- no --> E[it is restored and measured]
D -- yes --> F[the words go]
```
## Test Scope
```mermaid
---
title: Test scope
---
journey
section Setup
build the installed tree from this branch => a sandbox per run: 5: system
section Happy path
draft from the shortened text => every gap sits in the open questions section: 5: system
resolve the last gap through refine => the section reads None: 5: system
validate a spec whose gap sits elsewhere => invalid: 5: system
section Edge case - a complete request
every required field answered => draft the spec => no refusal rides on what it still asks: 1: system
```
## Tasks to do
### `1)` take the heading out of four files
> The template owns its own section name.

1. Name the section in `spec-template.md` and nowhere else.
2. Point the reference, both actions and the validator at it by description.

### `2)` cut the prose that changes no draft
> On the user asking for the existing text to be cut too, past this issue.

1. Drop the restated sub-bullet, the parenthetical the template already carries, and the example verbs.
2. Reduce the backlog declaration to its rules.

### `3)` restore every rule the cut dropped
> A cut that loses a behaviour is a defect, not a saving.

1. `None` when no entry remains, in refine's step 4 and its test row.
2. The definition of a gap in the validator, which an evaluator reads alone.
3. Refine links the template, so the heading resolves.
4. What `written_by` holds, and the trigger and one-support rule of the backlog declaration.

### `4)` take the refusal off an open gap
> Measured: a complete request still lists entries, so the refusal refused complete work.

1. Replace `open_questions_unresolved` with `open_questions_outside_their_section`.
2. Say in the criterion that a listed gap does not invalidate the spec.
3. Move zero-before-locking to the template's guidance, where nothing enforces it until #625.

## Test acceptance criteria
| Task | Acceptance criteria |
| --- | --- |
| 1 | only `spec-template.md` names the heading, and the installed tree still places every gap there |
| 2 | no rule is lost: each one still names a behaviour a reader can apply |
| 3 | the four restored clauses each pass three runs out of three |
| 4 | a spec with one open gap is valid, and one with a gap elsewhere is invalid |
38 changes: 38 additions & 0 deletions aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
objective: "Every gap a spec still carries is listed once under its own section, so two runs on the same feature place it identically."
status: implemented
---

# Plan: a spec declares its open questions
## Overview
| Field | Value |
| --- | --- |
| **Goal** | give the `TBD:` marker one deterministic home in a spec, and make a residual one invalidate the spec |
| **Source** | ai-driven-dev/framework#626 |

## Phases
| # | Phase | File |
| --- | --- | --- |
| 1 | the marker has a home | [`phase-1.md`](./phase-1.md) |
| 2 | the rules are cut to what changes the draft | [`phase-2.md`](./phase-2.md) |

## Resources
| Source | Verified |
| --- | --- |
| `plugins/aidd-pm/skills/04-spec/references/tbd-marker.md` | the marker's only definition is its form, `TBD: <precise question>` — no home is stated anywhere |
| `plugins/aidd-pm/skills/04-spec/actions/01-build.md` | step 3 replaces "any missing required field" with the marker, which is what leaves placement to the drafting agent |
| `plugins/aidd-pm/skills/04-spec/actions/02-refine.md` | step 4 says the same for a field still unanswered |
| `plugins/aidd-pm/skills/04-spec/SKILL.md` | its transversal rule says to mark every gap and names no home, so it needs no change |
| `plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml` | required criteria weigh 90 of 100, so a new weighted criterion would move `pass_threshold` |
| `cli/tests/golden/` | holds no copy of the spec template, so no snapshot is coupled to this change |

## Decisions
| Decision | Why |
| --- | --- |
| The home is the template's open questions section, required, and named in no other file | it mirrors `prd-template.md`, and the issue names it |
| Each gap is listed there once, and nowhere else | a gap written in both its eventual section and the list is the same non-determinism with an extra copy |
| Every section other than the open questions one carries resolved content only | a hard constraint that is undecided contradicts the validator's own definition of that section, and a copy under an optional section is the second of the two placements the issue measured |
| One noun, defined in the reference and repeated once in the validator | an evaluator reads the yml alone, measured: without the definition there it supplied its own and refused a complete spec. The one exception to one fact, one home, and the reason it is one | the template said `unresolved decision`, the reference said `gap` and the actions said `missing required field`. Three units in one skill, and the broadest of them now blocks validation |
| What no required section depends on is not a gap | it excludes over-asking without inventing a subjective category: `SKILL.md`'s `Hold intent, never implementation` and the validator's `contains_implementation_details` are boundaries a drafter already applies. Measured insufficient: the installed tree still asks for the column set 3 times out of 3, which is why no refusal rides on it | it excludes over-asking without inventing a subjective category: `SKILL.md`'s `Hold intent, never implementation` and the validator's `contains_implementation_details` are boundaries a drafter already applies |
| A gap written outside the section invalidates the spec through `hard_thresholds`; a gap merely still open does not | the required weights already total 90 against a `pass_threshold` of 90, so a weighted addition would need the threshold retuned; a zero weight checks the section's shape and leaves the arithmetic alone |
| `tbd-marker.md` states the home; the two actions link to it and `SKILL.md` is untouched | one fact, one home: a rule written in three places drifts in two of them. The guard would not have caught it, since `check-doc-duplication.js` scans only `docs`, the two memory banks and the READMEs |
Loading
Loading