@hustle-together/api-dev-tools 1.8.0 → 1.9.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 CHANGED
@@ -30,9 +30,9 @@ Six Python hooks that provide **real programmatic guarantees**:
30
30
 
31
31
  - **`enforce-external-research.py`** - (v1.7.0) Detects external API questions and requires research before answering
32
32
  - **`enforce-research.py`** - Blocks API code writing until research is complete
33
- - **`enforce-interview.py`** - Verifies user questions were actually asked (prevents self-answering)
33
+ - **`enforce-interview.py`** - (v1.8.0+) Verifies structured questions with options were asked; (v1.9.0+) Injects decision reminders on writes
34
34
  - **`verify-implementation.py`** - Checks implementation matches interview requirements
35
- - **`track-tool-use.py`** - Logs all research activity (Context7, WebSearch, WebFetch, AskUserQuestion)
35
+ - **`track-tool-use.py`** - (v1.9.0+) Captures user decisions from AskUserQuestion; logs all research activity
36
36
  - **`api-workflow-check.py`** - Prevents stopping until required phases are complete + git diff verification
37
37
 
38
38
  ### State Tracking
@@ -470,6 +470,34 @@ INJECTS: "RESEARCH REQUIRED: Use Context7/WebSearch before answering"
470
470
  CLAUDE: Researches first → Gives accurate answer
