Interview-driven, research-first API development with 100% phase enforcement
┌────────────────────────────────────────────────────────────────┐
│ ❯ npx @hustle-together/api-dev-tools --scope=project │
│ │
│ 🚀 Installing API Development Tools v3.6.0... │
│ │
│ ✅ Found Python 3.12.0 │
│ 📦 Installing commands: 24 slash commands │
│ 🔒 Installing hooks: 18 enforcement hooks │
│ ⚙️ Configuring settings.json │
│ 📊 Creating api-dev-state.json │
│ 📚 Setting up research cache │
│ 🔌 Configuring MCP servers: context7, github │
│ │
│ ═══════════════════════════════════════════════════════════ │
│ 🎉 API Development Tools installed successfully! │
│ ═══════════════════════════════════════════════════════════ │
│ │
│ Quick Start: /api-create my-endpoint │
└────────────────────────────────────────────────────────────────┘
LLMs have predictable failure modes when building APIs:
- Wrong Documentation - Uses training data instead of current docs
- Memory-Based Implementation - Forgets research by implementation time
- Self-Answering Questions - Asks itself questions instead of the user
- Context Dilution - Loses focus in long sessions
- Skipped Steps - Jumps to implementation without proper research
This package enforces a 12-phase workflow with Python hooks that BLOCK progress until each phase is complete with explicit user approval.
Here's exactly what happens when you run /api-create brandfetch:
┌──────────────────────────────────────────────────────────────────────────────┐
│ ❯ /api-create brandfetch │
│ │
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 0: DISAMBIGUATION │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ I found multiple things matching "brandfetch": │ │
│ │ │ │
│ │ [A] Brandfetch REST API (api.brandfetch.io) │ │
│ │ [B] @brandfetch/sdk npm package │ │
│ │ [C] Brandfetch WordPress plugin │ │
│ │ │ │
│ │ Which interpretation matches your intent? │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-disambiguation.py │ │
│ │ ├─ Blocks Write/Edit until user selects an option │ │
│ │ ├─ Requires: user_question_asked = true │ │
│ │ └─ Requires: phase_exit_confirmed = true (user said "yes") │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User selects: [A] Brandfetch REST API │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 1: SCOPE CONFIRMATION │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ I'll create: /api/v2/brandfetch │ │
│ │ │ │
│ │ Purpose: Fetch brand assets (logos, colors, fonts) from Brandfetch API │ │
│ │ │ │
│ │ Is this scope correct? [Yes / No, I need...] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-scope.py │ │
│ │ ├─ Blocks until user confirms scope │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, but also add a logo-only mode" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 2: INITIAL RESEARCH │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 🔍 Searching... │ │
│ │ │ │
│ │ [Context7] brandfetch → Found SDK documentation │ │
│ │ [WebSearch] "Brandfetch API documentation 2025" → Official docs │ │
│ │ [WebSearch] "Brandfetch API rate limits" → 5 req/sec │ │
│ │ │ │
│ │ ────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ RESEARCH SUMMARY: │ │
│ │ • Base URL: https://api.brandfetch.io/v2 │ │
│ │ • Auth: Bearer token (BRANDFETCH_API_KEY) │ │
│ │ • Rate limit: 5 requests/second │ │
│ │ • Returns: logos, colors, fonts, images │ │
│ │ │ │
│ │ Proceed to interview? [Yes / Search more first] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-research.py │ │
│ │ ├─ Blocks Write/Edit until research complete │ │
│ │ ├─ Requires: sources.length >= 2 │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 📝 TRACKED BY: track-tool-use.py │ │
│ │ └─ Logs all WebSearch/Context7 calls to state │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, proceed" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 3: STRUCTURED INTERVIEW │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Based on research, I have 5 questions: │ │
│ │ │ │
│ │ Q1: Which response format do you need? │ │
│ │ [ ] JSON only │ │
│ │ [x] JSON with asset URLs │ │
│ │ [ ] Include raw base64 assets │ │
│ │ │ │
│ │ Q2: Caching strategy? │ │
│ │ [ ] No caching │ │
│ │ [ ] Short (5 minutes) │ │
│ │ [x] Long (24 hours) - brand data rarely changes │ │
│ │ │ │
│ │ Q3: Error handling approach? │ │
│ │ [ ] Throw errors (caller handles) │ │
│ │ [x] Return error objects { success: false, error: {...} } │ │
│ │ │ │
│ │ Q4: Rate limit handling? │ │
│ │ [ ] Client handles retry │ │
│ │ [ ] Server-side retry with backoff │ │
│ │ [x] Expose rate limit headers (X-RateLimit-*) │ │
│ │ │ │
│ │ Q5: Which brand assets to include? │ │
│ │ [x] Logos │ │
│ │ [x] Colors │ │
│ │ [ ] Fonts │ │
│ │ [ ] Images │ │
│ │ │ │
│ │ Interview complete? [Yes, these answers are final / Modify answers] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-interview.py │ │
│ │ ├─ Blocks until structured_question_count >= 3 │ │
│ │ ├─ Uses AskUserQuestion tool (Claude's built-in dialog) │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Generate questions FROM research (not generic templates) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User confirms selections │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 4: DEEP RESEARCH (Adaptive) │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Based on your selections, I want to research: │ │
│ │ │ │
│ │ [x] "Brandfetch error response format" - for error objects │ │
│ │ [x] "Brandfetch rate limit headers" - for X-RateLimit-* headers │ │
│ │ [ ] "Brandfetch font API" - SKIPPED (you don't need fonts) │ │
│ │ │ │
│ │ Approve these searches? [Yes / Add more / Skip all] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-deep-research.py │ │
│ │ ├─ Blocks if approved_searches not executed │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Propose searches based on interview (not shotgun approach) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, also add authentication methods" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 5: SCHEMA CREATION │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Creating Zod schema from research + interview: │ │
│ │ │ │
│ │ // Request Schema │ │
│ │ const BrandfetchRequest = z.object({ │ │
│ │ domain: z.string().min(1), │ │
│ │ mode: z.enum(["full", "logo-only"]).default("full"), │ │
│ │ include: z.object({ │ │
│ │ logos: z.boolean().default(true), │ │
│ │ colors: z.boolean().default(true), │ │
│ │ }).optional(), │ │
│ │ }); │ │
│ │ │ │
│ │ // Response Schema (success) │ │
│ │ const BrandfetchResponse = z.object({ │ │
│ │ success: z.literal(true), │ │
│ │ data: z.object({ │ │
│ │ logos: z.array(LogoSchema), │ │
│ │ colors: z.array(ColorSchema), │ │
│ │ }), │ │
│ │ cached: z.boolean(), │ │
│ │ }); │ │
│ │ │ │
│ │ Schema matches your interview answers? [Yes / Modify] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-schema.py │ │
│ │ ├─ Blocks if schema not shown to user │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, looks good" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 6: ENVIRONMENT CHECK │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Checking required API keys: │ │
│ │ │ │
│ │ ✅ BRANDFETCH_API_KEY found in .env │ │
│ │ ✅ NODE_ENV = development │ │
│ │ │ │
│ │ Ready to begin TDD? [Yes / Need to set up keys first] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-environment.py │ │
│ │ ├─ Checks env vars based on endpoint name + interview │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, ready" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 7: TDD RED (Write Failing Tests) │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ TEST MATRIX (from interview decisions): │ │
│ │ │ │
│ │ ✅ Success Scenarios: │ │
│ │ • GET with valid domain → 200 + brand data │ │
│ │ • mode=logo-only → 200 + logos only │ │
│ │ • Cache hit → 200 + cached: true │ │
│ │ │ │
│ │ ✅ Error Scenarios (your choice: return objects): │ │
│ │ • Invalid domain → 400 + { success: false, error: {...} } │ │
│ │ • Missing API key → 401 + error object │ │
│ │ • Not found → 404 + error object │ │
│ │ • Rate limited → 429 + X-RateLimit-* headers │ │
│ │ │ │
│ │ ✅ Edge Cases: │ │
│ │ • Empty colors array → 200 + empty array (not error) │ │
│ │ • Timeout → 504 + error object │ │
│ │ │ │
│ │ Total: 9 test scenarios │ │
│ │ │ │
│ │ Test plan looks good? [Yes, write tests / Add more tests] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-tdd-red.py │ │
│ │ ├─ Blocks route.ts if no .test.ts exists │ │
│ │ ├─ Requires: test matrix shown to user │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Derive test scenarios from interview decisions │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, write them" │
│ │
│ 📝 Creating: src/app/api/v2/brandfetch/__tests__/route.test.ts │
│ ⏳ Running tests... 0/9 passing (expected - RED phase) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 8: TDD GREEN (Make Tests Pass) │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 📝 Creating: src/app/api/v2/brandfetch/route.ts │ │
│ │ │ │
│ │ ⏳ Running tests... │ │
│ │ │ │
│ │ ✅ GET with valid domain PASS │ │
│ │ ✅ mode=logo-only PASS │ │
│ │ ✅ Cache hit PASS │ │
│ │ ✅ Invalid domain → 400 PASS │ │
│ │ ✅ Missing API key → 401 PASS │ │
│ │ ✅ Not found → 404 PASS │ │
│ │ ✅ Rate limited → 429 PASS │ │
│ │ ✅ Empty colors array PASS │ │
│ │ ✅ Timeout → 504 PASS │ │
│ │ │ │
│ │ Tests: 9/9 passing │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: verify-after-green.py (PostToolUse on Bash) │ │
│ │ ├─ Detects "pnpm test" or "vitest" in command │ │
│ │ ├─ Parses output for pass/fail │ │
│ │ └─ Auto-triggers Phase 9 when all tests pass │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Write minimal implementation to pass tests │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 9: VERIFY (Re-Research After Green) │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ ⚠️ AUTO-TRIGGERED: Tests passed. Now verifying against docs... │ │
│ │ │ │
│ │ Re-reading Brandfetch API documentation... │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Feature │ In Docs │ Implemented │ Status │ │ │
│ │ ├────────────────┼─────────┼─────────────┼──────────────────────────│ │ │
│ │ │ domain param │ ✓ │ ✓ │ ✅ Match │ │ │
│ │ │ logo formats │ 4 │ 4 │ ✅ Match │ │ │
│ │ │ colors │ ✓ │ ✓ │ ✅ Match │ │ │
│ │ │ include_fonts │ ✓ │ ✗ │ ⚠️ GAP FOUND │ │ │
│ │ │ webhook │ ✓ │ ✗ │ ℹ️ Optional │ │ │
│ │ └────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ GAPS FOUND: 1 │ │
│ │ • include_fonts parameter (documented but not implemented) │ │
│ │ │ │
│ │ Fix gaps? [Yes, loop back to Phase 7 / Skip - document as omission] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-verify.py │ │
│ │ ├─ Blocks refactoring until verification complete │ │
│ │ ├─ Requires: gaps_fixed OR documented as omissions │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Re-read docs and compare feature-by-feature │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Skip - I don't need fonts" │
│ │
│ 📝 Documenting as intentional omission: include_fonts │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 10: TDD REFACTOR │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ Cleaning up implementation while keeping tests green... │ │
│ │ │ │
│ │ • Extracted rate limit logic to helper function │ │
│ │ • Added JSDoc comments │ │
│ │ • Removed dead code │ │
│ │ │ │
│ │ Tests: 9/9 still passing │ │
│ │ │ │
│ │ Refactoring complete? [Yes / Continue refactoring] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-refactor.py │ │
│ │ ├─ Only allows after verify phase complete │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 💡 LLM RESPONSIBILITY: │ │
│ │ └─ Improve code without changing behavior │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ PHASE 11: DOCUMENTATION │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ DOCUMENTATION CHECKLIST: │ │
│ │ │ │
│ │ ✅ api-tests-manifest.json │ │
│ │ • Added /api/v2/brandfetch entry │ │
│ │ • 9 test scenarios documented │ │
│ │ • Request/response schemas included │ │
│ │ │ │
│ │ ✅ Research Cache │ │
│ │ • .claude/research/brandfetch/sources.json │ │
│ │ • .claude/research/brandfetch/interview.json │ │
│ │ │ │
│ │ ⏭️ OpenAPI Spec (skipped - internal API) │ │
│ │ │ │
│ │ Documentation complete? [Yes / Need to add something] │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 🔒 ENFORCED BY: enforce-documentation.py │ │
│ │ ├─ Blocks completion until documentation updated │ │
│ │ ├─ Checks: manifest_updated OR research_cached │ │
│ │ └─ Requires: phase_exit_confirmed = true │ │
│ │ │ │
│ │ 🔒 ENFORCED BY: api-workflow-check.py (Stop hook) │ │
│ │ └─ Blocks "stop" if required phases not complete │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ User: "Yes, all done" │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ ✅ WORKFLOW COMPLETE │ │
│ │ ─────────────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ /api/v2/brandfetch created successfully! │ │
│ │ │ │
│ │ Files created: │ │
│ │ • src/app/api/v2/brandfetch/route.ts │ │
│ │ • src/app/api/v2/brandfetch/__tests__/route.test.ts │ │
│ │ • src/lib/schemas/brandfetch.ts │ │
│ │ │ │
│ │ Tests: 9/9 passing │ │
│ │ Coverage: 100% │ │
│ │ │ │
│ │ Interview decisions preserved in: │ │
│ │ • .claude/api-dev-state.json │ │
│ │ • .claude/research/brandfetch/ │ │
│ │ │ │
│ │ Intentional omissions documented: │ │
│ │ • include_fonts parameter │ │
│ │ • webhook support │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
| Phase | Hook File | What It Enforces |
|---|---|---|
| 0 | enforce-disambiguation.py |
User must select from options |
| 1 | enforce-scope.py |
User must confirm scope |
| 2 | enforce-research.py |
Minimum 2 sources required |
| 3 | enforce-interview.py |
Minimum 3 structured questions |
| 4 | enforce-deep-research.py |
Approved searches must execute |
| 5 | enforce-schema.py |
Schema must be shown to user |
| 6 | enforce-environment.py |
API keys must be verified |
| 7 | enforce-tdd-red.py |
Test file must exist before route |
| 8 | verify-after-green.py |
Tests must pass (auto-detect) |
| 9 | enforce-verify.py |
Gaps must be fixed or documented |
| 10 | enforce-refactor.py |
Verify phase must be complete |
| 11 | enforce-documentation.py |
Docs must be updated |
| Task | Guidance |
|---|---|
| Generate disambiguation options | Based on search results |
| Create interview questions | FROM research findings |
| Propose deep research searches | Based on interview answers |
| Write test scenarios | Derived from interview decisions |
| Compare docs to implementation | Feature-by-feature comparison |
| Refactor code | Without changing behavior |
| Hook | Trigger | Action |
|---|---|---|
session-startup.py |
Session start | Inject state context |
track-tool-use.py |
Any tool use | Log activity, count turns |
periodic-reground.py |
Every 7 turns | Re-inject context summary |
verify-after-green.py |
Tests pass | Trigger Phase 9 |
@hustle-together/api-dev-tools v3.6.0
│
├── bin/
│ └── cli.js # NPX installer (verifies 18 hooks)
│
├── commands/ # 24 slash commands
│ ├── api-create.md # Main 12-phase workflow
│ ├── api-interview.md # Structured interview
│ ├── api-research.md # Adaptive research
│ ├── api-verify.md # Manual verification
│ ├── api-env.md # Environment check
│ ├── api-status.md # Progress tracking
│ ├── red.md, green.md, refactor.md # TDD commands
│ ├── cycle.md # Full TDD cycle
│ ├── commit.md, pr.md # Git operations
│ └── ... (13 more)
│
├── hooks/ # 18 Python enforcement hooks
│ │
│ │ # Session lifecycle
│ ├── session-startup.py # SessionStart → inject context
│ │
│ │ # User prompt processing
│ ├── enforce-external-research.py # UserPromptSubmit → detect API terms
│ │
│ │ # PreToolUse (Write/Edit) - BLOCKING
│ ├── enforce-disambiguation.py # Phase 0
│ ├── enforce-scope.py # Phase 1
│ ├── enforce-research.py # Phase 2
│ ├── enforce-interview.py # Phase 3
│ ├── enforce-deep-research.py # Phase 4
│ ├── enforce-schema.py # Phase 5
│ ├── enforce-environment.py # Phase 6
│ ├── enforce-tdd-red.py # Phase 7
│ ├── verify-implementation.py # Phase 8 helper
│ ├── enforce-verify.py # Phase 9
│ ├── enforce-refactor.py # Phase 10
│ ├── enforce-documentation.py # Phase 11
│ │
│ │ # PostToolUse - TRACKING
│ ├── track-tool-use.py # Log all tool usage
│ ├── periodic-reground.py # Re-inject context every 7 turns
│ ├── verify-after-green.py # Trigger Phase 9 after tests pass
│ │
│ │ # Stop - BLOCKING
│ └── api-workflow-check.py # Block completion if incomplete
│
├── templates/
│ ├── api-dev-state.json # State file (12 phases + phase_exit_confirmed)
│ ├── settings.json # Hook registrations
│ ├── research-index.json # Research freshness tracking
│ └── CLAUDE-SECTION.md # CLAUDE.md injection
│
├── scripts/
│ ├── generate-test-manifest.ts # Parse tests → manifest (NO LLM)
│ ├── extract-parameters.ts # Extract Zod params
│ └── collect-test-results.ts # Run tests → results
│
└── package.json # v3.6.0
{
"version": "3.0.0",
"endpoint": "brandfetch",
"session_id": "abc123",
"turn_count": 47,
"phases": {
"disambiguation": {
"status": "complete",
"user_question_asked": true,
"user_selected": "Brandfetch REST API",
"phase_exit_confirmed": true,
"last_question_type": "exit_confirmation"
},
"scope": {
"status": "complete",
"confirmed": true,
"phase_exit_confirmed": true
},
"research_initial": {
"status": "complete",
"sources": ["context7", "websearch:brandfetch-api", "websearch:rate-limits"],
"phase_exit_confirmed": true
},
"interview": {
"status": "complete",
"structured_question_count": 5,
"decisions": {
"response_format": "json_with_urls",
"caching": "24h",
"error_handling": "return_objects",
"rate_limiting": "expose_headers",
"assets": ["logos", "colors"]
},
"phase_exit_confirmed": true
},
"research_deep": {
"status": "complete",
"proposed_searches": ["error-format", "rate-headers", "auth"],
"approved_searches": ["error-format", "rate-headers", "auth"],
"executed_searches": ["error-format", "rate-headers", "auth"],
"phase_exit_confirmed": true
},
"schema_creation": {
"status": "complete",
"schema_file": "src/lib/schemas/brandfetch.ts",
"phase_exit_confirmed": true
},
"environment_check": {
"status": "complete",
"keys_found": ["BRANDFETCH_API_KEY"],
"phase_exit_confirmed": true
},
"tdd_red": {
"status": "complete",
"test_file": "src/app/api/v2/brandfetch/__tests__/route.test.ts",
"test_count": 9,
"phase_exit_confirmed": true
},
"tdd_green": {
"status": "complete",
"all_tests_passing": true
},
"verify": {
"status": "complete",
"gaps_found": 2,
"gaps_fixed": 0,
"intentional_omissions": ["include_fonts", "webhook"],
"phase_exit_confirmed": true
},
"tdd_refactor": {
"status": "complete",
"phase_exit_confirmed": true
},
"documentation": {
"status": "complete",
"manifest_updated": true,
"research_cached": true,
"phase_exit_confirmed": true
}
},
"reground_history": [
{ "turn": 7, "phase": "interview" },
{ "turn": 14, "phase": "tdd_red" },
{ "turn": 21, "phase": "tdd_green" }
]
}# Install in your project
npx @hustle-together/api-dev-tools --scope=project
# Team-wide auto-installation (add to package.json)
{
"scripts": {
"postinstall": "npx @hustle-together/api-dev-tools --scope=project"
}
}- Node.js 14.0.0+
- Python 3 (for enforcement hooks)
- Claude Code CLI tool
Problem: JSON permissionDecision: "deny" blocks actions but Claude may continue normally. We needed a way to force Claude to actively respond to the error.
Solution: All blocking hooks now use sys.exit(2) with stderr messages instead of JSON deny:
# Before (passive - Claude sees reason but may continue)
print(json.dumps({"permissionDecision": "deny", "reason": "..."}))
sys.exit(0)
# After (active - forces Claude into feedback loop)
print("BLOCKED: ...", file=sys.stderr)
sys.exit(2)Upgraded hooks:
enforce-research.py- Forces/api-researchbefore implementationenforce-interview.py- Forces structured interview completionapi-workflow-check.py- Forces all phases complete before stoppingverify-implementation.py- Forces fix of critical mismatches
From Anthropic's docs:
"Exit code 2 creates a feedback loop directly to Claude. Claude sees your error message. Claude adjusts. Claude tries something different."
Problem: Claude was calling AskUserQuestion but immediately self-answering without waiting for user input.
Solution: Every phase now requires:
- An "exit confirmation" question (detected by patterns like "proceed", "ready", "confirm")
- An affirmative user response (detected by patterns like "yes", "approve", "looks good")
- Both conditions set
phase_exit_confirmed = truein state
# In track-tool-use.py
def _detect_question_type(question_text, options):
"""Detects: 'exit_confirmation', 'data_collection', 'clarification'"""
exit_patterns = ["proceed", "continue", "ready to", "approve", "confirm", ...]
def _is_affirmative_response(response, options):
"""Checks for: 'yes', 'proceed', 'approve', 'confirm', 'ready', ..."""- NPM: https://www.npmjs.com/package/@hustle-together/api-dev-tools
- GitHub: https://github.com/hustle-together/api-dev-tools
- Demo: https://hustle-together.github.io/api-dev-tools/demo/
MIT
Made with care for API developers using Claude Code
"Disambiguate, research, interview, verify, repeat"