@donotdev/cli 0.0.19 → 0.0.21
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 +31 -0
- package/dependencies-matrix.json +205 -50
- package/dist/bin/commands/agent-setup.js +2 -2
- package/dist/bin/commands/build.js +6 -6
- package/dist/bin/commands/bump.js +495 -70
- package/dist/bin/commands/cacheout.js +6 -6
- package/dist/bin/commands/coach.js +6 -6
- package/dist/bin/commands/create-app.js +24 -16
- package/dist/bin/commands/create-project.js +114 -18
- package/dist/bin/commands/db.js +142136 -0
- package/dist/bin/commands/deploy.js +354 -126
- package/dist/bin/commands/dev.js +6 -6
- package/dist/bin/commands/doctor.js +140 -33
- package/dist/bin/commands/emu.js +6 -6
- package/dist/bin/commands/format.js +6 -6
- package/dist/bin/commands/get-demo.js +11 -6
- package/dist/bin/commands/make-admin.js +14210 -13770
- package/dist/bin/commands/preview.js +6 -6
- package/dist/bin/commands/seed.js +142426 -0
- package/dist/bin/commands/setup-cicd.js +8904 -0
- package/dist/bin/commands/setup.js +259 -212
- package/dist/bin/commands/staging.js +361 -127
- package/dist/bin/commands/sync-secrets.js +55 -33
- package/dist/bin/commands/type-check.js +16 -10
- package/dist/bin/commands/wai.js +6 -6
- package/dist/bin/dndev.js +194 -188
- package/dist/bin/donotdev.js +139 -189
- package/dist/index.js +468 -144
- package/package.json +1 -1
- package/templates/app-demo/.env.example +1 -0
- package/templates/{root-consumer → app-demo}/entities/ExampleEntity.ts.example +15 -9
- package/templates/app-demo/index.html.example +1 -1
- package/templates/app-demo/public/apple-touch-icon.png.example +0 -0
- package/templates/app-demo/public/favicon.svg.example +1 -0
- package/templates/app-demo/public/icon-192x192.png.example +0 -0
- package/templates/app-demo/public/icon-512x512.png.example +0 -0
- package/templates/app-demo/src/App.tsx.example +3 -1
- package/templates/app-demo/src/config/app.ts.example +1 -0
- package/templates/app-demo/src/entities/booking.ts.example +75 -0
- package/templates/app-demo/src/entities/onboarding.ts.example +160 -0
- package/templates/app-demo/src/entities/product.ts.example +12 -0
- package/templates/app-demo/src/entities/quote.ts.example +70 -0
- package/templates/app-demo/src/pages/ChangelogPage.tsx.example +28 -1
- package/templates/app-demo/src/pages/ConditionalFormPage.tsx.example +88 -0
- package/templates/app-demo/src/pages/DashboardPage.tsx.example +2 -0
- package/templates/app-demo/src/pages/HomePage.tsx.example +355 -2
- package/templates/app-demo/src/pages/OnboardingPage.tsx.example +47 -0
- package/templates/app-demo/src/pages/PricingPage.tsx.example +28 -1
- package/templates/app-demo/src/pages/ProductsPage.tsx.example +2 -0
- package/templates/app-demo/src/pages/ProfilePage.tsx.example +2 -0
- package/templates/app-demo/src/pages/SettingsPage.tsx.example +2 -0
- package/templates/app-demo/src/pages/ShowcaseDetailPage.tsx.example +22 -16
- package/templates/app-demo/src/pages/ShowcasePage.tsx.example +3 -1
- package/templates/app-demo/src/pages/components/ComponentRenderer.tsx.example +147 -51
- package/templates/app-demo/src/pages/components/ComponentsData.tsx.example +103 -21
- package/templates/app-demo/src/pages/components/componentConfig.ts.example +139 -59
- package/templates/app-demo/src/pages/legal/LegalPage.tsx.example +12 -1
- package/templates/app-demo/src/pages/legal/PrivacyPage.tsx.example +10 -1
- package/templates/app-demo/src/pages/legal/TermsPage.tsx.example +10 -1
- package/templates/app-demo/src/themes.css.example +289 -77
- package/templates/app-demo/stats.html.example +4949 -0
- package/templates/app-dndev/index.html.example +164 -0
- package/templates/app-dndev/public/logo.svg.example +1 -0
- package/templates/app-dndev/public/manifest.json.example +10 -0
- package/templates/app-dndev/src/App.tsx.example +35 -0
- package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
- package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
- package/templates/app-dndev/src/components/Kanban.css.example +385 -0
- package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
- package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
- package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
- package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
- package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
- package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
- package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
- package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
- package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
- package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
- package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
- package/templates/app-dndev/src/config/app.ts.example +103 -0
- package/templates/app-dndev/src/config/commands.ts.example +171 -0
- package/templates/app-dndev/src/config/legal.ts.example +170 -0
- package/templates/app-dndev/src/config/providers.ts.example +7 -0
- package/templates/app-dndev/src/globals.css.example +10 -0
- package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
- package/templates/app-dndev/src/main.tsx.example +21 -0
- package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
- package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
- package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
- package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
- package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
- package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
- package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
- package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
- package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
- package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
- package/templates/app-dndev/src/themes.css.example +161 -0
- package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
- package/templates/app-dndev/tsconfig.json.example +9 -0
- package/templates/app-dndev/vite.config.ts.example +24 -0
- package/templates/app-vite/index.html.example +1 -1
- package/templates/functions-supabase/supabase/functions/.env.example +0 -2
- package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
- package/templates/root-consumer/.dndev.secrets.example +32 -0
- package/templates/root-consumer/.gitignore.example +3 -0
- package/templates/root-consumer/AI.md.example +4 -0
- package/templates/root-consumer/entities/index.ts.example +2 -5
- package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
- package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
- package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
- package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
- package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
- package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
- package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
- package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
- package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
- package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
- package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
- package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
- package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
- package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
- package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
- package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
- package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
- package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
- /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
- /package/templates/{root-consumer → app-demo}/entities/demo.ts.example +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Phase Detail — expanded view for selected phase
|
|
3
|
+
*
|
|
4
|
+
* Shows blueprint, agent, done-when criteria. Action buttons at the bottom
|
|
5
|
+
* inject MCP commands into the AI agent terminal.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Play, CheckCircle2, Eye } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
import { Badge, Button, Card, DescriptionList, Label, Stack, Text } from '@donotdev/components';
|
|
11
|
+
|
|
12
|
+
import { PHASE_BLUEPRINTS, getPhaseStatus } from './phaseData';
|
|
13
|
+
import { useDoNotDashStore } from '../../stores/dndevStore';
|
|
14
|
+
|
|
15
|
+
import type { ProtocolData, ProgressSection } from './phaseData';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// PROPS
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
interface PhaseDetailProps {
|
|
22
|
+
phaseId: number;
|
|
23
|
+
protocol: ProtocolData;
|
|
24
|
+
progressSections: ProgressSection[];
|
|
25
|
+
onOpenReview: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// COMPONENT
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export function PhaseDetail({
|
|
33
|
+
phaseId,
|
|
34
|
+
protocol,
|
|
35
|
+
progressSections,
|
|
36
|
+
onOpenReview,
|
|
37
|
+
}: PhaseDetailProps) {
|
|
38
|
+
const bp = PHASE_BLUEPRINTS[phaseId];
|
|
39
|
+
if (!bp) return null;
|
|
40
|
+
|
|
41
|
+
const status = getPhaseStatus(phaseId, protocol);
|
|
42
|
+
const isCurrentPhase = protocol.currentPhase === phaseId;
|
|
43
|
+
const phaseData = protocol.phases?.[phaseId];
|
|
44
|
+
|
|
45
|
+
// Description list items
|
|
46
|
+
const descItems = [
|
|
47
|
+
{ label: 'Agent', value: isCurrentPhase && protocol.agent ? protocol.agent : bp.agent },
|
|
48
|
+
{ label: 'Blueprint', value: bp.blueprint },
|
|
49
|
+
{ label: 'Done when', value: bp.doneWhen },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
if (protocol.currentModule && isCurrentPhase) {
|
|
53
|
+
descItems.push({ label: 'Module', value: protocol.currentModule });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (phaseData?.startedAt) {
|
|
57
|
+
descItems.push({ label: 'Started', value: new Date(phaseData.startedAt).toLocaleString() });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (phaseData?.completedAt) {
|
|
61
|
+
descItems.push({ label: 'Completed', value: new Date(phaseData.completedAt).toLocaleString() });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Card>
|
|
66
|
+
<Stack gap="tight">
|
|
67
|
+
{/* Header */}
|
|
68
|
+
<Stack direction="row" align="center" justify="between">
|
|
69
|
+
<Text level="h6" weight="semibold">Phase {bp.id}: {bp.name}</Text>
|
|
70
|
+
<Badge variant={STATUS_BADGE_VARIANT[status]}>{status}</Badge>
|
|
71
|
+
</Stack>
|
|
72
|
+
|
|
73
|
+
{/* Blueprint details */}
|
|
74
|
+
<DescriptionList items={descItems} />
|
|
75
|
+
|
|
76
|
+
{/* Actions */}
|
|
77
|
+
<PhaseActions
|
|
78
|
+
phaseId={phaseId}
|
|
79
|
+
status={status}
|
|
80
|
+
pendingReview={!!protocol.pendingReview && isCurrentPhase}
|
|
81
|
+
onOpenReview={onOpenReview}
|
|
82
|
+
/>
|
|
83
|
+
</Stack>
|
|
84
|
+
</Card>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const STATUS_BADGE_VARIANT = {
|
|
89
|
+
completed: 'success',
|
|
90
|
+
active: 'warning',
|
|
91
|
+
pending: 'muted',
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// PHASE ACTIONS — inject MCP commands into terminal
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
function PhaseActions({
|
|
99
|
+
phaseId,
|
|
100
|
+
status,
|
|
101
|
+
pendingReview,
|
|
102
|
+
onOpenReview,
|
|
103
|
+
}: {
|
|
104
|
+
phaseId: number;
|
|
105
|
+
status: 'pending' | 'active' | 'completed';
|
|
106
|
+
pendingReview: boolean;
|
|
107
|
+
onOpenReview: () => void;
|
|
108
|
+
}) {
|
|
109
|
+
const inject = (prompt: string) => {
|
|
110
|
+
useDoNotDashStore.getState().injectPrompt(prompt, { mode: 'ai-agent' });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (pendingReview) {
|
|
114
|
+
return (
|
|
115
|
+
<Button variant="default" icon={Eye} onClick={onOpenReview}>
|
|
116
|
+
Review
|
|
117
|
+
</Button>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (status === 'active') {
|
|
122
|
+
return (
|
|
123
|
+
<Button
|
|
124
|
+
variant="default"
|
|
125
|
+
icon={CheckCircle2}
|
|
126
|
+
onClick={() => inject(`Use the complete_phase MCP tool to submit phase ${phaseId} for review`)}
|
|
127
|
+
>
|
|
128
|
+
Complete Phase
|
|
129
|
+
</Button>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (status === 'pending') {
|
|
134
|
+
return (
|
|
135
|
+
<Button
|
|
136
|
+
variant="outline"
|
|
137
|
+
icon={Play}
|
|
138
|
+
onClick={() => inject(`Use the start_phase MCP tool to begin phase ${phaseId}`)}
|
|
139
|
+
>
|
|
140
|
+
Start Phase
|
|
141
|
+
</Button>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Completed
|
|
146
|
+
return <Label icon={CheckCircle2} plain>Phase completed</Label>;
|
|
147
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Review Panel — Sheet that opens when pendingReview is true
|
|
3
|
+
*
|
|
4
|
+
* Shows symbol coverage, tool activity, and approve/dismiss actions.
|
|
5
|
+
* Approve injects approve_phase() into the AI agent terminal.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CheckCircle2, X } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
import { Alert, Badge, Button, DescriptionList, Sheet, Stack, Text } from '@donotdev/components';
|
|
11
|
+
|
|
12
|
+
import { useDoNotDashStore } from '../../stores/dndevStore';
|
|
13
|
+
|
|
14
|
+
import type { ProtocolData } from './phaseData';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// PROPS
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
interface ReviewPanelProps {
|
|
21
|
+
protocol: ProtocolData;
|
|
22
|
+
open: boolean;
|
|
23
|
+
onOpenChange: (open: boolean) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// COMPONENT
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
export function ReviewPanel({ protocol, open, onOpenChange }: ReviewPanelProps) {
|
|
31
|
+
const symbols = protocol.lookedUpSymbols ?? [];
|
|
32
|
+
const toolCalls = protocol.toolCallCounts ?? {};
|
|
33
|
+
const toolEntries = Object.entries(toolCalls).filter(([, count]) => count > 0);
|
|
34
|
+
|
|
35
|
+
function handleApprove() {
|
|
36
|
+
useDoNotDashStore.getState().injectPrompt(
|
|
37
|
+
'Use the approve_phase MCP tool to approve the current phase',
|
|
38
|
+
{ mode: 'ai-agent' },
|
|
39
|
+
);
|
|
40
|
+
onOpenChange(false);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const descItems = [
|
|
44
|
+
{ label: 'Phase', value: `${protocol.currentPhase}: ${protocol.phaseName ?? 'Unknown'}` },
|
|
45
|
+
{ label: 'Agent', value: protocol.agent ?? 'Unknown' },
|
|
46
|
+
{ label: 'Symbols reviewed', value: String(symbols.length) },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
if (protocol.currentModule) {
|
|
50
|
+
descItems.push({ label: 'Module', value: protocol.currentModule });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (protocol.lessonsRecorded) {
|
|
54
|
+
descItems.push({ label: 'Lessons recorded', value: String(protocol.lessonsRecorded) });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Sheet
|
|
59
|
+
open={open}
|
|
60
|
+
onOpenChange={onOpenChange}
|
|
61
|
+
side="right"
|
|
62
|
+
title="Phase Review"
|
|
63
|
+
description="Review phase output before approving"
|
|
64
|
+
footer={
|
|
65
|
+
<Stack direction="row" gap="tight" justify="end">
|
|
66
|
+
<Button variant="outline" icon={X} onClick={() => onOpenChange(false)}>
|
|
67
|
+
Dismiss
|
|
68
|
+
</Button>
|
|
69
|
+
<Button variant="default" icon={CheckCircle2} onClick={handleApprove}>
|
|
70
|
+
Approve Phase
|
|
71
|
+
</Button>
|
|
72
|
+
</Stack>
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<Stack gap="tight">
|
|
76
|
+
<Alert
|
|
77
|
+
variant="info"
|
|
78
|
+
title="Phase submitted for review"
|
|
79
|
+
description="The AI agent has completed this phase and is waiting for your approval."
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
{/* Summary */}
|
|
83
|
+
<DescriptionList items={descItems} />
|
|
84
|
+
|
|
85
|
+
{/* Tool activity */}
|
|
86
|
+
{toolEntries.length > 0 && (
|
|
87
|
+
<Stack gap="tight">
|
|
88
|
+
<Text level="small" weight="semibold">Tool Activity</Text>
|
|
89
|
+
<DescriptionList
|
|
90
|
+
items={toolEntries.map(([name, count]) => ({
|
|
91
|
+
label: name.replace(/_/g, ' '),
|
|
92
|
+
value: String(count),
|
|
93
|
+
}))}
|
|
94
|
+
orientation="horizontal"
|
|
95
|
+
/>
|
|
96
|
+
</Stack>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{/* Symbol coverage */}
|
|
100
|
+
{symbols.length > 0 && (
|
|
101
|
+
<Stack gap="tight">
|
|
102
|
+
<Text level="small" weight="semibold">
|
|
103
|
+
Symbols Reviewed ({symbols.length})
|
|
104
|
+
</Text>
|
|
105
|
+
<Stack direction="row" gap="tight" style={{ flexWrap: 'wrap' }}>
|
|
106
|
+
{symbols.map((sym) => (
|
|
107
|
+
<Badge key={sym} variant="muted">{sym}</Badge>
|
|
108
|
+
))}
|
|
109
|
+
</Stack>
|
|
110
|
+
</Stack>
|
|
111
|
+
)}
|
|
112
|
+
</Stack>
|
|
113
|
+
</Sheet>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WAI-WAY phase constants, types, and shared parsers
|
|
3
|
+
*
|
|
4
|
+
* Shared across PhasesPage, HomePage, and phase sub-components.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LucideIcon } from 'lucide-react';
|
|
8
|
+
import { Lightbulb, FileCode, Database, Layers, Settings, CheckCircle2, Loader2, Circle } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// TYPES — Protocol v2
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
export interface PhaseData {
|
|
15
|
+
status?: 'pending' | 'active' | 'completed';
|
|
16
|
+
startedAt?: string;
|
|
17
|
+
completedAt?: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
fileCount?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ProtocolData {
|
|
23
|
+
version?: number;
|
|
24
|
+
currentPhase: number;
|
|
25
|
+
phaseName?: string;
|
|
26
|
+
agent?: string;
|
|
27
|
+
startedAt?: string;
|
|
28
|
+
phases: Record<number, PhaseData>;
|
|
29
|
+
lookedUpSymbols?: string[];
|
|
30
|
+
pendingReview?: boolean;
|
|
31
|
+
currentModule?: string;
|
|
32
|
+
toolCallCounts?: Record<string, number>;
|
|
33
|
+
completedPhases?: number[];
|
|
34
|
+
lessonsRecorded?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_PROTOCOL: ProtocolData = { currentPhase: -1, phases: {} };
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// TYPES — Progress
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
export interface ProgressItem {
|
|
44
|
+
text: string;
|
|
45
|
+
checked: boolean;
|
|
46
|
+
line: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ProgressSection {
|
|
50
|
+
title: string;
|
|
51
|
+
items: ProgressItem[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// TYPES — Lessons
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
export interface Lesson {
|
|
59
|
+
id: number;
|
|
60
|
+
content: string;
|
|
61
|
+
tags: string[];
|
|
62
|
+
confidence: number;
|
|
63
|
+
isAntiPattern: boolean;
|
|
64
|
+
addedAt: string | null;
|
|
65
|
+
phase: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// TYPES — Captain's Log
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
export interface CaptainLogSession {
|
|
73
|
+
id: number;
|
|
74
|
+
date: string;
|
|
75
|
+
phase: number;
|
|
76
|
+
phase_name: string;
|
|
77
|
+
module?: string;
|
|
78
|
+
started_at: string;
|
|
79
|
+
completed_at: string;
|
|
80
|
+
outcome: string;
|
|
81
|
+
files_touched: number;
|
|
82
|
+
symbols_used: number;
|
|
83
|
+
tool_calls: Record<string, number>;
|
|
84
|
+
lessons_recorded: number;
|
|
85
|
+
lessons_scored?: { helpful: number; harmful: number };
|
|
86
|
+
validation: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CaptainLogData {
|
|
90
|
+
project?: string;
|
|
91
|
+
started?: string;
|
|
92
|
+
sessions: CaptainLogSession[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// PHASE BLUEPRINTS
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export interface PhaseBlueprint {
|
|
100
|
+
id: number;
|
|
101
|
+
name: string;
|
|
102
|
+
agent: string;
|
|
103
|
+
icon: LucideIcon;
|
|
104
|
+
blueprint: string;
|
|
105
|
+
doneWhen: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const PHASE_BLUEPRINTS: PhaseBlueprint[] = [
|
|
109
|
+
{
|
|
110
|
+
id: 0,
|
|
111
|
+
name: 'Brainstorm',
|
|
112
|
+
agent: 'extractor',
|
|
113
|
+
icon: Lightbulb,
|
|
114
|
+
blueprint: 'Gather requirements, audit framework features, apply UX strategy (North Star, Kano), produce validated PRD/HLD/LLD.',
|
|
115
|
+
doneWhen: 'PRD.md, HLD.md, LLD.md validated. implementation.md generated with per-phase checklists.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 1,
|
|
119
|
+
name: 'Scaffold',
|
|
120
|
+
agent: 'extractor',
|
|
121
|
+
icon: FileCode,
|
|
122
|
+
blueprint: 'Create app, apply aesthetic essence, stub all pages as interactive prototype. Validate sitemap and click-depth.',
|
|
123
|
+
doneWhen: 'All pages exist with PageMeta. Navigation renders. Prototype clickable. App compiles clean.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 2,
|
|
127
|
+
name: 'Entities',
|
|
128
|
+
agent: 'architect',
|
|
129
|
+
icon: Database,
|
|
130
|
+
blueprint: 'Define all data models with defineEntity. Fields, states, access rules, visibility, relationships.',
|
|
131
|
+
doneWhen: 'All entities in entities/*.ts with index.ts barrel. Types compile. Relationships valid.',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 3,
|
|
135
|
+
name: 'Compose',
|
|
136
|
+
agent: 'builder',
|
|
137
|
+
icon: Layers,
|
|
138
|
+
blueprint: 'Build pages with framework components, wire CRUD, apply UX mandates (70/30 hierarchy, benefit-first copy).',
|
|
139
|
+
doneWhen: 'All pages functional with real data flow. CRUD wired. Strings hardcoded. No placeholders.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 4,
|
|
143
|
+
name: 'Configure',
|
|
144
|
+
agent: 'polisher',
|
|
145
|
+
icon: Settings,
|
|
146
|
+
blueprint: 'Generate tests, security rules, CI/CD. Contract check, heuristic audit, mobile validation.',
|
|
147
|
+
doneWhen: 'Tests pass. Security rules generated. CI green. Production-ready.',
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// STATUS ICONS
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
export function getPhaseStatus(
|
|
156
|
+
phaseId: number,
|
|
157
|
+
protocol: ProtocolData,
|
|
158
|
+
): 'pending' | 'active' | 'completed' {
|
|
159
|
+
const data = protocol.phases?.[phaseId];
|
|
160
|
+
if (data?.status) return data.status;
|
|
161
|
+
if (protocol.currentPhase === phaseId) return 'active';
|
|
162
|
+
if ((protocol.completedPhases ?? []).includes(phaseId)) return 'completed';
|
|
163
|
+
return 'pending';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getPhaseStatusIcon(status: string): LucideIcon {
|
|
167
|
+
switch (status) {
|
|
168
|
+
case 'completed': return CheckCircle2;
|
|
169
|
+
case 'active': return Loader2;
|
|
170
|
+
default: return Circle;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// PARSERS
|
|
176
|
+
// ============================================================================
|
|
177
|
+
|
|
178
|
+
/** Parse implementation.md into sections with checkbox items */
|
|
179
|
+
export function parseProgress(raw: string): ProgressSection[] {
|
|
180
|
+
const lines = raw.split('\n');
|
|
181
|
+
const sections: ProgressSection[] = [];
|
|
182
|
+
let current: ProgressSection | null = null;
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
const line = lines[i]!;
|
|
186
|
+
const headerMatch = line.match(/^###\s+(.+)/);
|
|
187
|
+
if (headerMatch) {
|
|
188
|
+
current = { title: headerMatch[1]!, items: [] };
|
|
189
|
+
sections.push(current);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const checkboxMatch = line.match(/^- \[([ x])\] (.+)/);
|
|
193
|
+
if (checkboxMatch && current) {
|
|
194
|
+
current.items.push({
|
|
195
|
+
text: checkboxMatch[2]!,
|
|
196
|
+
checked: checkboxMatch[1] === 'x',
|
|
197
|
+
line: i,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return sections;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Parse LESSONS.md into structured lesson objects */
|
|
206
|
+
export function parseLessons(raw: string): Lesson[] {
|
|
207
|
+
// Detect bullet-list format: lines starting with "- ["
|
|
208
|
+
const bulletLines = raw.split('\n').filter((l) => /^\s*-\s+\[/.test(l));
|
|
209
|
+
if (bulletLines.length > 0) return parseBulletLessons(bulletLines);
|
|
210
|
+
|
|
211
|
+
// Fallback: --- separated blocks
|
|
212
|
+
const lessons: Lesson[] = [];
|
|
213
|
+
const blocks = raw.split(/^---+$/m).filter((b) => b.trim());
|
|
214
|
+
|
|
215
|
+
if (blocks.length <= 1) {
|
|
216
|
+
// ### header blocks
|
|
217
|
+
const headerBlocks = raw.split(/^### /m).filter((b) => b.trim());
|
|
218
|
+
for (let i = 0; i < headerBlocks.length; i++) {
|
|
219
|
+
const block = headerBlocks[i]!;
|
|
220
|
+
const lines = block.split('\n');
|
|
221
|
+
const content = lines[0]?.trim() || '';
|
|
222
|
+
if (!content || /^#\s/.test(content)) continue;
|
|
223
|
+
|
|
224
|
+
const tags: string[] = [];
|
|
225
|
+
let confidence = 0.5;
|
|
226
|
+
let addedAt: string | null = null;
|
|
227
|
+
let phase: string | null = null;
|
|
228
|
+
let isAntiPattern = false;
|
|
229
|
+
|
|
230
|
+
for (const line of lines.slice(1)) {
|
|
231
|
+
const tagMatch = line.match(/^(?:Tags?|tags?):?\s*(.+)/);
|
|
232
|
+
if (tagMatch) {
|
|
233
|
+
tags.push(...tagMatch[1]!.split(/[,;]/).map((t) => t.trim().replace(/^[`#]|[`]$/g, '')).filter(Boolean));
|
|
234
|
+
}
|
|
235
|
+
const confMatch = line.match(/^(?:Confidence|confidence):?\s*([\d.]+)/);
|
|
236
|
+
if (confMatch) confidence = parseFloat(confMatch[1]!);
|
|
237
|
+
const dateMatch = line.match(/^(?:Added|added|Date):?\s*(.+)/);
|
|
238
|
+
if (dateMatch) addedAt = dateMatch[1]!.trim();
|
|
239
|
+
const phaseMatch = line.match(/^(?:Phase|phase):?\s*(.+)/);
|
|
240
|
+
if (phaseMatch) phase = phaseMatch[1]!.trim();
|
|
241
|
+
if (line.toLowerCase().includes('anti-pattern') || line.toLowerCase().includes('antipattern')) {
|
|
242
|
+
isAntiPattern = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
lessons.push({ id: i, content, tags: [...new Set(tags)], confidence, isAntiPattern, addedAt, phase });
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
250
|
+
const lines = blocks[i]!.trim().split('\n');
|
|
251
|
+
const content = lines.filter((l) => !l.match(/^(Tags?|Confidence|Added|Phase|Date):/i))[0]?.replace(/^#+\s*/, '').trim() || '';
|
|
252
|
+
if (!content) continue;
|
|
253
|
+
|
|
254
|
+
const tags: string[] = [];
|
|
255
|
+
let confidence = 0.5;
|
|
256
|
+
let addedAt: string | null = null;
|
|
257
|
+
let phase: string | null = null;
|
|
258
|
+
let isAntiPattern = false;
|
|
259
|
+
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
const tagMatch = line.match(/^(?:Tags?|tags?):?\s*(.+)/);
|
|
262
|
+
if (tagMatch) {
|
|
263
|
+
tags.push(...tagMatch[1]!.split(/[,;]/).map((t) => t.trim().replace(/^[`#]|[`]$/g, '')).filter(Boolean));
|
|
264
|
+
}
|
|
265
|
+
const confMatch = line.match(/^(?:Confidence|confidence):?\s*([\d.]+)/);
|
|
266
|
+
if (confMatch) confidence = parseFloat(confMatch[1]!);
|
|
267
|
+
const dateMatch = line.match(/^(?:Added|added|Date):?\s*(.+)/);
|
|
268
|
+
if (dateMatch) addedAt = dateMatch[1]!.trim();
|
|
269
|
+
const phaseMatch = line.match(/^(?:Phase|phase):?\s*(.+)/);
|
|
270
|
+
if (phaseMatch) phase = phaseMatch[1]!.trim();
|
|
271
|
+
if (line.toLowerCase().includes('anti-pattern')) isAntiPattern = true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
lessons.push({ id: i, content, tags: [...new Set(tags)], confidence, isAntiPattern, addedAt, phase });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return lessons;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Parse bullet-list format: `- [date] [Phase N: NAME][tag1, tag2] content {metadata}`
|
|
283
|
+
* This is the format produced by `record_lesson` MCP tool.
|
|
284
|
+
*/
|
|
285
|
+
function parseBulletLessons(lines: string[]): Lesson[] {
|
|
286
|
+
const lessons: Lesson[] = [];
|
|
287
|
+
|
|
288
|
+
for (let i = 0; i < lines.length; i++) {
|
|
289
|
+
const line = lines[i]!.replace(/^\s*-\s+/, '');
|
|
290
|
+
|
|
291
|
+
// Extract all bracketed segments from the front: [date] [Phase ...][tags]
|
|
292
|
+
let remaining = line;
|
|
293
|
+
let addedAt: string | null = null;
|
|
294
|
+
let phase: string | null = null;
|
|
295
|
+
const tags: string[] = [];
|
|
296
|
+
|
|
297
|
+
// Consume leading [...] groups
|
|
298
|
+
const bracketRe = /^\[([^\]]+)\]\s*/;
|
|
299
|
+
let match = bracketRe.exec(remaining);
|
|
300
|
+
while (match) {
|
|
301
|
+
const val = match[1]!;
|
|
302
|
+
|
|
303
|
+
if (!addedAt && /^\d/.test(val) && /\d{4}/.test(val)) {
|
|
304
|
+
// Date-like: starts with digit, contains a 4-digit year
|
|
305
|
+
addedAt = val;
|
|
306
|
+
} else if (!phase && /phase/i.test(val)) {
|
|
307
|
+
phase = val;
|
|
308
|
+
} else {
|
|
309
|
+
// Tags group — comma-separated
|
|
310
|
+
tags.push(...val.split(',').map((t) => t.trim()).filter(Boolean));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
remaining = remaining.slice(match[0].length);
|
|
314
|
+
match = bracketRe.exec(remaining);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Extract trailing {metadata} — e.g. {helpful:0,harmful:0,lastSeen:...,status:candidate}
|
|
318
|
+
let confidence = 0.5;
|
|
319
|
+
let isAntiPattern = false;
|
|
320
|
+
const metaMatch = remaining.match(/\{([^}]+)\}\s*$/);
|
|
321
|
+
if (metaMatch) {
|
|
322
|
+
remaining = remaining.slice(0, metaMatch.index).trim();
|
|
323
|
+
const meta = metaMatch[1]!;
|
|
324
|
+
|
|
325
|
+
const helpfulMatch = meta.match(/helpful:\s*(\d+)/);
|
|
326
|
+
const harmfulMatch = meta.match(/harmful:\s*(\d+)/);
|
|
327
|
+
if (helpfulMatch && harmfulMatch) {
|
|
328
|
+
const helpful = parseInt(helpfulMatch[1]!, 10);
|
|
329
|
+
const harmful = parseInt(harmfulMatch[1]!, 10);
|
|
330
|
+
const total = helpful + harmful;
|
|
331
|
+
confidence = total > 0 ? helpful / total : 0.5;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (meta.includes('anti-pattern') || meta.includes('antipattern')) {
|
|
335
|
+
isAntiPattern = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const content = remaining.trim();
|
|
340
|
+
if (!content) continue;
|
|
341
|
+
|
|
342
|
+
if (content.toLowerCase().includes('anti-pattern') || content.toLowerCase().includes('antipattern')) {
|
|
343
|
+
isAntiPattern = true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
lessons.push({ id: i, content, tags: [...new Set(tags)], confidence, isAntiPattern, addedAt, phase });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return lessons;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Calculate decay based on age (half-life: 90 days) */
|
|
353
|
+
export function getDecay(addedAt: string | null): number {
|
|
354
|
+
if (!addedAt) return 0;
|
|
355
|
+
const age = (Date.now() - new Date(addedAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
356
|
+
return 1 - Math.pow(0.5, age / 90);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Parse implementation.md into done/total ratio */
|
|
360
|
+
export function parseProgressRatio(md: string): { done: number; total: number } {
|
|
361
|
+
const doneMatches = md.match(/- \[x\]/gi);
|
|
362
|
+
const pendingMatches = md.match(/- \[ \]/g);
|
|
363
|
+
const done = doneMatches?.length ?? 0;
|
|
364
|
+
const total = done + (pendingMatches?.length ?? 0);
|
|
365
|
+
return { done, total };
|
|
366
|
+
}
|