@hustle-together/api-dev-tools 3.12.3 → 3.12.16
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/commands/hustle-build.md +259 -0
- package/.claude/commands/hustle-combine.md +1089 -0
- package/.claude/commands/hustle-ui-create-page.md +1078 -0
- package/.claude/commands/hustle-ui-create.md +1058 -0
- package/.claude/hooks/auto-answer.py +305 -0
- package/.claude/hooks/cache-research.py +337 -0
- package/.claude/hooks/check-api-routes.py +168 -0
- package/.claude/hooks/check-playwright-setup.py +103 -0
- package/.claude/hooks/check-storybook-setup.py +81 -0
- package/.claude/hooks/check-update.py +132 -0
- package/.claude/hooks/completion-promise-detector.py +293 -0
- package/.claude/hooks/context-capacity-warning.py +171 -0
- package/.claude/hooks/detect-interruption.py +165 -0
- package/.claude/hooks/docs-update-check.py +120 -0
- package/.claude/hooks/enforce-a11y-audit.py +202 -0
- package/.claude/hooks/enforce-brand-guide.py +241 -0
- package/.claude/hooks/enforce-component-type-confirm.py +97 -0
- package/.claude/hooks/enforce-dry-run.py +134 -0
- package/.claude/hooks/enforce-freshness.py +184 -0
- package/.claude/hooks/enforce-page-components.py +186 -0
- package/.claude/hooks/enforce-page-data-schema.py +155 -0
- package/.claude/hooks/enforce-questions-sourced.py +146 -0
- package/.claude/hooks/enforce-schema-from-interview.py +248 -0
- package/.claude/hooks/enforce-ui-disambiguation.py +108 -0
- package/.claude/hooks/enforce-ui-interview.py +130 -0
- package/.claude/hooks/generate-adr-options.py +282 -0
- package/.claude/hooks/generate-manifest-entry.py +1161 -0
- package/.claude/hooks/hook_utils.py +609 -0
- package/.claude/hooks/lib/__init__.py +1 -0
- package/.claude/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/greptile.py +355 -0
- package/.claude/hooks/lib/ntfy.py +209 -0
- package/.claude/hooks/notify-input-needed.py +73 -0
- package/.claude/hooks/notify-phase-complete.py +90 -0
- package/.claude/hooks/ntfy-on-question.py +240 -0
- package/.claude/hooks/orchestrator-completion.py +313 -0
- package/.claude/hooks/orchestrator-handoff.py +267 -0
- package/.claude/hooks/orchestrator-session-startup.py +146 -0
- package/.claude/hooks/parallel-orchestrator.py +451 -0
- package/.claude/hooks/project-document-prompt.py +302 -0
- package/.claude/hooks/remote-question-proxy.py +284 -0
- package/.claude/hooks/remote-question-server.py +1224 -0
- package/.claude/hooks/run-code-review.py +393 -0
- package/.claude/hooks/run-visual-qa.py +338 -0
- package/.claude/hooks/session-logger.py +323 -0
- package/.claude/hooks/test-orchestrator-reground.py +248 -0
- package/.claude/hooks/track-scope-coverage.py +220 -0
- package/.claude/hooks/track-token-usage.py +121 -0
- package/.claude/hooks/update-adr-decision.py +236 -0
- package/.claude/hooks/update-api-showcase.py +161 -0
- package/.claude/hooks/update-registry.py +352 -0
- package/.claude/hooks/update-testing-checklist.py +195 -0
- package/.claude/hooks/update-ui-showcase.py +224 -0
- package/.claude/settings.local.json +7 -1
- package/.claude/test-auto-answer-bot.py +183 -0
- package/.claude/test-completion-detector.py +263 -0
- package/.claude/test-orchestrator-state.json +20 -0
- package/.claude/test-orchestrator.sh +271 -0
- package/.skills/api-create/SKILL.md +88 -3
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/hustle-build/SKILL.md +459 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- package/CHANGELOG.md +87 -0
- package/README.md +86 -9
- package/bin/cli.js +1302 -88
- package/commands/hustle-api-create.md +22 -0
- package/commands/hustle-combine.md +81 -2
- package/commands/hustle-ui-create-page.md +84 -2
- package/commands/hustle-ui-create.md +82 -2
- package/hooks/auto-answer.py +228 -0
- package/hooks/check-update.py +132 -0
- package/hooks/ntfy-on-question.py +227 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +189 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/periodic-reground.py +230 -67
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- package/templates/api-showcase/_components/APIModal.tsx +100 -8
- package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
- package/templates/api-showcase/_components/APITester.tsx +367 -58
- package/templates/docs/page.tsx +230 -0
- package/templates/hustle-build-defaults.json +84 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/settings.json +88 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +1 -1
- package/templates/ui-showcase/page.tsx +1 -1
- package/.claude/api-dev-state.json +0 -466
|
@@ -468,6 +468,28 @@ Both conditions must be true for the flag to be set.
|
|
|
468
468
|
└───────────────────────────────────────────────────────────┘
|
|
469
469
|
```
|
|
470
470
|
|
|
471
|
+
### End-of-Workflow Summary
|
|
472
|
+
|
|
473
|
+
After successful API creation, display:
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
═══════════════════════════════════════════════════════════════
|
|
477
|
+
✅ API CREATED: [endpoint-name]
|
|
478
|
+
|
|
479
|
+
📍 Quick Links:
|
|
480
|
+
• Test it: /api-showcase
|
|
481
|
+
• API Docs: /docs/api/[endpoint-name]
|
|
482
|
+
• Run tests: pnpm test src/app/api/v2/[endpoint-name]
|
|
483
|
+
• TypeDoc: pnpm typedoc
|
|
484
|
+
|
|
485
|
+
📊 Dashboard: /hustle-dev-dashboard
|
|
486
|
+
|
|
487
|
+
Next Steps:
|
|
488
|
+
• /hustle-combine - Combine with other APIs
|
|
489
|
+
• /commit - Commit your changes
|
|
490
|
+
═══════════════════════════════════════════════════════════════
|
|
491
|
+
```
|
|
492
|
+
|
|
471
493
|
### Showcase Redirect
|
|
472
494
|
|
|
473
495
|
After successful API creation, output:
|
|
@@ -1,9 +1,65 @@
|
|
|
1
|
-
# Hustle Combine - API and UI Orchestration Workflow
|
|
1
|
+
# Hustle Combine - API and UI Orchestration Workflow v4.0.0
|
|
2
2
|
|
|
3
|
-
**Usage:** `/hustle-combine [api|ui]`
|
|
3
|
+
**Usage:** `/hustle-combine [api|ui] [--auto] [--resume [workflow-id]]`
|
|
4
4
|
|
|
5
5
|
**Purpose:** Combines existing APIs or UI elements from the registry into new orchestration endpoints or composed components.
|
|
6
6
|
|
|
7
|
+
## Arguments
|
|
8
|
+
|
|
9
|
+
- `[api|ui]` - Mode: combine APIs or UI elements
|
|
10
|
+
- `--auto` - Fully autonomous mode, auto-answers all questions with comprehensive defaults
|
|
11
|
+
- `--resume [workflow-id]` - Resume an interrupted workflow from its last phase
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Auto Mode (`--auto`)
|
|
16
|
+
|
|
17
|
+
When `--auto` flag is used:
|
|
18
|
+
|
|
19
|
+
1. **No Interactive Questions:**
|
|
20
|
+
- All questions auto-answered with comprehensive defaults
|
|
21
|
+
- Uses `.claude/hustle-build-defaults.json` for configured answers
|
|
22
|
+
- Falls back to "most comprehensive" option when no default exists
|
|
23
|
+
|
|
24
|
+
2. **Comprehensive Selection Logic:**
|
|
25
|
+
- Selects parallel execution (when APIs are independent)
|
|
26
|
+
- Uses partial-success error handling
|
|
27
|
+
- Enables unified caching strategy
|
|
28
|
+
- Uses exponential retry with 30s timeout
|
|
29
|
+
|
|
30
|
+
3. **API Selection in Orchestrated Mode:**
|
|
31
|
+
- When `orchestrated: true`, APIs are pre-selected by orchestrator
|
|
32
|
+
- Selection phase is skipped, proceeds directly to Scope
|
|
33
|
+
|
|
34
|
+
4. **Logging:**
|
|
35
|
+
- All decisions logged to `.claude/workflow-logs/hustle-combine/[workflow-id].json`
|
|
36
|
+
- Review with `/hustle-combine-review [workflow-id]`
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Resume Mode (`--resume`)
|
|
41
|
+
|
|
42
|
+
When `--resume [workflow-id]` is used:
|
|
43
|
+
|
|
44
|
+
1. Load state from `.claude/api-dev-state.json`
|
|
45
|
+
2. Find the last incomplete phase
|
|
46
|
+
3. Continue from that point
|
|
47
|
+
4. Preserve all previous decisions and API selections
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Orchestrated Mode
|
|
52
|
+
|
|
53
|
+
When running as part of `/hustle-build`:
|
|
54
|
+
|
|
55
|
+
1. `orchestrated: true` flag is set in state
|
|
56
|
+
2. `shared_decisions` are pre-filled from orchestrator interview
|
|
57
|
+
3. Source APIs are pre-selected based on orchestrator decomposition
|
|
58
|
+
4. Questions covered by shared_decisions are SKIPPED
|
|
59
|
+
5. Only combination-specific questions are asked (execution order, caching)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
7
63
|
## Overview
|
|
8
64
|
|
|
9
65
|
This command reads from `.claude/registry.json` to present available elements for combination. It creates NEW orchestration layers using EXISTING, tested components.
|
|
@@ -837,6 +893,29 @@ Update registry.json with the new combined API:
|
|
|
837
893
|
}
|
|
838
894
|
```
|
|
839
895
|
|
|
896
|
+
### End-of-Workflow Summary
|
|
897
|
+
|
|
898
|
+
After successful combination, display:
|
|
899
|
+
|
|
900
|
+
```
|
|
901
|
+
═══════════════════════════════════════════════════════════════
|
|
902
|
+
✅ COMBINED API CREATED: [combined-name]
|
|
903
|
+
|
|
904
|
+
📍 Quick Links:
|
|
905
|
+
• Test it: /api-showcase
|
|
906
|
+
• API Docs: /docs/api/[combined-name]
|
|
907
|
+
• Run tests: pnpm test src/app/api/v2/[combined-name]
|
|
908
|
+
• TypeDoc: pnpm typedoc
|
|
909
|
+
|
|
910
|
+
📦 Combined: [api1] + [api2]
|
|
911
|
+
📊 Dashboard: /hustle-dev-dashboard
|
|
912
|
+
|
|
913
|
+
Next Steps:
|
|
914
|
+
• /commit - Commit your changes
|
|
915
|
+
• /hustle-api-create - Create another API
|
|
916
|
+
═══════════════════════════════════════════════════════════════
|
|
917
|
+
```
|
|
918
|
+
|
|
840
919
|
---
|
|
841
920
|
|
|
842
921
|
## Mode B: Combine UI (Coming Soon)
|
|
@@ -5,11 +5,71 @@ argument-hint: [page-name]
|
|
|
5
5
|
|
|
6
6
|
# Hustle UI Create - Page Mode
|
|
7
7
|
|
|
8
|
-
**Version:**
|
|
8
|
+
**Version:** 4.0.0
|
|
9
9
|
**14-phase workflow for creating Next.js App Router pages**
|
|
10
10
|
|
|
11
|
+
**Usage:** `/hustle-ui-create-page [page-name] [--auto] [--resume [workflow-id]]`
|
|
12
|
+
|
|
11
13
|
You are creating a page using the Hustle Together interview-driven workflow.
|
|
12
14
|
|
|
15
|
+
## Arguments
|
|
16
|
+
|
|
17
|
+
- `[page-name]` - Name of the page to create
|
|
18
|
+
- `--auto` - Fully autonomous mode, auto-answers all questions with comprehensive defaults
|
|
19
|
+
- `--resume [workflow-id]` - Resume an interrupted workflow from its last phase
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Auto Mode (`--auto`)
|
|
24
|
+
|
|
25
|
+
When `--auto` flag is used:
|
|
26
|
+
|
|
27
|
+
1. **No Interactive Questions:**
|
|
28
|
+
- All questions auto-answered with comprehensive defaults
|
|
29
|
+
- Uses `.claude/hustle-build-defaults.json` for configured answers
|
|
30
|
+
- Falls back to "most comprehensive" option when no default exists
|
|
31
|
+
|
|
32
|
+
2. **Comprehensive Selection Logic:**
|
|
33
|
+
- Uses responsive-grid layout
|
|
34
|
+
- Enables full SEO (Open Graph, Twitter cards, JSON-LD)
|
|
35
|
+
- Creates loading states and error boundaries
|
|
36
|
+
- Enables prefetching and Suspense
|
|
37
|
+
- Uses all available components from registry
|
|
38
|
+
|
|
39
|
+
3. **Error Handling:**
|
|
40
|
+
- E2E test failures: Retry 3x, then log and continue
|
|
41
|
+
- Missing API routes: Log warning, create stubs
|
|
42
|
+
- Performance budget exceeded: Log warning, continue
|
|
43
|
+
|
|
44
|
+
4. **Logging:**
|
|
45
|
+
- All decisions logged to `.claude/workflow-logs/ui-create-page/[workflow-id].json`
|
|
46
|
+
- Review with `/hustle-ui-create-page-review [workflow-id]`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Resume Mode (`--resume`)
|
|
51
|
+
|
|
52
|
+
When `--resume [workflow-id]` is used:
|
|
53
|
+
|
|
54
|
+
1. Load state from `.claude/api-dev-state.json`
|
|
55
|
+
2. Find the last incomplete phase
|
|
56
|
+
3. Continue from that point
|
|
57
|
+
4. Preserve all previous decisions
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Orchestrated Mode
|
|
62
|
+
|
|
63
|
+
When running as part of `/hustle-build`:
|
|
64
|
+
|
|
65
|
+
1. `orchestrated: true` flag is set in state
|
|
66
|
+
2. `shared_decisions` are pre-filled from orchestrator interview
|
|
67
|
+
3. Questions covered by shared_decisions are SKIPPED (e.g., auth, brand guide, testing level)
|
|
68
|
+
4. Components to use are pre-selected based on orchestrator decomposition
|
|
69
|
+
5. Only page-specific questions are asked (layout, SEO details)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
13
73
|
## Pre-Flight Check
|
|
14
74
|
|
|
15
75
|
Before starting, verify state file exists:
|
|
@@ -911,6 +971,28 @@ Run `pnpm dev` and navigate to /ui-showcase to see it.
|
|
|
911
971
|
|
|
912
972
|
Update state: `phases.completion.status = "complete"`
|
|
913
973
|
|
|
974
|
+
### End-of-Workflow Summary
|
|
975
|
+
|
|
976
|
+
After successful page creation, display:
|
|
977
|
+
|
|
978
|
+
```
|
|
979
|
+
═══════════════════════════════════════════════════════════════
|
|
980
|
+
✅ PAGE CREATED: [PageName]
|
|
981
|
+
|
|
982
|
+
📍 Quick Links:
|
|
983
|
+
• View page: /[page-name]
|
|
984
|
+
• UI Showcase: /ui-showcase
|
|
985
|
+
• Run E2E: pnpm playwright test src/app/[page-name]
|
|
986
|
+
|
|
987
|
+
📊 Dashboard: /hustle-dev-dashboard
|
|
988
|
+
|
|
989
|
+
Next Steps:
|
|
990
|
+
• /hustle-ui-create - Create components for this page
|
|
991
|
+
• /hustle-api-create - Create APIs this page needs
|
|
992
|
+
• /commit - Commit your changes
|
|
993
|
+
═══════════════════════════════════════════════════════════════
|
|
994
|
+
```
|
|
995
|
+
|
|
914
996
|
---
|
|
915
997
|
|
|
916
998
|
# State File Structure
|
|
@@ -992,5 +1074,5 @@ Update state: `phases.completion.status = "complete"`
|
|
|
992
1074
|
|
|
993
1075
|
---
|
|
994
1076
|
|
|
995
|
-
**Version:**
|
|
1077
|
+
**Version:** 4.0.0
|
|
996
1078
|
**Last Updated:** 2025-12-28
|
|
@@ -5,11 +5,69 @@ argument-hint: [component-name]
|
|
|
5
5
|
|
|
6
6
|
# Hustle UI Create
|
|
7
7
|
|
|
8
|
-
**Version:**
|
|
8
|
+
**Version:** 4.0.0
|
|
9
9
|
**14-phase workflow for creating UI components and pages**
|
|
10
10
|
|
|
11
|
+
**Usage:** `/hustle-ui-create [component-name] [--auto] [--resume [workflow-id]]`
|
|
12
|
+
|
|
11
13
|
You are creating a UI element using the Hustle Together interview-driven workflow.
|
|
12
14
|
|
|
15
|
+
## Arguments
|
|
16
|
+
|
|
17
|
+
- `[component-name]` - Name of the component to create
|
|
18
|
+
- `--auto` - Fully autonomous mode, auto-answers all questions with comprehensive defaults
|
|
19
|
+
- `--resume [workflow-id]` - Resume an interrupted workflow from its last phase
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Auto Mode (`--auto`)
|
|
24
|
+
|
|
25
|
+
When `--auto` flag is used:
|
|
26
|
+
|
|
27
|
+
1. **No Interactive Questions:**
|
|
28
|
+
- All questions auto-answered with comprehensive defaults
|
|
29
|
+
- Uses `.claude/hustle-build-defaults.json` for configured answers
|
|
30
|
+
- Falls back to "most comprehensive" option when no default exists
|
|
31
|
+
|
|
32
|
+
2. **Comprehensive Selection Logic:**
|
|
33
|
+
- Selects ALL variants (size, color, state)
|
|
34
|
+
- Enables full accessibility (WCAG 2.1 AA)
|
|
35
|
+
- Enables animations and responsive design
|
|
36
|
+
- Creates all Storybook stories
|
|
37
|
+
|
|
38
|
+
3. **Error Handling:**
|
|
39
|
+
- Test failures: Retry 3x, then log and continue
|
|
40
|
+
- Visual regression: Update baselines automatically
|
|
41
|
+
- Missing packages: Install automatically
|
|
42
|
+
|
|
43
|
+
4. **Logging:**
|
|
44
|
+
- All decisions logged to `.claude/workflow-logs/ui-create/[workflow-id].json`
|
|
45
|
+
- Review with `/hustle-ui-create-review [workflow-id]`
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Resume Mode (`--resume`)
|
|
50
|
+
|
|
51
|
+
When `--resume [workflow-id]` is used:
|
|
52
|
+
|
|
53
|
+
1. Load state from `.claude/api-dev-state.json`
|
|
54
|
+
2. Find the last incomplete phase
|
|
55
|
+
3. Continue from that point
|
|
56
|
+
4. Preserve all previous decisions
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Orchestrated Mode
|
|
61
|
+
|
|
62
|
+
When running as part of `/hustle-build`:
|
|
63
|
+
|
|
64
|
+
1. `orchestrated: true` flag is set in state
|
|
65
|
+
2. `shared_decisions` are pre-filled from orchestrator interview
|
|
66
|
+
3. Questions covered by shared_decisions are SKIPPED (e.g., brand guide, testing level)
|
|
67
|
+
4. Only component-specific questions are asked (variants, props)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
13
71
|
## Pre-Flight Check
|
|
14
72
|
|
|
15
73
|
Before starting, verify state file exists:
|
|
@@ -901,6 +959,28 @@ For Sandpack live editing, install: `pnpm add @codesandbox/sandpack-react`
|
|
|
901
959
|
|
|
902
960
|
Update state: `phases.completion.status = "complete"`
|
|
903
961
|
|
|
962
|
+
### End-of-Workflow Summary
|
|
963
|
+
|
|
964
|
+
After successful component creation, display:
|
|
965
|
+
|
|
966
|
+
```
|
|
967
|
+
═══════════════════════════════════════════════════════════════
|
|
968
|
+
✅ COMPONENT CREATED: [ComponentName]
|
|
969
|
+
|
|
970
|
+
📍 Quick Links:
|
|
971
|
+
• Preview it: /ui-showcase
|
|
972
|
+
• Storybook: http://localhost:6006
|
|
973
|
+
• Run tests: pnpm test src/components/[ComponentName]
|
|
974
|
+
• Visual tests: pnpm playwright test --grep "[ComponentName]"
|
|
975
|
+
|
|
976
|
+
📊 Dashboard: /hustle-dev-dashboard
|
|
977
|
+
|
|
978
|
+
Next Steps:
|
|
979
|
+
• /hustle-ui-create-page - Create a page using this component
|
|
980
|
+
• /commit - Commit your changes
|
|
981
|
+
═══════════════════════════════════════════════════════════════
|
|
982
|
+
```
|
|
983
|
+
|
|
904
984
|
---
|
|
905
985
|
|
|
906
986
|
# Page Mode (14 Phases)
|
|
@@ -974,5 +1054,5 @@ See full page mode documentation in `/hustle-ui-create-page.md` (if implementing
|
|
|
974
1054
|
|
|
975
1055
|
---
|
|
976
1056
|
|
|
977
|
-
**Version:**
|
|
1057
|
+
**Version:** 4.0.0
|
|
978
1058
|
**Last Updated:** 2025-12-28
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Auto-answer hook for --auto mode.
|
|
4
|
+
|
|
5
|
+
This hook intercepts AskUserQuestion calls when running in auto-mode
|
|
6
|
+
and either:
|
|
7
|
+
1. Uses pre-configured defaults from hustle-build-defaults.json
|
|
8
|
+
2. Spawns a Haiku sub-agent to pick the most comprehensive option
|
|
9
|
+
|
|
10
|
+
Hook Type: PreToolUse (matcher: AskUserQuestion)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_state():
|
|
20
|
+
"""Load workflow state to check if in auto mode"""
|
|
21
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
22
|
+
|
|
23
|
+
# Check hustle-build state first
|
|
24
|
+
build_state = Path(project_dir) / ".claude" / "hustle-build-state.json"
|
|
25
|
+
if build_state.exists():
|
|
26
|
+
try:
|
|
27
|
+
state = json.loads(build_state.read_text())
|
|
28
|
+
if state.get("mode") == "auto":
|
|
29
|
+
return state, "build"
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# Check api-dev state
|
|
34
|
+
api_state = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
35
|
+
if api_state.exists():
|
|
36
|
+
try:
|
|
37
|
+
state = json.loads(api_state.read_text())
|
|
38
|
+
if state.get("mode") == "auto":
|
|
39
|
+
return state, "workflow"
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
return None, None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_defaults():
|
|
47
|
+
"""Load pre-configured default answers"""
|
|
48
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
49
|
+
defaults_file = Path(project_dir) / ".claude" / "hustle-build-defaults.json"
|
|
50
|
+
|
|
51
|
+
if defaults_file.exists():
|
|
52
|
+
try:
|
|
53
|
+
return json.loads(defaults_file.read_text())
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def find_comprehensive_option(options):
|
|
61
|
+
"""
|
|
62
|
+
Find the most comprehensive option based on keywords.
|
|
63
|
+
|
|
64
|
+
Comprehensive options typically include words like:
|
|
65
|
+
- "all", "full", "complete", "comprehensive"
|
|
66
|
+
- Higher numbers (e.g., "100%" vs "50%")
|
|
67
|
+
- More features listed
|
|
68
|
+
"""
|
|
69
|
+
if not options:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
comprehensive_keywords = [
|
|
73
|
+
"all", "full", "complete", "comprehensive", "everything",
|
|
74
|
+
"maximum", "extensive", "detailed", "thorough", "wcag-aa"
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Score each option
|
|
78
|
+
scored = []
|
|
79
|
+
for i, opt in enumerate(options):
|
|
80
|
+
label = opt.get("label", "").lower()
|
|
81
|
+
description = opt.get("description", "").lower()
|
|
82
|
+
text = f"{label} {description}"
|
|
83
|
+
|
|
84
|
+
score = 0
|
|
85
|
+
|
|
86
|
+
# Check for comprehensive keywords
|
|
87
|
+
for keyword in comprehensive_keywords:
|
|
88
|
+
if keyword in text:
|
|
89
|
+
score += 10
|
|
90
|
+
|
|
91
|
+
# Check for "(Recommended)" suffix
|
|
92
|
+
if "recommended" in label.lower():
|
|
93
|
+
score += 20
|
|
94
|
+
|
|
95
|
+
# Prefer options with more content (longer descriptions = more features)
|
|
96
|
+
score += len(description) / 50
|
|
97
|
+
|
|
98
|
+
scored.append((i, score, opt))
|
|
99
|
+
|
|
100
|
+
# Sort by score descending
|
|
101
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
102
|
+
|
|
103
|
+
# Return the index of the best option (0-based)
|
|
104
|
+
if scored:
|
|
105
|
+
return scored[0][0]
|
|
106
|
+
|
|
107
|
+
return 0 # Default to first option
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_question_key(questions):
|
|
111
|
+
"""Extract a key from the question for lookup in defaults"""
|
|
112
|
+
if not questions or len(questions) == 0:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
q = questions[0]
|
|
116
|
+
header = q.get("header", "").lower().replace(" ", "_")
|
|
117
|
+
return header
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
# Get tool input from environment
|
|
122
|
+
tool_input = os.environ.get("CLAUDE_TOOL_INPUT", "{}")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
input_data = json.loads(tool_input)
|
|
126
|
+
except Exception:
|
|
127
|
+
print(json.dumps({"continue": True}))
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Check if in auto mode
|
|
131
|
+
state, state_type = load_state()
|
|
132
|
+
|
|
133
|
+
if not state:
|
|
134
|
+
# Not in auto mode, continue normally
|
|
135
|
+
print(json.dumps({"continue": True}))
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Load defaults
|
|
139
|
+
defaults = load_defaults()
|
|
140
|
+
|
|
141
|
+
questions = input_data.get("questions", [])
|
|
142
|
+
if not questions:
|
|
143
|
+
print(json.dumps({"continue": True}))
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Try to find pre-configured answer
|
|
147
|
+
question_key = get_question_key(questions)
|
|
148
|
+
answers = {}
|
|
149
|
+
|
|
150
|
+
for q in questions:
|
|
151
|
+
header = q.get("header", "")
|
|
152
|
+
options = q.get("options", [])
|
|
153
|
+
question_text = q.get("question", "")
|
|
154
|
+
|
|
155
|
+
# Check defaults first
|
|
156
|
+
default_answer = None
|
|
157
|
+
if question_key and question_key in defaults:
|
|
158
|
+
default_answer = defaults[question_key]
|
|
159
|
+
elif header.lower().replace(" ", "_") in defaults:
|
|
160
|
+
default_answer = defaults[header.lower().replace(" ", "_")]
|
|
161
|
+
|
|
162
|
+
if default_answer is not None:
|
|
163
|
+
# Use pre-configured default
|
|
164
|
+
answers[question_text] = default_answer
|
|
165
|
+
else:
|
|
166
|
+
# Auto-select comprehensive option
|
|
167
|
+
best_idx = find_comprehensive_option(options)
|
|
168
|
+
if best_idx is not None and options:
|
|
169
|
+
answers[question_text] = options[best_idx].get("label", "")
|
|
170
|
+
|
|
171
|
+
if answers:
|
|
172
|
+
# Log the auto-answer
|
|
173
|
+
log_auto_answer(state, questions, answers)
|
|
174
|
+
|
|
175
|
+
# Return the auto-selected answers
|
|
176
|
+
# The hook will inject these as if the user selected them
|
|
177
|
+
result = {
|
|
178
|
+
"continue": True,
|
|
179
|
+
"additionalContext": f"""
|
|
180
|
+
## Auto-Mode Active
|
|
181
|
+
|
|
182
|
+
Questions were auto-answered with comprehensive defaults:
|
|
183
|
+
{json.dumps(answers, indent=2)}
|
|
184
|
+
|
|
185
|
+
These selections prioritize:
|
|
186
|
+
- Maximum feature coverage
|
|
187
|
+
- Full testing
|
|
188
|
+
- Comprehensive documentation
|
|
189
|
+
- Best practices
|
|
190
|
+
|
|
191
|
+
Review in `/hustle-build-review` after completion.
|
|
192
|
+
"""
|
|
193
|
+
}
|
|
194
|
+
print(json.dumps(result))
|
|
195
|
+
else:
|
|
196
|
+
print(json.dumps({"continue": True}))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def log_auto_answer(state, questions, answers):
|
|
200
|
+
"""Log auto-answered questions to build log"""
|
|
201
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
202
|
+
logs_dir = Path(project_dir) / ".claude" / "workflow-logs"
|
|
203
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
204
|
+
|
|
205
|
+
build_id = state.get("build_id", state.get("workflow_id", "unknown"))
|
|
206
|
+
log_file = logs_dir / f"{build_id}.json"
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
if log_file.exists():
|
|
210
|
+
log = json.loads(log_file.read_text())
|
|
211
|
+
else:
|
|
212
|
+
log = {"auto_answers": []}
|
|
213
|
+
|
|
214
|
+
from datetime import datetime
|
|
215
|
+
log["auto_answers"].append({
|
|
216
|
+
"timestamp": datetime.now().isoformat(),
|
|
217
|
+
"questions": [q.get("question") for q in questions],
|
|
218
|
+
"answers": answers,
|
|
219
|
+
"reason": "auto-comprehensive"
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
log_file.write_text(json.dumps(log, indent=2))
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Check for api-dev-tools updates at session start.
|
|
4
|
+
|
|
5
|
+
This hook runs at SessionStart and checks npm registry for newer versions.
|
|
6
|
+
Non-blocking - only injects a message if update available.
|
|
7
|
+
|
|
8
|
+
Hook Type: SessionStart
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_installed_version():
|
|
19
|
+
"""Get currently installed version from package.json"""
|
|
20
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
21
|
+
package_json = Path(project_dir) / "package.json"
|
|
22
|
+
|
|
23
|
+
if package_json.exists():
|
|
24
|
+
try:
|
|
25
|
+
data = json.loads(package_json.read_text())
|
|
26
|
+
# Check if this project uses api-dev-tools
|
|
27
|
+
deps = data.get("devDependencies", {})
|
|
28
|
+
deps.update(data.get("dependencies", {}))
|
|
29
|
+
|
|
30
|
+
if "@hustle-together/api-dev-tools" in deps:
|
|
31
|
+
version = deps["@hustle-together/api-dev-tools"]
|
|
32
|
+
# Remove ^ or ~ prefix
|
|
33
|
+
return version.lstrip("^~")
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# Check for version in state file
|
|
38
|
+
state_file = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
39
|
+
if state_file.exists():
|
|
40
|
+
try:
|
|
41
|
+
state = json.loads(state_file.read_text())
|
|
42
|
+
return state.get("version", "0.0.0")
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_latest_version():
|
|
50
|
+
"""Check npm registry for latest version"""
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["npm", "view", "@hustle-together/api-dev-tools", "version"],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
timeout=5
|
|
57
|
+
)
|
|
58
|
+
if result.returncode == 0:
|
|
59
|
+
return result.stdout.strip()
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def version_tuple(v):
|
|
66
|
+
"""Convert version string to tuple for comparison"""
|
|
67
|
+
try:
|
|
68
|
+
return tuple(map(int, v.split(".")))
|
|
69
|
+
except Exception:
|
|
70
|
+
return (0, 0, 0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
# Check if we should skip (already checked recently)
|
|
75
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
76
|
+
state_file = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
if state_file.exists():
|
|
80
|
+
state = json.loads(state_file.read_text())
|
|
81
|
+
last_check = state.get("last_update_check")
|
|
82
|
+
|
|
83
|
+
if last_check:
|
|
84
|
+
from datetime import datetime, timedelta
|
|
85
|
+
last_check_dt = datetime.fromisoformat(last_check)
|
|
86
|
+
if datetime.now() - last_check_dt < timedelta(hours=24):
|
|
87
|
+
# Already checked today, skip
|
|
88
|
+
print(json.dumps({"continue": True}))
|
|
89
|
+
return
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
installed = get_installed_version()
|
|
94
|
+
latest = get_latest_version()
|
|
95
|
+
|
|
96
|
+
result = {"continue": True}
|
|
97
|
+
|
|
98
|
+
if installed and latest:
|
|
99
|
+
if version_tuple(latest) > version_tuple(installed):
|
|
100
|
+
result["additionalContext"] = f"""
|
|
101
|
+
## Update Available
|
|
102
|
+
|
|
103
|
+
A new version of api-dev-tools is available:
|
|
104
|
+
- **Current**: {installed}
|
|
105
|
+
- **Latest**: {latest}
|
|
106
|
+
|
|
107
|
+
To update, run:
|
|
108
|
+
```bash
|
|
109
|
+
npx @hustle-together/api-dev-tools@latest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This update may include new features, bug fixes, and improved workflows.
|
|
113
|
+
"""
|
|
114
|
+
# Update state with last check time
|
|
115
|
+
try:
|
|
116
|
+
if state_file.exists():
|
|
117
|
+
state = json.loads(state_file.read_text())
|
|
118
|
+
else:
|
|
119
|
+
state = {}
|
|
120
|
+
|
|
121
|
+
from datetime import datetime
|
|
122
|
+
state["last_update_check"] = datetime.now().isoformat()
|
|
123
|
+
state["available_update"] = latest
|
|
124
|
+
state_file.write_text(json.dumps(state, indent=2))
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
print(json.dumps(result))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|