claude-dev-env 1.65.1 → 1.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/plan-packet-validator.md +34 -0
- package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +6 -0
- package/commands/plan.md +6 -52
- package/hooks/blocking/code_rules_enforcer.py +2 -0
- package/hooks/blocking/code_rules_test_assertions.py +123 -1
- package/hooks/blocking/open_questions_in_plans_blocker.py +8 -1
- package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +90 -0
- package/hooks/blocking/test_open_questions_in_plans_blocker.py +43 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/open_questions_in_plans_blocker_constants.py +4 -0
- package/hooks/hooks_constants/test_open_questions_in_plans_blocker_constants.py +13 -1
- package/package.json +1 -1
- package/skills/anthropic-plan/SKILL.md +46 -85
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/__init__.py +0 -0
- package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/validate_packet_constants.py +33 -0
- package/skills/anthropic-plan/scripts/test_validate_packet.py +405 -0
- package/skills/anthropic-plan/scripts/validate_packet.py +397 -0
- package/skills/anthropic-plan/templates/README.md +20 -0
- package/skills/anthropic-plan/templates/build-prompt.md +9 -0
- package/skills/anthropic-plan/templates/source-map.md +5 -0
- package/skills/anthropic-plan/test_skill_contract.py +53 -0
- package/skills/anthropic-plan/workflow/plan-packet.contract.test.mjs +79 -0
- package/skills/anthropic-plan/workflow/plan-packet.mjs +299 -0
|
@@ -1,107 +1,68 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: anthropic-plan
|
|
3
|
-
description:
|
|
3
|
+
description: Workflow-backed implementation planning that creates a deep repo-local packet under docs/plans/<slug>/ before any code changes. Use for /anthropic-plan, /plan, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial implementation requests that need source-grounded design, TDD steps, and validator approval before build work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Anthropic Plan
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Create a source-grounded plan packet through the Claude Code Workflow runtime. The output is a repo-local `docs/plans/<slug>/` folder with context, spec, implementation, validation, and handoff docs. Stop before implementation.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Launch
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Call the workflow with the user request and current working directory:
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Plan File
|
|
25
|
-
|
|
26
|
-
Write to `~/.claude/plans/<slug>.md`.
|
|
27
|
-
|
|
28
|
-
Generate the slug from the task -- descriptive, kebab-case, 2-4 words. Examples: `add-user-auth.md`, `fix-payment-retry.md`, `refactor-config-loading.md`. Avoid the random-word convention used by built-in plan mode.
|
|
29
|
-
|
|
30
|
-
**Announce at start:** "Planning: `<slug>` -- exploring before writing code."
|
|
31
|
-
|
|
32
|
-
## Workflow
|
|
33
|
-
|
|
34
|
-
### Phase 1: Explore
|
|
35
|
-
|
|
36
|
-
Understand the problem space before proposing solutions. Launch Explore agents in parallel -- up to 3, but use the minimum needed. Quality over quantity.
|
|
37
|
-
|
|
38
|
-
What to look for:
|
|
39
|
-
- Files and modules the task will touch
|
|
40
|
-
- Existing patterns for similar functionality
|
|
41
|
-
- Utilities, helpers, constants, and shared code to reuse
|
|
42
|
-
- Test patterns already in place
|
|
43
|
-
|
|
44
|
-
Skip this phase only if the task is trivial and full context already exists in the conversation.
|
|
45
|
-
|
|
46
|
-
### Phase 2: Design
|
|
47
|
-
|
|
48
|
-
Launch Plan agent(s) to design the implementation -- up to 3 for complex or multi-area tasks, skip entirely for trivial tasks. Feed them comprehensive context from Phase 1; they cannot explore on their own, so everything they need must come from you.
|
|
49
|
-
|
|
50
|
-
### Phase 3: Review
|
|
51
|
-
|
|
52
|
-
Before committing to the plan:
|
|
53
|
-
- Read the critical files yourself to verify the agents got it right
|
|
54
|
-
- Check alignment with what the user actually asked for
|
|
55
|
-
- If requirements are ambiguous, use AskUserQuestion now -- not after writing the plan
|
|
56
|
-
|
|
57
|
-
### Phase 4: Write the Plan
|
|
14
|
+
```js
|
|
15
|
+
Workflow({
|
|
16
|
+
scriptPath: "$HOME/.claude/skills/anthropic-plan/workflow/plan-packet.mjs",
|
|
17
|
+
input: {
|
|
18
|
+
task: "$ARGUMENTS",
|
|
19
|
+
cwd: "<current working directory>"
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
```
|
|
58
23
|
|
|
59
|
-
|
|
24
|
+
If the Workflow tool is unavailable, say `anthropic-plan requires the Workflow tool; aborting` and stop.
|
|
60
25
|
|
|
61
|
-
|
|
62
|
-
## Context
|
|
63
|
-
Why this change is needed, what problem it solves, what the outcome looks like.
|
|
26
|
+
## Workflow Contract
|
|
64
27
|
|
|
65
|
-
|
|
66
|
-
The recommended implementation. One approach, not a menu of alternatives.
|
|
67
|
-
Concise but detailed enough to execute without re-exploring.
|
|
28
|
+
The workflow handles the full planning loop:
|
|
68
29
|
|
|
69
|
-
|
|
70
|
-
|
|
30
|
+
1. Resolve repo root and packet path.
|
|
31
|
+
2. Read project instructions, rules, relevant skills, manifests, docs, tests, hooks, agents, commands, configs, and workflows.
|
|
32
|
+
3. Build a source inventory and extract source facts into `context/source-map.md`.
|
|
33
|
+
4. Write the packet under `docs/plans/<slug>/`.
|
|
34
|
+
5. Run `scripts/validate_packet.py`.
|
|
35
|
+
6. Spawn `plan-packet-validator` in fresh context.
|
|
36
|
+
7. Repair packet findings up to the workflow cap.
|
|
37
|
+
8. Return packet path, validation state, and findings.
|
|
38
|
+
9. Stop before implementation.
|
|
71
39
|
|
|
72
|
-
##
|
|
73
|
-
Existing functions, utilities, constants, or patterns to leverage.
|
|
74
|
-
Include file:line references so the implementer can find them instantly.
|
|
40
|
+
## Packet Shape
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
Ordered implementation steps. Each step is a discrete, testable unit of work.
|
|
42
|
+
Required root: `docs/plans/<slug>/`
|
|
78
43
|
|
|
79
|
-
|
|
80
|
-
How to confirm end-to-end that the implementation works.
|
|
81
|
-
Specific commands, test files, or manual verification steps.
|
|
44
|
+
Required top-level files and folders:
|
|
82
45
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
46
|
+
- `README.md`
|
|
47
|
+
- `packet.json`
|
|
48
|
+
- `context/`
|
|
49
|
+
- `spec/`
|
|
50
|
+
- `implementation/`
|
|
51
|
+
- `validation/`
|
|
52
|
+
- `handoff/`
|
|
90
53
|
|
|
91
|
-
|
|
54
|
+
The packet depth rule is strict: `README.md` is a thin hub, first-level folders group purpose, and second-level files carry detail. Add `context/subsystems/<name>.md` only when the planner finds more than twelve source files or more than three subsystems.
|
|
92
55
|
|
|
93
|
-
|
|
56
|
+
## Validation
|
|
94
57
|
|
|
95
|
-
|
|
96
|
-
- **Approve** -- proceed with implementation
|
|
97
|
-
- **Revise** -- user has feedback to incorporate
|
|
98
|
-
- **Cancel** -- abandon the plan
|
|
58
|
+
The deterministic validator checks required files, placeholders, `Open Questions`, source-map strength, TDD coverage, standalone handoff prompts, and `packet.json` consistency.
|
|
99
59
|
|
|
100
|
-
|
|
60
|
+
The `plan-packet-validator` agent checks source accuracy, scope, enough implementation detail for a blind build agent, real TDD order, invented APIs or commands, and end-to-end acceptance criteria.
|
|
101
61
|
|
|
102
|
-
|
|
62
|
+
## Rules
|
|
103
63
|
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
64
|
+
- Write docs only.
|
|
65
|
+
- Do not edit production code.
|
|
66
|
+
- Do not run implementation commands.
|
|
67
|
+
- Ask the user only for product choices that cannot be derived from local context.
|
|
68
|
+
- Fold resolved answers into the packet. Never leave an `Open Questions` section.
|
|
File without changes
|
package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/validate_packet_constants.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Constants for the plan packet validator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
ALL_REQUIRED_RELATIVE_PATHS: tuple[str, ...] = (
|
|
6
|
+
"README.md",
|
|
7
|
+
"packet.json",
|
|
8
|
+
"context/user-request.md",
|
|
9
|
+
"context/source-map.md",
|
|
10
|
+
"context/current-state.md",
|
|
11
|
+
"context/existing-patterns.md",
|
|
12
|
+
"context/constraints.md",
|
|
13
|
+
"context/glossary.md",
|
|
14
|
+
"spec/scope.md",
|
|
15
|
+
"spec/behavior.md",
|
|
16
|
+
"spec/interfaces.md",
|
|
17
|
+
"spec/data-flow.md",
|
|
18
|
+
"spec/failure-modes.md",
|
|
19
|
+
"spec/acceptance.md",
|
|
20
|
+
"implementation/strategy.md",
|
|
21
|
+
"implementation/steps.md",
|
|
22
|
+
"implementation/tdd-plan.md",
|
|
23
|
+
"implementation/file-plan.md",
|
|
24
|
+
"implementation/refactor-checkpoints.md",
|
|
25
|
+
"validation/validator-report.md",
|
|
26
|
+
"validation/deterministic-checks.md",
|
|
27
|
+
"validation/unresolved-risks.md",
|
|
28
|
+
"handoff/build-prompt.md",
|
|
29
|
+
"handoff/review-prompt.md",
|
|
30
|
+
"handoff/verification-commands.md",
|
|
31
|
+
)
|
|
32
|
+
MARKDOWN_FILE_SUFFIX: str = ".md"
|
|
33
|
+
EXIT_CODE_VALIDATION_FAILED: int = 2
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""Tests for the plan packet validator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
16
|
+
VALIDATOR_PATH = SCRIPTS_DIRECTORY / "validate_packet.py"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_validator_module() -> ModuleType:
|
|
20
|
+
if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
22
|
+
spec = importlib.util.spec_from_file_location("validate_packet", VALIDATOR_PATH)
|
|
23
|
+
assert spec is not None
|
|
24
|
+
assert spec.loader is not None
|
|
25
|
+
validator_module = importlib.util.module_from_spec(spec)
|
|
26
|
+
spec.loader.exec_module(validator_module)
|
|
27
|
+
return validator_module
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def write_valid_packet(packet_directory: Path) -> None:
|
|
31
|
+
all_relative_paths = load_validator_module().required_relative_paths()
|
|
32
|
+
for each_relative_path in all_relative_paths:
|
|
33
|
+
target_path = packet_directory / each_relative_path
|
|
34
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
target_path.write_text(valid_markdown_for(each_relative_path), encoding="utf-8")
|
|
36
|
+
|
|
37
|
+
packet_payload = {
|
|
38
|
+
"schemaVersion": 1,
|
|
39
|
+
"slug": "add-login",
|
|
40
|
+
"repoRoot": str(packet_directory.parent.parent.parent),
|
|
41
|
+
"packetPath": str(packet_directory),
|
|
42
|
+
"sourceFiles": ["src/auth.py"],
|
|
43
|
+
"assumptions": ["No migration is needed."],
|
|
44
|
+
"validator": {"deterministic": "pending", "semantic": "pending"},
|
|
45
|
+
}
|
|
46
|
+
(packet_directory / "packet.json").write_text(
|
|
47
|
+
json.dumps(packet_payload, indent=2),
|
|
48
|
+
encoding="utf-8",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def valid_markdown_for(relative_path: str) -> str:
|
|
53
|
+
if relative_path == "context/source-map.md":
|
|
54
|
+
return (
|
|
55
|
+
"# Source Map\n\n"
|
|
56
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
57
|
+
"|---|---|---|---|\n"
|
|
58
|
+
"| src/auth.py | Login flow entrypoint | Existing authenticate_user function handles password checks. | Reuse authenticate_user in the implementation. |\n"
|
|
59
|
+
)
|
|
60
|
+
if relative_path == "implementation/tdd-plan.md":
|
|
61
|
+
return (
|
|
62
|
+
"# TDD Plan\n\n"
|
|
63
|
+
"1. Failing test: add test_auth_login_success before production edits.\n"
|
|
64
|
+
"2. Production code: wire the existing authenticate_user call.\n"
|
|
65
|
+
"3. Refactor after green: remove duplicated setup.\n"
|
|
66
|
+
)
|
|
67
|
+
if relative_path == "implementation/steps.md":
|
|
68
|
+
return (
|
|
69
|
+
"# Steps\n\n"
|
|
70
|
+
"1. Test first: add login success coverage.\n"
|
|
71
|
+
"2. Production change: add the route handler; covered by test_auth_login_success.\n"
|
|
72
|
+
)
|
|
73
|
+
if relative_path == "handoff/build-prompt.md":
|
|
74
|
+
return (
|
|
75
|
+
"# Build Prompt\n\n"
|
|
76
|
+
"Use only this packet. Read README.md, then context/source-map.md, then implementation/steps.md. "
|
|
77
|
+
"Do not rely on prior chat history."
|
|
78
|
+
)
|
|
79
|
+
return f"# {relative_path}\n\nGrounded implementation detail for this packet file.\n"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def run_validator(packet_directory: Path) -> subprocess.CompletedProcess[str]:
|
|
83
|
+
return subprocess.run(
|
|
84
|
+
[sys.executable, str(VALIDATOR_PATH), str(packet_directory)],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
check=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_valid_packet_passes(tmp_path: Path) -> None:
|
|
92
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
93
|
+
write_valid_packet(packet_directory)
|
|
94
|
+
|
|
95
|
+
validator_run = run_validator(packet_directory)
|
|
96
|
+
|
|
97
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
98
|
+
assert "packet validation passed" in validator_run.stdout
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_missing_required_file_fails(tmp_path: Path) -> None:
|
|
102
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
103
|
+
write_valid_packet(packet_directory)
|
|
104
|
+
(packet_directory / "spec" / "behavior.md").unlink()
|
|
105
|
+
|
|
106
|
+
validator_run = run_validator(packet_directory)
|
|
107
|
+
|
|
108
|
+
assert validator_run.returncode == 2
|
|
109
|
+
assert "missing required file: spec/behavior.md" in validator_run.stderr
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_placeholder_text_fails(tmp_path: Path) -> None:
|
|
113
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
114
|
+
write_valid_packet(packet_directory)
|
|
115
|
+
(packet_directory / "spec" / "scope.md").write_text("TODO: fill this in", encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
validator_run = run_validator(packet_directory)
|
|
118
|
+
|
|
119
|
+
assert validator_run.returncode == 2
|
|
120
|
+
assert "placeholder text" in validator_run.stderr
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.mark.parametrize(
|
|
124
|
+
"template_placeholder_markdown",
|
|
125
|
+
[
|
|
126
|
+
"# <Plan Title>\n\nThis plan implements the feature.",
|
|
127
|
+
"This plan implements <feature name> for the <component> module.",
|
|
128
|
+
"| <path> | <reason> | <verified fact> | <implementation implication> |",
|
|
129
|
+
],
|
|
130
|
+
)
|
|
131
|
+
def test_angle_bracket_placeholder_text_fails(
|
|
132
|
+
tmp_path: Path,
|
|
133
|
+
template_placeholder_markdown: str,
|
|
134
|
+
) -> None:
|
|
135
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
136
|
+
write_valid_packet(packet_directory)
|
|
137
|
+
(packet_directory / "spec" / "scope.md").write_text(
|
|
138
|
+
template_placeholder_markdown,
|
|
139
|
+
encoding="utf-8",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
validator_run = run_validator(packet_directory)
|
|
143
|
+
|
|
144
|
+
assert validator_run.returncode == 2
|
|
145
|
+
assert "spec/scope.md contains placeholder text" in validator_run.stderr
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize(
|
|
149
|
+
"non_placeholder_markdown",
|
|
150
|
+
[
|
|
151
|
+
"<details>\n<summary>Notes</summary>\nGrounded detail.\n</details>",
|
|
152
|
+
"Type the annotation as `list[str]` and call `<command>` from a code span.",
|
|
153
|
+
"Reach the endpoint with `curl <url>` inside the inline code span.",
|
|
154
|
+
"The comparison `attempt_count < threshold` must hold before the retry.",
|
|
155
|
+
"The handler accepts a List<Item> of records.",
|
|
156
|
+
"Wrap the field in Optional<User> when it may be absent.",
|
|
157
|
+
"The cache stores an Array<string> of identifiers.",
|
|
158
|
+
"The lookup uses a Map<Key, Value> keyed by request id.",
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
def test_inline_html_and_code_does_not_flag_placeholder(
|
|
162
|
+
tmp_path: Path,
|
|
163
|
+
non_placeholder_markdown: str,
|
|
164
|
+
) -> None:
|
|
165
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
166
|
+
write_valid_packet(packet_directory)
|
|
167
|
+
(packet_directory / "spec" / "scope.md").write_text(
|
|
168
|
+
non_placeholder_markdown,
|
|
169
|
+
encoding="utf-8",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
validator_run = run_validator(packet_directory)
|
|
173
|
+
|
|
174
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_open_questions_heading_fails(tmp_path: Path) -> None:
|
|
178
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
179
|
+
write_valid_packet(packet_directory)
|
|
180
|
+
(packet_directory / "validation" / "unresolved-risks.md").write_text(
|
|
181
|
+
"## Open Questions\n- Which database?",
|
|
182
|
+
encoding="utf-8",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
validator_run = run_validator(packet_directory)
|
|
186
|
+
|
|
187
|
+
assert validator_run.returncode == 2
|
|
188
|
+
assert "Open Questions" in validator_run.stderr
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_weak_source_map_fails(tmp_path: Path) -> None:
|
|
192
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
193
|
+
write_valid_packet(packet_directory)
|
|
194
|
+
(packet_directory / "context" / "source-map.md").write_text(
|
|
195
|
+
"# Source Map\n\nNo sources yet.\n",
|
|
196
|
+
encoding="utf-8",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
validator_run = run_validator(packet_directory)
|
|
200
|
+
|
|
201
|
+
assert validator_run.returncode == 2
|
|
202
|
+
assert "source-map.md must include source-grounded rows" in validator_run.stderr
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_missing_tdd_plan_fails(tmp_path: Path) -> None:
|
|
206
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
207
|
+
write_valid_packet(packet_directory)
|
|
208
|
+
(packet_directory / "implementation" / "tdd-plan.md").write_text(
|
|
209
|
+
"# TDD Plan\n\nImplementation can start directly.",
|
|
210
|
+
encoding="utf-8",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
validator_run = run_validator(packet_directory)
|
|
214
|
+
|
|
215
|
+
assert validator_run.returncode == 2
|
|
216
|
+
assert "tdd-plan.md must name failing tests" in validator_run.stderr
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@pytest.mark.parametrize(
|
|
220
|
+
"forbidden_phrase",
|
|
221
|
+
["as discussed above", "from our chat", "previous conversation", "earlier in this thread"],
|
|
222
|
+
)
|
|
223
|
+
def test_handoff_prompt_depending_on_chat_history_fails(
|
|
224
|
+
tmp_path: Path,
|
|
225
|
+
forbidden_phrase: str,
|
|
226
|
+
) -> None:
|
|
227
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
228
|
+
write_valid_packet(packet_directory)
|
|
229
|
+
(packet_directory / "handoff" / "build-prompt.md").write_text(
|
|
230
|
+
f"# Build Prompt\n\nUse the details {forbidden_phrase}.",
|
|
231
|
+
encoding="utf-8",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
validator_run = run_validator(packet_directory)
|
|
235
|
+
|
|
236
|
+
assert validator_run.returncode == 2
|
|
237
|
+
assert "build-prompt.md must stand alone" in validator_run.stderr
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def test_tdd_plan_with_red_only_as_substring_fails(tmp_path: Path) -> None:
|
|
241
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
242
|
+
write_valid_packet(packet_directory)
|
|
243
|
+
(packet_directory / "implementation" / "tdd-plan.md").write_text(
|
|
244
|
+
"# TDD Plan\n\nImplementation is required. Wire production code directly.",
|
|
245
|
+
encoding="utf-8",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
validator_run = run_validator(packet_directory)
|
|
249
|
+
|
|
250
|
+
assert validator_run.returncode == 2
|
|
251
|
+
assert "tdd-plan.md must name failing tests" in validator_run.stderr
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_tdd_plan_naming_red_step_passes(tmp_path: Path) -> None:
|
|
255
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
256
|
+
write_valid_packet(packet_directory)
|
|
257
|
+
(packet_directory / "implementation" / "tdd-plan.md").write_text(
|
|
258
|
+
"# TDD Plan\n\n1. Red step: add the failing coverage.\n2. Green production code follows.",
|
|
259
|
+
encoding="utf-8",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
validator_run = run_validator(packet_directory)
|
|
263
|
+
|
|
264
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_packet_path_with_forward_slashes_matches_native_directory(tmp_path: Path) -> None:
|
|
268
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
269
|
+
write_valid_packet(packet_directory)
|
|
270
|
+
packet_file = packet_directory / "packet.json"
|
|
271
|
+
packet_payload = json.loads(packet_file.read_text(encoding="utf-8"))
|
|
272
|
+
packet_payload["packetPath"] = packet_directory.as_posix()
|
|
273
|
+
packet_file.write_text(json.dumps(packet_payload, indent=2), encoding="utf-8")
|
|
274
|
+
|
|
275
|
+
validator_run = run_validator(packet_directory)
|
|
276
|
+
|
|
277
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_packet_path_with_trailing_separator_matches_directory(tmp_path: Path) -> None:
|
|
281
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
282
|
+
write_valid_packet(packet_directory)
|
|
283
|
+
packet_file = packet_directory / "packet.json"
|
|
284
|
+
packet_payload = json.loads(packet_file.read_text(encoding="utf-8"))
|
|
285
|
+
packet_payload["packetPath"] = str(packet_directory) + "/"
|
|
286
|
+
packet_file.write_text(json.dumps(packet_payload, indent=2), encoding="utf-8")
|
|
287
|
+
|
|
288
|
+
validator_run = run_validator(packet_directory)
|
|
289
|
+
|
|
290
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_packet_path_mismatch_still_fails(tmp_path: Path) -> None:
|
|
294
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
295
|
+
write_valid_packet(packet_directory)
|
|
296
|
+
packet_file = packet_directory / "packet.json"
|
|
297
|
+
packet_payload = json.loads(packet_file.read_text(encoding="utf-8"))
|
|
298
|
+
packet_payload["packetPath"] = str(packet_directory.parent / "different-slug")
|
|
299
|
+
packet_file.write_text(json.dumps(packet_payload, indent=2), encoding="utf-8")
|
|
300
|
+
|
|
301
|
+
validator_run = run_validator(packet_directory)
|
|
302
|
+
|
|
303
|
+
assert validator_run.returncode == 2
|
|
304
|
+
assert "packet.json packetPath must match the validated packet directory" in validator_run.stderr
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_source_map_with_bare_non_python_source_passes(tmp_path: Path) -> None:
|
|
308
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
309
|
+
write_valid_packet(packet_directory)
|
|
310
|
+
(packet_directory / "context" / "source-map.md").write_text(
|
|
311
|
+
"# Source Map\n\n"
|
|
312
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
313
|
+
"|---|---|---|---|\n"
|
|
314
|
+
"| plan-packet.mjs | Workflow entry | Exports the run function. | Reuse the run shape. |\n",
|
|
315
|
+
encoding="utf-8",
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
validator_run = run_validator(packet_directory)
|
|
319
|
+
|
|
320
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def test_source_map_with_only_version_number_row_fails(tmp_path: Path) -> None:
|
|
324
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
325
|
+
write_valid_packet(packet_directory)
|
|
326
|
+
(packet_directory / "context" / "source-map.md").write_text(
|
|
327
|
+
"# Source Map\n\n"
|
|
328
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
329
|
+
"|---|---|---|---|\n"
|
|
330
|
+
"| The login subsystem | We reviewed version 2.0 of the design | No file path named here | Build accordingly |\n",
|
|
331
|
+
encoding="utf-8",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
validator_run = run_validator(packet_directory)
|
|
335
|
+
|
|
336
|
+
assert validator_run.returncode == 2
|
|
337
|
+
assert "source-map.md must include source-grounded rows" in validator_run.stderr
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def test_source_map_data_row_naming_source_and_facts_passes(tmp_path: Path) -> None:
|
|
341
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
342
|
+
write_valid_packet(packet_directory)
|
|
343
|
+
(packet_directory / "context" / "source-map.md").write_text(
|
|
344
|
+
"# Source Map\n\n"
|
|
345
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
346
|
+
"|---|---|---|---|\n"
|
|
347
|
+
"| src/auth.py | The source of truth for login | Key facts about hashing | Reuse it |\n",
|
|
348
|
+
encoding="utf-8",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
validator_run = run_validator(packet_directory)
|
|
352
|
+
|
|
353
|
+
assert validator_run.returncode == 0, validator_run.stderr
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_has_source_table_row_keeps_grounded_row_with_source_and_facts_prose() -> None:
|
|
357
|
+
validator_module = load_validator_module()
|
|
358
|
+
source_map_text = (
|
|
359
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
360
|
+
"|---|---|---|---|\n"
|
|
361
|
+
"| src/auth.py | The source of truth for login | Key facts about hashing | Reuse it |\n"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
assert validator_module.has_source_table_row(source_map_text) is True
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_has_source_table_row_skips_header_only_document() -> None:
|
|
368
|
+
validator_module = load_validator_module()
|
|
369
|
+
source_map_text = (
|
|
370
|
+
"| Source | Why it matters | Facts extracted | Plan implication |\n"
|
|
371
|
+
"|---|---|---|---|\n"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
assert validator_module.has_source_table_row(source_map_text) is False
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def test_bullet_list_steps_without_test_contract_fails(tmp_path: Path) -> None:
|
|
378
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
379
|
+
write_valid_packet(packet_directory)
|
|
380
|
+
(packet_directory / "implementation" / "steps.md").write_text(
|
|
381
|
+
"# Steps\n\n"
|
|
382
|
+
"- Add the route handler\n"
|
|
383
|
+
"- Wire the database call\n",
|
|
384
|
+
encoding="utf-8",
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
validator_run = run_validator(packet_directory)
|
|
388
|
+
|
|
389
|
+
assert validator_run.returncode == 2
|
|
390
|
+
assert "implementation/steps.md has steps without a test or non-code reason" in validator_run.stderr
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_bullet_list_steps_naming_test_contract_passes(tmp_path: Path) -> None:
|
|
394
|
+
packet_directory = tmp_path / "docs" / "plans" / "add-login"
|
|
395
|
+
write_valid_packet(packet_directory)
|
|
396
|
+
(packet_directory / "implementation" / "steps.md").write_text(
|
|
397
|
+
"# Steps\n\n"
|
|
398
|
+
"- Test first: add login success coverage.\n"
|
|
399
|
+
"- Production change: add the route handler; covered by test_auth_login_success.\n",
|
|
400
|
+
encoding="utf-8",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
validator_run = run_validator(packet_directory)
|
|
404
|
+
|
|
405
|
+
assert validator_run.returncode == 0, validator_run.stderr
|