@hustle-together/api-dev-tools 3.10.1 → 3.12.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/.claude/agents/code-reviewer.md +170 -0
- package/.claude/agents/docs-generator.md +80 -0
- package/.claude/agents/implementation-reviewer.md +119 -0
- package/.claude/agents/parallel-researcher.md +52 -0
- package/.claude/agents/research-validator.md +116 -0
- package/.claude/agents/schema-generator.md +70 -0
- package/.claude/agents/test-writer.md +104 -0
- package/.claude/api-dev-state.json +331 -0
- package/.claude/commands/README.md +196 -0
- package/.claude/commands/add-command.md +212 -0
- package/.claude/commands/api-create.md +510 -0
- package/.claude/commands/api-env.md +51 -0
- package/.claude/commands/api-interview.md +344 -0
- package/.claude/commands/api-research.md +357 -0
- package/.claude/commands/api-status.md +279 -0
- package/.claude/commands/api-verify.md +232 -0
- package/.claude/commands/beepboop.md +96 -0
- package/.claude/commands/busycommit.md +111 -0
- package/.claude/commands/commit.md +82 -0
- package/.claude/commands/cycle.md +137 -0
- package/.claude/commands/gap.md +85 -0
- package/.claude/commands/green.md +137 -0
- package/.claude/commands/issue.md +187 -0
- package/.claude/commands/ntfy-setup.md +91 -0
- package/.claude/commands/ntfy-test.md +74 -0
- package/.claude/commands/plan.md +167 -0
- package/.claude/commands/pr.md +121 -0
- package/.claude/commands/publish.md +40 -0
- package/.claude/commands/red.md +137 -0
- package/.claude/commands/refactor.md +137 -0
- package/.claude/commands/spike.md +137 -0
- package/.claude/commands/summarize.md +93 -0
- package/.claude/commands/tdd.md +139 -0
- package/.claude/commands/worktree-add.md +307 -0
- package/.claude/commands/worktree-cleanup.md +275 -0
- package/.claude/hooks/api-workflow-check.py +227 -0
- package/.claude/hooks/enforce-deep-research.py +185 -0
- package/.claude/hooks/enforce-disambiguation.py +155 -0
- package/.claude/hooks/enforce-documentation.py +192 -0
- package/.claude/hooks/enforce-environment.py +253 -0
- package/.claude/hooks/enforce-external-research.py +328 -0
- package/.claude/hooks/enforce-interview.py +421 -0
- package/.claude/hooks/enforce-refactor.py +189 -0
- package/.claude/hooks/enforce-research.py +159 -0
- package/.claude/hooks/enforce-schema.py +186 -0
- package/.claude/hooks/enforce-scope.py +160 -0
- package/.claude/hooks/enforce-tdd-red.py +250 -0
- package/.claude/hooks/enforce-verify.py +186 -0
- package/.claude/hooks/periodic-reground.py +154 -0
- package/.claude/hooks/session-startup.py +151 -0
- package/.claude/hooks/track-tool-use.py +626 -0
- package/.claude/hooks/verify-after-green.py +282 -0
- package/.claude/hooks/verify-implementation.py +225 -0
- package/.claude/research/index.json +6 -0
- package/.claude/settings.json +144 -0
- package/.claude/settings.local.json +12 -0
- package/.claude-plugin/marketplace.json +103 -0
- package/.skills/README.md +293 -0
- package/.skills/_shared/convert-commands.py +192 -0
- package/.skills/_shared/hooks/api-workflow-check.py +227 -0
- package/.skills/_shared/hooks/enforce-deep-research.py +185 -0
- package/.skills/_shared/hooks/enforce-disambiguation.py +155 -0
- package/.skills/_shared/hooks/enforce-documentation.py +192 -0
- package/.skills/_shared/hooks/enforce-environment.py +253 -0
- package/.skills/_shared/hooks/enforce-external-research.py +328 -0
- package/.skills/_shared/hooks/enforce-interview.py +421 -0
- package/.skills/_shared/hooks/enforce-refactor.py +189 -0
- package/.skills/_shared/hooks/enforce-research.py +159 -0
- package/.skills/_shared/hooks/enforce-schema.py +186 -0
- package/.skills/_shared/hooks/enforce-scope.py +160 -0
- package/.skills/_shared/hooks/enforce-tdd-red.py +250 -0
- package/.skills/_shared/hooks/enforce-verify.py +186 -0
- package/.skills/_shared/hooks/periodic-reground.py +154 -0
- package/.skills/_shared/hooks/session-startup.py +151 -0
- package/.skills/_shared/hooks/track-tool-use.py +626 -0
- package/.skills/_shared/hooks/verify-after-green.py +282 -0
- package/.skills/_shared/hooks/verify-implementation.py +225 -0
- package/.skills/_shared/install.sh +114 -0
- package/.skills/_shared/settings.json +93 -0
- package/.skills/add-command/SKILL.md +227 -0
- package/.skills/api-create/SKILL.md +623 -0
- package/.skills/api-env/SKILL.md +64 -0
- package/.skills/api-interview/SKILL.md +357 -0
- package/.skills/api-research/SKILL.md +370 -0
- package/.skills/api-status/SKILL.md +292 -0
- package/.skills/api-verify/SKILL.md +245 -0
- package/.skills/beepboop/SKILL.md +111 -0
- package/.skills/busycommit/SKILL.md +126 -0
- package/.skills/commit/SKILL.md +97 -0
- package/.skills/cycle/SKILL.md +152 -0
- package/.skills/gap/SKILL.md +100 -0
- package/.skills/green/SKILL.md +152 -0
- package/.skills/issue/SKILL.md +202 -0
- package/.skills/plan/SKILL.md +182 -0
- package/.skills/pr/SKILL.md +136 -0
- package/.skills/publish/SKILL.md +160 -0
- package/.skills/red/SKILL.md +152 -0
- package/.skills/refactor/SKILL.md +152 -0
- package/.skills/spike/SKILL.md +152 -0
- package/.skills/summarize/SKILL.md +108 -0
- package/.skills/tdd/SKILL.md +154 -0
- package/.skills/update-todos/SKILL.md +250 -0
- package/.skills/worktree-add/SKILL.md +322 -0
- package/.skills/worktree-cleanup/SKILL.md +290 -0
- package/CHANGELOG.md +115 -0
- package/README.md +161 -7101
- package/bin/cli.js +448 -805
- package/commands/README.md +66 -31
- package/commands/add-command.md +8 -5
- package/commands/beepboop.md +4 -5
- package/commands/busycommit.md +2 -3
- package/commands/commit.md +2 -3
- package/commands/cycle.md +2 -7
- package/commands/gap.md +2 -3
- package/commands/green.md +2 -7
- package/commands/hustle-api-continue.md +8 -5
- package/commands/hustle-api-create.md +70 -29
- package/commands/hustle-api-env.md +1 -0
- package/commands/hustle-api-interview.md +32 -19
- package/commands/hustle-api-research.md +47 -21
- package/commands/hustle-api-sessions.md +8 -7
- package/commands/hustle-api-status.md +21 -1
- package/commands/hustle-api-verify.md +14 -13
- package/commands/hustle-combine.md +488 -241
- package/commands/hustle-ui-create-page.md +113 -50
- package/commands/hustle-ui-create.md +179 -26
- package/commands/issue.md +3 -8
- package/commands/plan.md +2 -3
- package/commands/pr.md +2 -3
- package/commands/red.md +2 -7
- package/commands/refactor.md +2 -7
- package/commands/spike.md +2 -7
- package/commands/summarize.md +2 -3
- package/commands/tdd.md +2 -7
- package/commands/worktree-add.md +208 -216
- package/commands/worktree-cleanup.md +172 -178
- package/hooks/api-workflow-check.py +5 -3
- package/hooks/enforce-component-type-confirm.py +97 -0
- package/hooks/lib/__init__.py +1 -0
- package/hooks/lib/greptile.py +355 -0
- package/hooks/lib/ntfy.py +209 -0
- package/hooks/notify-input-needed.py +73 -0
- package/hooks/notify-phase-complete.py +90 -0
- package/hooks/run-code-review.py +246 -0
- package/hooks/track-token-usage.py +121 -0
- package/package.json +33 -12
- package/scripts/collect-test-results.ts +102 -77
- package/scripts/extract-parameters.ts +112 -70
- package/scripts/generate-test-manifest.ts +118 -77
- package/templates/.env.example +57 -0
- package/templates/BRAND_GUIDE.md +92 -52
- package/templates/CLAUDE-SECTION.md +40 -37
- package/templates/SPEC.json +186 -38
- package/templates/api-dev-state.json +33 -4
- package/templates/api-showcase/_components/APICard.tsx +22 -18
- package/templates/api-showcase/_components/APIModal.tsx +110 -64
- package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
- package/templates/api-showcase/_components/APITester.tsx +128 -67
- package/templates/api-showcase/page.tsx +4 -4
- package/templates/api-test/page.tsx +51 -30
- package/templates/api-test/test-structure/route.ts +43 -34
- package/templates/component/Component.stories.tsx +41 -39
- package/templates/component/Component.test.tsx +96 -78
- package/templates/component/Component.tsx +63 -52
- package/templates/component/Component.types.ts +10 -6
- package/templates/component/Component.visual.spec.ts +170 -0
- package/templates/component/index.ts +2 -2
- package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
- package/templates/dev-tools/page.tsx +4 -3
- package/templates/mcp-servers.json +30 -2
- package/templates/page/page.e2e.test.ts +56 -48
- package/templates/page/page.tsx +3 -3
- package/templates/shared/HeroHeader.tsx +16 -15
- package/templates/shared/index.ts +1 -1
- package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
- package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
- package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
- package/templates/ui-showcase/page.tsx +4 -4
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit
|
|
4
|
+
Purpose: Block writing API code if research phase not complete WITH USER CHECKPOINT
|
|
5
|
+
|
|
6
|
+
Phase 3 requires:
|
|
7
|
+
1. Execute 2-3 initial searches (Context7, WebSearch)
|
|
8
|
+
2. Present summary TABLE to user
|
|
9
|
+
3. USE AskUserQuestion: "Proceed? [Y] / Search more? [n]"
|
|
10
|
+
4. Loop back if user wants more research
|
|
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 sources required
|
|
23
|
+
MIN_SOURCES = 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 and "/api-test/" not in file_path:
|
|
38
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
39
|
+
sys.exit(0)
|
|
40
|
+
|
|
41
|
+
# Skip test files - TDD Red allows tests before research complete
|
|
42
|
+
if ".test." in file_path or "/__tests__/" in file_path:
|
|
43
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
if file_path.endswith(".md") or file_path.endswith(".json"):
|
|
47
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
48
|
+
sys.exit(0)
|
|
49
|
+
|
|
50
|
+
if not STATE_FILE.exists():
|
|
51
|
+
print(json.dumps({
|
|
52
|
+
"permissionDecision": "deny",
|
|
53
|
+
"reason": """❌ API development state not initialized.
|
|
54
|
+
|
|
55
|
+
Run /api-create [endpoint-name] to start the workflow."""
|
|
56
|
+
}))
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
state = json.loads(STATE_FILE.read_text())
|
|
61
|
+
except json.JSONDecodeError:
|
|
62
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
65
|
+
endpoint = state.get("endpoint", "unknown")
|
|
66
|
+
phases = state.get("phases", {})
|
|
67
|
+
research = phases.get("research_initial", {})
|
|
68
|
+
status = research.get("status", "not_started")
|
|
69
|
+
|
|
70
|
+
if status != "complete":
|
|
71
|
+
sources = research.get("sources", [])
|
|
72
|
+
user_question_asked = research.get("user_question_asked", False)
|
|
73
|
+
user_approved = research.get("user_approved", False)
|
|
74
|
+
summary_shown = research.get("summary_shown", False)
|
|
75
|
+
|
|
76
|
+
missing = []
|
|
77
|
+
if len(sources) < MIN_SOURCES:
|
|
78
|
+
missing.append(f"Sources ({len(sources)}/{MIN_SOURCES} minimum)")
|
|
79
|
+
if not summary_shown:
|
|
80
|
+
missing.append("Research summary table not shown to user")
|
|
81
|
+
if not user_question_asked:
|
|
82
|
+
missing.append("User checkpoint (AskUserQuestion not used)")
|
|
83
|
+
if not user_approved:
|
|
84
|
+
missing.append("User approval to proceed")
|
|
85
|
+
|
|
86
|
+
print(json.dumps({
|
|
87
|
+
"permissionDecision": "deny",
|
|
88
|
+
"reason": f"""❌ BLOCKED: Initial research (Phase 3) not complete.
|
|
89
|
+
|
|
90
|
+
Status: {status}
|
|
91
|
+
Sources consulted: {len(sources)}
|
|
92
|
+
Summary shown: {summary_shown}
|
|
93
|
+
User question asked: {user_question_asked}
|
|
94
|
+
User approved: {user_approved}
|
|
95
|
+
|
|
96
|
+
MISSING:
|
|
97
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
98
|
+
|
|
99
|
+
═══════════════════════════════════════════════════════════
|
|
100
|
+
⚠️ COMPLETE RESEARCH WITH USER CHECKPOINT
|
|
101
|
+
═══════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
REQUIRED STEPS:
|
|
104
|
+
|
|
105
|
+
1. Execute 2-3 initial searches:
|
|
106
|
+
• Context7: "{endpoint}"
|
|
107
|
+
• WebSearch: "{endpoint} official documentation"
|
|
108
|
+
• WebSearch: "site:[official-domain] {endpoint} API reference"
|
|
109
|
+
|
|
110
|
+
2. Present summary TABLE to user:
|
|
111
|
+
┌───────────────────────────────────────────────────────┐
|
|
112
|
+
│ RESEARCH SUMMARY │
|
|
113
|
+
│ │
|
|
114
|
+
│ │ Source │ Found │
|
|
115
|
+
│ ├────────────────┼────────────────────────────────────│
|
|
116
|
+
│ │ Official docs │ ✓ [URL] │
|
|
117
|
+
│ │ API Reference │ ✓ REST v2 │
|
|
118
|
+
│ │ Auth method │ ✓ Bearer token │
|
|
119
|
+
│ │ NPM package │ ? Not found │
|
|
120
|
+
│ │
|
|
121
|
+
│ Proceed? [Y] / Search more? [n] ____ │
|
|
122
|
+
└───────────────────────────────────────────────────────┘
|
|
123
|
+
|
|
124
|
+
3. USE AskUserQuestion:
|
|
125
|
+
question: "Research summary above. Proceed or search more?"
|
|
126
|
+
options: [
|
|
127
|
+
{{"value": "proceed", "label": "Proceed to interview"}},
|
|
128
|
+
{{"value": "more", "label": "Search more - I need [topic]"}},
|
|
129
|
+
{{"value": "specific", "label": "Search for something specific"}}
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
4. If user says "more" or "specific":
|
|
133
|
+
• Ask what they want to research
|
|
134
|
+
• Execute additional searches
|
|
135
|
+
• LOOP BACK and show updated summary
|
|
136
|
+
|
|
137
|
+
5. If user says "proceed":
|
|
138
|
+
• Set research_initial.user_approved = true
|
|
139
|
+
• Set research_initial.user_question_asked = true
|
|
140
|
+
• Set research_initial.summary_shown = true
|
|
141
|
+
• Set research_initial.status = "complete"
|
|
142
|
+
|
|
143
|
+
WHY: Implementation must match CURRENT API documentation."""
|
|
144
|
+
}))
|
|
145
|
+
sys.exit(0)
|
|
146
|
+
|
|
147
|
+
# Research complete - inject context
|
|
148
|
+
sources = research.get("sources", [])
|
|
149
|
+
print(json.dumps({
|
|
150
|
+
"permissionDecision": "allow",
|
|
151
|
+
"message": f"""✅ Initial research complete.
|
|
152
|
+
Sources: {len(sources)}
|
|
153
|
+
User approved proceeding to interview."""
|
|
154
|
+
}))
|
|
155
|
+
sys.exit(0)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
main()
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit
|
|
4
|
+
Purpose: Block writing implementation if schema not reviewed WITH USER CONFIRMATION
|
|
5
|
+
|
|
6
|
+
Phase 6 requires:
|
|
7
|
+
1. Create Zod schemas based on interview + research
|
|
8
|
+
2. SHOW schema to user in formatted display
|
|
9
|
+
3. USE AskUserQuestion: "Schema matches interview? [Y/n]"
|
|
10
|
+
4. Loop back if user wants changes
|
|
11
|
+
5. Only proceed when user confirms
|
|
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
|
+
# Skip documentation/config files
|
|
48
|
+
if file_path.endswith(".md") or file_path.endswith(".json"):
|
|
49
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
|
|
52
|
+
if not STATE_FILE.exists():
|
|
53
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
state = json.loads(STATE_FILE.read_text())
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
60
|
+
sys.exit(0)
|
|
61
|
+
|
|
62
|
+
endpoint = state.get("endpoint", "unknown")
|
|
63
|
+
phases = state.get("phases", {})
|
|
64
|
+
interview = phases.get("interview", {})
|
|
65
|
+
research_deep = phases.get("research_deep", {})
|
|
66
|
+
schema_creation = phases.get("schema_creation", {})
|
|
67
|
+
|
|
68
|
+
# Only enforce after interview is complete
|
|
69
|
+
if interview.get("status") != "complete":
|
|
70
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
|
|
73
|
+
# Only enforce after deep research is complete (or not needed)
|
|
74
|
+
deep_status = research_deep.get("status", "not_started")
|
|
75
|
+
proposed = research_deep.get("proposed_searches", [])
|
|
76
|
+
if proposed and deep_status != "complete":
|
|
77
|
+
# Let enforce-deep-research.py handle this
|
|
78
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
status = schema_creation.get("status", "not_started")
|
|
82
|
+
|
|
83
|
+
if status != "complete":
|
|
84
|
+
user_question_asked = schema_creation.get("user_question_asked", False)
|
|
85
|
+
user_confirmed = schema_creation.get("user_confirmed", False)
|
|
86
|
+
schema_shown = schema_creation.get("schema_shown", False)
|
|
87
|
+
schema_file = schema_creation.get("schema_file", None)
|
|
88
|
+
fields_count = schema_creation.get("fields_count", 0)
|
|
89
|
+
|
|
90
|
+
missing = []
|
|
91
|
+
if not schema_shown:
|
|
92
|
+
missing.append("Schema not shown to user")
|
|
93
|
+
if not user_question_asked:
|
|
94
|
+
missing.append("User review question (AskUserQuestion not used)")
|
|
95
|
+
if not user_confirmed:
|
|
96
|
+
missing.append("User hasn't confirmed schema matches interview")
|
|
97
|
+
|
|
98
|
+
print(json.dumps({
|
|
99
|
+
"permissionDecision": "deny",
|
|
100
|
+
"reason": f"""❌ BLOCKED: Schema creation (Phase 6) not complete.
|
|
101
|
+
|
|
102
|
+
Status: {status}
|
|
103
|
+
Schema shown: {schema_shown}
|
|
104
|
+
User question asked: {user_question_asked}
|
|
105
|
+
User confirmed: {user_confirmed}
|
|
106
|
+
Schema file: {schema_file or "Not created yet"}
|
|
107
|
+
Fields: {fields_count}
|
|
108
|
+
|
|
109
|
+
MISSING:
|
|
110
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
111
|
+
|
|
112
|
+
═══════════════════════════════════════════════════════════
|
|
113
|
+
⚠️ GET USER CONFIRMATION FOR SCHEMA
|
|
114
|
+
═══════════════════════════════════════════════════════════
|
|
115
|
+
|
|
116
|
+
REQUIRED STEPS:
|
|
117
|
+
|
|
118
|
+
1. Create Zod schemas based on:
|
|
119
|
+
• Interview answers (error handling, caching, etc.)
|
|
120
|
+
• Research findings (API parameters, response format)
|
|
121
|
+
|
|
122
|
+
2. SHOW formatted schema to user:
|
|
123
|
+
┌───────────────────────────────────────────────────────┐
|
|
124
|
+
│ SCHEMA REVIEW │
|
|
125
|
+
│ │
|
|
126
|
+
│ Request Schema: │
|
|
127
|
+
│ domain: z.string() ← From interview: domain │
|
|
128
|
+
│ mode: z.enum(["full", "logo"]) ← Your choice: full │
|
|
129
|
+
│ includeColors: z.boolean().default(true) │
|
|
130
|
+
│ │
|
|
131
|
+
│ Response Schema: │
|
|
132
|
+
│ success: z.boolean() │
|
|
133
|
+
│ data: BrandDataSchema │
|
|
134
|
+
│ cached: z.boolean() ← From interview: 24h │
|
|
135
|
+
│ error: ErrorSchema.optional() │
|
|
136
|
+
│ │
|
|
137
|
+
│ Based on YOUR interview answers: │
|
|
138
|
+
│ ✓ Error handling: Return objects │
|
|
139
|
+
│ ✓ Caching: 24h (long) │
|
|
140
|
+
│ ✓ Mode: Full brand kit │
|
|
141
|
+
│ │
|
|
142
|
+
│ Does this match your requirements? [Y/n] │
|
|
143
|
+
└───────────────────────────────────────────────────────┘
|
|
144
|
+
|
|
145
|
+
3. USE AskUserQuestion:
|
|
146
|
+
question: "Does this schema match your interview answers?"
|
|
147
|
+
options: [
|
|
148
|
+
{{"value": "confirm", "label": "Yes, schema looks correct"}},
|
|
149
|
+
{{"value": "modify", "label": "No, I need changes - [describe]"}},
|
|
150
|
+
{{"value": "restart", "label": "Let's redo the interview"}}
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
4. If user says "modify":
|
|
154
|
+
• Ask what changes they need
|
|
155
|
+
• Update schema accordingly
|
|
156
|
+
• LOOP BACK and show updated schema
|
|
157
|
+
|
|
158
|
+
5. If user says "restart":
|
|
159
|
+
• Reset interview phase
|
|
160
|
+
• Go back to Phase 4
|
|
161
|
+
|
|
162
|
+
6. If user says "confirm":
|
|
163
|
+
• Set schema_creation.user_confirmed = true
|
|
164
|
+
• Set schema_creation.user_question_asked = true
|
|
165
|
+
• Set schema_creation.schema_shown = true
|
|
166
|
+
• Set schema_creation.status = "complete"
|
|
167
|
+
|
|
168
|
+
WHY: Schema is the CONTRACT. User must approve before implementation."""
|
|
169
|
+
}))
|
|
170
|
+
sys.exit(0)
|
|
171
|
+
|
|
172
|
+
# Schema complete
|
|
173
|
+
schema_file = schema_creation.get("schema_file", "")
|
|
174
|
+
fields_count = schema_creation.get("fields_count", 0)
|
|
175
|
+
print(json.dumps({
|
|
176
|
+
"permissionDecision": "allow",
|
|
177
|
+
"message": f"""✅ Schema creation complete.
|
|
178
|
+
Schema file: {schema_file}
|
|
179
|
+
Fields: {fields_count}
|
|
180
|
+
User confirmed schema matches interview requirements."""
|
|
181
|
+
}))
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: PreToolUse for Write/Edit
|
|
4
|
+
Purpose: Block writing API code if scope not confirmed BY USER
|
|
5
|
+
|
|
6
|
+
Phase 2 requires:
|
|
7
|
+
1. Present scope understanding to user
|
|
8
|
+
2. USE AskUserQuestion: "Is this correct? [Y/n]"
|
|
9
|
+
3. Record any modifications user requests
|
|
10
|
+
4. Loop back if user wants changes
|
|
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
|
+
|
|
23
|
+
def main():
|
|
24
|
+
try:
|
|
25
|
+
input_data = json.load(sys.stdin)
|
|
26
|
+
except json.JSONDecodeError:
|
|
27
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
|
|
30
|
+
tool_input = input_data.get("tool_input", {})
|
|
31
|
+
file_path = tool_input.get("file_path", "")
|
|
32
|
+
|
|
33
|
+
# Only enforce for API route files
|
|
34
|
+
if "/api/" not in file_path:
|
|
35
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
|
|
38
|
+
# Skip test files
|
|
39
|
+
if ".test." in file_path or "/__tests__/" in file_path or ".spec." in file_path:
|
|
40
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
if file_path.endswith(".md") or file_path.endswith(".json"):
|
|
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")
|
|
58
|
+
if not endpoint:
|
|
59
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
60
|
+
sys.exit(0)
|
|
61
|
+
|
|
62
|
+
phases = state.get("phases", {})
|
|
63
|
+
disambiguation = phases.get("disambiguation", {})
|
|
64
|
+
scope = phases.get("scope", {})
|
|
65
|
+
|
|
66
|
+
# Check disambiguation is complete first
|
|
67
|
+
if disambiguation.get("status") != "complete":
|
|
68
|
+
print(json.dumps({"permissionDecision": "allow"}))
|
|
69
|
+
sys.exit(0)
|
|
70
|
+
|
|
71
|
+
status = scope.get("status", "not_started")
|
|
72
|
+
user_confirmed = scope.get("user_confirmed", False)
|
|
73
|
+
user_question_asked = scope.get("user_question_asked", False)
|
|
74
|
+
phase_exit_confirmed = scope.get("phase_exit_confirmed", False)
|
|
75
|
+
|
|
76
|
+
if status != "complete" or not user_confirmed or not phase_exit_confirmed:
|
|
77
|
+
endpoint_path = scope.get("endpoint_path", f"/api/v2/{endpoint}")
|
|
78
|
+
modifications = scope.get("modifications", [])
|
|
79
|
+
|
|
80
|
+
missing = []
|
|
81
|
+
if not user_question_asked:
|
|
82
|
+
missing.append("User question (AskUserQuestion not used)")
|
|
83
|
+
if not user_confirmed:
|
|
84
|
+
missing.append("User confirmation (user hasn't said 'yes')")
|
|
85
|
+
if not phase_exit_confirmed:
|
|
86
|
+
missing.append("Phase exit confirmation (user must explicitly approve to proceed)")
|
|
87
|
+
|
|
88
|
+
print(json.dumps({
|
|
89
|
+
"permissionDecision": "deny",
|
|
90
|
+
"reason": f"""❌ BLOCKED: Scope confirmation (Phase 2) not complete.
|
|
91
|
+
|
|
92
|
+
Status: {status}
|
|
93
|
+
User question asked: {user_question_asked}
|
|
94
|
+
User confirmed: {user_confirmed}
|
|
95
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
96
|
+
Proposed path: {endpoint_path}
|
|
97
|
+
Modifications: {len(modifications)}
|
|
98
|
+
|
|
99
|
+
MISSING:
|
|
100
|
+
{chr(10).join(f" • {m}" for m in missing)}
|
|
101
|
+
|
|
102
|
+
═══════════════════════════════════════════════════════════
|
|
103
|
+
⚠️ GET USER CONFIRMATION OF SCOPE
|
|
104
|
+
═══════════════════════════════════════════════════════════
|
|
105
|
+
|
|
106
|
+
REQUIRED STEPS:
|
|
107
|
+
|
|
108
|
+
1. Present your understanding:
|
|
109
|
+
┌───────────────────────────────────────────────────────┐
|
|
110
|
+
│ SCOPE CONFIRMATION │
|
|
111
|
+
│ │
|
|
112
|
+
│ I understand you want: {endpoint_path} │
|
|
113
|
+
│ Purpose: [describe inferred purpose] │
|
|
114
|
+
│ External API: [service name if any] │
|
|
115
|
+
│ │
|
|
116
|
+
│ Is this correct? [Y/n] │
|
|
117
|
+
│ Any modifications needed? ____ │
|
|
118
|
+
└───────────────────────────────────────────────────────┘
|
|
119
|
+
|
|
120
|
+
2. USE AskUserQuestion:
|
|
121
|
+
question: "Is this scope correct? Any modifications?"
|
|
122
|
+
options: [
|
|
123
|
+
{{"value": "yes", "label": "Yes, proceed"}},
|
|
124
|
+
{{"value": "modify", "label": "I have modifications"}},
|
|
125
|
+
{{"value": "no", "label": "No, let me clarify"}}
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
3. If user says "modify" or "no":
|
|
129
|
+
• Ask for their modifications
|
|
130
|
+
• Record them in scope.modifications
|
|
131
|
+
• LOOP BACK and confirm again
|
|
132
|
+
|
|
133
|
+
4. If user says "yes":
|
|
134
|
+
• Set scope.user_confirmed = true
|
|
135
|
+
• Set scope.user_question_asked = true
|
|
136
|
+
• Set scope.status = "complete"
|
|
137
|
+
|
|
138
|
+
WHY: Prevents building the wrong thing."""
|
|
139
|
+
}))
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
|
|
142
|
+
# Scope confirmed - inject context
|
|
143
|
+
endpoint_path = scope.get("endpoint_path", f"/api/v2/{endpoint}")
|
|
144
|
+
modifications = scope.get("modifications", [])
|
|
145
|
+
|
|
146
|
+
context = [f"✅ Scope confirmed: {endpoint_path}"]
|
|
147
|
+
if modifications:
|
|
148
|
+
context.append("User modifications:")
|
|
149
|
+
for mod in modifications[:3]:
|
|
150
|
+
context.append(f" • {mod}")
|
|
151
|
+
|
|
152
|
+
print(json.dumps({
|
|
153
|
+
"permissionDecision": "allow",
|
|
154
|
+
"message": "\n".join(context)
|
|
155
|
+
}))
|
|
156
|
+
sys.exit(0)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|