471
471
  ```
472
472
 
473
+ ### Gap 7: Interview Decisions Not Used During Implementation (v1.9.0+)
474
+ **Problem:** AI asks good interview questions but then ignores the answers when writing code.
475
+
476
+ **Example:**
477
+ - Interview: User selected "server environment variables only" for API key handling
478
+ - Implementation: AI writes code with custom header overrides (not what user wanted!)
479
+
480
+ **Fix:** Two-part solution in `track-tool-use.py` and `enforce-interview.py`:
481
+
482
+ 1. **track-tool-use.py** now captures:
483
+ - The user's actual response from AskUserQuestion
484
+ - Matches responses to option values
485
+ - Stores decisions in categorized `decisions` dict (purpose, api_key_handling, etc.)
486
+
487
+ 2. **enforce-interview.py** now injects a decision summary on EVERY write:
488
+ ```
489
+ ✅ Interview complete. REMEMBER THE USER'S DECISIONS:
490
+
491
+ • Primary Purpose: full_brand_kit
492
+ • API Key Handling: server_only
493
+ • Response Format: JSON with asset URLs
494
+ • Error Handling: detailed (error, code, details)
495
+
496
+ Your implementation MUST align with these choices.
497
+ ```
498
+
499
+ This ensures the AI is constantly reminded of what the user actually wanted throughout the entire implementation phase.
500
+
473
501
  ## 🔧 Requirements
474
502
 
475
503
  - **Node.js** 14.0.0 or higher
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>API Dev Tools - Interactive Workflow Demo</title>
6
+ <title>API Dev Tools - Interview-Driven API Development Demo</title>
7
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
8
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
9
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/TextPlugin.min.js"></script>
@@ -957,18 +957,18 @@
957
957
  <div class="ascii-border">
958
958
  <h1 id="titleText">INTERVIEW-DRIVEN API DEVELOPMENT</h1>
959
959
  <div class="package-name" id="packageName">@hustle-together/api-dev-tools</div>
960
- <div class="version" id="versionText">v1.6.0</div>
960
+ <div class="version" id="versionText">v1.9.0</div>
961
961
  <p class="tagline">"Interview first, test first, document always"<span class="cursor"></span></p>
962
962
 
963
963
  <div class="intro-text">
964
964
  <p>
965
965
  This tool helps you build APIs the right way by <strong>enforcing a structured workflow</strong>.
966
- Instead of letting AI assistants jump straight to code, it ensures they first understand
967
- what you actually want, research the right libraries, and follow test-driven development.
966
+ Instead of letting AI assistants jump straight to code, it ensures they first research,
967
+ then ask YOU questions with <strong>multiple-choice options</strong> based on what they found.
968
968
  </p>
969
969
  <p style="margin-top: 20px;">
970
970
  <strong>Scroll down</strong> to see how it works, step by step, using a real example:
971
- building an AI chat API with the Vercel AI SDK.
971
+ creating a <strong>Brandfetch API endpoint</strong> from scratch.
972
972
  </p>
973
973
  </div>
974
974
 
@@ -1253,31 +1253,30 @@
1253
1253
  <section id="walkthrough">
1254
1254
  <div class="ascii-border">
1255
1255
  <h2>REAL EXAMPLE</h2>
1256
- <h3>Building the AI Chat API</h3>
1256
+ <h3>Creating a Brandfetch API</h3>
1257
1257
 
1258
1258
  <div class="explanation">
1259
- <div class="explanation-title">A Real Story</div>
1259
+ <div class="explanation-title">The Complete Flow</div>
1260
1260
  <p>
1261
- Let's walk through what actually happened when we built an AI chat API
1262
- using the <strong>Vercel AI SDK</strong>. This shows both <strong>what worked</strong>
1263
- and <strong>what the system caught</strong> that would have been missed otherwise.
1261
+ Let's walk through what happens when you run <code>/api-create brandfetch</code>.
1262
+ This shows the full interview-driven workflow, from research to implementation,
1263
+ with <strong>structured questions based on real documentation</strong>.
1264
1264
  </p>
1265
1265
  </div>
1266
1266
 
1267
1267
  <div class="walkthrough-step">
1268
1268
  <div class="walkthrough-header">
1269
1269
  <div class="walkthrough-num">1</div>
1270
- <div class="walkthrough-title">User Request</div>
1270
+ <div class="walkthrough-title">You Run the Command</div>
1271
1271
  </div>
1272
1272
  <div class="walkthrough-content">
1273
1273
  <div class="walkthrough-desc">
1274
- The user asked for an AI chat API with multi-provider support
1275
- using a <strong>single API key via Vercel AI Gateway</strong>.
1274
+ You type the command to create a new Brandfetch API endpoint.
1275
+ The workflow immediately begins.
1276
1276
  </div>
1277
1277
  <div class="walkthrough-example">
1278
- <div class="label">User Said:</div>
1279
- "I want to use OpenAI, Anthropic, Google, Perplexity - all <strong>via Vercel AI Gateway</strong>
1280
- so I only need one API key instead of four."
1278
+ <div class="label">Your Command:</div>
1279
+ /api-create brandfetch
1281
1280
  </div>
1282
1281
  </div>
1283
1282
  </div>
@@ -1285,19 +1284,19 @@
1285
1284
  <div class="walkthrough-step">
1286
1285
  <div class="walkthrough-header">
1287
1286
  <div class="walkthrough-num">2</div>
1288
- <div class="walkthrough-title">Research Phase</div>
1287
+ <div class="walkthrough-title">Research Phase (Automatic)</div>
1289
1288
  </div>
1290
1289
  <div class="walkthrough-content">
1291
1290
  <div class="walkthrough-desc">
1292
- System forced the AI to research before writing any code.
1293
- It fetched live documentation from Context7 and searched the web.
1291
+ Before asking any questions, the AI is <strong>required</strong> to research.
1292
+ It fetches live documentation from Context7 and searches the web.
1294
1293
  </div>
1295
1294
  <div class="walkthrough-example">
1296
- <div class="label">Research Sources Logged:</div>
1297
- - Context7: Vercel AI SDK docs<br>
1298
- - WebSearch: GPT-5.1 models<br>
1299
- - WebSearch: Gemini 3 Pro<br>
1300
- - Context7: generateText, streamText functions
1295
+ <div class="label">Research Logged to State File:</div>
1296
+ - Context7: Brandfetch SDK docs → found logos, colors, fonts endpoints<br>
1297
+ - WebSearch: "Brandfetch API rate limits 2025" → 5 req/second<br>
1298
+ - WebSearch: "Brandfetch API response format" → JSON with asset URLs<br>
1299
+ - Context7: Authentication Bearer token required
1301
1300
  </div>
1302
1301
  </div>
1303
1302
  </div>
@@ -1305,75 +1304,87 @@
1305
1304
  <div class="walkthrough-step">
1306
1305
  <div class="walkthrough-header">
1307
1306
  <div class="walkthrough-num">3</div>
1308
- <div class="walkthrough-title">Interview Phase</div>
1307
+ <div class="walkthrough-title">Structured Interview (Based on Research)</div>
1309
1308
  </div>
1310
1309
  <div class="walkthrough-content">
1311
1310
  <div class="walkthrough-desc">
1312
- AI asked 6 questions about requirements.
1313
- User specified: providers, capabilities, streaming modes, default models.
1311
+ Now the AI asks YOU questions - but with <strong>multiple-choice options</strong>
1312
+ derived from what it actually found in the documentation. No guessing!
1314
1313
  </div>
1315
1314
  <div class="walkthrough-example">
1316
- <div class="label">Interview Results Recorded:</div>
1317
- - Providers: OpenAI, Anthropic, Google, Perplexity <strong>via Vercel AI Gateway</strong><br>
1318
- - Capabilities: Chat, image, speech, transcription, embeddings<br>
1319
- - Default model: Budget tier for cost efficiency
1315
+ <div class="label">Question 1 (with options from research):</div>
1316
+ "What's the primary purpose of this endpoint?"<br><br>
1317
+ <strong>1.</strong> Brand lookup (get brand by domain)<br>
1318
+ <strong>2.</strong> Logo extraction (get logo assets)<br>
1319
+ <strong>3.</strong> Color palette extraction<br>
1320
+ <strong>4.</strong> Full brand kit (all assets)<br>
1321
+ <strong>5.</strong> Type something else...<br><br>
1322
+ <em>You select: "4. Full brand kit (all assets)"</em>
1320
1323
  </div>
1321
1324
  </div>
1322
1325
  </div>
1323
1326
 
1324
- <div class="walkthrough-step" style="border-color: var(--white);">
1327
+ <div class="walkthrough-step">
1325
1328
  <div class="walkthrough-header">
1326
- <div class="walkthrough-num">!</div>
1327
- <div class="walkthrough-title">Gap Detected</div>
1329
+ <div class="walkthrough-num">4</div>
1330
+ <div class="walkthrough-title">More Questions, All Tracked</div>
1328
1331
  </div>
1329
1332
  <div class="walkthrough-content">
1330
1333
  <div class="walkthrough-desc">
1331
- The AI wrote the implementation using <strong>direct provider SDKs</strong>
1332
- instead of the Vercel AI Gateway. The verification hook caught this mismatch!
1334
+ Each question is tracked with your answer. These decisions are stored
1335
+ in the state file and <strong>injected during implementation</strong> to ensure consistency.
1333
1336
  </div>
1334
1337
  <div class="walkthrough-example">
1335
- <div class="label">What Happened:</div>
1336
- Interview said: "via Vercel AI Gateway"<br>
1337
- Code used: @ai-sdk/openai, @ai-sdk/anthropic (direct calls)<br><br>
1338
- <strong>DETECTED:</strong> Implementation doesn't match interview requirements!
1338
+ <div class="label">Decisions Captured:</div>
1339
+ Purpose: full_brand_kit<br>
1340
+ Response Format: JSON with asset URLs<br>
1341
+ API Key Handling: server environment variables only<br>
1342
+ • Error Handling: detailed (error, code, details)<br>
1343
+ • Required Params: domain (string)<br>
1344
+ • Optional Params: include_colors, include_fonts
1339
1345
  </div>
1340
1346
  </div>
1341
1347
  </div>
1342
1348
 
1343
1349
  <div class="walkthrough-step">
1344
1350
  <div class="walkthrough-header">
1345
- <div class="walkthrough-num">4</div>
1346
- <div class="walkthrough-title">Fixed Implementation</div>
1351
+ <div class="walkthrough-num">5</div>
1352
+ <div class="walkthrough-title">Implementation (With Decision Reminders)</div>
1347
1353
  </div>
1348
1354
  <div class="walkthrough-content">
1349
1355
  <div class="walkthrough-desc">
1350
- After the gap was caught, the code was updated to actually use
1351
- <strong>@ai-sdk/gateway</strong> with a single API key.
1356
+ When the AI writes code, the hook <strong>injects your decisions</strong> as a reminder.
1357
+ This ensures the implementation matches what you actually asked for.
1352
1358
  </div>
1353
1359
  <div class="walkthrough-example">
1354
- <div class="label">Corrected Code:</div>
1355
- import { gateway } from '@ai-sdk/gateway';<br>
1356
- const client = createAIClient({ apiKey: AI_GATEWAY_API_KEY });
1360
+ <div class="label">Injected on Every Write:</div>
1361
+ Interview complete. REMEMBER THE USER'S DECISIONS:<br><br>
1362
+ Purpose: Full brand kit (all assets)<br>
1363
+ • Response Format: JSON with asset URLs<br>
1364
+ • API Key Handling: server environment variables only<br>
1365
+ • Error Handling: detailed (error, code, details)<br><br>
1366
+ <em>Your implementation MUST align with these choices.</em>
1357
1367
  </div>
1358
1368
  </div>
1359
1369
  </div>
1360
1370
 
1361
1371
  <div class="walkthrough-step">
1362
1372
  <div class="walkthrough-header">
1363
- <div class="walkthrough-num">5</div>
1364
- <div class="walkthrough-title">Final Result</div>
1373
+ <div class="walkthrough-num">6</div>
1374
+ <div class="walkthrough-title">TDD + Final Result</div>
1365
1375
  </div>
1366
1376
  <div class="walkthrough-content">
1367
1377
  <div class="walkthrough-desc">
1368
- Complete implementation with 160 tests, proper documentation,
1369
- and code that actually matches what the user asked for.
1378
+ Tests are written first (TDD), then implementation, then docs.
1379
+ Everything is tracked and verified against your interview decisions.
1370
1380
  </div>
1371
1381
  <div class="walkthrough-example">
1372
1382
  <div class="label">Files Created:</div>
1373
- - src/lib/ai/client.ts (gateway implementation)<br>
1374
- - src/lib/ai/schemas.ts (Zod validation)<br>
1375
- - src/app/api/v2/ai/chat/route.ts<br>
1376
- - 160 tests passing
1383
+ - src/lib/schemas/brandfetch.ts (Zod validation)<br>
1384
+ - src/lib/__tests__/brandfetch.test.ts (tests first!)<br>
1385
+ - src/app/api/v2/brandfetch/route.ts<br>
1386
+ - Interview doc: src/v2/docs/endpoints/brandfetch.md<br>
1387
+ - All tests passing ✓
1377
1388
  </div>
1378
1389
  </div>
1379
1390
  </div>
@@ -1386,14 +1397,14 @@
1386
1397
  <section id="demo">
1387
1398
  <div class="ascii-border">
1388
1399
  <h2>LIVE SIMULATION</h2>
1389
- <h3>See the Hooks in Action</h3>
1400
+ <h3>The Interview Flow</h3>
1390
1401
 
1391
1402
  <div class="explanation">
1392
- <div class="explanation-title">Watch the Enforcement</div>
1403
+ <div class="explanation-title">Structured Questions in Action</div>
1393
1404
  <p>
1394
- This simulates what happens during a Claude Code session.
1395
- Notice how actions get <strong>BLOCKED</strong> until prerequisites are met,
1396
- and how everything gets <strong>LOGGED</strong> for verification.
1405
+ This simulates the interview phase. Notice how the AI uses
1406
+ <strong>AskUserQuestion with options</strong> based on research findings.
1407
+ Your answers are captured and used throughout implementation.
1397
1408
  </p>
1398
1409
  </div>
1399
1410
 
@@ -1402,70 +1413,80 @@
1402
1413
  <div class="terminal-dot"></div>
1403
1414
  <div class="terminal-dot"></div>
1404
1415
  <div class="terminal-dot"></div>
1405
- <div class="terminal-title">claude-code session</div>
1416
+ <div class="terminal-title">claude-code session: /api-create brandfetch</div>
1406
1417
  </div>
1407
1418
 
1408
- <div class="terminal-comment" data-step="0">// AI tries to write code immediately</div>
1419
+ <div class="terminal-comment" data-step="0">// Step 1: Research phase starts automatically</div>
1409
1420
  <div class="terminal-line" data-step="1">
1410
1421
  <span class="terminal-prompt">claude></span>
1411
- <span class="terminal-command">Write src/app/api/v2/ai/chat/route.ts</span>
1422
+ <span class="terminal-command">mcp__context7__get-library-docs("brandfetch")</span>
1412
1423
  </div>
1413
1424
  <div class="terminal-line terminal-result" data-step="2">
1414
- <span class="terminal-blocked">BLOCKED: Research phase not complete.<br>Run /api-research first to fetch live documentation.</span>
1415
- </div>
1416
-
1417
- <div class="terminal-comment" data-step="3">// AI does research - gets logged</div>
1418
- <div class="terminal-line" data-step="4">
1419
- <span class="terminal-prompt">claude></span>
1420
- <span class="terminal-command">WebSearch "Vercel AI Gateway documentation"</span>
1421
- </div>
1422
- <div class="terminal-line terminal-result" data-step="5">
1423
- <span class="terminal-logged">[LOGGED] Research source added to api-dev-state.json</span>
1425
+ <span class="terminal-logged">[LOGGED] Context7 source: brandfetch SDK documentation</span>
1424
1426
  </div>
1425
1427
 
1426
- <div class="terminal-comment" data-step="6">// AI tries to write code again</div>
1427
- <div class="terminal-line" data-step="7">
1428
+ <div class="terminal-line" data-step="3">
1428
1429
  <span class="terminal-prompt">claude></span>
1429
- <span class="terminal-command">Write src/app/api/v2/ai/chat/route.ts</span>
1430
+ <span class="terminal-command">WebSearch "Brandfetch API endpoints 2025"</span>
1430
1431
  </div>
1431
- <div class="terminal-line terminal-result" data-step="8">
1432
- <span class="terminal-blocked">BLOCKED: Interview phase not complete.<br>Use AskUserQuestion to understand requirements.</span>
1432
+ <div class="terminal-line terminal-result" data-step="4">
1433
+ <span class="terminal-logged">[LOGGED] WebSearch: found /v2/brands, /v2/search, /v2/logos</span>
1433
1434
  </div>
1434
1435
 
1435
- <div class="terminal-comment" data-step="9">// AI asks user a question</div>
1436
- <div class="terminal-line" data-step="10">
1436
+ <div class="terminal-comment" data-step="5">// Step 2: Interview with structured options (from research)</div>
1437
+ <div class="terminal-line" data-step="6">
1437
1438
  <span class="terminal-prompt">claude></span>
1438
- <span class="terminal-command">AskUserQuestion "Which providers should this support?"</span>
1439
+ <span class="terminal-command">AskUserQuestion(<br>
1440
+ &nbsp;&nbsp;question: "What's the primary purpose?"<br>
1441
+ &nbsp;&nbsp;options: [<br>
1442
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "lookup", label: "Brand lookup by domain"},<br>
1443
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "logos", label: "Logo extraction only"},<br>
1444
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "full_kit", label: "Full brand kit"},<br>
1445
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "custom", label: "Type something..."}<br>
1446
+ &nbsp;&nbsp;]<br>
1447
+ )</span>
1439
1448
  </div>
1440
- <div class="terminal-line terminal-result" data-step="11">
1441
- <span class="terminal-logged">[TRACKED] Question logged with tool_used: true</span>
1449
+ <div class="terminal-line terminal-result" data-step="7">
1450
+ <span class="terminal-allowed">[USER] Selected: "full_kit"<br>[TRACKED] decisions.purpose = "full_kit"</span>
1442
1451
  </div>
1443
1452
 
1444
- <div class="terminal-comment" data-step="12">// After 3+ questions, AI can write code</div>
1445
- <div class="terminal-line" data-step="13">
1453
+ <div class="terminal-line" data-step="8">
1446
1454
  <span class="terminal-prompt">claude></span>
1447
- <span class="terminal-command">Write src/app/api/v2/ai/chat/route.ts</span>
1455
+ <span class="terminal-command">AskUserQuestion(<br>
1456
+ &nbsp;&nbsp;question: "How should API keys be handled?"<br>
1457
+ &nbsp;&nbsp;options: [<br>
1458
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "server_only", label: "Server env vars only"},<br>
1459
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "header", label: "Custom header override"},<br>
1460
+ &nbsp;&nbsp;&nbsp;&nbsp;{value: "both", label: "Both methods"},<br>
1461
+ &nbsp;&nbsp;]<br>
1462
+ )</span>
1448
1463
  </div>
1449
- <div class="terminal-line terminal-result" data-step="14">
1450
- <span class="terminal-allowed">ALLOWED: All prerequisites met. Writing file...</span>
1464
+ <div class="terminal-line terminal-result" data-step="9">
1465
+ <span class="terminal-allowed">[USER] Selected: "server_only"<br>[TRACKED] decisions.api_key_handling = "server_only"</span>
1451
1466
  </div>
1452
1467
 
1453
- <div class="terminal-comment" data-step="15">// AI tries to stop before finishing all phases</div>
1454
- <div class="terminal-line" data-step="16">
1468
+ <div class="terminal-comment" data-step="10">// Step 3: Interview complete, now writing code</div>
1469
+ <div class="terminal-line" data-step="11">
1455
1470
  <span class="terminal-prompt">claude></span>
1456
- <span class="terminal-command">[STOP - end conversation]</span>
1471
+ <span class="terminal-command">Write src/app/api/v2/brandfetch/route.ts</span>
1457
1472
  </div>
1458
- <div class="terminal-line terminal-result" data-step="17">
1459
- <span class="terminal-blocked">BLOCKED: Required phases incomplete.<br>- TDD Green phase (not started)<br>- Documentation (not started)</span>
1473
+ <div class="terminal-line terminal-result" data-step="12">
1474
+ <span class="terminal-allowed">✅ Interview complete. REMEMBER THE USER'S DECISIONS:<br>
1475
+ &nbsp;&nbsp;• Purpose: full_kit<br>
1476
+ &nbsp;&nbsp;• API Key Handling: server_only<br>
1477
+ ALLOWED: Writing file...</span>
1460
1478
  </div>
1461
1479
 
1462
- <div class="terminal-comment" data-step="18">// After completing all phases</div>
1463
- <div class="terminal-line" data-step="19">
1480
+ <div class="terminal-comment" data-step="13">// Decisions are injected on EVERY write operation</div>
1481
+ <div class="terminal-line" data-step="14">
1464
1482
  <span class="terminal-prompt">claude></span>
1465
- <span class="terminal-command">[STOP - all phases complete]</span>
1483
+ <span class="terminal-command">Write src/lib/schemas/brandfetch.ts</span>
1466
1484
  </div>
1467
- <div class="terminal-line terminal-result" data-step="20">
1468
- <span class="terminal-allowed">ALLOWED: Workflow complete!<br>Files created: 12 | Tests: 160 passing</span>
1485
+ <div class="terminal-line terminal-result" data-step="15">
1486
+ <span class="terminal-allowed">✅ REMEMBER THE USER'S DECISIONS:<br>
1487
+ &nbsp;&nbsp;• Purpose: full_kit<br>
1488
+ &nbsp;&nbsp;• API Key Handling: server_only<br>
1489
+ ALLOWED: Writing schema file...</span>
1469
1490
  </div>
1470
1491
  </div>
1471
1492
  </div>
@@ -1483,33 +1504,41 @@
1483
1504
  <div class="explanation-title">Everything Gets Recorded</div>
1484
1505
  <p>
1485
1506
  All progress is saved to <code>.claude/api-dev-state.json</code>.
1486
- This creates an <strong>audit trail</strong> of what was researched, what questions
1487
- were asked, and what was built. You can always check the current state.
1507
+ This includes <strong>your interview decisions</strong>, which are injected
1508
+ during implementation to ensure consistency.
1488
1509
  </p>
1489
1510
  </div>
1490
1511
 
1491
1512
  <div class="json-viewer">
1492
1513
  <div class="json-line"><span class="json-key">{</span></div>
1493
- <div class="json-line"> <span class="json-key">"endpoint":</span> <span class="json-string">"ai-foundation"</span>,</div>
1494
- <div class="json-line"> <span class="json-key">"library":</span> <span class="json-string">"Vercel AI SDK"</span>,</div>
1514
+ <div class="json-line"> <span class="json-key">"endpoint":</span> <span class="json-string">"brandfetch"</span>,</div>
1495
1515
  <div class="json-line"> <span class="json-key">"phases":</span> {</div>
1496
- <div class="json-line highlight"> <span class="json-key">"research_initial":</span> { <span class="json-key">"status":</span> <span class="json-value">"complete"</span> }, <span class="json-comment">// Logged 6 sources</span></div>
1516
+ <div class="json-line highlight"> <span class="json-key">"research_initial":</span> {</div>
1517
+ <div class="json-line highlight"> <span class="json-key">"status":</span> <span class="json-value">"complete"</span>,</div>
1518
+ <div class="json-line highlight"> <span class="json-key">"sources":</span> [<span class="json-string">"context7"</span>, <span class="json-string">"websearch"</span>, <span class="json-string">"websearch"</span>]</div>
1519
+ <div class="json-line highlight"> },</div>
1497
1520
  <div class="json-line highlight"> <span class="json-key">"interview":</span> {</div>
1498
1521
  <div class="json-line highlight"> <span class="json-key">"status":</span> <span class="json-value">"complete"</span>,</div>
1499
- <div class="json-line highlight"> <span class="json-key">"user_question_count":</span> <span class="json-value">6</span> <span class="json-comment">// Actual questions asked</span></div>
1522
+ <div class="json-line highlight"> <span class="json-key">"user_question_count":</span> <span class="json-value">6</span>,</div>
1523
+ <div class="json-line highlight"> <span class="json-key">"structured_question_count":</span> <span class="json-value">5</span>, <span class="json-comment">// Questions with options</span></div>
1524
+ <div class="json-line highlight"> <span class="json-key">"decisions":</span> { <span class="json-comment">// YOUR CHOICES - INJECTED ON WRITES</span></div>
1525
+ <div class="json-line highlight"> <span class="json-key">"purpose":</span> {<span class="json-key">"value":</span> <span class="json-string">"full_kit"</span>},</div>
1526
+ <div class="json-line highlight"> <span class="json-key">"api_key_handling":</span> {<span class="json-key">"value":</span> <span class="json-string">"server_only"</span>},</div>
1527
+ <div class="json-line highlight"> <span class="json-key">"response_format":</span> {<span class="json-key">"value":</span> <span class="json-string">"json"</span>},</div>
1528
+ <div class="json-line highlight"> <span class="json-key">"error_handling":</span> {<span class="json-key">"value":</span> <span class="json-string">"detailed"</span>}</div>
1529
+ <div class="json-line highlight"> },</div>
1530
+ <div class="json-line highlight"> <span class="json-key">"questions":</span> [</div>
1531
+ <div class="json-line highlight"> {<span class="json-key">"tool_used":</span> <span class="json-value">true</span>, <span class="json-key">"has_options":</span> <span class="json-value">true</span>, <span class="json-key">"user_response":</span> <span class="json-string">"full_kit"</span>},</div>
1532
+ <div class="json-line highlight"> <span class="json-comment">// ... more questions tracked</span></div>
1533
+ <div class="json-line highlight"> ]</div>
1500
1534
  <div class="json-line highlight"> },</div>
1501
- <div class="json-line highlight"> <span class="json-key">"tdd_red":</span> { <span class="json-key">"status":</span> <span class="json-value">"complete"</span>, <span class="json-key">"tests_count":</span> <span class="json-value">146</span> },</div>
1502
- <div class="json-line highlight"> <span class="json-key">"tdd_green":</span> { <span class="json-key">"status":</span> <span class="json-value">"complete"</span> }</div>
1503
- <div class="json-line"> },</div>
1504
- <div class="json-line"> <span class="json-key">"verification":</span> {</div>
1505
- <div class="json-line"> <span class="json-key">"all_sources_fetched":</span> <span class="json-value">true</span>,</div>
1506
- <div class="json-line"> <span class="json-key">"all_tests_passing":</span> <span class="json-value">true</span></div>
1535
+ <div class="json-line"> <span class="json-key">"tdd_red":</span> { <span class="json-key">"status":</span> <span class="json-value">"complete"</span> },</div>
1536
+ <div class="json-line"> <span class="json-key">"tdd_green":</span> { <span class="json-key">"status":</span> <span class="json-value">"complete"</span> }</div>
1507
1537
  <div class="json-line"> },</div>
1508
1538
  <div class="json-line"> <span class="json-key">"files_created":</span> [</div>
1509
- <div class="json-line"> <span class="json-string">"src/lib/ai/client.ts"</span>,</div>
1510
- <div class="json-line"> <span class="json-string">"src/lib/ai/schemas.ts"</span>,</div>
1511
- <div class="json-line"> <span class="json-string">"src/app/api/v2/ai/chat/route.ts"</span>,</div>
1512
- <div class="json-line"> <span class="json-string">"... and 9 more"</span></div>
1539
+ <div class="json-line"> <span class="json-string">"src/lib/schemas/brandfetch.ts"</span>,</div>
1540
+ <div class="json-line"> <span class="json-string">"src/app/api/v2/brandfetch/route.ts"</span>,</div>
1541
+ <div class="json-line"> <span class="json-string">"src/v2/docs/endpoints/brandfetch.md"</span></div>
1513
1542
  <div class="json-line"> ]</div>
1514
1543
  <div class="json-line"><span class="json-key">}</span></div>
1515
1544
  </div>
@@ -1604,7 +1633,7 @@
1604
1633
  <div class="made-with">
1605
1634
  <p>Made for developers who want AI assistants<br>that actually follow instructions.</p>
1606
1635
  <p style="margin-top: 20px; color: var(--dark-grey);">
1607
- v1.6.0 | MIT License<br>
1636
+ v1.9.0 | MIT License<br>
1608
1637
  "Interview first, test first, document always"
1609
1638
  </p>
1610
1639
  </div>
@@ -259,11 +259,61 @@ Reset the interview and ask with options based on research."""
259
259
  }))
260
260
  sys.exit(0)
261
261
 
262
- # All checks passed
263
- print(json.dumps({"permissionDecision": "allow"}))
262
+ # All checks passed - inject interview decisions as context reminder
263
+ decisions = interview.get("decisions", {})
264
+
265
+ if decisions:
266
+ # Build a reminder of what the user decided
267
+ decision_summary = _build_decision_summary(decisions)
268
+
269
+ # Allow but inject context about user decisions
270
+ print(json.dumps({
271
+ "permissionDecision": "allow",
272
+ "message": f"""✅ Interview complete. REMEMBER THE USER'S DECISIONS:
273
+
274
+ {decision_summary}
275
+
276
+ Your implementation MUST align with these choices.
277
+ The state file tracks these for consistency verification."""
278
+ }))
279
+ else:
280
+ print(json.dumps({"permissionDecision": "allow"}))
281
+
264
282
  sys.exit(0)
265
283
 
266
284
 
285
+ def _build_decision_summary(decisions: dict) -> str:
286
+ """Build a human-readable summary of user decisions from the interview."""
287
+ if not decisions:
288
+ return "No key decisions recorded."
289
+
290
+ lines = []
291
+ decision_labels = {
292
+ "provider": "AI Provider",
293
+ "purpose": "Primary Purpose",
294
+ "response_format": "Response Format",
295
+ "required_params": "Required Parameters",
296
+ "optional_params": "Optional Parameters",
297
+ "error_handling": "Error Handling",
298
+ "api_key_handling": "API Key Handling",
299
+ "external_services": "External Services",
300
+ }
301
+
302
+ for key, data in decisions.items():
303
+ label = decision_labels.get(key, key.replace("_", " ").title())
304
+ response = data.get("response", "")
305
+ value = data.get("value", "")
306
+
307
+ if value:
308
+ lines.append(f"• {label}: {value}")
309
+ elif response:
310
+ # Truncate long responses
311
+ short_response = response[:80] + "..." if len(response) > 80 else response
312
+ lines.append(f"• {label}: {short_response}")
313
+
314
+ return "\n".join(lines) if lines else "No key decisions recorded."
315
+
316
+
267
317
  def _build_research_based_example(research_queries: list) -> str:
268
318
  """Build an example question based on actual research queries."""
269
319
  if not research_queries:
@@ -61,7 +61,8 @@ def main():
61
61
  "status": "not_started",
62
62
  "questions": [],
63
63
  "user_question_count": 0,
64
- "structured_question_count": 0
64
+ "structured_question_count": 0,
65
+ "decisions": {} # Track key decisions for consistency checking
65
66
  })
66
67
 
67
68
  # Track the question
@@ -78,16 +79,62 @@ def main():
78
79
  structured_count = interview.get("structured_question_count", 0) + 1
79
80
  interview["structured_question_count"] = structured_count
80
81
 
82
+ # IMPORTANT: Capture the user's response from tool_output
83
+ # PostToolUse runs AFTER the tool completes, so we have the response
84
+ user_response = None
85
+ selected_value = None
86
+
87
+ # tool_output contains the user's response
88
+ if isinstance(tool_output, str):
89
+ user_response = tool_output
90
+ elif isinstance(tool_output, dict):
91
+ user_response = tool_output.get("response", tool_output.get("result", str(tool_output)))
92
+
93
+ # Try to match response to an option value
94
+ if has_options and user_response:
95
+ response_lower = user_response.lower().strip()
96
+ for opt in options:
97
+ opt_value = opt.get("value", "").lower()
98
+ opt_label = opt.get("label", "").lower()
99
+ # Check if response matches value or label
100
+ if opt_value in response_lower or response_lower in opt_label or opt_label in response_lower:
101
+ selected_value = opt.get("value")
102
+ break
103
+
81
104
  question_entry = {
82
105
  "question": tool_input.get("question", ""),
83
106
  "timestamp": datetime.now().isoformat(),
84
107
  "tool_used": True, # Proves AskUserQuestion was actually called
85
108
  "has_options": has_options,
86
109
  "options_count": len(options),
87
- "options": [opt.get("label", opt.get("value", "")) for opt in options[:5]] if options else []
110
+ "options": [opt.get("label", opt.get("value", "")) for opt in options[:5]] if options else [],
111
+ "user_response": user_response[:500] if user_response else None, # Capture actual response
112
+ "selected_value": selected_value # Matched option value if applicable
88
113
  }
89
114
  questions.append(question_entry)
90
115
 
116
+ # Track key decisions in a summary dict for easy reference during implementation
117
+ decisions = interview.setdefault("decisions", {})
118
+ question_text = tool_input.get("question", "").lower()
119
+
120
+ # Categorize common decision types
121
+ if "provider" in question_text or "ai provider" in question_text:
122
+ decisions["provider"] = {"response": user_response, "value": selected_value}
123
+ elif "purpose" in question_text or "primary purpose" in question_text:
124
+ decisions["purpose"] = {"response": user_response, "value": selected_value}
125
+ elif "format" in question_text or "response format" in question_text:
126
+ decisions["response_format"] = {"response": user_response, "value": selected_value}
127
+ elif "parameter" in question_text and "required" in question_text:
128
+ decisions["required_params"] = {"response": user_response, "value": selected_value}
129
+ elif "parameter" in question_text and "optional" in question_text:
130
+ decisions["optional_params"] = {"response": user_response, "value": selected_value}
131
+ elif "error" in question_text:
132
+ decisions["error_handling"] = {"response": user_response, "value": selected_value}
133
+ elif "api key" in question_text or "key" in question_text:
134
+ decisions["api_key_handling"] = {"response": user_response, "value": selected_value}
135
+ elif "service" in question_text or "external" in question_text:
136
+ decisions["external_services"] = {"response": user_response, "value": selected_value}
137
+
91
138
  # Update interview status
92
139
  if interview.get("status") == "not_started":
93
140
  interview["status"] = "in_progress"
@@ -100,6 +147,8 @@ def main():
100
147
  interview["last_structured_question"] = {
101
148
  "question": tool_input.get("question", "")[:100],
102
149
  "options_count": len(options),
150
+ "user_response": user_response[:100] if user_response else None,
151
+ "selected_value": selected_value,
103
152
  "timestamp": datetime.now().isoformat()
104
153
  }
105
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hustle-together/api-dev-tools",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Interview-driven API development workflow for Claude Code - Automates research, testing, and documentation",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {