@hustle-together/api-dev-tools 3.12.16 → 4.5.3
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 +10 -0
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- package/.claude/settings.local.json +1 -7
- 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 +34 -20
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +365 -38
- 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 +488 -0
- package/README.md +346 -53
- package/bin/cli.js +359 -123
- 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 +97 -20
- package/{.claude/hooks → hooks}/completion-promise-detector.py +0 -0
- package/{.claude/hooks → hooks}/context-capacity-warning.py +0 -0
- package/{.claude/hooks → hooks}/docs-update-check.py +0 -0
- package/{.claude/hooks → hooks}/enforce-dry-run.py +0 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/{.claude/hooks → hooks}/generate-adr-options.py +0 -0
- package/{.claude/hooks → hooks}/hook_utils.py +0 -0
- package/hooks/ntfy-on-question.py +15 -2
- package/hooks/orchestrator-handoff.py +81 -3
- package/{.claude/hooks → hooks}/parallel-orchestrator.py +0 -0
- package/hooks/periodic-reground.py +40 -0
- package/{.claude/hooks → hooks}/remote-question-server.py +0 -0
- package/hooks/run-code-review.py +176 -29
- package/{.claude/hooks → hooks}/run-visual-qa.py +0 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/{.claude/hooks → hooks}/update-adr-decision.py +0 -0
- package/package.json +1 -1
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- package/templates/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- 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 +53 -1
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +74 -7
- package/templates/ui-showcase/_components/UIShowcase.tsx +47 -0
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- package/.claude/commands/hustle-combine.md +0 -1089
- package/.claude/commands/hustle-ui-create-page.md +0 -1078
- package/.claude/commands/hustle-ui-create.md +0 -1058
- package/.claude/hooks/auto-answer.py +0 -305
- package/.claude/hooks/cache-research.py +0 -337
- package/.claude/hooks/check-api-routes.py +0 -168
- package/.claude/hooks/check-playwright-setup.py +0 -103
- package/.claude/hooks/check-storybook-setup.py +0 -81
- package/.claude/hooks/check-update.py +0 -132
- package/.claude/hooks/detect-interruption.py +0 -165
- package/.claude/hooks/enforce-a11y-audit.py +0 -202
- package/.claude/hooks/enforce-brand-guide.py +0 -241
- package/.claude/hooks/enforce-component-type-confirm.py +0 -97
- package/.claude/hooks/enforce-freshness.py +0 -184
- package/.claude/hooks/enforce-page-components.py +0 -186
- package/.claude/hooks/enforce-page-data-schema.py +0 -155
- package/.claude/hooks/enforce-questions-sourced.py +0 -146
- package/.claude/hooks/enforce-schema-from-interview.py +0 -248
- package/.claude/hooks/enforce-ui-disambiguation.py +0 -108
- package/.claude/hooks/enforce-ui-interview.py +0 -130
- package/.claude/hooks/generate-manifest-entry.py +0 -1161
- package/.claude/hooks/lib/__init__.py +0 -1
- package/.claude/hooks/lib/greptile.py +0 -355
- package/.claude/hooks/lib/ntfy.py +0 -209
- package/.claude/hooks/notify-input-needed.py +0 -73
- package/.claude/hooks/notify-phase-complete.py +0 -90
- package/.claude/hooks/ntfy-on-question.py +0 -240
- package/.claude/hooks/orchestrator-completion.py +0 -313
- package/.claude/hooks/orchestrator-handoff.py +0 -267
- package/.claude/hooks/orchestrator-session-startup.py +0 -146
- package/.claude/hooks/run-code-review.py +0 -393
- package/.claude/hooks/session-logger.py +0 -323
- package/.claude/hooks/test-orchestrator-reground.py +0 -248
- package/.claude/hooks/track-scope-coverage.py +0 -220
- package/.claude/hooks/track-token-usage.py +0 -121
- package/.claude/hooks/update-api-showcase.py +0 -161
- package/.claude/hooks/update-registry.py +0 -352
- package/.claude/hooks/update-ui-showcase.py +0 -224
- package/.claude/test-auto-answer-bot.py +0 -183
- package/.claude/test-completion-detector.py +0 -263
- package/.claude/test-orchestrator-state.json +0 -20
- package/.claude/test-orchestrator.sh +0 -271
- /package/{.claude/commands → commands}/hustle-build.md +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/project-document-prompt.py +0 -0
- /package/{.claude/hooks → hooks}/remote-question-proxy.py +0 -0
- /package/{.claude/hooks → hooks}/update-testing-checklist.py +0 -0
|
@@ -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,
|