diff --git a/CHANGELOG.md b/CHANGELOG.md index 01aef34..68476c3 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.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) - `CLAUDE.md` is now the contributor guide (ADR 0019): about 650 words of pointers and change rules, down from about 2,000 that restated the skills, the tier model, the plugin tree, and the ADR list. Four runtime rules that never shipped in the plugin stay in a labelled section pending issue #62 diff --git a/checks/tests/test_handoff.py b/checks/tests/test_handoff.py new file mode 100644 index 0000000..71ba846 --- /dev/null +++ b/checks/tests/test_handoff.py @@ -0,0 +1,37 @@ +"""Tests for the generated implementation handoff (ADR 0010 TC-3, issue #65). +Run: python -m unittest discover -s checks/tests +""" +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "plugin" / "scripts")) +from generate_handoff import generate_handoff # noqa: E402 + +FIXTURE = ROOT / "examples" / "tea-tracker" + + +class HandoffCase(unittest.TestCase): + def setUp(self): + self.generated = generate_handoff(FIXTURE / "spec-package", "spec-package") + + 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"): + self.assertIn(phrase, section) + + def test_no_instruction_tells_the_agent_only_to_stop(self): + self.assertNotIn("stop and check the ADRs", self.generated) + + def test_committed_example_handoff_matches_the_generator(self): + self.assertEqual( + (FIXTURE / "CLAUDE.md").read_text(), self.generated, + "examples/tea-tracker/CLAUDE.md is stale — run: " + "python plugin/scripts/generate_handoff.py examples/tea-tracker/spec-package", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/docs/adrs/0010-spec-package-compilation-target.md b/docs/adrs/0010-spec-package-compilation-target.md index e361e0f..44874af 100644 --- a/docs/adrs/0010-spec-package-compilation-target.md +++ b/docs/adrs/0010-spec-package-compilation-target.md @@ -103,3 +103,34 @@ separate "Python pipeline" CI job already failed the build on an invalid fixture; graduation makes the Stop-gate agree with it and makes the failure message name this ADR. `checks/enforcement.config.json` is authoritative for current status. + +--- + +## Amendment (2026-10-12): The handoff names a move for requests outside the spec + +Issue #65. The generated handoff (`CLAUDE.md` for the implementation agent) +told the agent not to invent requirements and, when building beyond the spec, +to "stop and check the ADRs". The extracted ADRs list exclusions only, so for +a request the spec neither includes nor excludes that instruction resolved +nothing: the agent was left to halt or to build unannounced. + +**Decision:** the handoff carries a "When a Request Is Not in the Spec" +section. If an ADR excludes the request, name it and do not build. Otherwise +say the spec is silent, propose the smallest spec change that would add it, +ask, and on agreement update the spec files before implementing — which keeps +the package the source of truth, the point of this ADR. Implementation +choices the spec leaves open stay with the agent. Plugin version 0.5.2. + +**Evidence and its limit:** the constitution-lift experiment +(chrispezza/portfolio#106) saw a rule with no compliant path halt a harmless +feature in 6 of 6 runs, on a different repo. Nothing has measured the +generated handoff, before or after this change. + +**Testable consequence added:** TC-3: `examples/tea-tracker/CLAUDE.md` equals +what `generate_handoff.py` produces from the reference package, and contains +the section. Verified by `checks/tests/test_handoff.py` in the enforcement CI +job; no runner check. + +**Still not machine-checkable:** the handoff's prohibitions are not backed by +any check on the built code — `validate_spec.py` validates the package, not +the implementation. diff --git a/docs/adrs/README.md b/docs/adrs/README.md index 33e1b90..04b6697 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) | — | — | +| [0010](0010-spec-package-compilation-target.md) | Spec Package as Compilation Target | Accepted | `spec-fixture` (block) | — | 1, 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 aa7016d..f418f5f 100644 --- a/examples/tea-tracker/CLAUDE.md +++ b/examples/tea-tracker/CLAUDE.md @@ -84,7 +84,7 @@ Rules use IF/THEN logic — translate directly to code. Pay attention to `edge_c - Import from spreadsheet - Image upload for tea packaging -If you find yourself building something not in the spec, stop and check the ADRs in `docs/adrs/`. +If you find yourself building something not in the spec, follow "When a Request Is Not in the Spec" below. ## Opinionated Defaults @@ -112,9 +112,19 @@ After building, check: - Error responses match what the spec defines (don't add unspecified errors) - No features exist that aren't in the spec (check `docs/adrs/` for exclusions) +## When a Request Is Not in the Spec + +You will be asked for things this package does not cover. Neither refuse silently nor build them unannounced: + +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. + +Small implementation choices the spec leaves open (file layout, naming, library internals) are yours to make and do not need this process. + ## Do NOT -- Add features not in the spec — if it's not specified, it's not in scope +- Add features not in the spec on your own initiative — if it's not specified, it's not in scope until the spec changes - Invent error codes or validation rules beyond what `rules.yaml` defines - 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) diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 2e2cdd3..cbad74d 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.1", + "version": "0.5.2", "author": { "name": "clownware" }, diff --git a/plugin/scripts/generate_handoff.py b/plugin/scripts/generate_handoff.py index c1867d9..929d0b7 100644 --- a/plugin/scripts/generate_handoff.py +++ b/plugin/scripts/generate_handoff.py @@ -135,7 +135,7 @@ def generate_handoff(pkg_dir: Path, spec_package_rel: str = "spec-package") -> s scope_section += "\n**Do NOT build these** — they are explicitly deferred:\n" for d in deferred: scope_section += f"- {d}\n" - scope_section += "\nIf you find yourself building something not in the spec, stop and check the ADRs in `docs/adrs/`.\n" + scope_section += "\nIf you find yourself building something not in the spec, follow \"When a Request Is Not in the Spec\" below.\n" # Build defaults section defaults_lines = [] @@ -238,8 +238,18 @@ def generate_handoff(pkg_dir: Path, spec_package_rel: str = "spec-package") -> s sections.append("- Error responses match what the spec defines (don't add unspecified errors)") sections.append("- No features exist that aren't in the spec (check `docs/adrs/` for exclusions)\n") + # A prohibition with no stated alternative leaves the agent two bad + # options when asked for something reasonable the spec is silent on: halt, + # or build it unannounced. Name the move instead (issue #65). + sections.append("## When a Request Is Not in the Spec\n") + 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("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") - sections.append("- Add features not in the spec — if it's not specified, it's not in scope") + sections.append("- Add features not in the spec on your own initiative — if it's not specified, it's not in scope until the spec changes") sections.append("- Invent error codes or validation rules beyond what `rules.yaml` defines") 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)")