diff --git a/.changeset/wise-rams-swim.md b/.changeset/wise-rams-swim.md
new file mode 100644
index 000000000..fc7828787
--- /dev/null
+++ b/.changeset/wise-rams-swim.md
@@ -0,0 +1,5 @@
+---
+type: Fixed
+pr: 3014
+---
+**`/gsd-spike` no longer blends unrelated ideas' requirements together** — `.planning/spikes/MANIFEST.md` now scopes each idea's paragraph and Requirements under its own idea key, and `/gsd-spike --wrap-up` only emits a feature area's owning idea's requirements instead of the whole file. (#1700)
diff --git a/gsd-core/references/artifact-types.md b/gsd-core/references/artifact-types.md
index bc4045e8d..b2cbc0183 100644
--- a/gsd-core/references/artifact-types.md
+++ b/gsd-core/references/artifact-types.md
@@ -72,11 +72,11 @@ reads is inert — the consumption mechanism is what gives an artifact meaning.
- **Location**: `.planning/spikes/SPIKE-NNN/`
- **Consumed by**: Planner when spike is referenced; `pause-work` for spike context handoff
-### Spike README.md / MANIFEST.md (per-spike, via /gsd:spike)
-- **Shape**: YAML frontmatter (spike, name, validates, verdict, related, tags) + run instructions + results
+### Spike README.md (per-spike) / MANIFEST.md (per-project index, via /gsd:spike)
+- **Shape**: README — YAML frontmatter (spike, idea, name, validates, verdict, related, tags) + run instructions + results. MANIFEST.md — one `### {idea-key}` section per idea under `## Ideas` (idea paragraph + its own scoped Requirements), plus one durable `## Spikes` table (with an Idea column) indexing every spike across every idea.
- **Lifecycle**: Created by `/gsd:spike` → Verified → Wrapped up by `/gsd-spike-wrap-up`
- **Location**: `.planning/spikes/NNN-name/README.md`, `.planning/spikes/MANIFEST.md`
-- **Consumed by**: `/gsd-spike-wrap-up` for curation; `pause-work` for spike context handoff
+- **Consumed by**: `/gsd-spike-wrap-up` for curation (Requirements pulled only from the idea key(s) it wraps); `pause-work` for spike context handoff
### Sketch README.md / MANIFEST.md / index.html (per-sketch)
- **Shape**: YAML frontmatter (sketch, name, question, winner, tags) + variants as tabbed HTML
diff --git a/gsd-core/workflows/sketch.md b/gsd-core/workflows/sketch.md
index 6c8cd937b..82623aab7 100644
--- a/gsd-core/workflows/sketch.md
+++ b/gsd-core/workflows/sketch.md
@@ -134,7 +134,7 @@ If spikes exist for this project, read them to ground the sketches in reality. M
**a.** Glob for `./.claude/skills/spike-findings-*/SKILL.md` and read any that exist, plus their `references/*.md`. These contain validated patterns and requirements.
-**b.** Read `.planning/spikes/MANIFEST.md` if it exists — check the Requirements section for non-negotiable design constraints (e.g., "must support streaming", "must render markdown"). These requirements should be visible in the mockup even though the mockup doesn't implement them for real.
+**b.** Read `.planning/spikes/MANIFEST.md` if it exists — it may hold separate `### {idea-key}` sections for several unrelated ideas. Check the Requirements list of the idea key relevant to this sketch's design direction (or all of them if none clearly matches) for non-negotiable design constraints (e.g., "must support streaming", "must render markdown"). These requirements should be visible in the mockup even though the mockup doesn't implement them for real.
**c.** Read `.planning/spikes/CONVENTIONS.md` if it exists — the established stack informs what's buildable and what interaction patterns are idiomatic.
diff --git a/gsd-core/workflows/spike-wrap-up.md b/gsd-core/workflows/spike-wrap-up.md
index 8cfef2fb6..bdccecaf7 100644
--- a/gsd-core/workflows/spike-wrap-up.md
+++ b/gsd-core/workflows/spike-wrap-up.md
@@ -22,8 +22,12 @@ Read all files referenced by the invoking prompt's execution_context before star
## Gather Spike Inventory
-1. Read `.planning/spikes/MANIFEST.md` for the overall idea context and requirements
-2. Glob `.planning/spikes/*/README.md` and parse YAML frontmatter from each
+1. Read `.planning/spikes/MANIFEST.md` for the `## Ideas` sections (each idea's paragraph and
+ its own scoped Requirements list) and the `## Spikes` table (its Idea column tells you which
+ idea key each spike row belongs to).
+2. Glob `.planning/spikes/*/README.md` and parse YAML frontmatter from each — each spike's
+ `idea:` field is the idea key that owns it. If a README predates #1700 and has no `idea:`
+ field, resolve its idea key from the matching `## Spikes` table row's Idea column instead.
3. Check if `./.claude/skills/spike-findings-*/SKILL.md` exists for this project
- If yes: read its `processed_spikes` list from the metadata section and filter those out
- If no: all spikes are candidates
@@ -101,7 +105,12 @@ For each feature-area group, write a reference file at `references/[feature-area
## Requirements
-[Non-negotiable design decisions from MANIFEST.md Requirements section that apply to this feature area. These MUST be honored in the real build. E.g., "Must use streaming JSON output", "Must support reconnection".]
+[Non-negotiable design decisions pulled ONLY from the Requirements list of the idea key(s) that
+own the spikes in this feature-area group — match each spike's `idea:` frontmatter (or Idea
+column) to its `### {idea-key}` Requirements list in MANIFEST.md. These MUST be honored in the
+real build. E.g., "Must use streaming JSON output", "Must support reconnection".
+
+Never include a requirement from an idea key that has no spike in this group.]
## How to Build It
@@ -136,7 +145,8 @@ description: Implementation blueprint from spike experiments. Requirements, prov
## Project: [project-dir-name]
-[One paragraph from MANIFEST.md describing the overall idea]
+[One paragraph per idea key represented among the wrapped spikes, taken from that idea's
+`### {idea-key}` section in MANIFEST.md — not the whole MANIFEST.md if it holds unrelated ideas.]
Spike sessions wrapped: [date(s)]
@@ -144,7 +154,11 @@ Spike sessions wrapped: [date(s)]
## Requirements
-[Copied directly from MANIFEST.md Requirements section. These are non-negotiable design decisions that emerged from the user's choices during spiking. Every feature area reference must honor these.]
+[Union of the Requirements lists for every idea key represented among the spikes being wrapped
+in this session — never the whole MANIFEST.md. These are non-negotiable design decisions that
+emerged from the user's choices while spiking those specific idea(s). Every feature area
+reference must honor these. If this wrap-up spans more than one idea key, group the list by
+idea key so a future reader can tell which requirement belongs to which idea.]
- [requirement 1]
- [requirement 2]
@@ -299,6 +313,7 @@ After the summary, present next-step options:
- [ ] Spikes grouped by feature area
- [ ] Spike-findings skill exists at `./.claude/skills/` with SKILL.md (including requirements), references/, sources/
- [ ] Reference files are implementation blueprints with Requirements, How to Build It, What to Avoid, Constraints
+- [ ] Requirements in each reference file and in SKILL.md are scoped to the idea key(s) actually represented among the wrapped spikes — never blended with an unrelated idea's requirements
- [ ] `.planning/spikes/CONVENTIONS.md` created or updated with recurring stack/structure/pattern choices
- [ ] `.planning/spikes/WRAP-UP-SUMMARY.md` written for project history
- [ ] Project CLAUDE.md has auto-load routing line
diff --git a/gsd-core/workflows/spike.md b/gsd-core/workflows/spike.md
index 2a6e578d6..db1def70a 100644
--- a/gsd-core/workflows/spike.md
+++ b/gsd-core/workflows/spike.md
@@ -53,7 +53,8 @@ If no `.planning/spikes/` directory exists, tell the user there's nothing to ana
Otherwise, load in this order:
-**a. MANIFEST.md** — the overall idea, requirements, and spike table with verdicts.
+**a. MANIFEST.md** — every idea section under `## Ideas` (each idea's paragraph and its own
+scoped Requirements) and the `## Spikes` table with verdicts (each row tagged by idea).
**b. Findings skills** — glob `./.claude/skills/spike-findings-*/SKILL.md` and read any that exist, plus their `references/*.md`. These contain curated knowledge from prior wrap-ups.
@@ -74,7 +75,8 @@ If integration risks exist, present them as concrete proposed spikes with names
### Analyze for Frontier Spikes
-Think laterally about the overall idea from MANIFEST.md and what's been proven so far. Consider:
+Think laterally about every idea section from MANIFEST.md and what's been proven so far for
+each. Consider:
- **Gaps in the vision:** Capabilities assumed but unproven.
- **Discovered dependencies:** Findings that reveal new questions.
@@ -86,7 +88,7 @@ Present frontier spikes as concrete proposals numbered from the highest existing
### Get Alignment and Execute
-Present all integration and frontier candidates, then ask which to run. When the user picks spikes, write definitions into `.planning/spikes/MANIFEST.md` (appending to existing table) and proceed directly to building them starting at `research`.
+Present all integration and frontier candidates, then ask which to run. When the user picks spikes, write definitions into `.planning/spikes/MANIFEST.md` (appending to the existing table, with each row's Idea column set to the idea key(s) it extends or validates) and proceed directly to building them starting at `research`.
@@ -212,33 +214,63 @@ If 2+ credible approaches exist, plan to build quick variants within the spike a
-Create or update `.planning/spikes/MANIFEST.md`:
+Create or update `.planning/spikes/MANIFEST.md`.
+
+**Assign an idea key.** Derive a short, stable, kebab-case slug (2-4 words) summarizing the
+idea being spiked right now, e.g. `realtime-llm-streaming`. Reuse the exact same idea key for
+every spike in this session and any later session that continues the same idea. Only mint a
+new idea key when the current idea is not a continuation of one already indexed in
+MANIFEST.md — never reuse an existing idea key for an unrelated idea, and never merge two
+different ideas under one key.
+
+If `.planning/spikes/MANIFEST.md` doesn't exist, create it:
```markdown
# Spike Manifest
-## Idea
-[One paragraph describing the overall idea being explored]
+## Ideas
-## Requirements
-[Design decisions that emerged from the user's choices during spiking. Non-negotiable for the real build. Updated as spikes progress.]
+### {idea-key}
+[One paragraph describing this idea]
+
+**Requirements:**
+[Design decisions that emerged from the user's choices while spiking THIS idea. Non-negotiable
+for the real build of this idea. Updated as spikes progress. Never copy or merge requirements
+from a different idea key into this list.]
- [e.g., "Must use streaming JSON output, not single-response"]
- [e.g., "Must support reconnection on network failure"]
## Spikes
-| # | Name | Type | Validates | Verdict | Tags |
-|---|------|------|-----------|---------|------|
+| # | Idea | Name | Type | Validates | Verdict | Tags |
+|---|------|------|------|-----------|---------|------|
```
-**Track requirements as they emerge.** When the user expresses a preference during spiking, add it to the Requirements section immediately.
+If `.planning/spikes/MANIFEST.md` already exists:
+
+- **Same idea key already has a `### {idea-key}` section under `## Ideas`:** append to that
+ section's Requirements list as new requirements emerge. Never overwrite or rewrite its
+ `## Idea` paragraph.
+- **New idea key, not yet present:** append a new `### {idea-key}` subsection under `## Ideas`,
+ after any existing idea sections. Never touch, merge into, or delete another idea's section.
+- **A pre-#1700 MANIFEST.md with the old flat shape** (a single top-level `## Idea` / `##
+ Requirements` pair, no `## Ideas` heading): treat its existing content as one implicit idea.
+ Derive an idea key from its `## Idea` paragraph, migrate it in place to `## Ideas` >
+ `### {idea-key}` — preserving the paragraph and every existing Requirements bullet and
+ Spikes row verbatim — then continue as above. Do this migration once; do not repeat it once
+ `## Ideas` exists.
+
+Every row appended to `## Spikes` carries an **Idea** column set to the idea key it belongs to.
+
+**Track requirements as they emerge.** When the user expresses a preference during spiking, add
+it to the current idea's Requirements list immediately — never to a different idea's list.
## Re-Ground Before Each Spike
-Before starting each spike (not just the first), re-read `.planning/spikes/MANIFEST.md` and `.planning/spikes/CONVENTIONS.md` to prevent drift within long sessions. Check the Requirements section — make sure the spike doesn't contradict any established requirements.
+Before starting each spike (not just the first), re-read `.planning/spikes/MANIFEST.md` and `.planning/spikes/CONVENTIONS.md` to prevent drift within long sessions. Check the current idea's `### {idea-key}` Requirements list — make sure the spike doesn't contradict any established requirement for this idea. Do not apply another idea's requirements.
@@ -287,6 +319,7 @@ Multiple files per spike are expected for complex questions (e.g., `test-basic.j
```markdown
---
spike: NNN
+idea: {idea-key}
name: descriptive-name
type: standard
validates: "Given [precondition], when [action], then [expected outcome]"
@@ -337,7 +370,8 @@ tags: [tag1, tag2]
→ Does this match what you expected? Describe what you see.
──────────────────────────────────────────────────────────────
-**h.** Update `.planning/spikes/MANIFEST.md` with the spike's row.
+**h.** Update `.planning/spikes/MANIFEST.md` with the spike's row, setting the Idea column to
+this spike's idea key.
**i.** Commit (if `COMMIT_DOCS` is true):
```bash
@@ -450,10 +484,10 @@ gsd_run query commit "docs(spikes): update conventions" --files .planning/spikes
- [ ] Depth over speed — edge cases tested, surprising findings followed, investigation trail documented
- [ ] Comparison spikes built back-to-back with head-to-head verdict
- [ ] Spikes needing human interaction have forensic log layer
-- [ ] Requirements tracked in MANIFEST.md as they emerge from user choices
+- [ ] Requirements tracked in MANIFEST.md, scoped to the idea key that produced them, as they emerge from user choices
- [ ] CONVENTIONS.md created or updated with patterns that emerged
-- [ ] Each spike README has complete frontmatter, Investigation Trail, and Results
-- [ ] MANIFEST.md is current (with Type column and Requirements section)
+- [ ] Each spike README has complete frontmatter (including its idea key), Investigation Trail, and Results
+- [ ] MANIFEST.md is current (with Idea and Type columns, and each idea's own scoped Requirements section)
- [ ] Commits use `docs(spike-NNN): [VERDICT]` format
- [ ] Consolidated report presented with next-step routing
diff --git a/tests/emitted-drift-acks/1700-spike-manifest-idea-scoping.json b/tests/emitted-drift-acks/1700-spike-manifest-idea-scoping.json
new file mode 100644
index 000000000..3f6481ffc
--- /dev/null
+++ b/tests/emitted-drift-acks/1700-spike-manifest-idea-scoping.json
@@ -0,0 +1,8 @@
+{
+ "version": 1,
+ "paths": {
+ "spike.md": "#1700: create_manifest step now derives an explicit idea key and restructures the .planning/spikes/MANIFEST.md template from one flat top-level `## Idea` / `## Requirements` pair (single-idea only) to `## Ideas` > `### {idea-key}` subsections, each carrying its own scoped Requirements list — the fix for the leak where one idea's requirements bled into another idea's build via spike-wrap-up.md. Growth is: the idea-key derivation instructions and the expanded MANIFEST.md template (new/existing-idea/pre-#1700-flat-shape-migration branches) in create_manifest; an Idea column added to the `## Spikes` table header everywhere it's referenced; an `idea:` field added to the build_spikes README frontmatter template; the reground step scoping its established-requirements check to the current idea only; the frontier_mode load step and 'Get Alignment and Execute' step describing per-idea sections instead of one flat idea; and two success_criteria bullets updated to call out idea-key scoping. This is the write side of the fix, not incidental prose.",
+ "spike-wrap-up.md": "#1700: gather/synthesize/write_skill steps now resolve each spike's idea key (from its README `idea:` frontmatter, falling back to the `## Spikes` table's Idea column for pre-#1700 spikes) and scope which Requirements they read and re-emit, so a feature-area reference or the generated spike-findings SKILL.md never pulls in an unrelated idea's requirements — the read/re-emit side of the #1700 fix. Growth is: the idea-key resolution instructions added to the gather step; the synthesize step's Requirements block rewritten to pull ONLY from the owning idea key(s) with an explicit 'never include a requirement from an idea key that has no spike in this group' rule; the write_skill step's and blocks scoped to the wrapped idea key(s) instead of 'the whole MANIFEST.md'; and one new success_criteria bullet forbidding blended requirements across ideas.",
+ "sketch.md": "#1700: sketch.md's build_sketches step (b) reads spikes/MANIFEST.md's Requirements for non-negotiable design constraints; once spike.md restructures that file from one flat Requirements section to per-idea `### {idea-key}` sections (same PR), sketch.md's single-flat-section instruction would silently go stale — collateral of the seam this fix touches, required so sketch.md keeps reading the file correctly rather than a defect introduced by this change. Growth is the one clause distinguishing per-idea sections and instructing the reader to check the Requirements list of the relevant idea key (or all of them if none clearly matches)."
+ }
+}
diff --git a/tests/fix-1700-spike-manifest-idea-scoping.test.cjs b/tests/fix-1700-spike-manifest-idea-scoping.test.cjs
new file mode 100644
index 000000000..a96d874b3
--- /dev/null
+++ b/tests/fix-1700-spike-manifest-idea-scoping.test.cjs
@@ -0,0 +1,221 @@
+// allow-test-rule: source-text-is-the-product (#1700)
+// spike.md / spike-wrap-up.md / artifact-types.md / sketch.md are workflow and reference
+// markdown — their text IS the contract loaded by the agent at runtime. Testing text content
+// tests the deployed contract; there is no build step, CLI entrypoint, or generated code to
+// exercise for this class of file (confirmed via mcp__memtrace__find_code: only .md files and
+// unrelated `.planning/phases/*/REQUIREMENTS.md` symbols in gap-checker.cts/milestone.cts
+// reference this seam — a different artifact family, not this one).
+//
+// Regression test for #1700: `.planning/spikes/MANIFEST.md` held a single un-keyed `## Idea` /
+// `## Requirements` pair while its own read path (`spike.md:138`, frontier mode) assumed the
+// tree holds several unrelated ideas across campaigns. `spike-wrap-up.md` then copied that flat
+// Requirements list verbatim into every generated feature-area reference and the generated
+// spike-findings skill, so one idea's requirements leaked into another idea's build as binding
+// constraints. The fix scopes both the write side (spike.md's create_manifest) and the read/
+// re-emit side (spike-wrap-up.md's gather/synthesize/write_skill) by an explicit idea key,
+// carried through spike README frontmatter and a new Idea column in the `## Spikes` table.
+//
+// QA-matrix note: none of CONTRIBUTING.md's CLI / parser / filesystem-write / security matrices
+// apply — this is a workflow-prompt template edit with no runtime code path, parser, or write
+// seam of its own (the `.planning/spikes/` tree is written by an LLM agent following the
+// workflow prose, not by GSD's own code). No numeric limit is introduced, so the limit-1/limit/
+// limit+1 boundary-coverage rule and the fast-check property-test requirement do not apply
+// either — there is no parser, budget, or bijective contract in this change. See
+// `.gsd/bug/fix-1700-spike-manifest-idea-scoping/50-test-matrix.md` for the full row table.
+
+'use strict';
+
+const { test, describe } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+
+const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
+const REFERENCES_DIR = path.join(__dirname, '..', 'gsd-core', 'references');
+
+const spikeMd = () => fs.readFileSync(path.join(WORKFLOWS_DIR, 'spike.md'), 'utf-8');
+const spikeWrapUpMd = () => fs.readFileSync(path.join(WORKFLOWS_DIR, 'spike-wrap-up.md'), 'utf-8');
+const sketchMd = () => fs.readFileSync(path.join(WORKFLOWS_DIR, 'sketch.md'), 'utf-8');
+const artifactTypesMd = () => fs.readFileSync(path.join(REFERENCES_DIR, 'artifact-types.md'), 'utf-8');
+
+describe('fix-1700: spike MANIFEST.md scopes Idea/Requirements per idea key', () => {
+ // Row 1 — the failing-first regression test (50-test-matrix.md row 1).
+ test('spike.md create_manifest step defines an explicit idea key and a scoped ## Ideas structure', () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /idea key/i,
+ 'create_manifest step must introduce an explicit "idea key" concept — without one, ' +
+ 'the write side has no way to tell a continuation of the current idea from an unrelated new idea (#1700).'
+ );
+ assert.match(
+ content,
+ /##\s*Ideas\b/,
+ 'MANIFEST.md template must have a `## Ideas` section (plural) that can hold more than one ' +
+ 'idea, replacing the old single flat `## Idea` paragraph.'
+ );
+ assert.match(
+ content,
+ /###\s*\{idea-key\}/,
+ "MANIFEST.md template must scope each idea under its own `### {idea-key}` subsection so " +
+ "idea A's paragraph is never overwritten by idea B (#1700 acceptance criterion 1)."
+ );
+ });
+
+ test('create_manifest step instructs appending a new idea section instead of overwriting an existing one', () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /new idea key[\s\S]{0,200}?append/i,
+ 'When the current idea is new (not a continuation), the workflow must append a new idea ' +
+ 'section rather than overwrite/merge into an existing one.'
+ );
+ assert.match(
+ content,
+ /[Nn]ever touch[\s\S]{0,80}?(?:another|other) idea/i,
+ "The workflow must explicitly forbid touching another idea's section when adding to the current one."
+ );
+ });
+
+ test("create_manifest step migrates a pre-#1700 flat-shape MANIFEST.md instead of discarding it (Hyrum's Law)", () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /old flat[\s\S]{0,40}?shape/i,
+ 'The workflow must handle an existing MANIFEST.md written before this fix (flat `## Idea` / ' +
+ '`## Requirements`, no `## Ideas` heading) by migrating it in place, not discarding user data.'
+ );
+ });
+
+ test('## Spikes table gains an Idea column so every spike is attributable to an idea (acceptance criterion 3)', () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /\|\s*#\s*\|\s*Idea\s*\|\s*Name\s*\|\s*Type\s*\|\s*Validates\s*\|\s*Verdict\s*\|\s*Tags\s*\|/,
+ 'The `## Spikes` table header must include an Idea column between # and Name, so the ' +
+ 'durable index still covers every spike across every idea while attributing each row.'
+ );
+ });
+
+ test('spike README frontmatter template carries an idea key field', () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /spike:\s*NNN\r?\nidea:\s*\{idea-key\}/,
+ 'The build_spikes README frontmatter template must record the owning idea key directly ' +
+ 'on the spike, so spike-wrap-up can attribute requirements without re-deriving it.'
+ );
+ });
+
+ test("reground step checks the CURRENT idea's requirements, not all requirements", () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /current idea's[\s\S]{0,80}?Requirements/i,
+ 'The reground step must scope its established-requirements check to the idea being spiked, ' +
+ 'not the whole MANIFEST.md, or it would falsely flag idea B as contradicting idea A.'
+ );
+ });
+
+ test("frontier_mode load step reads every idea's scoped requirements, not one flat section", () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /every idea section/i,
+ "Frontier mode's MANIFEST.md load step must describe reading per-idea sections, matching " +
+ 'the new ## Ideas structure it depends on.'
+ );
+ });
+
+ test("spike-wrap-up.md gather step resolves each spike's idea key before reading requirements", () => {
+ const content = spikeWrapUpMd();
+ assert.match(
+ content,
+ /idea key/i,
+ "spike-wrap-up.md must resolve an idea key per spike (from frontmatter or the Spikes table) " +
+ 'before it can scope which Requirements apply — otherwise it still reads the whole ' +
+ 'MANIFEST.md Requirements section flat (#1700 root cause on the consumer side).'
+ );
+ });
+
+ test("spike-wrap-up.md synthesize step scopes Requirements to the feature area's owning idea key(s)", () => {
+ const content = spikeWrapUpMd();
+ assert.match(
+ content,
+ /ONLY from the Requirements list of the idea key/,
+ "The synthesize step's Requirements block must pull only from the idea key(s) that own the " +
+ 'spikes in that feature-area group — never the whole MANIFEST.md (#1700 acceptance criterion 2).'
+ );
+ assert.match(
+ content,
+ /[Nn]ever include a requirement from an idea key that has no spike in this group/,
+ "The synthesize step must explicitly forbid leaking an unrelated idea's requirement into a " +
+ 'feature-area reference it has no spike in.'
+ );
+ });
+
+ test("spike-wrap-up.md write_skill step unions only the wrapped idea keys' requirements", () => {
+ const content = spikeWrapUpMd();
+ assert.match(
+ content,
+ /never the whole MANIFEST\.md/,
+ "The generated SKILL.md block must be built from only the idea key(s) " +
+ 'represented among the spikes actually being wrapped in this session.'
+ );
+ });
+
+ test('artifact-types.md no longer labels the project-level MANIFEST.md "per-spike" (acceptance criterion 4)', () => {
+ const content = artifactTypesMd();
+ assert.doesNotMatch(
+ content,
+ /MANIFEST\.md \(per-spike/,
+ 'artifact-types.md must not describe the project-level, durable-index MANIFEST.md as ' +
+ '"per-spike" — that mislabeling is the third independent statement of the #1700 ambiguity ' +
+ "the maintainer's triage flagged."
+ );
+ assert.match(
+ content,
+ /MANIFEST\.md \(per-project index/,
+ 'artifact-types.md must instead label MANIFEST.md as the per-project durable index.'
+ );
+ });
+
+ test('artifact-types.md keeps the legacy /gsd:spike colon form untouched (out of scope, #2903)', () => {
+ const content = artifactTypesMd();
+ assert.match(
+ content,
+ /via \/gsd:spike/,
+ 'The colon-form command reference belongs to #2903 and must not be touched by this fix.'
+ );
+ });
+
+ test('sketch.md no longer assumes spikes/MANIFEST.md has a single flat Requirements section', () => {
+ const content = sketchMd();
+ assert.match(
+ content,
+ /separate `### \{idea-key\}` sections/,
+ "sketch.md reads spikes/MANIFEST.md for design constraints; once MANIFEST.md nests " +
+ "Requirements under per-idea sections, sketch.md's single-section instruction goes stale " +
+ 'unless updated in the same PR (collateral of this fix\'s seam, not a separate concern).'
+ );
+ });
+
+ test('success criteria in spike.md reflect idea-scoped requirement tracking', () => {
+ const content = spikeMd();
+ assert.match(
+ content,
+ /scoped to the idea key/,
+ 'spike.md success_criteria must call out that Requirements are tracked per idea key, not globally.'
+ );
+ });
+
+ test('success criteria in spike-wrap-up.md forbid blending requirements across ideas', () => {
+ const content = spikeWrapUpMd();
+ assert.match(
+ content,
+ /never blended with an unrelated idea/,
+ 'spike-wrap-up.md success_criteria must explicitly call out that requirements must never ' +
+ 'blend across idea keys.'
+ );
+ });
+});