@hustle-together/api-dev-tools 2.0.7 → 3.1.0
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/README.md +343 -467
- package/bin/cli.js +229 -15
- package/commands/README.md +124 -251
- package/commands/api-create.md +318 -136
- package/commands/api-interview.md +252 -256
- package/commands/api-research.md +209 -234
- package/commands/api-verify.md +231 -0
- package/demo/audio/generate-all-narrations.js +581 -0
- package/demo/audio/generate-narration.js +120 -56
- package/demo/audio/generate-voice-previews.js +140 -0
- package/demo/audio/narration-adam-timing.json +4675 -0
- package/demo/audio/narration-adam.mp3 +0 -0
- package/demo/audio/narration-creature-timing.json +4675 -0
- package/demo/audio/narration-creature.mp3 +0 -0
- package/demo/audio/narration-gaming-timing.json +4675 -0
- package/demo/audio/narration-gaming.mp3 +0 -0
- package/demo/audio/narration-hope-timing.json +4675 -0
- package/demo/audio/narration-hope.mp3 +0 -0
- package/demo/audio/narration-mark-timing.json +4675 -0
- package/demo/audio/narration-mark.mp3 +0 -0
- package/demo/audio/previews/manifest.json +30 -0
- package/demo/audio/previews/preview-creature.mp3 +0 -0
- package/demo/audio/previews/preview-gaming.mp3 +0 -0
- package/demo/audio/previews/preview-hope.mp3 +0 -0
- package/demo/audio/previews/preview-mark.mp3 +0 -0
- package/demo/audio/voices-manifest.json +50 -0
- package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +30 -28
- package/demo/hustle-together/blog/interview-driven-api-development.html +37 -23
- package/demo/hustle-together/index.html +142 -109
- package/demo/workflow-demo.html +2618 -1036
- package/hooks/api-workflow-check.py +2 -0
- package/hooks/enforce-deep-research.py +180 -0
- package/hooks/enforce-disambiguation.py +149 -0
- package/hooks/enforce-documentation.py +187 -0
- package/hooks/enforce-environment.py +249 -0
- package/hooks/enforce-refactor.py +187 -0
- package/hooks/enforce-research.py +93 -46
- package/hooks/enforce-schema.py +186 -0
- package/hooks/enforce-scope.py +156 -0
- package/hooks/enforce-tdd-red.py +246 -0
- package/hooks/enforce-verify.py +186 -0
- package/hooks/periodic-reground.py +154 -0
- package/hooks/session-startup.py +151 -0
- package/hooks/track-tool-use.py +109 -17
- package/hooks/verify-after-green.py +282 -0
- package/package.json +3 -2
- package/scripts/collect-test-results.ts +404 -0
- package/scripts/extract-parameters.ts +483 -0
- package/scripts/generate-test-manifest.ts +520 -0
- package/templates/CLAUDE-SECTION.md +84 -0
- package/templates/api-dev-state.json +83 -8
- package/templates/api-test/page.tsx +315 -0
- package/templates/api-test/test-structure/route.ts +269 -0
- package/templates/research-index.json +6 -0
- package/templates/settings.json +59 -0
|
@@ -29,6 +29,8 @@ REQUIRED_PHASES = [
|
|
|
29
29
|
("interview", "User interview"),
|
|
30
30
|
("tdd_red", "TDD Red phase (failing tests written)"),
|
|
31
31
|
("tdd_green", "TDD Green phase (tests passing)"),
|
|
32
|
+
("verify", "Verification phase (re-checked against docs)"),
|
|
33
|
+
("documentation", "Documentation updates (manifest/research cached)"),
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
# Phases that SHOULD be complete (warning but don't block)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit
|
|
4
|
+
Purpose: Block writing if deep research not completed WITH USER APPROVAL
|
|
5
|
+
|
|
6
|
+
Phase 4 requires:
|
|
7
|
+
1. PROPOSE searches based on interview answers
|
|
8
|
+
2. Show checkbox list to user
|
|
9
|
+
3. USE AskUserQuestion: "Approve? [Y] / Add more? ____"
|
|
10
|
+
4. Execute only approved searches
|
|
11
|
+
5. Loop back if user wants additions
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
- {"permissionDecision": "allow"} - Let the tool run
|
|
15
|
+
- {"permissionDecision": "deny", "reason": "..."} - Block with explanation
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
try:
|
|
26
|
+
input_data = json.load(sys.stdin)
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
29
|
+
sys.exit(0)
|
|
30
|
+
|
|
31
|
+
tool_input = input_data.get("tool_input", {})
|
|
32
|
+
file_path = tool_input.get("file_path", "")
|
|
33
|
+
|
|
34
|
+
# Only enforce for API route and schema files
|
|
35
|
+
is_api_file = "/api/" in file_path and file_path.endswith(".ts")
|
|
36
|
+
is_schema_file = "/schemas/" in file_path and file_path.endswith(".ts")
|
|
37
|
+
|
|
38
|
+
if not is_api_file and not is_schema_file:
|
|
39
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
|
|
42
|
+
# Skip test files
|
|
43
|
+
if ".test." in file_path or "/__tests__/" in file_path or ".spec." in file_path:
|
|
44
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
if not STATE_FILE.exists():
|
|
48
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
state = json.loads(STATE_FILE.read_text())
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
endpoint = state.get("endpoint", "unknown")
|
|
58
|
+
phases = state.get("phases", {})
|
|
59
|
+
interview = phases.get("interview", {})
|
|
60
|
+
research_deep = phases.get("research_deep", {})
|
|
61
|
+
|
|
62
|
+
# Only enforce after interview is complete
|
|
63
|
+
if interview.get("status") != "complete":
|
|
64
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
|
|
67
|
+
status = research_deep.get("status", "not_started")
|
|
68
|
+
|
|
69
|
+
# If deep research was not needed (no proposed searches), allow
|
|
70
|
+
proposed = research_deep.get("proposed_searches", [])
|
|
71
|
+
if not proposed and status == "not_started":
|
|
72
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
73
|
+
sys.exit(0)
|
|
74
|
+
|
|
75
|
+
if status != "complete":
|
|
76
|
+
user_question_asked = research_deep.get("user_question_asked", False)
|
|
77
|
+
user_approved = research_deep.get("user_approved", False)
|
|
78
|
+
proposals_shown = research_deep.get("proposals_shown", False)
|
|
79
|
+
approved_searches = research_deep.get("approved_searches", [])
|
|
80
|
+
executed_searches = research_deep.get("executed_searches", [])
|
|
81
|
+
skipped_searches = research_deep.get("skipped_searches", [])
|
|
82
|
+
|
|
83
|
+
# Calculate pending
|
|
84
|
+
pending = [s for s in approved_searches if s not in executed_searches and s not in skipped_searches]
|
|
85
|
+
|
|
86
|
+
missing = []
|
|
87
|
+
if not proposals_shown:
|
|
88
|
+
missing.append("Proposed searches not shown to user")
|
|
89
|
+
if not user_question_asked:
|
|
90
|
+
missing.append("User approval question (AskUserQuestion not used)")
|
|
91
|
+
if not user_approved:
|
|
92
|
+
missing.append("User hasn't approved the search list")
|
|
93
|
+
if pending:
|
|
94
|
+
missing.append(f"Approved searches not executed ({len(pending)} pending)")
|
|
95
|
+
|
|
96
|
+
print(json.dumps({
|
|
97
|
+
"permissionDecision": "deny",
|
|
98
|
+
"reason": f"""❌ BLOCKED: Deep research (Phase 4) not complete.
|
|
99
|
+
|
|
100
|
+
Status: {status}
|
|
101
|
+
Proposed searches: {len(proposed)}
|
|
102
|
+
User shown proposals: {proposals_shown}
|
|
103
|
+
User question asked: {user_question_asked}
|
|
104
|
+
User approved: {user_approved}
|
|
105
|
+
Approved: {len(approved_searches)}
|
|
106
|
+
Executed: {len(executed_searches)}
|
|
107
|
+
Skipped: {len(skipped_searches)}
|
|
108
|
+
Pending: {len(pending)}
|
|
109
|
+
|
|
110
|
+
MISSING:
|
|
111
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
112
|
+
|
|
113
|
+
═══════════════════════════════════════════════════════════
|
|
114
|
+
⚠️ GET USER APPROVAL FOR DEEP RESEARCH
|
|
115
|
+
═══════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
REQUIRED STEPS:
|
|
118
|
+
|
|
119
|
+
1. Based on interview, PROPOSE targeted searches:
|
|
120
|
+
┌───────────────────────────────────────────────────────┐
|
|
121
|
+
│ PROPOSED DEEP RESEARCH │
|
|
122
|
+
│ │
|
|
123
|
+
│ Based on your interview answers, I want to research: │
|
|
124
|
+
│ │
|
|
125
|
+
│ [x] Error response format (for error handling) │
|
|
126
|
+
│ [x] Rate limiting behavior (caching selected) │
|
|
127
|
+
│ [ ] Webhook support (not selected in interview) │
|
|
128
|
+
│ [x] Authentication edge cases │
|
|
129
|
+
│ │
|
|
130
|
+
│ Approve these searches? [Y] │
|
|
131
|
+
│ Add more: ____ │
|
|
132
|
+
│ Skip and proceed: [n] │
|
|
133
|
+
└───────────────────────────────────────────────────────┘
|
|
134
|
+
|
|
135
|
+
2. USE AskUserQuestion:
|
|
136
|
+
question: "Approve these deep research searches?"
|
|
137
|
+
options: [
|
|
138
|
+
{{"value": "approve", "label": "Yes, run these searches"}},
|
|
139
|
+
{{"value": "add", "label": "Add more - I also need [topic]"}},
|
|
140
|
+
{{"value": "skip", "label": "Skip deep research, proceed to schema"}}
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
3. If user says "add":
|
|
144
|
+
• Ask what additional topics they need
|
|
145
|
+
• Add to proposed_searches
|
|
146
|
+
• LOOP BACK and show updated list
|
|
147
|
+
|
|
148
|
+
4. If user says "approve":
|
|
149
|
+
• Execute each approved search
|
|
150
|
+
• Record results in executed_searches
|
|
151
|
+
|
|
152
|
+
5. If user says "skip":
|
|
153
|
+
• Record all as skipped_searches with reason
|
|
154
|
+
• Proceed to schema
|
|
155
|
+
|
|
156
|
+
6. After all searches complete (or skipped):
|
|
157
|
+
• Set research_deep.user_approved = true
|
|
158
|
+
• Set research_deep.user_question_asked = true
|
|
159
|
+
• Set research_deep.proposals_shown = true
|
|
160
|
+
• Set research_deep.status = "complete"
|
|
161
|
+
|
|
162
|
+
WHY: Research is ADAPTIVE based on interview, not shotgun."""
|
|
163
|
+
}))
|
|
164
|
+
sys.exit(0)
|
|
165
|
+
|
|
166
|
+
# Complete
|
|
167
|
+
executed = research_deep.get("executed_searches", [])
|
|
168
|
+
skipped = research_deep.get("skipped_searches", [])
|
|
169
|
+
print(json.dumps({
|
|
170
|
+
"permissionDecision": "allow",
|
|
171
|
+
"message": f"""✅ Deep research complete.
|
|
172
|
+
Executed: {len(executed)} searches
|
|
173
|
+
Skipped: {len(skipped)} (with reasons)
|
|
174
|
+
User approved the search plan."""
|
|
175
|
+
}))
|
|
176
|
+
sys.exit(0)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
main()
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit
|
|
4
|
+
Purpose: Block writing API code if disambiguation phase not complete WITH USER CONFIRMATION
|
|
5
|
+
|
|
6
|
+
Phase 0 requires:
|
|
7
|
+
1. Search 3-5 variations of the term
|
|
8
|
+
2. Present options to user via AskUserQuestion
|
|
9
|
+
3. User selects which interpretation
|
|
10
|
+
4. Loop back if still ambiguous
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
- {"permissionDecision": "allow"} - Let the tool run
|
|
14
|
+
- {"permissionDecision": "deny", "reason": "..."} - Block with explanation
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
21
|
+
|
|
22
|
+
# Minimum search variations required
|
|
23
|
+
MIN_SEARCH_VARIATIONS = 2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main():
|
|
27
|
+
try:
|
|
28
|
+
input_data = json.load(sys.stdin)
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
31
|
+
sys.exit(0)
|
|
32
|
+
|
|
33
|
+
tool_input = input_data.get("tool_input", {})
|
|
34
|
+
file_path = tool_input.get("file_path", "")
|
|
35
|
+
|
|
36
|
+
# Only enforce for API route files
|
|
37
|
+
if "/api/" not in file_path:
|
|
38
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
39
|
+
sys.exit(0)
|
|
40
|
+
|
|
41
|
+
# Skip test files
|
|
42
|
+
if ".test." in file_path or "/__tests__/" in file_path or ".spec." in file_path:
|
|
43
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
# Skip documentation/config files
|
|
47
|
+
if file_path.endswith(".md") or file_path.endswith(".json"):
|
|
48
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
if not STATE_FILE.exists():
|
|
52
|
+
print(json.dumps({
|
|
53
|
+
"permissionDecision": "deny",
|
|
54
|
+
"reason": """❌ API workflow not started.
|
|
55
|
+
|
|
56
|
+
Run /api-create [endpoint-name] to begin the interview-driven workflow.
|
|
57
|
+
|
|
58
|
+
Phase 0 (Disambiguation) is required before any implementation."""
|
|
59
|
+
}))
|
|
60
|
+
sys.exit(0)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
state = json.loads(STATE_FILE.read_text())
|
|
64
|
+
except json.JSONDecodeError:
|
|
65
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
endpoint = state.get("endpoint")
|
|
69
|
+
if not endpoint:
|
|
70
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
phases = state.get("phases", {})
|
|
74
|
+
disambiguation = phases.get("disambiguation", {})
|
|
75
|
+
status = disambiguation.get("status", "not_started")
|
|
76
|
+
|
|
77
|
+
if status != "complete":
|
|
78
|
+
search_variations = disambiguation.get("search_variations", [])
|
|
79
|
+
user_question_asked = disambiguation.get("user_question_asked", False)
|
|
80
|
+
user_selected = disambiguation.get("user_selected", None)
|
|
81
|
+
|
|
82
|
+
# Check what's missing
|
|
83
|
+
missing = []
|
|
84
|
+
if len(search_variations) < MIN_SEARCH_VARIATIONS:
|
|
85
|
+
missing.append(f"Search variations ({len(search_variations)}/{MIN_SEARCH_VARIATIONS})")
|
|
86
|
+
if not user_question_asked:
|
|
87
|
+
missing.append("User question (AskUserQuestion not used)")
|
|
88
|
+
if not user_selected:
|
|
89
|
+
missing.append("User selection (no choice recorded)")
|
|
90
|
+
|
|
91
|
+
print(json.dumps({
|
|
92
|
+
"permissionDecision": "deny",
|
|
93
|
+
"reason": f"""❌ BLOCKED: Disambiguation phase (Phase 0) not complete.
|
|
94
|
+
|
|
95
|
+
Status: {status}
|
|
96
|
+
Search variations: {len(search_variations)}
|
|
97
|
+
User question asked: {user_question_asked}
|
|
98
|
+
User selection: {user_selected or "None"}
|
|
99
|
+
|
|
100
|
+
MISSING:
|
|
101
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
102
|
+
|
|
103
|
+
═══════════════════════════════════════════════════════════
|
|
104
|
+
⚠️ COMPLETE DISAMBIGUATION WITH USER CONFIRMATION
|
|
105
|
+
═══════════════════════════════════════════════════════════
|
|
106
|
+
|
|
107
|
+
REQUIRED STEPS:
|
|
108
|
+
|
|
109
|
+
1. Search 2-3 variations:
|
|
110
|
+
• WebSearch: "{endpoint}"
|
|
111
|
+
• WebSearch: "{endpoint} API"
|
|
112
|
+
• WebSearch: "{endpoint} SDK npm package"
|
|
113
|
+
|
|
114
|
+
2. USE AskUserQuestion with options:
|
|
115
|
+
┌───────────────────────────────────────────────────────┐
|
|
116
|
+
│ I found multiple things matching "{endpoint}": │
|
|
117
|
+
│ │
|
|
118
|
+
│ [A] The official REST API │
|
|
119
|
+
│ [B] The npm/SDK wrapper package │
|
|
120
|
+
│ [C] Both (API + SDK) │
|
|
121
|
+
│ [D] Something else: ____ │
|
|
122
|
+
│ │
|
|
123
|
+
│ Which should this endpoint use? │
|
|
124
|
+
└───────────────────────────────────────────────────────┘
|
|
125
|
+
|
|
126
|
+
3. Record user's choice in state:
|
|
127
|
+
disambiguation.user_selected = "A" (or user's choice)
|
|
128
|
+
disambiguation.user_question_asked = true
|
|
129
|
+
disambiguation.status = "complete"
|
|
130
|
+
|
|
131
|
+
4. LOOP BACK if user is still unsure - search more variations
|
|
132
|
+
|
|
133
|
+
WHY: Different interpretations = different implementations."""
|
|
134
|
+
}))
|
|
135
|
+
sys.exit(0)
|
|
136
|
+
|
|
137
|
+
# Complete - inject context
|
|
138
|
+
selected = disambiguation.get("user_selected", "")
|
|
139
|
+
print(json.dumps({
|
|
140
|
+
"permissionDecision": "allow",
|
|
141
|
+
"message": f"""✅ Disambiguation complete.
|
|
142
|
+
User selected: {selected}
|
|
143
|
+
Proceeding with this interpretation."""
|
|
144
|
+
}))
|
|
145
|
+
sys.exit(0)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
main()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit (and Stop)
|
|
4
|
+
Purpose: Block completion until documentation confirmed WITH USER REVIEW
|
|
5
|
+
|
|
6
|
+
Phase 11 (Documentation) requires:
|
|
7
|
+
1. Update api-tests-manifest.json
|
|
8
|
+
2. Cache research to .claude/research/
|
|
9
|
+
3. Update OpenAPI spec if applicable
|
|
10
|
+
4. SHOW documentation checklist to user
|
|
11
|
+
5. USE AskUserQuestion: "Documentation complete? [Y/n]"
|
|
12
|
+
6. Only allow completion when user confirms
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
- {"permissionDecision": "allow"} - Let the tool run
|
|
16
|
+
- {"permissionDecision": "deny", "reason": "..."} - Block with explanation
|
|
17
|
+
"""
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
try:
|
|
27
|
+
input_data = json.load(sys.stdin)
|
|
28
|
+
except json.JSONDecodeError:
|
|
29
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
30
|
+
sys.exit(0)
|
|
31
|
+
|
|
32
|
+
tool_input = input_data.get("tool_input", {})
|
|
33
|
+
file_path = tool_input.get("file_path", "")
|
|
34
|
+
|
|
35
|
+
# Only enforce for documentation files or when trying to mark workflow complete
|
|
36
|
+
is_manifest = "api-tests-manifest.json" in file_path
|
|
37
|
+
is_openapi = "openapi" in file_path.lower() and file_path.endswith((".json", ".yaml", ".yml"))
|
|
38
|
+
is_readme = file_path.endswith("README.md") and "/api/" in file_path
|
|
39
|
+
|
|
40
|
+
# Also check for state file updates (marking complete)
|
|
41
|
+
is_state_update = "api-dev-state.json" in file_path
|
|
42
|
+
|
|
43
|
+
if not is_manifest and not is_openapi and not is_readme and not is_state_update:
|
|
44
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
if not STATE_FILE.exists():
|
|
48
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
state = json.loads(STATE_FILE.read_text())
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
endpoint = state.get("endpoint", "unknown")
|
|
58
|
+
phases = state.get("phases", {})
|
|
59
|
+
tdd_refactor = phases.get("tdd_refactor", {})
|
|
60
|
+
documentation = phases.get("documentation", {})
|
|
61
|
+
|
|
62
|
+
# Only enforce after refactor is complete
|
|
63
|
+
if tdd_refactor.get("status") != "complete":
|
|
64
|
+
# Allow documentation updates during development
|
|
65
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
|
|
68
|
+
status = documentation.get("status", "not_started")
|
|
69
|
+
|
|
70
|
+
if status != "complete":
|
|
71
|
+
user_question_asked = documentation.get("user_question_asked", False)
|
|
72
|
+
user_confirmed = documentation.get("user_confirmed", False)
|
|
73
|
+
checklist_shown = documentation.get("checklist_shown", False)
|
|
74
|
+
manifest_updated = documentation.get("manifest_updated", False)
|
|
75
|
+
research_cached = documentation.get("research_cached", False)
|
|
76
|
+
openapi_updated = documentation.get("openapi_updated", False)
|
|
77
|
+
|
|
78
|
+
missing = []
|
|
79
|
+
if not manifest_updated:
|
|
80
|
+
missing.append("api-tests-manifest.json not updated")
|
|
81
|
+
if not research_cached:
|
|
82
|
+
missing.append("Research not cached to .claude/research/")
|
|
83
|
+
if not checklist_shown:
|
|
84
|
+
missing.append("Documentation checklist not shown to user")
|
|
85
|
+
if not user_question_asked:
|
|
86
|
+
missing.append("User confirmation question (AskUserQuestion not used)")
|
|
87
|
+
if not user_confirmed:
|
|
88
|
+
missing.append("User hasn't confirmed documentation complete")
|
|
89
|
+
|
|
90
|
+
print(json.dumps({
|
|
91
|
+
"permissionDecision": "deny",
|
|
92
|
+
"reason": f"""❌ BLOCKED: Documentation (Phase 11) not complete.
|
|
93
|
+
|
|
94
|
+
Status: {status}
|
|
95
|
+
Manifest updated: {manifest_updated}
|
|
96
|
+
Research cached: {research_cached}
|
|
97
|
+
OpenAPI updated: {openapi_updated}
|
|
98
|
+
Checklist shown: {checklist_shown}
|
|
99
|
+
User question asked: {user_question_asked}
|
|
100
|
+
User confirmed: {user_confirmed}
|
|
101
|
+
|
|
102
|
+
MISSING:
|
|
103
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
104
|
+
|
|
105
|
+
═══════════════════════════════════════════════════════════
|
|
106
|
+
⚠️ GET USER CONFIRMATION FOR DOCUMENTATION
|
|
107
|
+
═══════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
REQUIRED STEPS:
|
|
110
|
+
|
|
111
|
+
1. Update api-tests-manifest.json with:
|
|
112
|
+
• Endpoint path, method, description
|
|
113
|
+
• Request/response schemas
|
|
114
|
+
• Test coverage info
|
|
115
|
+
• Code examples
|
|
116
|
+
• Testing notes
|
|
117
|
+
|
|
118
|
+
2. Cache research to .claude/research/{endpoint}/:
|
|
119
|
+
• sources.json - URLs and summaries
|
|
120
|
+
• interview.json - User decisions
|
|
121
|
+
• schema.json - Final Zod schemas
|
|
122
|
+
|
|
123
|
+
3. Update OpenAPI spec (if applicable):
|
|
124
|
+
• Add endpoint definition
|
|
125
|
+
• Document parameters
|
|
126
|
+
• Document responses
|
|
127
|
+
|
|
128
|
+
4. SHOW documentation checklist to user:
|
|
129
|
+
┌───────────────────────────────────────────────────────┐
|
|
130
|
+
│ DOCUMENTATION CHECKLIST │
|
|
131
|
+
│ │
|
|
132
|
+
│ ✓ api-tests-manifest.json │
|
|
133
|
+
│ • Added {endpoint} entry │
|
|
134
|
+
│ • 8 test scenarios documented │
|
|
135
|
+
│ • Code examples included │
|
|
136
|
+
│ │
|
|
137
|
+
│ ✓ Research Cache │
|
|
138
|
+
│ • .claude/research/{endpoint}/sources.json │
|
|
139
|
+
│ • .claude/research/{endpoint}/interview.json │
|
|
140
|
+
│ │
|
|
141
|
+
│ {'✓' if openapi_updated else '⏭'} OpenAPI Spec │
|
|
142
|
+
│ • {'Updated' if openapi_updated else 'Skipped (internal API)'} │
|
|
143
|
+
│ │
|
|
144
|
+
│ All documentation complete? [Y] │
|
|
145
|
+
│ Need to add something? [n] ____ │
|
|
146
|
+
└───────────────────────────────────────────────────────┘
|
|
147
|
+
|
|
148
|
+
5. USE AskUserQuestion:
|
|
149
|
+
question: "Documentation checklist complete?"
|
|
150
|
+
options: [
|
|
151
|
+
{{"value": "confirm", "label": "Yes, all documentation is done"}},
|
|
152
|
+
{{"value": "add", "label": "No, I need to add [what]"}},
|
|
153
|
+
{{"value": "skip", "label": "Skip docs for now (not recommended)"}}
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
6. If user says "add":
|
|
157
|
+
• Ask what documentation is missing
|
|
158
|
+
• Update the relevant files
|
|
159
|
+
• LOOP BACK and show updated checklist
|
|
160
|
+
|
|
161
|
+
7. If user says "confirm":
|
|
162
|
+
• Set documentation.user_confirmed = true
|
|
163
|
+
• Set documentation.user_question_asked = true
|
|
164
|
+
• Set documentation.checklist_shown = true
|
|
165
|
+
• Set documentation.manifest_updated = true
|
|
166
|
+
• Set documentation.research_cached = true
|
|
167
|
+
• Set documentation.status = "complete"
|
|
168
|
+
• Mark entire workflow as complete!
|
|
169
|
+
|
|
170
|
+
WHY: Documentation ensures next developer (or future Claude) has context."""
|
|
171
|
+
}))
|
|
172
|
+
sys.exit(0)
|
|
173
|
+
|
|
174
|
+
# Documentation complete
|
|
175
|
+
print(json.dumps({
|
|
176
|
+
"permissionDecision": "allow",
|
|
177
|
+
"message": f"""✅ Documentation complete for {endpoint}.
|
|
178
|
+
Manifest updated: {manifest_updated}
|
|
179
|
+
Research cached: {research_cached}
|
|
180
|
+
OpenAPI updated: {openapi_updated}
|
|
181
|
+
User confirmed documentation is complete."""
|
|
182
|
+
}))
|
|
183
|
+
sys.exit(0)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
main()
|