diff --git a/CHANGELOG.md b/CHANGELOG.md index 68476c3..839a3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `skills-claude-ai/` — Claude.ai Agent Skill ports of product-ideation and product-flow (issue #12): bundled prompts with skill-relative paths, conversation-scoped artifact ledger with `product-dev-artifacts.md` export replacing the `.product-dev/` registry ### Changed +- Plugin 0.5.3: the `/compile` handoff no longer tells the implementation agent to edit the spec package. `/compile` rebuilds the package from the source artifacts, so a hand edit was unvalidated and lost on the next compile; in a trial half of such edits failed validation. The agent now writes the agreed change to `spec-change-request.md`, names the source artifact for each spec file, asks the builder to recompile, and builds the feature from the recompiled package (issue #71) - Plugin 0.5.2: the `/compile` handoff (`CLAUDE.md` for the implementation agent) now says what to do when asked for something the spec does not cover: check the exclusion ADRs, otherwise say so, propose the spec change, and ask before building. It previously said only to stop and check the ADRs, which list exclusions and so resolved nothing for an unlisted request (issue #65) - `manifest` check: a missing `claude` CLI is now a note locally and a finding only under `--require-validator`, which CI passes. With that fixed the check graduates warn → block (issue #63); 13 of 18 checks now block - Plugin 0.5.1: tier escalation is now offered, not applied, in every workflow skill. Four rules that existed only in this repo's `CLAUDE.md` ship in `plugin/docs/registry-operations.md` § Workflow Rules (offer Tier 2 prompts and let the user pick; never auto-escalate to Tier 3; ask when a gate condition is unclear; entry points are exclusive). `product-ideation` previously inserted Tier 2 prompts on a signal; `product-flow` and `tech-spec` had no escalation procedure (issue #62) diff --git a/checks/tests/test_handoff.py b/checks/tests/test_handoff.py index 71ba846..df18c24 100644 --- a/checks/tests/test_handoff.py +++ b/checks/tests/test_handoff.py @@ -19,9 +19,19 @@ def setUp(self): def test_handoff_names_a_move_for_requests_outside_the_spec(self): self.assertIn("## When a Request Is Not in the Spec", self.generated) section = self.generated.split("## When a Request Is Not in the Spec", 1)[1].split("\n## ", 1)[0] - for phrase in ("If an ADR excludes it", "propose the smallest spec change", "Ask before building"): + for phrase in ("If an ADR excludes it", "propose the smallest spec change", "Ask before building", + "do **not** edit anything under", "spec-change-request.md", "re-run `/compile`"): self.assertIn(phrase, section) + def test_handoff_never_tells_the_agent_to_edit_the_compiled_package(self): + # Issue #71: /compile rebuilds the package, so hand edits are lost and unvalidated. + self.assertNotIn("update the spec files first", self.generated) + + def test_artifact_table_matches_the_compiler(self): + from compile_spec import SPEC_ARTIFACTS + for artifact, filename in SPEC_ARTIFACTS.items(): + self.assertIn(f"| `spec/{filename}` | `{artifact}` |", self.generated) + def test_no_instruction_tells_the_agent_only_to_stop(self): self.assertNotIn("stop and check the ADRs", self.generated) diff --git a/docs/adrs/0010-spec-package-compilation-target.md b/docs/adrs/0010-spec-package-compilation-target.md index 72323ba..e25e543 100644 --- a/docs/adrs/0010-spec-package-compilation-target.md +++ b/docs/adrs/0010-spec-package-compilation-target.md @@ -173,3 +173,50 @@ ADR makes the package the source of truth. **Limits.** One fixture, one model, one request, six runs per cell, one bounded layer of work, and no follow-up turn — whether the agent then updates the spec before implementing, as the section says, was not observed. + +--- + +## Amendment (2026-10-12): The agent requests spec changes; it does not edit the package + +Issue #71. This corrects the handoff decision two amendments above, which +told the agent, once the builder agreed, to "update the spec files first, then +implement". That contradicted this ADR's own "Compilation over generation": +`/compile` deletes the package and rebuilds it from the source artifacts, so a +hand edit to a spec file is never validated and is lost on the next compile. + +A follow-up trial showed the cost. Six isolated two-turn runs (same setup as +above; second turn "Yes, add it. Go ahead."): all six edited the spec before +any code, and three of the six edited packages failed `validate_spec.py`, each +on the same required-versus-optional mismatch between the entity and the +create endpoint. + +**Decision:** after the builder agrees, the agent does not edit anything under +the package directory. It writes the agreed change to `spec-change-request.md` +beside the handoff — per spec file, the exact YAML and the source artifact it +belongs in — and asks the builder to apply it to those artifacts and re-run +`/compile`. It builds the new feature from the recompiled package, and keeps +building what the spec already covers meanwhile. The handoff carries the +spec-file-to-artifact table, and a test keeps that table equal to the +compiler's `SPEC_ARTIFACTS` map. Plugin version 0.5.3. + +**Measured the same day,** six isolated two-turn runs with the revised +handoff: + +| | Runs | +|---|---| +| Turn 1: withheld the field and asked | 6 of 6 | +| Turn 2: wrote `spec-change-request.md` and nothing else | 6 of 6 | +| Edited any file under the package | 0 of 6 | +| Built the field before a recompile | 0 of 6 | +| Request names the source artifacts and gives the YAML | 6 of 6 | +| Final message asks the builder to re-run `/compile` | 6 of 6 | + +**What it costs.** A feature outside the spec now needs the builder to edit +the source artifacts and recompile before the agent builds it. That is slower +than the agent editing the package, and it is the only path on which the +change is validated and survives. + +**Limits.** Same as above: one fixture, one model, one request, six runs. The +loop was not closed — no run was given a recompiled package to build from — +and the request files were not themselves validated; at least one repeats the +required-versus-optional mismatch, which the compile step exists to catch. diff --git a/docs/adrs/README.md b/docs/adrs/README.md index 8435604..579df8f 100644 --- a/docs/adrs/README.md +++ b/docs/adrs/README.md @@ -19,7 +19,7 @@ listed under "Amended by" override its earlier text. | [0007](0007-json-index-deprecation.md) | Deprecation of JSON Index Files and Stream Deck Document | Accepted | `no-legacy` (block) | — | — | | [0008](0008-plugin-architecture.md) | Plugin Architecture | Accepted | `manifest` (block), `component-census` (block) | 0011 | 1, latest 2026-10-12 | | [0009](0009-prompt-enhancement-pattern.md) | Prompt Enhancement Pattern v2 | Accepted | `frontmatter-v2` (block), `name-uniqueness` (block), `body-structure` (block), `placeholder-syntax` (block), `word-limit` (warn) | — | 1, latest 2026-07-25 | -| [0010](0010-spec-package-compilation-target.md) | Spec Package as Compilation Target | Accepted | `spec-fixture` (block) | — | 2, latest 2026-10-12 | +| [0010](0010-spec-package-compilation-target.md) | Spec Package as Compilation Target | Accepted | `spec-fixture` (block) | — | 3, latest 2026-10-12 | | [0011](0011-plugin-self-containment.md) | Plugin Self-Containment via Bundled Assets | Accepted | `self-containment` (warn) | — | 1, latest 2026-07-25 | | [0012](0012-adr-enforcement-architecture.md) | ADR Enforcement Architecture | Accepted | `adr-status` (block), `adr-index` (warn), `adr-amendment-link` (warn) | — | 4, latest 2026-10-12 | | [0013](0013-ux-optimization-reverse-pass.md) | UX Optimization Reverse Pass (ux-optimization skill) | Accepted | `component-census` (block) | — | 1, latest 2026-10-12 | diff --git a/examples/tea-tracker/CLAUDE.md b/examples/tea-tracker/CLAUDE.md index f418f5f..12be54c 100644 --- a/examples/tea-tracker/CLAUDE.md +++ b/examples/tea-tracker/CLAUDE.md @@ -118,7 +118,18 @@ You will be asked for things this package does not cover. Neither refuse silentl 1. Check `docs/adrs/`. If an ADR excludes it, say which one and do not build it. Reversing an exclusion is the builder's decision. 2. If no ADR excludes it and the spec does not include it, say so plainly, then propose the smallest spec change that would add it: which of `entities.yaml`, `screens.yaml`, `endpoints.yaml`, `rules.yaml`, and `flows.yaml` would change, and how. -3. Ask before building. Once the builder agrees, update the spec files first, then implement from them, so the spec stays the source of truth. +3. Ask before building. +4. Once the builder agrees, do **not** edit anything under `spec-package/`. It is compiled output: `/compile` rebuilds it from the builder's source artifacts, discards hand edits, and is the only step that validates it. Instead, write the agreed change to `spec-change-request.md` next to this file: for each spec file, the exact YAML to add or change, and the source artifact it belongs in (table below). Ask the builder to apply it to those artifacts and re-run `/compile`. +5. Build the new feature only after the recompiled package is in place, and build it from that package. Until then, keep building what the spec already covers. + +| Spec file | Source artifact the builder edits | +|-----------|-----------------------------------| +| `spec/entities.yaml` | `data_models` | +| `spec/endpoints.yaml` | `api_contracts` | +| `spec/screens.yaml` | `screen_inventory` | +| `spec/rules.yaml` | `business_rules` | +| `spec/flows.yaml` | `user_flow` | +| `spec/constraints.yaml` | `nfr` | Small implementation choices the spec leaves open (file layout, naming, library internals) are yours to make and do not need this process. @@ -129,3 +140,4 @@ Small implementation choices the spec leaves open (file layout, naming, library - Build admin interfaces, settings pages, or onboarding flows unless they're in `screens.yaml` - Over-engineer for scale — this is a prototype (see `constraints.yaml` for targets) - Guess at field types or API shapes — everything is specified in the YAML files +- Edit files under `spec-package/` — request the change and let the builder recompile diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index cbad74d..93b5b48 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "product-dev", "description": "AI-assisted product development framework. Guides from vague idea to technical spec through structured UX research, hypothesis formation, and prototype planning.", - "version": "0.5.2", + "version": "0.5.3", "author": { "name": "clownware" }, diff --git a/plugin/scripts/generate_handoff.py b/plugin/scripts/generate_handoff.py index 929d0b7..13a4acd 100644 --- a/plugin/scripts/generate_handoff.py +++ b/plugin/scripts/generate_handoff.py @@ -245,7 +245,20 @@ def generate_handoff(pkg_dir: Path, spec_package_rel: str = "spec-package") -> s sections.append("You will be asked for things this package does not cover. Neither refuse silently nor build them unannounced:\n") sections.append("1. Check `docs/adrs/`. If an ADR excludes it, say which one and do not build it. Reversing an exclusion is the builder's decision.") sections.append("2. If no ADR excludes it and the spec does not include it, say so plainly, then propose the smallest spec change that would add it: which of `entities.yaml`, `screens.yaml`, `endpoints.yaml`, `rules.yaml`, and `flows.yaml` would change, and how.") - sections.append("3. Ask before building. Once the builder agrees, update the spec files first, then implement from them, so the spec stays the source of truth.\n") + sections.append("3. Ask before building.") + # The package is compiled output: /compile deletes and rebuilds it from the + # source artifacts, and only a compile re-runs validation. A hand edit here + # is unvalidated and is lost on the next compile (issue #71). + sections.append(f"4. Once the builder agrees, do **not** edit anything under `{spec_package_rel}/`. It is compiled output: `/compile` rebuilds it from the builder's source artifacts, discards hand edits, and is the only step that validates it. Instead, write the agreed change to `spec-change-request.md` next to this file: for each spec file, the exact YAML to add or change, and the source artifact it belongs in (table below). Ask the builder to apply it to those artifacts and re-run `/compile`.") + sections.append("5. Build the new feature only after the recompiled package is in place, and build it from that package. Until then, keep building what the spec already covers.\n") + sections.append("| Spec file | Source artifact the builder edits |") + sections.append("|-----------|-----------------------------------|") + sections.append("| `spec/entities.yaml` | `data_models` |") + sections.append("| `spec/endpoints.yaml` | `api_contracts` |") + sections.append("| `spec/screens.yaml` | `screen_inventory` |") + sections.append("| `spec/rules.yaml` | `business_rules` |") + sections.append("| `spec/flows.yaml` | `user_flow` |") + sections.append("| `spec/constraints.yaml` | `nfr` |\n") sections.append("Small implementation choices the spec leaves open (file layout, naming, library internals) are yours to make and do not need this process.\n") sections.append("## Do NOT\n") @@ -254,6 +267,7 @@ def generate_handoff(pkg_dir: Path, spec_package_rel: str = "spec-package") -> s sections.append("- Build admin interfaces, settings pages, or onboarding flows unless they're in `screens.yaml`") sections.append("- Over-engineer for scale — this is a prototype (see `constraints.yaml` for targets)") sections.append("- Guess at field types or API shapes — everything is specified in the YAML files") + sections.append(f"- Edit files under `{spec_package_rel}/` — request the change and let the builder recompile") return "\n".join(sections) + "\n"