@hustle-together/api-dev-tools 3.12.3 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/adr-requests/.gitkeep +10 -0
- package/.claude/agents/adr-researcher.md +109 -0
- package/.claude/agents/visual-analyzer.md +183 -0
- package/.claude/api-dev-state.json +7 -463
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- package/.claude/workflow-logs/None.json +49 -0
- package/.claude/workflow-logs/session-20251230-143727.json +106 -0
- package/.skills/adr-deep-research/SKILL.md +351 -0
- package/.skills/api-create/SKILL.md +116 -17
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +786 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- package/.skills/parallel-spawn/SKILL.md +212 -0
- package/.skills/ralph-continue/SKILL.md +151 -0
- package/.skills/ralph-loop/SKILL.md +341 -0
- package/.skills/ralph-status/SKILL.md +87 -0
- package/.skills/refactor/SKILL.md +59 -0
- package/.skills/shadcn/SKILL.md +522 -0
- package/.skills/test-all/SKILL.md +210 -0
- package/.skills/test-builds/SKILL.md +208 -0
- package/.skills/test-debug/SKILL.md +212 -0
- package/.skills/test-e2e/SKILL.md +168 -0
- package/.skills/test-review/SKILL.md +707 -0
- package/.skills/test-unit/SKILL.md +143 -0
- package/.skills/test-visual/SKILL.md +301 -0
- package/.skills/token-report/SKILL.md +132 -0
- package/CHANGELOG.md +575 -0
- package/README.md +426 -56
- package/bin/cli.js +1538 -88
- package/commands/hustle-api-create.md +22 -0
- package/commands/hustle-build.md +259 -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/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
- package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
- package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
- package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
- package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
- package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
- package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
- package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
- package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
- package/hooks/api-workflow-check.py +34 -0
- package/hooks/auto-answer.py +305 -0
- package/hooks/check-update.py +132 -0
- package/hooks/completion-promise-detector.py +293 -0
- package/hooks/context-capacity-warning.py +171 -0
- package/hooks/docs-update-check.py +120 -0
- package/hooks/enforce-dry-run.py +134 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/hooks/generate-adr-options.py +282 -0
- package/hooks/hook_utils.py +609 -0
- package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/hooks/ntfy-on-question.py +240 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +267 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/parallel-orchestrator.py +451 -0
- package/hooks/periodic-reground.py +270 -67
- package/hooks/project-document-prompt.py +302 -0
- package/hooks/remote-question-proxy.py +284 -0
- package/hooks/remote-question-server.py +1224 -0
- package/hooks/run-code-review.py +176 -29
- package/hooks/run-visual-qa.py +338 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/hooks/update-adr-decision.py +236 -0
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-testing-checklist.py +195 -0
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- 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/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- package/templates/docs/page.tsx +230 -0
- package/templates/eslint-plugin-zod-schema/index.js +446 -0
- package/templates/eslint-plugin-zod-schema/package.json +26 -0
- package/templates/github-workflows/security.yml +274 -0
- package/templates/hustle-build-defaults.json +136 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +155 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- package/templates/ui-showcase/page.tsx +1 -1
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
## Hustle API Development Workflow (
|
|
1
|
+
## Hustle API Development Workflow (v4.0.0)
|
|
2
2
|
|
|
3
|
-
This project uses **@hustle-together/api-dev-tools** for interview-driven, research-first
|
|
3
|
+
This project uses **@hustle-together/api-dev-tools** for interview-driven, research-first development.
|
|
4
|
+
|
|
5
|
+
### Project Context
|
|
6
|
+
|
|
7
|
+
<!-- INSTALLER: Replace these with actual project values -->
|
|
8
|
+
**Tech Stack:** [Framework] + [Language] + [Database]
|
|
9
|
+
**UI Library:** [UI framework or component library]
|
|
10
|
+
**Testing:** [Test framework] + [E2E framework]
|
|
11
|
+
|
|
12
|
+
### Existing Elements
|
|
13
|
+
|
|
14
|
+
<!-- AUTO-POPULATED: Updated by registry hooks -->
|
|
15
|
+
**APIs:** (check `.claude/registry.json`)
|
|
16
|
+
**Components:** (check `.claude/registry.json`)
|
|
17
|
+
**Pages:** (check `.claude/registry.json`)
|
|
4
18
|
|
|
5
19
|
### Available Commands
|
|
6
20
|
|
|
7
21
|
| Command | Purpose |
|
|
8
22
|
| ---------------------------------- | ------------------------------------- |
|
|
9
|
-
| `/hustle-
|
|
10
|
-
| `/
|
|
11
|
-
| `/hustle-
|
|
12
|
-
| `/hustle-
|
|
13
|
-
| `/hustle-
|
|
14
|
-
| `/
|
|
15
|
-
| `/
|
|
16
|
-
| `/
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
| `/hustle-build [description]` | Orchestrated multi-workflow build |
|
|
24
|
+
| `/api-create [endpoint]` | Complete 14-phase API workflow |
|
|
25
|
+
| `/hustle-ui-create [component]` | Component with Storybook |
|
|
26
|
+
| `/hustle-ui-create-page [page]` | Page with Playwright E2E |
|
|
27
|
+
| `/hustle-combine [name]` | Combine multiple APIs |
|
|
28
|
+
| `/api-research [library]` | Adaptive propose-approve research |
|
|
29
|
+
| `/api-interview [endpoint]` | Questions FROM research findings |
|
|
30
|
+
| `/api-verify [endpoint]` | Re-research and verify implementation |
|
|
31
|
+
| `/api-env [endpoint]` | Check API keys |
|
|
32
|
+
| `/api-status [endpoint]` | Track progress |
|
|
33
|
+
|
|
34
|
+
### 14-Phase Flow
|
|
19
35
|
|
|
20
36
|
```
|
|
21
37
|
Phase 1: DISAMBIGUATION - Clarify ambiguous terms before research
|
|
@@ -28,18 +44,20 @@ Phase 7: ENVIRONMENT - Verify API keys exist
|
|
|
28
44
|
Phase 8: TDD RED - Write failing tests from schema
|
|
29
45
|
Phase 9: TDD GREEN - Minimal implementation to pass tests
|
|
30
46
|
Phase 10: VERIFY - Re-research docs, compare to implementation
|
|
31
|
-
Phase 11:
|
|
32
|
-
Phase 12:
|
|
33
|
-
Phase 13:
|
|
47
|
+
Phase 11: CODE REVIEW - AI review (bugs, security, performance)
|
|
48
|
+
Phase 12: TDD REFACTOR - Fix review issues + clean up code
|
|
49
|
+
Phase 13: DOCUMENTATION - Update manifests, cache research
|
|
50
|
+
Phase 14: COMPLETION - Final verification, commit
|
|
34
51
|
```
|
|
35
52
|
|
|
36
53
|
### Key Principles
|
|
37
54
|
|
|
38
|
-
1. **
|
|
55
|
+
1. **Research-First** - Never write code from memory, always verify docs
|
|
39
56
|
2. **Questions FROM Research** - Never use generic template questions
|
|
40
|
-
3. **
|
|
57
|
+
3. **Loop Until Green** - Every verification phase loops back if not successful
|
|
41
58
|
4. **7-Turn Re-grounding** - Context injected every 7 turns to prevent dilution
|
|
42
59
|
5. **Verify After Green** - Re-research to catch memory-based implementation errors
|
|
60
|
+
6. **Registry Awareness** - Don't recreate existing elements
|
|
43
61
|
|
|
44
62
|
### State Tracking
|
|
45
63
|
|
|
@@ -49,7 +67,17 @@ All progress is tracked in `.claude/api-dev-state.json`:
|
|
|
49
67
|
- Interview decisions (injected during implementation)
|
|
50
68
|
- Research sources with freshness tracking
|
|
51
69
|
- Turn count for re-grounding
|
|
52
|
-
-
|
|
70
|
+
- Deferred features list
|
|
71
|
+
- Test run history
|
|
72
|
+
|
|
73
|
+
### Registry
|
|
74
|
+
|
|
75
|
+
`.claude/registry.json` tracks all created elements:
|
|
76
|
+
|
|
77
|
+
- APIs with endpoints, schemas, and examples
|
|
78
|
+
- Components with props and variants
|
|
79
|
+
- Pages with routes and data requirements
|
|
80
|
+
- Combined APIs with orchestration patterns
|
|
53
81
|
|
|
54
82
|
### Research Cache
|
|
55
83
|
|
|
@@ -59,61 +87,58 @@ Research is cached in `.claude/research/` with 7-day freshness:
|
|
|
59
87
|
- `[api-name]/CURRENT.md` - Latest research
|
|
60
88
|
- `[api-name]/sources.json` - Research sources
|
|
61
89
|
- `[api-name]/interview.json` - Interview decisions
|
|
62
|
-
- `[api-name]/schema.json` - Schema snapshot
|
|
63
90
|
- Stale research (>7 days) triggers re-research prompt
|
|
64
91
|
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
When Phase 12 completes, `generate-manifest-entry.py` automatically creates:
|
|
98
|
-
|
|
99
|
-
- **Comprehensive curl examples** (minimal, full, auth, enum variations, boundary values)
|
|
100
|
-
- **Complete test cases** (success, validation, required fields, types, boundaries, arrays)
|
|
101
|
-
- **Parameter documentation** with all possible values
|
|
102
|
-
- **Ready-to-use entries** for `api-tests-manifest.json`
|
|
92
|
+
### Re-grounding System
|
|
93
|
+
|
|
94
|
+
Every 7 turns, the system injects a reminder with:
|
|
95
|
+
|
|
96
|
+
- Current endpoint and phase progress
|
|
97
|
+
- Key interview decisions
|
|
98
|
+
- Existing registry elements (don't recreate)
|
|
99
|
+
- Deferred features (don't re-suggest)
|
|
100
|
+
- Last test status (GREEN/RED)
|
|
101
|
+
- Brand guide status
|
|
102
|
+
- Research freshness warnings
|
|
103
|
+
- Orchestrator progress (if in /hustle-build)
|
|
104
|
+
|
|
105
|
+
See: [docs/REGROUNDING.md](./docs/REGROUNDING.md)
|
|
106
|
+
|
|
107
|
+
### Brand Guide
|
|
108
|
+
|
|
109
|
+
If `.claude/BRAND_GUIDE.md` exists:
|
|
110
|
+
|
|
111
|
+
- All UI components use brand colors/fonts
|
|
112
|
+
- Enforce hook checks before component creation
|
|
113
|
+
- Re-grounding reminds about brand guide
|
|
114
|
+
|
|
115
|
+
### Hooks (45+ Automatic Enforcement)
|
|
116
|
+
|
|
117
|
+
| Category | Hooks | Purpose |
|
|
118
|
+
| -------- | ----- | ------- |
|
|
119
|
+
| SessionStart | 4 | Inject state, detect interruptions, check updates |
|
|
120
|
+
| UserPromptSubmit | 1 | Require research before API questions |
|
|
121
|
+
| PreToolUse | 22 | Phase enforcement, schema validation |
|
|
122
|
+
| PostToolUse | 12 | Tracking, re-grounding, registry updates |
|
|
123
|
+
| Stop | 2 | Workflow completion, session logging |
|
|
103
124
|
|
|
104
125
|
### Usage
|
|
105
126
|
|
|
106
127
|
```bash
|
|
107
|
-
#
|
|
108
|
-
/hustle-
|
|
128
|
+
# Orchestrated build (recommended for features)
|
|
129
|
+
/hustle-build dashboard with user stats and activity charts
|
|
109
130
|
|
|
110
|
-
#
|
|
111
|
-
/
|
|
112
|
-
/hustle-
|
|
113
|
-
/hustle-
|
|
131
|
+
# Individual workflows
|
|
132
|
+
/api-create stripe-checkout
|
|
133
|
+
/hustle-ui-create StatCard
|
|
134
|
+
/hustle-ui-create-page Dashboard
|
|
135
|
+
|
|
136
|
+
# TDD cycle
|
|
114
137
|
/red
|
|
115
138
|
/green
|
|
116
|
-
/hustle-api-verify [endpoint]
|
|
117
139
|
/refactor
|
|
140
|
+
|
|
141
|
+
# Git
|
|
118
142
|
/commit
|
|
143
|
+
/pr
|
|
119
144
|
```
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface ADR {
|
|
6
|
+
number: number;
|
|
7
|
+
title: string;
|
|
8
|
+
status: 'proposed' | 'accepted' | 'deprecated' | 'superseded';
|
|
9
|
+
date: string;
|
|
10
|
+
phase: 'initial_research' | 'interview' | 'deep_research';
|
|
11
|
+
endpoint: string;
|
|
12
|
+
file: string;
|
|
13
|
+
supersededBy?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ADRIndex {
|
|
17
|
+
adrs: ADR[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const statusColors: Record<ADR['status'], string> = {
|
|
21
|
+
proposed: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
|
22
|
+
accepted: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
23
|
+
deprecated: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
|
24
|
+
superseded: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const phaseLabels: Record<ADR['phase'], string> = {
|
|
28
|
+
initial_research: 'Initial Research',
|
|
29
|
+
interview: 'Interview',
|
|
30
|
+
deep_research: 'Deep Research',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function StatusBadge({ status }: { status: ADR['status'] }) {
|
|
34
|
+
return (
|
|
35
|
+
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusColors[status]}`}>
|
|
36
|
+
{status.charAt(0).toUpperCase() + status.slice(1)}
|
|
37
|
+
</span>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function PhaseBadge({ phase }: { phase: ADR['phase'] }) {
|
|
42
|
+
const phaseColors: Record<ADR['phase'], string> = {
|
|
43
|
+
initial_research: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
|
44
|
+
interview: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200',
|
|
45
|
+
deep_research: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${phaseColors[phase]}`}>
|
|
50
|
+
{phaseLabels[phase]}
|
|
51
|
+
</span>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ADRCard({ adr, onClick }: { adr: ADR; onClick: () => void }) {
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
onClick={onClick}
|
|
59
|
+
className="p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
<div className="flex items-start justify-between">
|
|
62
|
+
<div>
|
|
63
|
+
<div className="flex items-center gap-2 mb-1">
|
|
64
|
+
<span className="text-sm font-mono text-gray-500">
|
|
65
|
+
ADR-{adr.number.toString().padStart(4, '0')}
|
|
66
|
+
</span>
|
|
67
|
+
<StatusBadge status={adr.status} />
|
|
68
|
+
</div>
|
|
69
|
+
<h3 className="text-lg font-semibold">{adr.title}</h3>
|
|
70
|
+
</div>
|
|
71
|
+
<span className="text-sm text-gray-500">{adr.date}</span>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="mt-2 flex items-center gap-2">
|
|
75
|
+
<PhaseBadge phase={adr.phase} />
|
|
76
|
+
<span className="text-sm text-gray-500">→</span>
|
|
77
|
+
<span className="text-sm font-medium text-blue-600 dark:text-blue-400">
|
|
78
|
+
{adr.endpoint}
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{adr.supersededBy && (
|
|
83
|
+
<div className="mt-2 text-sm text-gray-500">
|
|
84
|
+
Superseded by ADR-{adr.supersededBy.toString().padStart(4, '0')}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ADRDetail({ adr, content, onBack }: { adr: ADR; content: string; onBack: () => void }) {
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<button
|
|
95
|
+
onClick={onBack}
|
|
96
|
+
className="mb-4 flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
|
|
97
|
+
>
|
|
98
|
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
99
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
100
|
+
</svg>
|
|
101
|
+
Back to list
|
|
102
|
+
</button>
|
|
103
|
+
|
|
104
|
+
<div className="flex items-center gap-4 mb-4">
|
|
105
|
+
<span className="text-lg font-mono text-gray-500">
|
|
106
|
+
ADR-{adr.number.toString().padStart(4, '0')}
|
|
107
|
+
</span>
|
|
108
|
+
<StatusBadge status={adr.status} />
|
|
109
|
+
<PhaseBadge phase={adr.phase} />
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<h1 className="text-2xl font-bold mb-2">{adr.title}</h1>
|
|
113
|
+
|
|
114
|
+
<div className="text-sm text-gray-500 mb-6">
|
|
115
|
+
{adr.date} • {adr.endpoint}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div
|
|
119
|
+
className="prose dark:prose-invert max-w-none"
|
|
120
|
+
dangerouslySetInnerHTML={{ __html: content }}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function EmptyState() {
|
|
127
|
+
return (
|
|
128
|
+
<div className="text-center py-12">
|
|
129
|
+
<svg className="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
130
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
131
|
+
</svg>
|
|
132
|
+
<h3 className="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No ADRs yet</h3>
|
|
133
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
134
|
+
ADRs are created during Interview and Deep Research phases.
|
|
135
|
+
</p>
|
|
136
|
+
<p className="mt-2 text-xs text-gray-400">
|
|
137
|
+
Run <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">/api-create</code> to generate your first ADR.
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function FilterBar({
|
|
144
|
+
statusFilter,
|
|
145
|
+
setStatusFilter,
|
|
146
|
+
phaseFilter,
|
|
147
|
+
setPhaseFilter,
|
|
148
|
+
searchQuery,
|
|
149
|
+
setSearchQuery,
|
|
150
|
+
}: {
|
|
151
|
+
statusFilter: ADR['status'] | 'all';
|
|
152
|
+
setStatusFilter: (v: ADR['status'] | 'all') => void;
|
|
153
|
+
phaseFilter: ADR['phase'] | 'all';
|
|
154
|
+
setPhaseFilter: (v: ADR['phase'] | 'all') => void;
|
|
155
|
+
searchQuery: string;
|
|
156
|
+
setSearchQuery: (v: string) => void;
|
|
157
|
+
}) {
|
|
158
|
+
return (
|
|
159
|
+
<div className="flex flex-wrap gap-4 mb-6">
|
|
160
|
+
<input
|
|
161
|
+
type="text"
|
|
162
|
+
placeholder="Search ADRs..."
|
|
163
|
+
value={searchQuery}
|
|
164
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
165
|
+
className="flex-1 min-w-[200px] px-3 py-2 border rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
<select
|
|
169
|
+
value={statusFilter}
|
|
170
|
+
onChange={(e) => setStatusFilter(e.target.value as ADR['status'] | 'all')}
|
|
171
|
+
className="px-3 py-2 border rounded-lg bg-white dark:bg-gray-800"
|
|
172
|
+
>
|
|
173
|
+
<option value="all">All Statuses</option>
|
|
174
|
+
<option value="proposed">Proposed</option>
|
|
175
|
+
<option value="accepted">Accepted</option>
|
|
176
|
+
<option value="deprecated">Deprecated</option>
|
|
177
|
+
<option value="superseded">Superseded</option>
|
|
178
|
+
</select>
|
|
179
|
+
|
|
180
|
+
<select
|
|
181
|
+
value={phaseFilter}
|
|
182
|
+
onChange={(e) => setPhaseFilter(e.target.value as ADR['phase'] | 'all')}
|
|
183
|
+
className="px-3 py-2 border rounded-lg bg-white dark:bg-gray-800"
|
|
184
|
+
>
|
|
185
|
+
<option value="all">All Phases</option>
|
|
186
|
+
<option value="initial_research">Initial Research</option>
|
|
187
|
+
<option value="interview">Interview</option>
|
|
188
|
+
<option value="deep_research">Deep Research</option>
|
|
189
|
+
</select>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default function ADRViewer() {
|
|
195
|
+
const [adrs, setAdrs] = useState<ADR[]>([]);
|
|
196
|
+
const [selectedAdr, setSelectedAdr] = useState<ADR | null>(null);
|
|
197
|
+
const [adrContent, setAdrContent] = useState<string>('');
|
|
198
|
+
const [loading, setLoading] = useState(true);
|
|
199
|
+
const [statusFilter, setStatusFilter] = useState<ADR['status'] | 'all'>('all');
|
|
200
|
+
const [phaseFilter, setPhaseFilter] = useState<ADR['phase'] | 'all'>('all');
|
|
201
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
// In production, fetch from .claude/adrs/index.json
|
|
205
|
+
// For now, use mock data or empty state
|
|
206
|
+
const fetchADRs = async () => {
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch('/.claude/adrs/index.json');
|
|
209
|
+
if (res.ok) {
|
|
210
|
+
const data: ADRIndex = await res.json();
|
|
211
|
+
setAdrs(data.adrs);
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// No ADRs yet - show empty state
|
|
215
|
+
setAdrs([]);
|
|
216
|
+
} finally {
|
|
217
|
+
setLoading(false);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
fetchADRs();
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
const handleSelectAdr = async (adr: ADR) => {
|
|
225
|
+
setSelectedAdr(adr);
|
|
226
|
+
try {
|
|
227
|
+
const res = await fetch(`/.claude/adrs/${adr.file}`);
|
|
228
|
+
if (res.ok) {
|
|
229
|
+
const text = await res.text();
|
|
230
|
+
// Basic markdown to HTML (in production, use a proper markdown parser)
|
|
231
|
+
const html = text
|
|
232
|
+
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
|
233
|
+
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
234
|
+
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
235
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
236
|
+
.replace(/\n\n/g, '</p><p>')
|
|
237
|
+
.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
238
|
+
setAdrContent(`<p>${html}</p>`);
|
|
239
|
+
}
|
|
240
|
+
} catch {
|
|
241
|
+
setAdrContent('<p>Failed to load ADR content.</p>');
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const filteredAdrs = adrs.filter((adr) => {
|
|
246
|
+
if (statusFilter !== 'all' && adr.status !== statusFilter) return false;
|
|
247
|
+
if (phaseFilter !== 'all' && adr.phase !== phaseFilter) return false;
|
|
248
|
+
if (searchQuery) {
|
|
249
|
+
const query = searchQuery.toLowerCase();
|
|
250
|
+
return (
|
|
251
|
+
adr.title.toLowerCase().includes(query) ||
|
|
252
|
+
adr.endpoint.toLowerCase().includes(query) ||
|
|
253
|
+
`adr-${adr.number}`.includes(query)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (loading) {
|
|
260
|
+
return (
|
|
261
|
+
<div className="flex items-center justify-center py-12">
|
|
262
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (selectedAdr) {
|
|
268
|
+
return (
|
|
269
|
+
<ADRDetail
|
|
270
|
+
adr={selectedAdr}
|
|
271
|
+
content={adrContent}
|
|
272
|
+
onBack={() => setSelectedAdr(null)}
|
|
273
|
+
/>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div>
|
|
279
|
+
<div className="mb-6">
|
|
280
|
+
<h1 className="text-2xl font-bold">Architecture Decision Records</h1>
|
|
281
|
+
<p className="text-gray-500 mt-1">
|
|
282
|
+
Significant decisions made during research and interview phases
|
|
283
|
+
</p>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
{adrs.length > 0 && (
|
|
287
|
+
<FilterBar
|
|
288
|
+
statusFilter={statusFilter}
|
|
289
|
+
setStatusFilter={setStatusFilter}
|
|
290
|
+
phaseFilter={phaseFilter}
|
|
291
|
+
setPhaseFilter={setPhaseFilter}
|
|
292
|
+
searchQuery={searchQuery}
|
|
293
|
+
setSearchQuery={setSearchQuery}
|
|
294
|
+
/>
|
|
295
|
+
)}
|
|
296
|
+
|
|
297
|
+
{filteredAdrs.length === 0 ? (
|
|
298
|
+
adrs.length === 0 ? (
|
|
299
|
+
<EmptyState />
|
|
300
|
+
) : (
|
|
301
|
+
<div className="text-center py-8 text-gray-500">
|
|
302
|
+
No ADRs match your filters
|
|
303
|
+
</div>
|
|
304
|
+
)
|
|
305
|
+
) : (
|
|
306
|
+
<div className="space-y-4">
|
|
307
|
+
{filteredAdrs.map((adr) => (
|
|
308
|
+
<ADRCard key={adr.number} adr={adr} onClick={() => handleSelectAdr(adr)} />
|
|
309
|
+
))}
|
|
310
|
+
</div>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
<div className="mt-8 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
314
|
+
<h3 className="font-medium mb-2">About ADRs</h3>
|
|
315
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
316
|
+
Architecture Decision Records capture significant decisions made during the development workflow.
|
|
317
|
+
They are created automatically during Interview (Phase 4) and Deep Research (Phase 5)
|
|
318
|
+
when you make choices about authentication, error handling, caching, and other architectural concerns.
|
|
319
|
+
</p>
|
|
320
|
+
<p className="text-sm text-gray-500 mt-2">
|
|
321
|
+
See <code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">docs/ARCHITECTURE_DECISION_RECORDS.md</code> for details.
|
|
322
|
+
</p>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.
|
|
2
|
+
"version": "3.11.0",
|
|
3
3
|
"created_at": null,
|
|
4
4
|
"workflow": null,
|
|
5
5
|
"_workflow_options": [
|
|
@@ -12,6 +12,38 @@
|
|
|
12
12
|
"active_element": null,
|
|
13
13
|
"endpoints": {},
|
|
14
14
|
"elements": {},
|
|
15
|
+
|
|
16
|
+
"session_archives": {
|
|
17
|
+
"_description": "Completed workflow sessions archived for reference and learning",
|
|
18
|
+
"_archive_policy": "Sessions archived on workflow completion or manual archival",
|
|
19
|
+
"completed": [],
|
|
20
|
+
"interrupted": [],
|
|
21
|
+
"_archive_template": {
|
|
22
|
+
"id": "uuid",
|
|
23
|
+
"workflow": "api-create",
|
|
24
|
+
"element_name": "stripe-subscriptions",
|
|
25
|
+
"status": "completed",
|
|
26
|
+
"started_at": "2025-12-29T10:00:00Z",
|
|
27
|
+
"completed_at": "2025-12-29T14:30:00Z",
|
|
28
|
+
"duration_minutes": 270,
|
|
29
|
+
"phases_completed": 14,
|
|
30
|
+
"total_turns": 45,
|
|
31
|
+
"research_sources_count": 8,
|
|
32
|
+
"interview_decisions_count": 5,
|
|
33
|
+
"files_created": ["src/app/api/stripe/route.ts", "src/lib/stripe.ts"],
|
|
34
|
+
"files_modified": ["registry.json"],
|
|
35
|
+
"test_coverage": 92,
|
|
36
|
+
"learnings": ["stripe webhooks require idempotency keys", "always validate signature first"],
|
|
37
|
+
"archived_at": "2025-12-29T14:31:00Z",
|
|
38
|
+
"archive_reason": "workflow_completed"
|
|
39
|
+
},
|
|
40
|
+
"_learnings_aggregated": {
|
|
41
|
+
"_description": "Patterns learned across all archived sessions - used for re-grounding",
|
|
42
|
+
"common_pitfalls": [],
|
|
43
|
+
"successful_patterns": [],
|
|
44
|
+
"service_specific": {}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
15
47
|
"combine_config": {
|
|
16
48
|
"_comment": "Configuration for combine-api workflow",
|
|
17
49
|
"mode": null,
|