From a4d185a52dca808292ea8d5f8f3b3be72da88507 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 13:26:22 +0200 Subject: [PATCH 01/17] fix(aidd-pm): give spec gaps one home under open questions 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- plugins/aidd-pm/skills/04-spec/actions/01-build.md | 3 ++- plugins/aidd-pm/skills/04-spec/actions/02-refine.md | 4 ++-- plugins/aidd-pm/skills/04-spec/assets/spec-template.md | 6 ++++++ plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml | 6 ++++++ plugins/aidd-pm/skills/04-spec/references/tbd-marker.md | 5 +++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 7ff9affa9..9b5e91213 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -16,7 +16,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the 2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. - PRD path: lift its target, hard constraints, non-goals, and done-when. - Free-form request: map it directly onto the template sections. -3. **Gaps.** Replace any missing required field per [tbd-marker.md](../references/tbd-marker.md). +3. **Gaps.** List every missing required field under `## Open Questions` per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. 4. **Check.** Confirm every section the validator requires is present. Omit an optional section (stakeholders, context) that has nothing to say rather than emit a placeholder. 5. **Write.** Resolve the feature folder: reuse an existing `aidd_docs/tasks//_/` match for this feature, or create one. Save it there. 6. **Declare.** When the request names a backlog item — a ticket already resolved (e.g. by `/aidd-pm:01-ticket-info`), or a path to its Markdown artefact — and the folder carries no `backlog-link.json` yet, write one there, naming that item on whatever support it lives: @@ -41,6 +41,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the | The action completes | `spec.md` exists in the feature folder | | The file is validated | every section required by [spec-validator.yml](../assets/spec-validator.yml) is present | | The spec is read back | it carries no library name, framework pattern, or source-file layout | +| A required field is missing | it is listed once under `## Open Questions`, and no required section carries a marker | | Too vague | no write; one clarifying question returned | | The request names a backlog item | the folder's `backlog-link.json` names it, with `written_at` and `written_by` | | The request names none | no `backlog-link.json` is written, and nothing errors | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index cf4e7f6f3..31ea48ce2 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,7 +15,7 @@ The refined spec at the same path, or no change if not written. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. -4. **Gaps.** Replace any field still unanswered per [tbd-marker.md](../references/tbd-marker.md). +4. **Gaps.** List every field still unanswered under `## Open Questions` per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. 5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. 6. **Verify.** Report what changed as `before -> after`, and the verification result. @@ -25,6 +25,6 @@ The refined spec at the same path, or no change if not written. | --- | --- | | The action completes | the spec still exists at its path with every section required by [spec-validator.yml](../assets/spec-validator.yml) | | A finding is resolved | the spec changed at the section it names | -| A finding cannot be resolved | the marker from [tbd-marker.md](../references/tbd-marker.md) marks it in place | +| A finding cannot be resolved | it is listed once under `## Open Questions`, and no required section carries a marker | | A write happened | the result reports the stable identity, `before -> after` fields, and verification result | | No write happened | the result states that no persisted change occurred | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index c669e7551..d184ba599 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -29,6 +29,12 @@ - - +## Open Questions + +`. Write `None` when nothing is left. Resolve every entry before the spec validates.> + +- TBD: + ## Stakeholders (optional) - Decider: diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index 5b1f55d8f..a3a5fd95c 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -22,6 +22,11 @@ criteria: required: true weight: 25 + - id: open_questions + description: An Open Questions section present, listing each unresolved decision once as a precise question, or reading None. Checks the section's shape and carries no weight. + required: true + weight: 0 + - id: stakeholders description: Decider, owner, and consumer roles identified. Optional but encouraged for non-trivial specs. required: false @@ -37,6 +42,7 @@ hard_thresholds: target_unclear_or_multiple_targets: invalid done_when_unverifiable: invalid contains_implementation_details: invalid + open_questions_unresolved: invalid scoring: pass_threshold: 90 diff --git a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md index 9cbcbda63..b2ef630d7 100644 --- a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md +++ b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md @@ -1,3 +1,8 @@ # TBD marker `TBD: ` + +The marker lives in the spec's `## Open Questions` section, nowhere else. + +List each gap there as one entry. +A required section carries resolved content only, never a marker. From 3f785340e9badfa1842e7502c5488ddc0374a074 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 13:28:08 +0200 Subject: [PATCH 02/17] docs(aidd-pm): record the plan behind the spec open questions fix The Deliver zone wrote this plan and its phase, so it is committed with the work it describes. It carries the backlog link to ai-driven-dev/framework#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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../backlog-link.json | 5 ++ .../2026_10_07_spec-open-questions/phase-1.md | 88 +++++++++++++++++++ .../2026_10_07_spec-open-questions/plan.md | 35 ++++++++ 3 files changed, 128 insertions(+) create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/backlog-link.json create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/backlog-link.json b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/backlog-link.json new file mode 100644 index 000000000..fc797c5b8 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/backlog-link.json @@ -0,0 +1,5 @@ +{ + "backlog": "ai-driven-dev/framework#626", + "written_at": "2026-10-07T12:00:00Z", + "written_by": "aidd-dev:01-plan" +} diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md new file mode 100644 index 000000000..385912f29 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md @@ -0,0 +1,88 @@ +--- +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 its required sections => Target, Hard constraints, Non-goals and Done-when carry resolved content only: 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: `. +2. Say the home is the spec's `## Open Questions` section. +3. Say each gap is one entry there, and that no required 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 missing required field under `## Open Questions` per `tbd-marker.md`, never inside the section it would fill. +2. `02-refine.md` step 4: the same for a field still unanswered. +3. Add one test row to each: a gap is listed once under `## Open Questions`, and no required section carries a marker. + +### `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 `## Open Questions` as the home and allows one entry per gap | +| 2 | `spec-template.md` carries `## Open Questions` 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 | diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md new file mode 100644 index 000000000..f1984efe7 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md @@ -0,0 +1,35 @@ +--- +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) | + +## Resources +| Source | Verified | +| --- | --- | +| `plugins/aidd-pm/skills/04-spec/references/tbd-marker.md` | the marker's only definition is its form, `TBD: ` — 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 `## Open Questions`, a required section of the template | 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 | +| The template's required sections carry resolved content only | a hard constraint that is undecided contradicts the validator's own definition of that section | +| A residual question invalidates the spec through `hard_thresholds`, and the section is checked by a criterion carrying no weight | 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 — and `check-doc-duplication.js` fails a sentence written in two documents | From 50d023f26a177d818f1aedd1fa7f1aec48baba7a Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 13:40:57 +0200 Subject: [PATCH 03/17] fix(aidd-pm): scope the spec marker rule to every other section and let refine clear entries Review of a4d185a5 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- plugins/aidd-pm/skills/04-spec/actions/01-build.md | 2 +- plugins/aidd-pm/skills/04-spec/actions/02-refine.md | 5 +++-- plugins/aidd-pm/skills/04-spec/assets/spec-template.md | 2 -- plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml | 2 +- plugins/aidd-pm/skills/04-spec/references/tbd-marker.md | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 9b5e91213..58950c9cd 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -41,7 +41,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the | The action completes | `spec.md` exists in the feature folder | | The file is validated | every section required by [spec-validator.yml](../assets/spec-validator.yml) is present | | The spec is read back | it carries no library name, framework pattern, or source-file layout | -| A required field is missing | it is listed once under `## Open Questions`, and no required section carries a marker | +| A required field is missing | it is listed once under `## Open Questions`, and no other section carries a marker | | Too vague | no write; one clarifying question returned | | The request names a backlog item | the folder's `backlog-link.json` names it, with `written_at` and `written_by` | | The request names none | no `backlog-link.json` is written, and nothing errors | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 31ea48ce2..1d6b1c18e 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,7 +15,7 @@ The refined spec at the same path, or no change if not written. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. -4. **Gaps.** List every field still unanswered under `## Open Questions` per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. +4. **Gaps.** Remove from `## Open Questions` every entry the rewrite answered, then list each field still unanswered there per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. Write `None` when no entry remains. 5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. 6. **Verify.** Report what changed as `before -> after`, and the verification result. @@ -25,6 +25,7 @@ The refined spec at the same path, or no change if not written. | --- | --- | | The action completes | the spec still exists at its path with every section required by [spec-validator.yml](../assets/spec-validator.yml) | | A finding is resolved | the spec changed at the section it names | -| A finding cannot be resolved | it is listed once under `## Open Questions`, and no required section carries a marker | +| A finding resolves an open question | its entry is gone from `## Open Questions`, and the section reads `None` when it was the last | +| A finding cannot be resolved | it is listed once under `## Open Questions`, and no other section carries a marker | | A write happened | the result reports the stable identity, `before -> after` fields, and verification result | | No write happened | the result states that no persisted change occurred | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index d184ba599..7bf888e65 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -33,8 +33,6 @@ `. Write `None` when nothing is left. Resolve every entry before the spec validates.> -- TBD: - ## Stakeholders (optional) - Decider: diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index a3a5fd95c..86861368f 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -23,7 +23,7 @@ criteria: weight: 25 - id: open_questions - description: An Open Questions section present, listing each unresolved decision once as a precise question, or reading None. Checks the section's shape and carries no weight. + description: An Open Questions section present, listing each unresolved decision once as a precise question, or reading None. required: true weight: 0 diff --git a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md index b2ef630d7..e35920b93 100644 --- a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md +++ b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md @@ -5,4 +5,4 @@ The marker lives in the spec's `## Open Questions` section, nowhere else. List each gap there as one entry. -A required section carries resolved content only, never a marker. +Every other section carries resolved content only, never a marker. From 94e0bc5dbe5acd5cd9e51224497173603032b6bc Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 13:57:22 +0200 Subject: [PATCH 04/17] docs(aidd-pm): name the gap once in the spec open questions plan 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10/2026_10_07_spec-open-questions/phase-1.md | 9 +++++---- .../tasks/2026_10/2026_10_07_spec-open-questions/plan.md | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md index 385912f29..0654503fd 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md @@ -48,7 +48,8 @@ journey 1. Keep the form `TBD: `. 2. Say the home is the spec's `## Open Questions` section. -3. Say each gap is one entry there, and that no required section carries a marker. +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. @@ -67,9 +68,9 @@ journey ### `4)` route both actions to the home > Neither action places a marker by judgement any more. -1. `01-build.md` step 3: list every missing required field under `## Open Questions` per `tbd-marker.md`, never inside the section it would fill. -2. `02-refine.md` step 4: the same for a field still unanswered. -3. Add one test row to each: a gap is listed once under `## Open Questions`, and no required section carries a marker. +1. `01-build.md` step 3: list every gap under `## Open Questions` per `tbd-marker.md`, never inside the section it would fill. +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 under `## Open Questions`, and no other section carries a marker. ### `5)` prove it > The change holds under the repository's own gates. diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md index f1984efe7..0981ab64a 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md @@ -30,6 +30,8 @@ status: implemented | --- | --- | | The home is `## Open Questions`, a required section of the template | 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 | -| The template's required sections carry resolved content only | a hard constraint that is undecided contradicts the validator's own definition of that section | +| Every section other than `## Open Questions` 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 once: a gap is an unresolved decision a required section depends on | 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, and neither is an implementation detail | 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 residual question invalidates the spec through `hard_thresholds`, and the section is checked by a criterion carrying no weight | 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 — and `check-doc-duplication.js` fails a sentence written in two documents | +| `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 | From 2731f892eae75142190b3dca02b9fe4f5839e21d Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 13:59:14 +0200 Subject: [PATCH 05/17] fix(aidd-pm): define a spec gap once and make the validator criterion 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 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- plugins/aidd-pm/skills/04-spec/actions/01-build.md | 4 ++-- plugins/aidd-pm/skills/04-spec/actions/02-refine.md | 2 +- plugins/aidd-pm/skills/04-spec/assets/spec-template.md | 2 +- plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml | 2 +- plugins/aidd-pm/skills/04-spec/references/tbd-marker.md | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 58950c9cd..7081acc3f 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -16,7 +16,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the 2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. - PRD path: lift its target, hard constraints, non-goals, and done-when. - Free-form request: map it directly onto the template sections. -3. **Gaps.** List every missing required field under `## Open Questions` per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. +3. **Gaps.** List every gap under `## Open Questions` as [tbd-marker.md](../references/tbd-marker.md) defines it, never inside the section it would fill. 4. **Check.** Confirm every section the validator requires is present. Omit an optional section (stakeholders, context) that has nothing to say rather than emit a placeholder. 5. **Write.** Resolve the feature folder: reuse an existing `aidd_docs/tasks//_/` match for this feature, or create one. Save it there. 6. **Declare.** When the request names a backlog item — a ticket already resolved (e.g. by `/aidd-pm:01-ticket-info`), or a path to its Markdown artefact — and the folder carries no `backlog-link.json` yet, write one there, naming that item on whatever support it lives: @@ -41,7 +41,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the | The action completes | `spec.md` exists in the feature folder | | The file is validated | every section required by [spec-validator.yml](../assets/spec-validator.yml) is present | | The spec is read back | it carries no library name, framework pattern, or source-file layout | -| A required field is missing | it is listed once under `## Open Questions`, and no other section carries a marker | +| A gap exists | it is listed once under `## Open Questions`, and no other section carries a marker | | Too vague | no write; one clarifying question returned | | The request names a backlog item | the folder's `backlog-link.json` names it, with `written_at` and `written_by` | | The request names none | no `backlog-link.json` is written, and nothing errors | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 1d6b1c18e..9e1d1afe5 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,7 +15,7 @@ The refined spec at the same path, or no change if not written. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. -4. **Gaps.** Remove from `## Open Questions` every entry the rewrite answered, then list each field still unanswered there per [tbd-marker.md](../references/tbd-marker.md), never inside the section it would fill. Write `None` when no entry remains. +4. **Gaps.** Remove from `## Open Questions` every entry whose decision the rewrite fully answered, and rewrite one answered in part to the question that remains. Then list each gap still open there as [tbd-marker.md](../references/tbd-marker.md) defines it, never inside the section it would fill. Write `None` when no entry remains. 5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. 6. **Verify.** Report what changed as `before -> after`, and the verification result. diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index 7bf888e65..72ef744b7 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -31,7 +31,7 @@ ## Open Questions -`. Write `None` when nothing is left. Resolve every entry before the spec validates.> +`; what a gap is, `references/tbd-marker.md` defines. Write `None` when nothing is left. Resolve every entry before the spec validates.> ## Stakeholders (optional) diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index 86861368f..61cf07dd2 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -23,7 +23,7 @@ criteria: weight: 25 - id: open_questions - description: An Open Questions section present, listing each unresolved decision once as a precise question, or reading None. + description: An Open Questions section present, listing each gap (an unresolved decision a required section depends on, per references/tbd-marker.md) once as a precise question, or reading None. Spec-wide - no `TBD:` marker appears anywhere in the spec outside the Open Questions section, in any section including Context. required: true weight: 0 diff --git a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md index e35920b93..b4048ef28 100644 --- a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md +++ b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md @@ -2,7 +2,10 @@ `TBD: ` -The marker lives in the spec's `## Open Questions` section, nowhere else. +A gap is an unresolved decision a required section depends on. +What no required section depends on is not a gap, and neither is an implementation detail. + +The marker lives in the spec's `## Open Questions` section, nowhere else, anywhere in the spec. List each gap there as one entry. Every other section carries resolved content only, never a marker. From 5592698a39d57b7a9595de1d137de9ccd0f6037d Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:02:49 +0200 Subject: [PATCH 06/17] docs(aidd-pm): record the spec open questions journey and review 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 112 ++++++++++++++++++ .../2026_10_07_spec-open-questions/review.md | 32 +++++ .../skills/04-spec/references/tbd-marker.md | 2 +- 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/review.md diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md new file mode 100644 index 000000000..bb8dcc3ed --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -0,0 +1,112 @@ +# Journey evidence + +The phase's Test Scope, run end to end. Nothing in this repository evaluates +`spec-validator.yml`, and no gate validates a prose behaviour change, so each run is a fresh +independent context given the skill text and the task, and nothing else. Pass criteria were +fixed before the runs. No run was repeated to get a better answer. + +## 1 - Thin request, three runs - FAIL + +The request answers the target, the retention and the row cap, states no non-goals, and +carries no sentence suppressing further questions. The earlier clean result used one, which +is why it proved nothing. + +> Finance users need to export their invoices as CSV. A user picks a date range and gets a +> downloadable file. An export stays downloadable for 7 days after it is generated. An export +> covering more than 50,000 rows is refused. + +Pass criterion: none of the three runs asks a question about implementation or presentation +detail. Measured: five, four and six entries, and two of the three ask which fields the CSV +must carry. Two of the three also ask what the user is told on refusal and after the window +closes. + +Run 1: +```markdown +## Open Questions + +- TBD: Which invoice date does the selected date range filter on (issue date, due date, or payment date), and are both range endpoints inclusive? +- TBD: Which invoices may a finance user export (all invoices in their organization, or a narrower set), and how is "finance user" determined? +- TBD: Does the 50,000-row limit count invoices or invoice line items? +- TBD: When an export is refused for exceeding 50,000 rows, what must the user be told (for example the row count or a prompt to narrow the range)? +- TBD: After the 7-day window ends, must the user be able to see that the export expired, or may it simply disappear? +``` + +Run 2: +```markdown +## Open Questions + +- TBD: Which invoice date does the selected date range filter on (issue date, due date, or payment date)? +- TBD: Does a "row" in the 50,000 limit mean one invoice or one invoice line item? +- TBD: Which fields must each exported CSV row contain? +- TBD: Which invoices may a finance user export (all invoices, or only those within their own organization or entity)? +``` + +Run 3: +```markdown +## Open Questions + +- TBD: Which invoice date does the selected range filter on (issue date, due date, or payment date)? +- TBD: What counts as a row toward the 50,000 limit: one invoice, or one invoice line item? +- TBD: Which invoices may a finance user export (all invoices, or only those of their own organization or account)? +- TBD: Which invoice fields must the CSV contain? +- TBD: What must the user see when an export is refused for exceeding 50,000 rows? +- TBD: What must the user see when they try to download an export after its 7 days have passed? +``` + +The widened unit did not bound which gaps are raised. It bounds where they are written, and +that holds. `01-build.md` says to list every gap as `tbd-marker.md` defines it, and +`tbd-marker.md` excludes what no required section depends on. A drafter applied the placement +rule in every run and the exclusion in one. + +## 2 - The measured case, one run - PASS + +The two decisions #626 measured, left open: + +> We have not decided how long an export stays downloadable once it is generated, nor the +> maximum number of rows a single export may cover. + +Pass criterion: both are still listed. The widened unit must not lose the case the issue +measured. + +```markdown +## Open Questions + +- TBD: How long does a generated export stay downloadable before it expires? +- TBD: What is the maximum number of rows a single export may cover? +``` + +## 3 - The validator, two runs - PASS + +Each run is a fresh context given `spec-validator.yml` and one spec, asked whether the spec +is valid and why. Both cases are `phase-1.md`'s own edge cases, and neither had ever been +run. They stand in for a gate that arrives with #625. + +A spec carrying one residual `## Open Questions` entry: + +``` +VERDICT: invalid + +- The hard threshold `open_questions_unresolved` forces invalid. The Open Questions section lists one unresolved gap: "Which invoice date does the selected date range filter on?" +- Target, Hard constraints, Non-goals and Done-when each meet their criteria, and the `TBD:` marker sits only inside Open Questions, which is allowed. +- The score is 90 (stakeholders and context are absent), which meets `pass_threshold: 90`, but a hard threshold overrides the score. +``` + +A spec whose `## Open Questions` reads `None` while a `TBD:` sits under `## Context`, the +second of the two placements #626 measured: + +``` +VERDICT: invalid + +- `open_questions` (required, hard threshold `open_questions_unresolved`): a `TBD:` marker sits in Context, outside Open Questions. Open Questions reads "None" even though that gap is unresolved, so the criterion isn't fulfilled. +- Context says the range filters on issue date, then asks which date it filters on. The spec contradicts itself. +- The other required criteria (target, hard_constraints, non_goals, done_when) are fulfilled, but one unfulfilled required criterion forces invalid. +``` + +Before this change the criterion was ambiguous about its scope, and on the section-only +reading that second spec validated. It now refuses it, and names the reason. + +## What these runs did not measure + +The three thin-request runs were asked for the `## Open Questions` section alone, so they +re-measure the unit, not the placement. Placement rests on the previous round's eight +drafts, each of which carried no marker outside that section. diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/review.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/review.md new file mode 100644 index 000000000..8a675b606 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/review.md @@ -0,0 +1,32 @@ +# Review: spec-open-questions +- **Verdict**: changes-requested +- **Diff**: `origin/next...HEAD` (`a4d185a5`, `3f785340`, `50d023f2`) +- **Axes run**: code, functional, relevancy +- **Date**: 2026_10_07 +- **Findings**: 0 critical, 4 warning, 2 minor + +## Phases +### Phase 1 — the marker has a home +- [x] `tbd-marker.md` names `## Open Questions` as the home and allows one entry per gap — `plugins/aidd-pm/skills/04-spec/references/tbd-marker.md:5,7` +- [x] `spec-template.md` carries `## Open Questions` with its guidance, and `None` is the empty form — `plugins/aidd-pm/skills/04-spec/assets/spec-template.md:32,34` +- [x] `spec-validator.yml` calls a spec with an unresolved question invalid, and no weight or threshold moved — `plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml:45`; weights 25+20+20+25+0+5+5=100, required 90, `pass_threshold` 90, both edits are insertions +- [x] each action's gap step points at the home, and each `## Test` table has the row for it — `actions/01-build.md:19,44`, `actions/02-refine.md:18,28,29` +- [ ] pre-commit is green and the work is committed — partial: the `plan.md` correction is uncommitted (`M` in the working tree), and only part of pre-commit was replayed (`architecture-rules` 351 governed files no violation, `markdown-links` 0 broken in 815 files, `commitlint HEAD~3..HEAD` exit 0, validator YAML parses; no `skill-frontmatter`, `context-imports` or catalog regeneration check) + +## Findings +| Sev | Kind | Phase | Location | Issue | Fix | +| --- | --- | --- | --- | --- | --- | +| 🟡 | rot | 1 | `plugins/aidd-pm/skills/04-spec/actions/01-build.md:19`, `actions/02-refine.md:18`, `assets/spec-template.md:34`, `assets/spec-validator.yml:26`, `references/tbd-marker.md:7`, `SKILL.md:26` | One entry is defined by three units, and the two actions hold the narrowest. The actions trigger on a missing or unanswered **field**; the template and the validator, both new with this change, on an unresolved **decision**; `tbd-marker.md` and the transversal rule on a **gap**. A drafter reading all five picks the broadest. Measured: a request answering target, role, range, retention and the row cap and stating only no non-goals produced six entries (CSV column order, which date the range filters on, invoice scope, the refusal wording, post-window behaviour, who may download). Two of those are arguable `Hard constraints` ambiguities; column order, refusal wording and the post-window message are optional detail that no required section depends on. Under `open_questions_unresolved: invalid` each one now blocks validation, so the unspecified unit does not merely over-ask, it refuses a spec whose required content is complete. | Name one unit everywhere, and **widen** rather than narrow: "an unresolved decision a required section depends on", with optional detail explicitly not an open question. `01-build.md:19` and `02-refine.md:18` move from "field" to that unit; `spec-template.md:34`, `spec-validator.yml:26` and `tbd-marker.md:7` adopt the same words. Do not narrow to "field": #626's measured case is a pending decision inside a `Hard constraints` section that exists (`draft1.md`, `draft2.md`), which a field-level trigger would stop catching, forcing the drafter to drop or invent the value. | +| 🟡 | fit | 1 | `plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml:25-28,40-45` | Ambiguous criterion, flagged rather than rated. `open_questions` reads "listing each unresolved decision once"; read spec-wide, a `TBD:` under `## Context` while `## Open Questions` reads `None` fails it, read section-only it passes, and nothing in the file says which. No hard threshold covers the case and `context` is `required: false`, so on the section-only reading no criterion fails that spec. `Context` is one of the two placements #626 measured, and #626's Expected behaviour names `spec-validator.yml` as needing the matching update ("TBD's home becomes `## Open Questions`"). The required sections are covered indirectly — a vague constraint fails `hard_constraints` — so only the optional ones leak. | Make the exclusivity explicit instead of leaving it to the evaluator: extend `open_questions`'s description with "and no other section carries a marker", or add `marker_outside_open_questions: invalid` to `hard_thresholds`. | +| 🟡 | functional | 1 | `aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md:35` | Criterion 5 ("pre-commit is green and the work is committed") is partial. The Decision 5 rationale correction exists only in the working tree; `git show HEAD:…/plan.md` line 35 still reads "`check-doc-duplication.js` fails a sentence written in two documents", which is false for these paths — that script's `SCANNED_DIRECTORIES` are `docs`, `aidd_docs/memory`, `cli/aidd_docs/memory` plus three README globs (`scripts/check-doc-duplication.js:24-26`), none of which reach `plugins/**`. The candidate diff therefore still ships the rationale the previous round rejected. | Commit the `plan.md` correction, then replay the full pre-commit so the catalog and frontmatter gates are covered too. | +| 🟡 | rot | 1 | `aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md:33`, `phase-1.md:51,72` | `50d023f2` widened the rule to "every other section", but the binding artifact was not followed: Decision 3 still reads "The template's **required** sections carry resolved content only", and `phase-1.md:51,72` still say "no **required** section carries a marker". The shipped text is stricter than the plan that authorised it, so the plan now understates the rule it is the authority for. | Align Decision 3 and `phase-1.md:51,72` with "every other section". | +| 🟢 | fit | 1 | `plugins/aidd-pm/skills/04-spec/actions/02-refine.md:18` | Step 4 removes "every entry the rewrite answered", then lists "each **field** still unanswered". A two-part entry whose first half a finding answered stops being an unanswered field once the section carries content, so nothing re-lists the surviving half: `None` is written and the decision is dropped. Unexercised by the journey — its finding answered both halves of the cap question at once (`refine2.md`). Same root as finding 1. | Scope step 4 to the entry, not the field: re-list every entry a finding did not fully answer, narrowed to what is still open. | +| 🟢 | conform | 1 | `plugins/aidd-pm/skills/04-spec/SKILL.md:26` | #626's Expected behaviour and Affected files both name `SKILL.md`'s Transversal rules; the diff leaves the file untouched. Plan Decision 5 makes the call deliberately and defensibly: the rule names no home, so there is nothing to correct, and one fact belongs in one place. A documented deviation, not a defect — but the issue reads as unmet unless the pull request body says so. | Cite the deviation and its reason in the PR body. | + +## Verification +| Metric | Value | +| --- | --- | +| Verified | 80% (4/5) | +| Files checked | `plugins/aidd-pm/skills/04-spec/SKILL.md`, `actions/01-build.md`, `actions/02-refine.md`, `assets/spec-template.md`, `assets/spec-validator.yml`, `references/tbd-marker.md`, `aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md`, `phase-1.md`, `backlog-link.json`, `plugins/aidd-pm/skills/03-prd/assets/prd-template.md`, `plugins/aidd-pm/CATALOG.md`, `docs/CATALOG.md`, `scripts/check-doc-duplication.js`, `cli/tests/golden/snapshots/framework-build/golden.json`, `cli/tests/fixtures/framework-real/plugins/aidd-pm/skills/05-spec/SKILL.md`, 8 journey outputs under the session scratchpad | +| Unchecked | criterion 5 — `fix` | +| Unplanned | `backlog-link.json` is in the diff but not in phase-1's architecture projection; it names `ai-driven-dev/framework#626` with `written_by: "aidd-dev:01-plan"`, which `01-frame.md` authorises. Evidence limits: placement is well evidenced (8 journey outputs, no marker outside `## Open Questions` in any, the same request drafted twice placed both entries in the same section), but `draft_all2.md`'s `None` does not isolate completeness — its request ends with "No other decision is pending.", an explicit suppressor, and it is n=1 against the n=1 that produced six entries. Phase-1's Test Scope case "a gap is still listed → the validator calls it invalid" and the stray-marker case were never executed; two validator runs would settle both (a spec with one residual entry; a spec whose `## Open Questions` reads `None` with a `TBD:` under `## Context`). The frozen fixture `cli/tests/fixtures/framework-real/.../05-spec/SKILL.md:51,53,72` still carries the old placement-free wording and is correctly untouched: it is hashed by `cli/tests/golden/snapshots/framework-build/golden.json`. | diff --git a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md index b4048ef28..94f019707 100644 --- a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md +++ b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md @@ -5,7 +5,7 @@ A gap is an unresolved decision a required section depends on. What no required section depends on is not a gap, and neither is an implementation detail. -The marker lives in the spec's `## Open Questions` section, nowhere else, anywhere in the spec. +The marker lives in the spec's `## Open Questions` section and nowhere else in the spec. List each gap there as one entry. Every other section carries resolved content only, never a marker. From 717558eb264a93181ef815651d12aec938c3a601 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:05:21 +0200 Subject: [PATCH 07/17] fix(aidd-pm): cut the spec gap rules to the words that change the draft 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 111 ++++-------------- .../skills/04-spec/actions/01-build.md | 4 +- .../skills/04-spec/actions/02-refine.md | 6 +- .../skills/04-spec/assets/spec-template.md | 2 +- .../skills/04-spec/assets/spec-validator.yml | 2 +- .../skills/04-spec/references/tbd-marker.md | 8 +- 6 files changed, 35 insertions(+), 98 deletions(-) diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index bb8dcc3ed..596f763ac 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -1,112 +1,53 @@ # Journey evidence -The phase's Test Scope, run end to end. Nothing in this repository evaluates -`spec-validator.yml`, and no gate validates a prose behaviour change, so each run is a fresh -independent context given the skill text and the task, and nothing else. Pass criteria were -fixed before the runs. No run was repeated to get a better answer. +Nothing here evaluates the validator, and no gate reads prose. So each run is a fresh context +given the skill text and the task. Criteria fixed before running. No run repeated. -## 1 - Thin request, three runs - FAIL +## Placement - pass -The request answers the target, the retention and the row cap, states no non-goals, and -carries no sentence suppressing further questions. The earlier clean result used one, which -is why it proved nothing. +Three drafts, two requests. No marker outside the open questions section in any of them, with +no file naming that heading but the template. + +## Which gaps - pass after the text was cut + +Request answering target, retention and cap, stating no non-goals, suppressing nothing: > Finance users need to export their invoices as CSV. A user picks a date range and gets a > downloadable file. An export stays downloadable for 7 days after it is generated. An export > covering more than 50,000 rows is refused. -Pass criterion: none of the three runs asks a question about implementation or presentation -detail. Measured: five, four and six entries, and two of the three ask which fields the CSV -must carry. Two of the three also ask what the user is told on refusal and after the window -closes. - -Run 1: -```markdown -## Open Questions - -- TBD: Which invoice date does the selected date range filter on (issue date, due date, or payment date), and are both range endpoints inclusive? -- TBD: Which invoices may a finance user export (all invoices in their organization, or a narrower set), and how is "finance user" determined? -- TBD: Does the 50,000-row limit count invoices or invoice line items? -- TBD: When an export is refused for exceeding 50,000 rows, what must the user be told (for example the row count or a prompt to narrow the range)? -- TBD: After the 7-day window ends, must the user be able to see that the export expired, or may it simply disappear? -``` - -Run 2: -```markdown -## Open Questions - -- TBD: Which invoice date does the selected date range filter on (issue date, due date, or payment date)? -- TBD: Does a "row" in the 50,000 limit mean one invoice or one invoice line item? -- TBD: Which fields must each exported CSV row contain? -- TBD: Which invoices may a finance user export (all invoices, or only those within their own organization or entity)? -``` - -Run 3: -```markdown -## Open Questions +Criterion: no run asks about implementation or presentation detail. -- TBD: Which invoice date does the selected range filter on (issue date, due date, or payment date)? -- TBD: What counts as a row toward the 50,000 limit: one invoice, or one invoice line item? -- TBD: Which invoices may a finance user export (all invoices, or only those of their own organization or account)? -- TBD: Which invoice fields must the CSV contain? -- TBD: What must the user see when an export is refused for exceeding 50,000 rows? -- TBD: What must the user see when they try to download an export after its 7 days have passed? -``` +| Text | Entries | Over-ask | +| --- | --- | --- | +| before the cut | 5, 4, 6 | CSV field list in 2 of 3, refusal wording and post-window message in 2 of 3 | +| after the cut | 2, 4, 2 | none in 3 of 3 | -The widened unit did not bound which gaps are raised. It bounds where they are written, and -that holds. `01-build.md` says to list every gap as `tbd-marker.md` defines it, and -`tbd-marker.md` excludes what no required section depends on. A drafter applied the placement -rule in every run and the exclusion in one. +Every surviving entry is a decision a required section depends on: which date the range +filters on, what counts as a row against the cap, who may export. The rule did not change +between the two rows of that table. Only its length did. -## 2 - The measured case, one run - PASS +## The measured case - pass -The two decisions #626 measured, left open: +Retention and the row cap left open, the two #626 measured: both listed. -> We have not decided how long an export stays downloadable once it is generated, nor the -> maximum number of rows a single export may cover. +## The validator - pass -Pass criterion: both are still listed. The widened unit must not lose the case the issue -measured. +Fresh context, the yml and one spec, both cases `phase-1.md` named and nobody had run. They +stand in for the gate #625 brings. -```markdown -## Open Questions - -- TBD: How long does a generated export stay downloadable before it expires? -- TBD: What is the maximum number of rows a single export may cover? -``` - -## 3 - The validator, two runs - PASS - -Each run is a fresh context given `spec-validator.yml` and one spec, asked whether the spec -is valid and why. Both cases are `phase-1.md`'s own edge cases, and neither had ever been -run. They stand in for a gate that arrives with #625. - -A spec carrying one residual `## Open Questions` entry: +A spec carrying one residual entry: ``` VERDICT: invalid -- The hard threshold `open_questions_unresolved` forces invalid. The Open Questions section lists one unresolved gap: "Which invoice date does the selected date range filter on?" -- Target, Hard constraints, Non-goals and Done-when each meet their criteria, and the `TBD:` marker sits only inside Open Questions, which is allowed. -- The score is 90 (stakeholders and context are absent), which meets `pass_threshold: 90`, but a hard threshold overrides the score. +- Hard threshold `open_questions_unresolved`: the Open Questions section holds an unresolved "TBD" item (which invoice date the range filters on). ``` -A spec whose `## Open Questions` reads `None` while a `TBD:` sits under `## Context`, the -second of the two placements #626 measured: +A `None` section with a marker under `## Context`, the second placement #626 measured: ``` VERDICT: invalid -- `open_questions` (required, hard threshold `open_questions_unresolved`): a `TBD:` marker sits in Context, outside Open Questions. Open Questions reads "None" even though that gap is unresolved, so the criterion isn't fulfilled. -- Context says the range filters on issue date, then asks which date it filters on. The spec contradicts itself. -- The other required criteria (target, hard_constraints, non_goals, done_when) are fulfilled, but one unfulfilled required criterion forces invalid. +- `open_questions` fails: the spec says "None" but has a `TBD:` marker in Context. The criterion forbids any TBD marker outside the open questions section. Hard threshold `open_questions_unresolved` forces invalid. ``` - -Before this change the criterion was ambiguous about its scope, and on the section-only -reading that second spec validated. It now refuses it, and names the reason. - -## What these runs did not measure - -The three thin-request runs were asked for the `## Open Questions` section alone, so they -re-measure the unit, not the placement. Placement rests on the previous round's eight -drafts, each of which carried no marker outside that section. diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 7081acc3f..90fae28f8 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -16,7 +16,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the 2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. - PRD path: lift its target, hard constraints, non-goals, and done-when. - Free-form request: map it directly onto the template sections. -3. **Gaps.** List every gap under `## Open Questions` as [tbd-marker.md](../references/tbd-marker.md) defines it, never inside the section it would fill. +3. **Gaps.** List every gap per [tbd-marker.md](../references/tbd-marker.md). 4. **Check.** Confirm every section the validator requires is present. Omit an optional section (stakeholders, context) that has nothing to say rather than emit a placeholder. 5. **Write.** Resolve the feature folder: reuse an existing `aidd_docs/tasks//_/` match for this feature, or create one. Save it there. 6. **Declare.** When the request names a backlog item — a ticket already resolved (e.g. by `/aidd-pm:01-ticket-info`), or a path to its Markdown artefact — and the folder carries no `backlog-link.json` yet, write one there, naming that item on whatever support it lives: @@ -41,7 +41,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with the | The action completes | `spec.md` exists in the feature folder | | The file is validated | every section required by [spec-validator.yml](../assets/spec-validator.yml) is present | | The spec is read back | it carries no library name, framework pattern, or source-file layout | -| A gap exists | it is listed once under `## Open Questions`, and no other section carries a marker | +| A gap exists | it is listed once, in the open questions section only | | Too vague | no write; one clarifying question returned | | The request names a backlog item | the folder's `backlog-link.json` names it, with `written_at` and `written_by` | | The request names none | no `backlog-link.json` is written, and nothing errors | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 9e1d1afe5..217e5fead 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,7 +15,7 @@ The refined spec at the same path, or no change if not written. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. -4. **Gaps.** Remove from `## Open Questions` every entry whose decision the rewrite fully answered, and rewrite one answered in part to the question that remains. Then list each gap still open there as [tbd-marker.md](../references/tbd-marker.md) defines it, never inside the section it would fill. Write `None` when no entry remains. +4. **Gaps.** Drop every entry the rewrite answered in full, rewrite one answered in part to what remains, then list the rest per [tbd-marker.md](../references/tbd-marker.md). 5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. 6. **Verify.** Report what changed as `before -> after`, and the verification result. @@ -25,7 +25,7 @@ The refined spec at the same path, or no change if not written. | --- | --- | | The action completes | the spec still exists at its path with every section required by [spec-validator.yml](../assets/spec-validator.yml) | | A finding is resolved | the spec changed at the section it names | -| A finding resolves an open question | its entry is gone from `## Open Questions`, and the section reads `None` when it was the last | -| A finding cannot be resolved | it is listed once under `## Open Questions`, and no other section carries a marker | +| A finding resolves a gap | its entry is gone, and the section is empty when it was the last | +| A finding cannot be resolved | it is listed once, in the open questions section only | | A write happened | the result reports the stable identity, `before -> after` fields, and verification result | | No write happened | the result states that no persisted change occurred | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index 72ef744b7..7a3efec41 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -31,7 +31,7 @@ ## Open Questions -`; what a gap is, `references/tbd-marker.md` defines. Write `None` when nothing is left. Resolve every entry before the spec validates.> + ## Stakeholders (optional) diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index 61cf07dd2..ef9f262d8 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -23,7 +23,7 @@ criteria: weight: 25 - id: open_questions - description: An Open Questions section present, listing each gap (an unresolved decision a required section depends on, per references/tbd-marker.md) once as a precise question, or reading None. Spec-wide - no `TBD:` marker appears anywhere in the spec outside the Open Questions section, in any section including Context. + description: Every gap listed once in the open questions section, each a precise question, or None when there is none. Spec-wide, no TBD marker sits in any other section. required: true weight: 0 diff --git a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md index 94f019707..2104a7eda 100644 --- a/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md +++ b/plugins/aidd-pm/skills/04-spec/references/tbd-marker.md @@ -2,10 +2,6 @@ `TBD: ` -A gap is an unresolved decision a required section depends on. -What no required section depends on is not a gap, and neither is an implementation detail. +A gap is an unresolved decision a required section depends on. Nothing else is one. -The marker lives in the spec's `## Open Questions` section and nowhere else in the spec. - -List each gap there as one entry. -Every other section carries resolved content only, never a marker. +Every gap goes to the template's open questions section, once, and to no other section. From e7959a06009df734f066d4eca7b6fdfb902c6222 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:10:25 +0200 Subject: [PATCH 08/17] fix(aidd-pm): say the refine gap step once It said answered twice and rewrite twice. Three clauses, three behaviours, no word spent on a second pass. Refs ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- plugins/aidd-pm/skills/04-spec/actions/02-refine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 217e5fead..580e57273 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,7 +15,7 @@ The refined spec at the same path, or no change if not written. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. -4. **Gaps.** Drop every entry the rewrite answered in full, rewrite one answered in part to what remains, then list the rest per [tbd-marker.md](../references/tbd-marker.md). +4. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). 5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. 6. **Verify.** Report what changed as `before -> after`, and the verification result. From 7a40f5981e4606a08504eaf4e8ae3674e2b9dc20 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:16:42 +0200 Subject: [PATCH 09/17] refactor(aidd-pm): cut the spec actions to the words that change the 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../skills/04-spec/actions/01-build.md | 26 +++++++++---------- .../skills/04-spec/actions/02-refine.md | 10 +++---- .../skills/04-spec/assets/spec-template.md | 4 +-- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 90fae28f8..5c4b4b818 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -8,18 +8,16 @@ A free-form request, or a path to an existing PRD. A feature name for the folder ## Output -The path to `spec.md` in the feature folder, drafted from the template, with the ambiguities and assumptions noted, or no write when the request is too vague. +The path to `spec.md` in the feature folder, drafted from the template, with its gaps listed. No write when the request is too vague. ## Process -1. **Qualify.** When the request is too vague to draft anything useful, stop and ask for a clearer one. -2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. - - PRD path: lift its target, hard constraints, non-goals, and done-when. - - Free-form request: map it directly onto the template sections. +1. **Qualify.** Stop and ask for a clearer request when this one is too vague to draft. +2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. From a PRD, lift its target, hard constraints, non-goals and done-when. 3. **Gaps.** List every gap per [tbd-marker.md](../references/tbd-marker.md). -4. **Check.** Confirm every section the validator requires is present. Omit an optional section (stakeholders, context) that has nothing to say rather than emit a placeholder. -5. **Write.** Resolve the feature folder: reuse an existing `aidd_docs/tasks//_/` match for this feature, or create one. Save it there. -6. **Declare.** When the request names a backlog item — a ticket already resolved (e.g. by `/aidd-pm:01-ticket-info`), or a path to its Markdown artefact — and the folder carries no `backlog-link.json` yet, write one there, naming that item on whatever support it lives: +4. **Check.** Every required section present. Omit an optional one with nothing to say, never a placeholder. +5. **Write.** Save it in `aidd_docs/tasks//_/`, reusing this feature's folder when one exists. +6. **Declare.** When the request names a backlog item and the folder carries no `backlog-link.json`, write one there: ```json { @@ -29,10 +27,10 @@ The path to `spec.md` in the feature folder, drafted from the template, with the } ``` - `backlog` is the one field: a forge reference (`"owner/repo#123"`) or a project-relative Markdown path (`"aidd_docs/backlog/tasks/x.md"`), never both and never a second field for the other support. `written_at` is now, in ISO 8601 UTC; `written_by` is this skill's own name, verbatim. + `backlog` is one field, a forge reference or a project-relative Markdown path, never both. `written_at` is now, ISO 8601 UTC. - When the request names no backlog item, write nothing — a folder with no declaration is a normal state, never an error. When `backlog-link.json` already exists, leave it untouched: it is correctable by hand, and a later run must never overwrite a correction. -7. **Return.** Surface its path and the notes. + Write nothing when the request names none: an undeclared folder is normal. Never overwrite the file, so a correction by hand survives. +7. **Return.** Its path and its gaps. ## Test @@ -43,6 +41,6 @@ The path to `spec.md` in the feature folder, drafted from the template, with the | The spec is read back | it carries no library name, framework pattern, or source-file layout | | A gap exists | it is listed once, in the open questions section only | | Too vague | no write; one clarifying question returned | -| The request names a backlog item | the folder's `backlog-link.json` names it, with `written_at` and `written_by` | -| The request names none | no `backlog-link.json` is written, and nothing errors | -| `backlog-link.json` already exists | it is left unchanged, even when the request names a different item | +| The request names a backlog item | `backlog-link.json` names it, with `written_at` and `written_by` | +| It names none | no file is written, and nothing errors | +| The file exists | it is left unchanged, even for a different item | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 580e57273..7357baffd 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -8,22 +8,22 @@ The path to the current spec, and the findings to address, a list or free text. ## Output -The refined spec at the same path, or no change if not written. +The refined spec at the same path, or no write. ## Process 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. -3. **Rewrite.** Apply each finding in place: clarify wording, add missing fields, remove invalid claims. Leave untouched sections as they are. +3. **Rewrite.** Apply each finding in place. Leave every other section as it is. 4. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). -5. **Check.** Confirm every section the validator requires is present, then overwrite the spec at its path. -6. **Verify.** Report what changed as `before -> after`, and the verification result. +5. **Check.** Every required section present, then overwrite the spec. +6. **Verify.** Report each change as `before -> after`, with the result. ## Test | Case | Pass | | --- | --- | -| The action completes | the spec still exists at its path with every section required by [spec-validator.yml](../assets/spec-validator.yml) | +| The action completes | the spec is still at its path, with every section [spec-validator.yml](../assets/spec-validator.yml) requires | | A finding is resolved | the spec changed at the section it names | | A finding resolves a gap | its entry is gone, and the section is empty when it was the last | | A finding cannot be resolved | it is listed once, in the open questions section only | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index 7a3efec41..ea7e3dc12 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -17,7 +17,7 @@ ## Non-goals - + - - @@ -41,4 +41,4 @@ ## Context (optional) - + From 83ba31038313ba687a6f464bc9d3e53389344b7d Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:25:42 +0200 Subject: [PATCH 10/17] fix(aidd-pm): put back the three clauses the cut should have kept 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 72 ++++++++++--------- .../2026_10_07_spec-open-questions/phase-1.md | 10 +-- .../2026_10_07_spec-open-questions/plan.md | 4 +- .../skills/04-spec/actions/01-build.md | 2 +- .../skills/04-spec/actions/02-refine.md | 6 +- .../skills/04-spec/assets/spec-validator.yml | 2 +- 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 596f763ac..16e60768b 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -1,53 +1,61 @@ # Journey evidence -Nothing here evaluates the validator, and no gate reads prose. So each run is a fresh context -given the skill text and the task. Criteria fixed before running. No run repeated. +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 -Three drafts, two requests. No marker outside the open questions section in any of them, with -no file naming that heading but the template. +22 drafts, three requests, both text versions. Markers outside the open questions section: +**0**. Heading exact: **22/22**, though only the template names it. -## Which gaps - pass after the text was cut +## Driving to zero - pass -Request answering target, retention and cap, stating no non-goals, suppressing nothing: +A request answering every required field. Entries listed: -> Finance users need to export their invoices as CSV. A user picks a date range and gets a -> downloadable file. An export stays downloadable for 7 days after it is generated. An export -> covering more than 50,000 rows is refused. +| text | runs | +| --- | --- | +| shipped | 0, 0, 0 | +| before the cut | 3, 2, 0 | -Criterion: no run asks about implementation or presentation detail. +The shipped text reaches `None` on a resolved request three times out of three. -| Text | Entries | Over-ask | -| --- | --- | --- | -| before the cut | 5, 4, 6 | CSV field list in 2 of 3, refusal wording and post-window message in 2 of 3 | -| after the cut | 2, 4, 2 | none in 3 of 3 | - -Every surviving entry is a decision a required section depends on: which date the range -filters on, what counts as a row against the cap, who may export. The rule did not change -between the two rows of that table. Only its length did. +On the thin request that answers the same fields but states no non-goals, the shipped text +lists 2, 3, 2, 2, 4 entries and the pre-cut text 3, 4, 3, 4, 5. **No causal claim is made.** +The cut changed several clauses at once, so length is not isolated, and the ranges overlap. ## The measured case - pass -Retention and the row cap left open, the two #626 measured: both listed. +Retention and the row cap left open, the two #626 measured: **2, 2, 2** entries, both versions. -## The validator - pass +## Refine - pass -Fresh context, the yml and one spec, both cases `phase-1.md` named and nobody had run. They -stand in for the gate #625 brings. +| 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 | -A spec carrying one residual entry: +The last two rows failed before this round. Cutting `None` from step 4 produced an empty +section 3/3, which the validator then refuses, and refine never linked the template, so it +wrote `## Open questions` 3/3. + +## The validator - pass -``` -VERDICT: invalid +Fresh context, the yml and one spec. Stands in for the gate #625 brings. -- Hard threshold `open_questions_unresolved`: the Open Questions section holds an unresolved "TBD" item (which invoice date the range filters on). -``` +| Spec | Verdict | +| --- | --- | +| one residual entry | invalid 3/3 | +| `None`, with a marker 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 | -A `None` section with a marker under `## Context`, the second placement #626 measured: +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. -``` -VERDICT: invalid +## Not measured -- `open_questions` fails: the spec says "None" but has a `TBD:` marker in Context. The criterion forbids any TBD marker outside the open questions section. Hard threshold `open_questions_unresolved` forces invalid. -``` +The installed skill, routed through `SKILL.md` by a real session. One model. And no judge +classified the entries, so which gaps a draft raises rests on reading them. diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md index 0654503fd..2f3eb19a8 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md @@ -47,7 +47,7 @@ journey > `tbd-marker.md` carries the form, the home, and the one-entry rule. 1. Keep the form `TBD: `. -2. Say the home is the spec's `## Open Questions` section. +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. @@ -68,9 +68,9 @@ journey ### `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 under `## Open Questions` per `tbd-marker.md`, never inside the section it would fill. +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 under `## Open Questions`, and no other section carries a marker. +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. @@ -82,8 +82,8 @@ journey ## Test acceptance criteria | Task | Acceptance criteria | | --- | --- | -| 1 | `tbd-marker.md` names `## Open Questions` as the home and allows one entry per gap | -| 2 | `spec-template.md` carries `## Open Questions` with its guidance, and `None` is the empty form | +| 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 | diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md index 0981ab64a..a4986b853 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md @@ -28,9 +28,9 @@ status: implemented ## Decisions | Decision | Why | | --- | --- | -| The home is `## Open Questions`, a required section of the template | it mirrors `prd-template.md`, and the issue names it | +| 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 `## Open Questions` 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 | +| 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 once: a gap is an unresolved decision a required section depends on | 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, and neither is an implementation detail | 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 residual question invalidates the spec through `hard_thresholds`, and the section is checked by a criterion carrying no weight | 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 | diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 5c4b4b818..c69e8d2bc 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -27,7 +27,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with its } ``` - `backlog` is one field, a forge reference or a project-relative Markdown path, never both. `written_at` is now, ISO 8601 UTC. + `backlog` is one field, a forge reference or a project-relative Markdown path, never both. `written_at` is now, ISO 8601 UTC; `written_by` is this skill's name. Write nothing when the request names none: an undeclared folder is normal. Never overwrite the file, so a correction by hand survives. 7. **Return.** Its path and its gaps. diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 7357baffd..459612949 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -15,8 +15,8 @@ The refined spec at the same path, or no write. 1. **Load.** Read the spec and the findings. 2. **Map.** Pair each finding with the section it touches. 3. **Rewrite.** Apply each finding in place. Leave every other section as it is. -4. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). -5. **Check.** Every required section present, then overwrite the spec. +4. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). `None` when none remains. +5. **Check.** Every section [spec-template.md](../assets/spec-template.md) requires present, then overwrite the spec. 6. **Verify.** Report each change as `before -> after`, with the result. ## Test @@ -25,7 +25,7 @@ The refined spec at the same path, or no write. | --- | --- | | The action completes | the spec is still at its path, with every section [spec-validator.yml](../assets/spec-validator.yml) requires | | A finding is resolved | the spec changed at the section it names | -| A finding resolves a gap | its entry is gone, and the section is empty when it was the last | +| A finding resolves a gap | its entry is gone, and the section reads `None` when it was the last | | A finding cannot be resolved | it is listed once, in the open questions section only | | A write happened | the result reports the stable identity, `before -> after` fields, and verification result | | No write happened | the result states that no persisted change occurred | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index ef9f262d8..cb4ddab05 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -23,7 +23,7 @@ criteria: weight: 25 - id: open_questions - description: Every gap listed once in the open questions section, each a precise question, or None when there is none. Spec-wide, no TBD marker sits in any other section. + description: Every gap listed once in the open questions section, each a precise question, or None when there is none. A gap is an unresolved decision a required section depends on, per references/tbd-marker.md. Spec-wide, no `TBD:` marker sits in any other section. required: true weight: 0 From 03e1494b77e248e39c0e997b26fdcc2e2c7ead7a Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:45:00 +0200 Subject: [PATCH 11/17] docs(aidd-pm): record what the installed tree does, and what it refuses 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 16e60768b..3d4d9861b 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -55,7 +55,38 @@ Fresh context, the yml and one spec. Stands in for the gate #625 brings. 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 installed skill, routed through `SKILL.md` by a real session. One model. And no judge -classified the entries, so which gaps a draft raises rests on reading them. +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. From 667018768f8434020379a3a002fc816856f01aad Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 14:52:43 +0200 Subject: [PATCH 12/17] fix(aidd-pm): a gap written elsewhere refuses the spec, a gap still open 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 34 ++++---- .../2026_10_07_spec-open-questions/phase-1.md | 2 +- .../2026_10_07_spec-open-questions/phase-2.md | 77 +++++++++++++++++++ .../2026_10_07_spec-open-questions/plan.md | 7 +- .../skills/04-spec/actions/01-build.md | 6 +- .../skills/04-spec/actions/02-refine.md | 2 +- .../skills/04-spec/assets/spec-template.md | 2 +- .../skills/04-spec/assets/spec-validator.yml | 4 +- 8 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-2.md diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 3d4d9861b..9f94bdbb8 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -9,20 +9,18 @@ fixed before running, no run repeated, every count below mechanical. 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 - pass +## Driving to zero - not reached, and nothing now refuses a spec for it -A request answering every required field. Entries listed: +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. -| text | runs | -| --- | --- | -| shipped | 0, 0, 0 | -| before the cut | 3, 2, 0 | - -The shipped text reaches `None` on a resolved request three times out of three. +**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). -On the thin request that answers the same fields but states no non-goals, the shipped text -lists 2, 3, 2, 2, 4 entries and the pre-cut text 3, 4, 3, 4, 5. **No causal claim is made.** -The cut changed several clauses at once, so length is not isolated, and the ranges overlap. +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 @@ -37,9 +35,10 @@ Retention and the row cap left open, the two #626 measured: **2, 2, 2** entries, | answers the last | `None`, 3/3 | | spec carries no such section | the section is added, heading exact, 3/3 | -The last two rows failed before this round. Cutting `None` from step 4 produced an empty -section 3/3, which the validator then refuses, and refine never linked the template, so it -wrote `## Open questions` 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 @@ -47,11 +46,14 @@ Fresh context, the yml and one spec. Stands in for the gate #625 brings. | Spec | Verdict | | --- | --- | -| one residual entry | invalid 3/3 | -| `None`, with a marker under `## Context` | invalid 3/3 | +| 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. diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md index 2f3eb19a8..fc43aa6c5 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-1.md @@ -35,7 +35,7 @@ journey 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 its required sections => Target, Hard constraints, Non-goals and Done-when carry resolved content only: 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 diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-2.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-2.md new file mode 100644 index 000000000..50f27d526 --- /dev/null +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/phase-2.md @@ -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 | diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md index a4986b853..c4944c9f0 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/plan.md @@ -14,6 +14,7 @@ status: implemented | # | 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 | @@ -31,7 +32,7 @@ status: implemented | 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 once: a gap is an unresolved decision a required section depends on | 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, and neither is an implementation detail | 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 residual question invalidates the spec through `hard_thresholds`, and the section is checked by a criterion carrying no weight | 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 | +| 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 | diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index c69e8d2bc..18ceee2ec 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -15,9 +15,9 @@ The path to `spec.md` in the feature folder, drafted from the template, with its 1. **Qualify.** Stop and ask for a clearer request when this one is too vague to draft. 2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. From a PRD, lift its target, hard constraints, non-goals and done-when. 3. **Gaps.** List every gap per [tbd-marker.md](../references/tbd-marker.md). -4. **Check.** Every required section present. Omit an optional one with nothing to say, never a placeholder. +4. **Check.** Every section [spec-template.md](../assets/spec-template.md) requires present. Omit an optional one with nothing to say, never a placeholder. 5. **Write.** Save it in `aidd_docs/tasks//_/`, reusing this feature's folder when one exists. -6. **Declare.** When the request names a backlog item and the folder carries no `backlog-link.json`, write one there: +6. **Declare.** A backlog item is a ticket already resolved, or a path to its Markdown artefact, never raw text. When the request names one and the folder carries no `backlog-link.json`, write one there: ```json { @@ -27,7 +27,7 @@ The path to `spec.md` in the feature folder, drafted from the template, with its } ``` - `backlog` is one field, a forge reference or a project-relative Markdown path, never both. `written_at` is now, ISO 8601 UTC; `written_by` is this skill's name. + `backlog` is the one field, a forge reference (`owner/repo#123`) or a project-relative Markdown path, never both and never a second field for the other support. `written_at` is now, ISO 8601 UTC; `written_by` is this skill's name. Write nothing when the request names none: an undeclared folder is normal. Never overwrite the file, so a correction by hand survives. 7. **Return.** Its path and its gaps. diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index 459612949..dff193e0e 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -23,7 +23,7 @@ The refined spec at the same path, or no write. | Case | Pass | | --- | --- | -| The action completes | the spec is still at its path, with every section [spec-validator.yml](../assets/spec-validator.yml) requires | +| The action completes | the spec is still at its path, with every section [spec-template.md](../assets/spec-template.md) requires | | A finding is resolved | the spec changed at the section it names | | A finding resolves a gap | its entry is gone, and the section reads `None` when it was the last | | A finding cannot be resolved | it is listed once, in the open questions section only | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index ea7e3dc12..1de490532 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -31,7 +31,7 @@ ## Open Questions - + ## Stakeholders (optional) diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index cb4ddab05..206c10948 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -23,7 +23,7 @@ criteria: weight: 25 - id: open_questions - description: Every gap listed once in the open questions section, each a precise question, or None when there is none. A gap is an unresolved decision a required section depends on, per references/tbd-marker.md. Spec-wide, no `TBD:` marker sits in any other section. + description: Every gap listed once in the open questions section, each a precise question, or None when there is none. A gap is an unresolved decision a required section depends on, per references/tbd-marker.md. Spec-wide, no `TBD:` marker sits in any other section. A listed gap does not invalidate the spec; one written anywhere else does. required: true weight: 0 @@ -42,7 +42,7 @@ hard_thresholds: target_unclear_or_multiple_targets: invalid done_when_unverifiable: invalid contains_implementation_details: invalid - open_questions_unresolved: invalid + open_questions_outside_their_section: invalid scoring: pass_threshold: 90 From 3cb10eef865ef95505097dd45f4b60f5fc9c7e40 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 15:03:50 +0200 Subject: [PATCH 13/17] refactor(aidd-pm): the spec skill keeps only the words that change a 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 18 ++++++++++++ plugins/aidd-pm/CATALOG.md | 1 + plugins/aidd-pm/skills/04-spec/SKILL.md | 1 - .../skills/04-spec/actions/01-build.md | 29 +++++-------------- .../skills/04-spec/actions/02-refine.md | 17 +++++------ .../skills/04-spec/assets/spec-template.md | 21 ++++++-------- .../skills/04-spec/assets/spec-validator.yml | 22 ++++++-------- .../skills/04-spec/references/backlog-link.md | 19 ++++++++++++ ...a-backlog-link-the-reader-can-read.test.js | 12 ++++---- 9 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 plugins/aidd-pm/skills/04-spec/references/backlog-link.md diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 9f94bdbb8..715400889 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -92,3 +92,21 @@ outright. So the Skill tool's own registration is still unmeasured, and only it 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. + diff --git a/plugins/aidd-pm/CATALOG.md b/plugins/aidd-pm/CATALOG.md index 027f408b7..f2460ab36 100644 --- a/plugins/aidd-pm/CATALOG.md +++ b/plugins/aidd-pm/CATALOG.md @@ -77,6 +77,7 @@ Auto-generated index of skills, agents, references and assets shipped by the `ai | `actions` | [01-build.md](skills/04-spec/actions/01-build.md) | - | | `actions` | [02-refine.md](skills/04-spec/actions/02-refine.md) | - | | `assets` | [spec-template.md](skills/04-spec/assets/spec-template.md) | - | +| `references` | [backlog-link.md](skills/04-spec/references/backlog-link.md) | - | | `references` | [tbd-marker.md](skills/04-spec/references/tbd-marker.md) | - | | `-` | [SKILL.md](skills/04-spec/SKILL.md) | `Generate or refine a spec, a feature's immutable contract, from a request, a PRD, or review findings. Use when the user wants to draft or refine a spec. Not for writing code, a full PRD, or changing a locked spec.` | diff --git a/plugins/aidd-pm/skills/04-spec/SKILL.md b/plugins/aidd-pm/skills/04-spec/SKILL.md index 10afd34c0..ec9127be2 100644 --- a/plugins/aidd-pm/skills/04-spec/SKILL.md +++ b/plugins/aidd-pm/skills/04-spec/SKILL.md @@ -26,5 +26,4 @@ Run the flow above. Read only the next action file. - Never invent; mark every gap instead of guessing. - Never explore the codebase. - Hold intent, never implementation: solution-agnostic, no how, few acceptance criteria. -- Keep it readable: clear headers, bulleted criteria, explicit non-goals. - Immutable once validated: never rewrite a locked spec. diff --git a/plugins/aidd-pm/skills/04-spec/actions/01-build.md b/plugins/aidd-pm/skills/04-spec/actions/01-build.md index 18ceee2ec..e12ef7caf 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/01-build.md +++ b/plugins/aidd-pm/skills/04-spec/actions/01-build.md @@ -4,43 +4,28 @@ Draft a fresh spec from a free-form request, or by lifting fields from an existi ## Input -A free-form request, or a path to an existing PRD. A feature name for the folder, derived from the request when absent. +A free-form request, or a path to a PRD. ## Output -The path to `spec.md` in the feature folder, drafted from the template, with its gaps listed. No write when the request is too vague. +The path to `spec.md`, or no write. ## Process 1. **Qualify.** Stop and ask for a clearer request when this one is too vague to draft. -2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md), dropping any implementation detail. From a PRD, lift its target, hard constraints, non-goals and done-when. +2. **Source.** Map the input onto [spec-template.md](../assets/spec-template.md). From a PRD, lift its target, hard constraints, non-goals and done-when. 3. **Gaps.** List every gap per [tbd-marker.md](../references/tbd-marker.md). -4. **Check.** Every section [spec-template.md](../assets/spec-template.md) requires present. Omit an optional one with nothing to say, never a placeholder. +4. **Check.** No required section missing. Omit an optional one with nothing to say, never a placeholder. 5. **Write.** Save it in `aidd_docs/tasks//_/`, reusing this feature's folder when one exists. -6. **Declare.** A backlog item is a ticket already resolved, or a path to its Markdown artefact, never raw text. When the request names one and the folder carries no `backlog-link.json`, write one there: - - ```json - { - "backlog": "owner/repo#123", - "written_at": "2026-08-21T09:00:00Z", - "written_by": "aidd-pm:04-spec" - } - ``` - - `backlog` is the one field, a forge reference (`owner/repo#123`) or a project-relative Markdown path, never both and never a second field for the other support. `written_at` is now, ISO 8601 UTC; `written_by` is this skill's name. - - Write nothing when the request names none: an undeclared folder is normal. Never overwrite the file, so a correction by hand survives. +6. **Declare.** Declare the request's backlog item per [backlog-link.md](../references/backlog-link.md). 7. **Return.** Its path and its gaps. ## Test | Case | Pass | | --- | --- | -| The action completes | `spec.md` exists in the feature folder | -| The file is validated | every section required by [spec-validator.yml](../assets/spec-validator.yml) is present | +| The action completes | `spec.md` exists, with every section [spec-template.md](../assets/spec-template.md) requires | | The spec is read back | it carries no library name, framework pattern, or source-file layout | | A gap exists | it is listed once, in the open questions section only | | Too vague | no write; one clarifying question returned | -| The request names a backlog item | `backlog-link.json` names it, with `written_at` and `written_by` | -| It names none | no file is written, and nothing errors | -| The file exists | it is left unchanged, even for a different item | +| A backlog item is named, or none is, or the file is already there | each handled per [backlog-link.md](../references/backlog-link.md) | diff --git a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md index dff193e0e..860bb0c62 100644 --- a/plugins/aidd-pm/skills/04-spec/actions/02-refine.md +++ b/plugins/aidd-pm/skills/04-spec/actions/02-refine.md @@ -4,7 +4,7 @@ Rewrite an existing spec in place to address review findings. ## Input -The path to the current spec, and the findings to address, a list or free text. +The spec's path, and the findings. ## Output @@ -12,12 +12,11 @@ The refined spec at the same path, or no write. ## Process -1. **Load.** Read the spec and the findings. -2. **Map.** Pair each finding with the section it touches. -3. **Rewrite.** Apply each finding in place. Leave every other section as it is. -4. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). `None` when none remains. -5. **Check.** Every section [spec-template.md](../assets/spec-template.md) requires present, then overwrite the spec. -6. **Verify.** Report each change as `before -> after`, with the result. +1. **Map.** Pair each finding with the section it touches. +2. **Rewrite.** Apply each finding in place. Leave every other section as it is. +3. **Gaps.** Drop a resolved entry, narrow a half-answered one, list the rest per [tbd-marker.md](../references/tbd-marker.md). `None` when none remains. +4. **Check.** No required section missing, then overwrite the spec. +5. **Verify.** Report each change as `before -> after`, with the result. ## Test @@ -27,5 +26,5 @@ The refined spec at the same path, or no write. | A finding is resolved | the spec changed at the section it names | | A finding resolves a gap | its entry is gone, and the section reads `None` when it was the last | | A finding cannot be resolved | it is listed once, in the open questions section only | -| A write happened | the result reports the stable identity, `before -> after` fields, and verification result | -| No write happened | the result states that no persisted change occurred | +| A write happened | the result reports the path and each `before -> after` | +| No write | the result says nothing was written | diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index 1de490532..6d99a72c9 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -4,30 +4,27 @@ ## Target - + > Example: "Allow authenticated users to export their account data as a downloadable JSON file." ## Hard constraints - + -- -- +- ## Non-goals -- -- +- ## Done-when - + -- -- +- ## Open Questions @@ -35,9 +32,9 @@ ## Stakeholders (optional) -- Decider: -- Owner: -- Consumer: +- Decider: +- Owner: +- Consumer: ## Context (optional) diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml index 206c10948..2cb016ef2 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-validator.yml @@ -1,39 +1,38 @@ -# Checklist a spec is validated against. -# Each criterion is independently scored. Required criteria must be `fulfilled` for the spec to pass. +# Checklist a spec is validated against. Each criterion is scored on its own. criteria: - id: target - description: A single clear sentence stating what the system must achieve. Unambiguous, verifiable, single target (not multiple disguised as one). + description: One sentence, unambiguous and verifiable, naming a single target. required: true weight: 25 - id: hard_constraints - description: Non-negotiable constraints listed explicitly. Each constraint is concrete and testable, not vague. + description: Non-negotiable constraints, each concrete and testable. required: true weight: 20 - id: non_goals - description: Out-of-scope items listed explicitly. Reader knows what NOT to build. + description: Out-of-scope items listed, so a reader knows what not to build. required: true weight: 20 - id: done_when - description: Completion conditions listed, each an observable outcome a user or the system can be seen producing. Verifiable by observation, not a command and not a feeling. + description: Completion conditions, each an observable outcome, never a command. required: true weight: 25 - id: open_questions - description: Every gap listed once in the open questions section, each a precise question, or None when there is none. A gap is an unresolved decision a required section depends on, per references/tbd-marker.md. Spec-wide, no `TBD:` marker sits in any other section. A listed gap does not invalidate the spec; one written anywhere else does. + description: Every gap listed once in the open questions section as a precise question, or None. A gap is an unresolved decision a required section depends on. A listed gap is valid; a marker written in any other section is not. required: true weight: 0 - id: stakeholders - description: Decider, owner, and consumer roles identified. Optional but encouraged for non-trivial specs. + description: Decider, owner and consumer named. required: false weight: 5 - id: context - description: Relevant background or links provided when needed for downstream decisions. + description: Background or links a downstream decision needs. required: false weight: 5 @@ -46,7 +45,4 @@ hard_thresholds: scoring: pass_threshold: 90 - notes: | - A spec is valid when every required criterion is fulfilled AND the weighted score - is at or above the pass_threshold. Hard thresholds force `invalid` regardless of score. - Implementation details (libraries, patterns, file layout) belong to the plan, not the spec. + notes: Valid when every required criterion is fulfilled and the weighted score reaches pass_threshold. A hard threshold forces invalid whatever the score. diff --git a/plugins/aidd-pm/skills/04-spec/references/backlog-link.md b/plugins/aidd-pm/skills/04-spec/references/backlog-link.md new file mode 100644 index 000000000..cd35e0785 --- /dev/null +++ b/plugins/aidd-pm/skills/04-spec/references/backlog-link.md @@ -0,0 +1,19 @@ +# Backlog link + +A backlog item is a ticket already resolved, or a path to its Markdown artefact. Never raw text. + +Declare it in `backlog-link.json`, in the feature folder: + +```json +{ + "backlog": "owner/repo#123", + "written_at": "2026-08-21T09:00:00Z", + "written_by": "aidd-pm:04-spec" +} +``` + +`backlog` is the one field: a forge reference or a project-relative Markdown path, never both. +`written_at` is now, ISO 8601 UTC. `written_by` is the skill that wrote it. + +No item named, no file: an undeclared folder is normal. +Never overwrite one, so a correction by hand survives. diff --git a/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js b/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js index a883c622b..f126c735a 100644 --- a/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js +++ b/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js @@ -57,20 +57,20 @@ describe("every backlog declaration in this repository is one the report can rea }); /** The two skills that teach the file are the only things that decide what gets written, - * so they have to teach the same three names — and the same three this test asks for. + * so they have to teach the same three names, wherever inside each skill the lesson lives — and the same three this test asks for. * Read from the skills rather than trusted: a taught shape drifting away from the reader * is exactly what produced the two unreadable files, and a guard restating the fields * without checking the lesson would have stayed green through it. */ it("is the shape both skills that write it actually teach", () => { - const TEACHING_ACTIONS = [ - "plugins/aidd-pm/skills/04-spec/actions/01-build.md", + const TEACHING_FILES = [ + "plugins/aidd-pm/skills/04-spec/references/backlog-link.md", "plugins/aidd-dev/skills/01-plan/actions/04-plan.md", ]; - for (const action of TEACHING_ACTIONS) { - const text = fs.readFileSync(path.join(ROOT, action), "utf8"); + for (const file of TEACHING_FILES) { + const text = fs.readFileSync(path.join(ROOT, file), "utf8"); for (const field of REQUIRED_FIELDS) { - assert.ok(text.includes(`"${field}"`), `${action} must teach the field "${field}"`); + assert.ok(text.includes(`"${field}"`), `${file} must teach the field "${field}"`); } } }); From 4771989d6d4865f31152a7d85c4778c9e3b2b5c2 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 15:11:45 +0200 Subject: [PATCH 14/17] refactor(aidd-pm): the backlog declaration shape becomes an asset 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10_07_spec-open-questions/journey.md | 15 +++++++++++++++ plugins/aidd-pm/CATALOG.md | 1 + .../04-spec/assets/backlog-link-template.json | 5 +++++ .../skills/04-spec/references/backlog-link.md | 11 ++--------- .../a-backlog-link-the-reader-can-read.test.js | 2 +- 5 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 plugins/aidd-pm/skills/04-spec/assets/backlog-link-template.json diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 715400889..35a78396b 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -110,3 +110,18 @@ reference added. Four sessions through the installed tree and six validator verd 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. + diff --git a/plugins/aidd-pm/CATALOG.md b/plugins/aidd-pm/CATALOG.md index f2460ab36..8c2029194 100644 --- a/plugins/aidd-pm/CATALOG.md +++ b/plugins/aidd-pm/CATALOG.md @@ -76,6 +76,7 @@ Auto-generated index of skills, agents, references and assets shipped by the `ai |-------|------|---| | `actions` | [01-build.md](skills/04-spec/actions/01-build.md) | - | | `actions` | [02-refine.md](skills/04-spec/actions/02-refine.md) | - | +| `assets` | [backlog-link-template.json](skills/04-spec/assets/backlog-link-template.json) | - | | `assets` | [spec-template.md](skills/04-spec/assets/spec-template.md) | - | | `references` | [backlog-link.md](skills/04-spec/references/backlog-link.md) | - | | `references` | [tbd-marker.md](skills/04-spec/references/tbd-marker.md) | - | diff --git a/plugins/aidd-pm/skills/04-spec/assets/backlog-link-template.json b/plugins/aidd-pm/skills/04-spec/assets/backlog-link-template.json new file mode 100644 index 000000000..3fd848d37 --- /dev/null +++ b/plugins/aidd-pm/skills/04-spec/assets/backlog-link-template.json @@ -0,0 +1,5 @@ +{ + "backlog": "owner/repo#123", + "written_at": "2026-08-21T09:00:00Z", + "written_by": "aidd-pm:04-spec" +} diff --git a/plugins/aidd-pm/skills/04-spec/references/backlog-link.md b/plugins/aidd-pm/skills/04-spec/references/backlog-link.md index cd35e0785..24b966ffa 100644 --- a/plugins/aidd-pm/skills/04-spec/references/backlog-link.md +++ b/plugins/aidd-pm/skills/04-spec/references/backlog-link.md @@ -2,15 +2,8 @@ A backlog item is a ticket already resolved, or a path to its Markdown artefact. Never raw text. -Declare it in `backlog-link.json`, in the feature folder: - -```json -{ - "backlog": "owner/repo#123", - "written_at": "2026-08-21T09:00:00Z", - "written_by": "aidd-pm:04-spec" -} -``` +Declare it in the feature folder as `backlog-link.json`, from +[backlog-link-template.json](../assets/backlog-link-template.json). `backlog` is the one field: a forge reference or a project-relative Markdown path, never both. `written_at` is now, ISO 8601 UTC. `written_by` is the skill that wrote it. diff --git a/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js b/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js index f126c735a..5e388cbed 100644 --- a/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js +++ b/scripts/__tests__/a-backlog-link-the-reader-can-read.test.js @@ -63,7 +63,7 @@ describe("every backlog declaration in this repository is one the report can rea * without checking the lesson would have stayed green through it. */ it("is the shape both skills that write it actually teach", () => { const TEACHING_FILES = [ - "plugins/aidd-pm/skills/04-spec/references/backlog-link.md", + "plugins/aidd-pm/skills/04-spec/assets/backlog-link-template.json", "plugins/aidd-dev/skills/01-plan/actions/04-plan.md", ]; From 19ea466d4ec6ed9ef9094c534cd09edded73a900 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 15:29:58 +0200 Subject: [PATCH 15/17] fix(aidd-pm): a section says it is optional in its guidance, not in its 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../2026_10/2026_10_07_spec-open-questions/journey.md | 11 +++++++++++ .../aidd-pm/skills/04-spec/assets/spec-template.md | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md index 35a78396b..b8adb85ee 100644 --- a/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md +++ b/aidd_docs/tasks/2026_10/2026_10_07_spec-open-questions/journey.md @@ -125,3 +125,14 @@ Through the tree, after the declaration moved to a reference and its shape to an 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. + diff --git a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md index 6d99a72c9..88a0ae38e 100644 --- a/plugins/aidd-pm/skills/04-spec/assets/spec-template.md +++ b/plugins/aidd-pm/skills/04-spec/assets/spec-template.md @@ -30,12 +30,12 @@ -## Stakeholders (optional) +## Stakeholders + - Decider: - Owner: - Consumer: -## Context (optional) - - +## Context + From 1da8138adbb6dbce201b195819607755da2a78b5 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 15:50:25 +0200 Subject: [PATCH 16/17] test(cli): the taught backlog example can be an asset, not only a fenced 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- ...sk-backlog-skill-shape.integration.test.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts b/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts index d0e649d1f..8673c3371 100644 --- a/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts +++ b/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts @@ -6,17 +6,18 @@ import { afterEach, describe, expect, it } from "vitest"; import { TaskBacklogAdapter } from "../../../../src/contexts/telemetry/infrastructure/task-backlog-adapter.js"; import { REPOSITORY_ROOT } from "../../../helpers/repository-root.js"; -/** Each skill's own fenced example is fed through the real adapter over a real temp folder, - * never a stand-in parser, so a field renamed on either side fails here. */ +/** Each skill's own example is fed through the real adapter over a real temp folder, never a + * stand-in parser, so a field renamed on either side fails here. One skill ships the example + * as an asset and the other fences it inline, so the loader below accepts both. */ const REPO_ROOT = REPOSITORY_ROOT; -const SPEC_SKILL_MD = join( +const SPEC_EXAMPLE_JSON = join( REPO_ROOT, "plugins", "aidd-pm", "skills", "04-spec", - "actions", - "01-build.md" + "assets", + "backlog-link-template.json" ); const PLAN_SKILL_MD = join( REPO_ROOT, @@ -28,10 +29,12 @@ const PLAN_SKILL_MD = join( "04-plan.md" ); -/** The first fenced json block: the literal example a skill tells an agent to write, - * tolerant of a numbered-list item's own indentation. `null` when none is found. */ -function fencedJsonExample(markdown: string): string | null { - const match = /^[ \t]*```json\r?\n([\s\S]*?)\r?\n[ \t]*```/mu.exec(markdown); +/** The literal example a skill tells an agent to write: the asset's own bytes, or the first + * fenced json block, tolerant of a numbered-list item's indentation. `null` when none. */ +function taughtExample(path: string): string | null { + const text = readFileSync(path, "utf8"); + if (path.endsWith(".json")) return text.trim(); + const match = /^[ \t]*```json\r?\n([\s\S]*?)\r?\n[ \t]*```/mu.exec(text); return match?.[1] ?? null; } @@ -51,18 +54,16 @@ async function projectWithLink(json: string): Promise<{ root: string; taskFolder } describe.each([ - ["aidd-pm:04-spec", SPEC_SKILL_MD], + ["aidd-pm:04-spec", SPEC_EXAMPLE_JSON], ["aidd-dev:01-plan", PLAN_SKILL_MD], ])("%s's own backlog-link.json example matches what the reader accepts", (_skill, path) => { - it("names a fenced JSON example at all (guards against a no-op extraction)", () => { - const markdown = readFileSync(path, "utf8"); - expect(fencedJsonExample(markdown)).not.toBeNull(); + it("names a JSON example at all (guards against a no-op extraction)", () => { + expect(taughtExample(path)).not.toBeNull(); }); it("parses through the real TaskBacklogAdapter as a declared item", async () => { - const markdown = readFileSync(path, "utf8"); - const example = fencedJsonExample(markdown); - if (example === null) throw new Error("no fenced json example to test"); + const example = taughtExample(path); + if (example === null) throw new Error("no json example to test"); const { root, taskFolder } = await projectWithLink(`${example}\n`); const adapter = new TaskBacklogAdapter(root); @@ -80,8 +81,8 @@ describe.each([ describe("both skills agree with each other, not only with the reader", () => { it("write the identical field names, so neither can drift from the other unnoticed", () => { - const specExample = fencedJsonExample(readFileSync(SPEC_SKILL_MD, "utf8")); - const planExample = fencedJsonExample(readFileSync(PLAN_SKILL_MD, "utf8")); + const specExample = taughtExample(SPEC_EXAMPLE_JSON); + const planExample = taughtExample(PLAN_SKILL_MD); expect(specExample).not.toBeNull(); expect(planExample).not.toBeNull(); From 2a93e711aabcd7ded445941a799e0f1eaec65892 Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Wed, 7 Oct 2026 15:52:06 +0200 Subject: [PATCH 17/17] test(cli): drop the comments the code already says 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 ai-driven-dev/framework#626 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c --- .../task-backlog-skill-shape.integration.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts b/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts index 8673c3371..eebc56a12 100644 --- a/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts +++ b/cli/tests/contexts/telemetry/infrastructure/task-backlog-skill-shape.integration.test.ts @@ -6,9 +6,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { TaskBacklogAdapter } from "../../../../src/contexts/telemetry/infrastructure/task-backlog-adapter.js"; import { REPOSITORY_ROOT } from "../../../helpers/repository-root.js"; -/** Each skill's own example is fed through the real adapter over a real temp folder, never a - * stand-in parser, so a field renamed on either side fails here. One skill ships the example - * as an asset and the other fences it inline, so the loader below accepts both. */ +/** Fed through the real adapter, never a stand-in parser, so a rename on either side fails. */ const REPO_ROOT = REPOSITORY_ROOT; const SPEC_EXAMPLE_JSON = join( REPO_ROOT, @@ -29,8 +27,6 @@ const PLAN_SKILL_MD = join( "04-plan.md" ); -/** The literal example a skill tells an agent to write: the asset's own bytes, or the first - * fenced json block, tolerant of a numbered-list item's indentation. `null` when none. */ function taughtExample(path: string): string | null { const text = readFileSync(path, "utf8"); if (path.endsWith(".json")) return text.trim();