@donotdev/cli 0.0.20 → 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.
Files changed (103) hide show
  1. package/README.md +31 -0
  2. package/dependencies-matrix.json +86 -19
  3. package/dist/bin/commands/agent-setup.js +2 -2
  4. package/dist/bin/commands/build.js +6 -6
  5. package/dist/bin/commands/bump.js +491 -69
  6. package/dist/bin/commands/cacheout.js +6 -6
  7. package/dist/bin/commands/coach.js +6 -6
  8. package/dist/bin/commands/create-app.js +23 -15
  9. package/dist/bin/commands/create-project.js +101 -16
  10. package/dist/bin/commands/db.js +142136 -0
  11. package/dist/bin/commands/deploy.js +336 -126
  12. package/dist/bin/commands/dev.js +6 -6
  13. package/dist/bin/commands/doctor.js +140 -33
  14. package/dist/bin/commands/emu.js +6 -6
  15. package/dist/bin/commands/format.js +6 -6
  16. package/dist/bin/commands/get-demo.js +11 -6
  17. package/dist/bin/commands/make-admin.js +14210 -13770
  18. package/dist/bin/commands/preview.js +6 -6
  19. package/dist/bin/commands/seed.js +142426 -0
  20. package/dist/bin/commands/setup-cicd.js +8904 -0
  21. package/dist/bin/commands/setup.js +256 -212
  22. package/dist/bin/commands/staging.js +343 -127
  23. package/dist/bin/commands/sync-secrets.js +55 -33
  24. package/dist/bin/commands/type-check.js +6 -6
  25. package/dist/bin/commands/wai.js +6 -6
  26. package/dist/bin/dndev.js +76 -11
  27. package/dist/bin/donotdev.js +21 -12
  28. package/dist/index.js +437 -142
  29. package/package.json +1 -1
  30. package/templates/app-demo/.env.example +1 -0
  31. package/templates/{root-consumer → app-demo}/entities/ExampleEntity.ts.example +15 -9
  32. package/templates/app-demo/index.html.example +1 -1
  33. package/templates/app-dndev/index.html.example +164 -0
  34. package/templates/app-dndev/public/logo.svg.example +1 -0
  35. package/templates/app-dndev/public/manifest.json.example +10 -0
  36. package/templates/app-dndev/src/App.tsx.example +35 -0
  37. package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
  38. package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
  39. package/templates/app-dndev/src/components/Kanban.css.example +385 -0
  40. package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
  41. package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
  42. package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
  43. package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
  44. package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
  45. package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
  46. package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
  47. package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
  48. package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
  49. package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
  50. package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
  51. package/templates/app-dndev/src/config/app.ts.example +103 -0
  52. package/templates/app-dndev/src/config/commands.ts.example +171 -0
  53. package/templates/app-dndev/src/config/legal.ts.example +170 -0
  54. package/templates/app-dndev/src/config/providers.ts.example +7 -0
  55. package/templates/app-dndev/src/globals.css.example +10 -0
  56. package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
  57. package/templates/app-dndev/src/main.tsx.example +21 -0
  58. package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
  59. package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
  60. package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
  61. package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
  62. package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
  63. package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
  64. package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
  65. package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
  66. package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
  67. package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
  68. package/templates/app-dndev/src/themes.css.example +161 -0
  69. package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
  70. package/templates/app-dndev/tsconfig.json.example +9 -0
  71. package/templates/app-dndev/vite.config.ts.example +24 -0
  72. package/templates/app-next/src/locales/home_en.json.example +6 -6
  73. package/templates/app-vite/index.html.example +1 -1
  74. package/templates/app-vite/src/locales/home_en.json.example +6 -6
  75. package/templates/functions-supabase/supabase/functions/.env.example +0 -2
  76. package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
  77. package/templates/root-consumer/.dndev.secrets.example +32 -0
  78. package/templates/root-consumer/.gitignore.example +3 -0
  79. package/templates/root-consumer/AI.md.example +4 -0
  80. package/templates/root-consumer/entities/index.ts.example +2 -5
  81. package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
  82. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
  83. package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
  84. package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
  85. package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
  86. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
  87. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
  88. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
  89. package/templates/root-consumer/guides/dndev/advanced/COOKIE_REFERENCE.md.example +252 -252
  90. package/templates/root-consumer/guides/dndev/advanced/VERSION_CONTROL.md.example +174 -174
  91. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
  92. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
  93. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
  94. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
  95. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
  96. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
  97. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
  98. package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
  99. package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
  100. package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
  101. package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
  102. /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
  103. /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
+ }