Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions checks/tests/test_handoff.py
Original file line number Diff line number Diff line change
@@ -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()
31 changes: 31 additions & 0 deletions docs/adrs/0010-spec-package-compilation-target.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion docs/adrs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
14 changes: 12 additions & 2 deletions examples/tea-tracker/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
},
Expand Down
14 changes: 12 additions & 2 deletions plugin/scripts/generate_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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)")
Expand Down
Loading