popeye-cli 1.0.0 → 1.1.0

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 (171) hide show
  1. package/README.md +521 -125
  2. package/dist/adapters/claude.d.ts +16 -4
  3. package/dist/adapters/claude.d.ts.map +1 -1
  4. package/dist/adapters/claude.js +679 -33
  5. package/dist/adapters/claude.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +55 -0
  7. package/dist/adapters/gemini.d.ts.map +1 -0
  8. package/dist/adapters/gemini.js +318 -0
  9. package/dist/adapters/gemini.js.map +1 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +41 -7
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/auth/claude.d.ts +11 -9
  14. package/dist/auth/claude.d.ts.map +1 -1
  15. package/dist/auth/claude.js +107 -71
  16. package/dist/auth/claude.js.map +1 -1
  17. package/dist/auth/gemini.d.ts +58 -0
  18. package/dist/auth/gemini.d.ts.map +1 -0
  19. package/dist/auth/gemini.js +172 -0
  20. package/dist/auth/gemini.js.map +1 -0
  21. package/dist/auth/index.d.ts +11 -7
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -5
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/keychain.d.ts +20 -7
  26. package/dist/auth/keychain.d.ts.map +1 -1
  27. package/dist/auth/keychain.js +85 -29
  28. package/dist/auth/keychain.js.map +1 -1
  29. package/dist/auth/openai.d.ts +2 -2
  30. package/dist/auth/openai.d.ts.map +1 -1
  31. package/dist/auth/openai.js +30 -32
  32. package/dist/auth/openai.js.map +1 -1
  33. package/dist/cli/index.d.ts.map +1 -1
  34. package/dist/cli/index.js +4 -7
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/interactive.d.ts +2 -2
  37. package/dist/cli/interactive.d.ts.map +1 -1
  38. package/dist/cli/interactive.js +1380 -183
  39. package/dist/cli/interactive.js.map +1 -1
  40. package/dist/config/defaults.d.ts +6 -1
  41. package/dist/config/defaults.d.ts.map +1 -1
  42. package/dist/config/defaults.js +10 -2
  43. package/dist/config/defaults.js.map +1 -1
  44. package/dist/config/index.d.ts +10 -0
  45. package/dist/config/index.d.ts.map +1 -1
  46. package/dist/config/index.js +19 -0
  47. package/dist/config/index.js.map +1 -1
  48. package/dist/config/schema.d.ts +20 -0
  49. package/dist/config/schema.d.ts.map +1 -1
  50. package/dist/config/schema.js +7 -0
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/generators/python.d.ts.map +1 -1
  53. package/dist/generators/python.js +1 -0
  54. package/dist/generators/python.js.map +1 -1
  55. package/dist/generators/typescript.d.ts.map +1 -1
  56. package/dist/generators/typescript.js +1 -0
  57. package/dist/generators/typescript.js.map +1 -1
  58. package/dist/state/index.d.ts +108 -0
  59. package/dist/state/index.d.ts.map +1 -1
  60. package/dist/state/index.js +551 -4
  61. package/dist/state/index.js.map +1 -1
  62. package/dist/state/registry.d.ts +52 -0
  63. package/dist/state/registry.d.ts.map +1 -0
  64. package/dist/state/registry.js +215 -0
  65. package/dist/state/registry.js.map +1 -0
  66. package/dist/types/cli.d.ts +4 -0
  67. package/dist/types/cli.d.ts.map +1 -1
  68. package/dist/types/cli.js.map +1 -1
  69. package/dist/types/consensus.d.ts +69 -4
  70. package/dist/types/consensus.d.ts.map +1 -1
  71. package/dist/types/consensus.js +24 -3
  72. package/dist/types/consensus.js.map +1 -1
  73. package/dist/types/workflow.d.ts +55 -0
  74. package/dist/types/workflow.d.ts.map +1 -1
  75. package/dist/types/workflow.js +16 -0
  76. package/dist/types/workflow.js.map +1 -1
  77. package/dist/workflow/auto-fix.d.ts +45 -0
  78. package/dist/workflow/auto-fix.d.ts.map +1 -0
  79. package/dist/workflow/auto-fix.js +274 -0
  80. package/dist/workflow/auto-fix.js.map +1 -0
  81. package/dist/workflow/consensus.d.ts +44 -2
  82. package/dist/workflow/consensus.d.ts.map +1 -1
  83. package/dist/workflow/consensus.js +565 -17
  84. package/dist/workflow/consensus.js.map +1 -1
  85. package/dist/workflow/execution-mode.d.ts +10 -4
  86. package/dist/workflow/execution-mode.d.ts.map +1 -1
  87. package/dist/workflow/execution-mode.js +547 -58
  88. package/dist/workflow/execution-mode.js.map +1 -1
  89. package/dist/workflow/index.d.ts +14 -2
  90. package/dist/workflow/index.d.ts.map +1 -1
  91. package/dist/workflow/index.js +69 -6
  92. package/dist/workflow/index.js.map +1 -1
  93. package/dist/workflow/milestone-workflow.d.ts +34 -0
  94. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  95. package/dist/workflow/milestone-workflow.js +414 -0
  96. package/dist/workflow/milestone-workflow.js.map +1 -0
  97. package/dist/workflow/plan-mode.d.ts +14 -1
  98. package/dist/workflow/plan-mode.d.ts.map +1 -1
  99. package/dist/workflow/plan-mode.js +589 -47
  100. package/dist/workflow/plan-mode.js.map +1 -1
  101. package/dist/workflow/plan-storage.d.ts +142 -0
  102. package/dist/workflow/plan-storage.d.ts.map +1 -0
  103. package/dist/workflow/plan-storage.js +331 -0
  104. package/dist/workflow/plan-storage.js.map +1 -0
  105. package/dist/workflow/project-verification.d.ts +37 -0
  106. package/dist/workflow/project-verification.d.ts.map +1 -0
  107. package/dist/workflow/project-verification.js +381 -0
  108. package/dist/workflow/project-verification.js.map +1 -0
  109. package/dist/workflow/task-workflow.d.ts +37 -0
  110. package/dist/workflow/task-workflow.d.ts.map +1 -0
  111. package/dist/workflow/task-workflow.js +383 -0
  112. package/dist/workflow/task-workflow.js.map +1 -0
  113. package/dist/workflow/test-runner.d.ts +1 -0
  114. package/dist/workflow/test-runner.d.ts.map +1 -1
  115. package/dist/workflow/test-runner.js +9 -5
  116. package/dist/workflow/test-runner.js.map +1 -1
  117. package/dist/workflow/ui-designer.d.ts +82 -0
  118. package/dist/workflow/ui-designer.d.ts.map +1 -0
  119. package/dist/workflow/ui-designer.js +234 -0
  120. package/dist/workflow/ui-designer.js.map +1 -0
  121. package/dist/workflow/ui-setup.d.ts +58 -0
  122. package/dist/workflow/ui-setup.d.ts.map +1 -0
  123. package/dist/workflow/ui-setup.js +685 -0
  124. package/dist/workflow/ui-setup.js.map +1 -0
  125. package/dist/workflow/ui-verification.d.ts +114 -0
  126. package/dist/workflow/ui-verification.d.ts.map +1 -0
  127. package/dist/workflow/ui-verification.js +258 -0
  128. package/dist/workflow/ui-verification.js.map +1 -0
  129. package/dist/workflow/workflow-logger.d.ts +110 -0
  130. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  131. package/dist/workflow/workflow-logger.js +267 -0
  132. package/dist/workflow/workflow-logger.js.map +1 -0
  133. package/package.json +2 -2
  134. package/src/adapters/claude.ts +815 -34
  135. package/src/adapters/gemini.ts +373 -0
  136. package/src/adapters/openai.ts +40 -7
  137. package/src/auth/claude.ts +120 -78
  138. package/src/auth/gemini.ts +207 -0
  139. package/src/auth/index.ts +28 -8
  140. package/src/auth/keychain.ts +95 -28
  141. package/src/auth/openai.ts +29 -36
  142. package/src/cli/index.ts +4 -7
  143. package/src/cli/interactive.ts +1641 -216
  144. package/src/config/defaults.ts +10 -2
  145. package/src/config/index.ts +21 -0
  146. package/src/config/schema.ts +7 -0
  147. package/src/generators/python.ts +1 -0
  148. package/src/generators/typescript.ts +1 -0
  149. package/src/state/index.ts +713 -4
  150. package/src/state/registry.ts +278 -0
  151. package/src/types/cli.ts +4 -0
  152. package/src/types/consensus.ts +65 -6
  153. package/src/types/workflow.ts +35 -0
  154. package/src/workflow/auto-fix.ts +340 -0
  155. package/src/workflow/consensus.ts +750 -16
  156. package/src/workflow/execution-mode.ts +673 -74
  157. package/src/workflow/index.ts +95 -6
  158. package/src/workflow/milestone-workflow.ts +576 -0
  159. package/src/workflow/plan-mode.ts +696 -50
  160. package/src/workflow/plan-storage.ts +482 -0
  161. package/src/workflow/project-verification.ts +471 -0
  162. package/src/workflow/task-workflow.ts +525 -0
  163. package/src/workflow/test-runner.ts +10 -5
  164. package/src/workflow/ui-designer.ts +337 -0
  165. package/src/workflow/ui-setup.ts +797 -0
  166. package/src/workflow/ui-verification.ts +357 -0
  167. package/src/workflow/workflow-logger.ts +353 -0
  168. package/tests/config/config.test.ts +1 -1
  169. package/tests/types/consensus.test.ts +3 -3
  170. package/tests/workflow/plan-mode.test.ts +213 -0
  171. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Plan Storage System
3
+ * Manages plans in markdown files to reduce API calls and maintain tracking
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ /**
10
+ * Feedback entry from a reviewer
11
+ */
12
+ export interface ReviewerFeedback {
13
+ reviewer: 'openai' | 'gemini' | 'claude';
14
+ score: number;
15
+ timestamp: string;
16
+ concerns: string[];
17
+ recommendations: string[];
18
+ analysis: string;
19
+ }
20
+
21
+ /**
22
+ * Plan metadata for tracking
23
+ */
24
+ export interface PlanMetadata {
25
+ id: string;
26
+ type: 'master' | 'milestone' | 'task';
27
+ milestoneId?: string;
28
+ milestoneName?: string;
29
+ taskId?: string;
30
+ taskName?: string;
31
+ version: number;
32
+ createdAt: string;
33
+ updatedAt: string;
34
+ consensusScore?: number;
35
+ status: 'draft' | 'reviewing' | 'approved' | 'implemented';
36
+ }
37
+
38
+ /**
39
+ * Stored plan with metadata
40
+ */
41
+ export interface StoredPlan {
42
+ metadata: PlanMetadata;
43
+ content: string;
44
+ feedback: ReviewerFeedback[];
45
+ revisionHistory: Array<{
46
+ version: number;
47
+ timestamp: string;
48
+ changes: string;
49
+ score?: number;
50
+ }>;
51
+ }
52
+
53
+ /**
54
+ * Plan Storage Manager
55
+ */
56
+ export class PlanStorage {
57
+ private projectDir: string;
58
+ private plansDir: string;
59
+
60
+ constructor(projectDir: string) {
61
+ this.projectDir = projectDir;
62
+ this.plansDir = path.join(projectDir, 'docs', 'plans');
63
+ }
64
+
65
+ /**
66
+ * Initialize the plans directory structure
67
+ */
68
+ async initialize(): Promise<void> {
69
+ await fs.mkdir(this.plansDir, { recursive: true });
70
+ }
71
+
72
+ /**
73
+ * Get the path for a plan file
74
+ */
75
+ private getPlanPath(
76
+ type: 'master' | 'milestone' | 'task',
77
+ milestoneId?: string,
78
+ taskId?: string
79
+ ): string {
80
+ if (type === 'master') {
81
+ return path.join(this.projectDir, 'docs', 'PLAN.md');
82
+ }
83
+
84
+ if (type === 'milestone' && milestoneId) {
85
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
86
+ return path.join(milestoneDir, 'plan.md');
87
+ }
88
+
89
+ if (type === 'task' && milestoneId && taskId) {
90
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
91
+ return path.join(milestoneDir, `task-${taskId}-plan.md`);
92
+ }
93
+
94
+ throw new Error(`Invalid plan type or missing IDs: ${type}`);
95
+ }
96
+
97
+ /**
98
+ * Get the path for feedback file
99
+ */
100
+ private getFeedbackPath(milestoneId: string, taskId?: string): string {
101
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
102
+ if (taskId) {
103
+ return path.join(milestoneDir, `task-${taskId}-feedback.md`);
104
+ }
105
+ return path.join(milestoneDir, 'feedback.md');
106
+ }
107
+
108
+ /**
109
+ * Get the path for metadata file
110
+ */
111
+ private getMetadataPath(milestoneId: string, taskId?: string): string {
112
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
113
+ if (taskId) {
114
+ return path.join(milestoneDir, `task-${taskId}-metadata.json`);
115
+ }
116
+ return path.join(milestoneDir, 'metadata.json');
117
+ }
118
+
119
+ /**
120
+ * Save a plan to file
121
+ */
122
+ async savePlan(
123
+ content: string,
124
+ type: 'master' | 'milestone' | 'task',
125
+ options: {
126
+ milestoneId?: string;
127
+ milestoneName?: string;
128
+ taskId?: string;
129
+ taskName?: string;
130
+ score?: number;
131
+ } = {}
132
+ ): Promise<string> {
133
+ const planPath = this.getPlanPath(type, options.milestoneId, options.taskId);
134
+
135
+ // Ensure directory exists
136
+ await fs.mkdir(path.dirname(planPath), { recursive: true });
137
+
138
+ // Add header with metadata
139
+ const header = this.generatePlanHeader(type, options);
140
+ const fullContent = `${header}\n\n${content}`;
141
+
142
+ await fs.writeFile(planPath, fullContent, 'utf-8');
143
+
144
+ // Save metadata separately for easy parsing
145
+ if (options.milestoneId) {
146
+ await this.saveMetadata(type, options);
147
+ }
148
+
149
+ return planPath;
150
+ }
151
+
152
+ /**
153
+ * Generate plan header with tracking info
154
+ */
155
+ private generatePlanHeader(
156
+ type: 'master' | 'milestone' | 'task',
157
+ options: {
158
+ milestoneId?: string;
159
+ milestoneName?: string;
160
+ taskId?: string;
161
+ taskName?: string;
162
+ score?: number;
163
+ }
164
+ ): string {
165
+ const lines: string[] = [];
166
+ lines.push('---');
167
+ lines.push(`type: ${type}`);
168
+ if (options.milestoneId) lines.push(`milestone_id: ${options.milestoneId}`);
169
+ if (options.milestoneName) lines.push(`milestone_name: ${options.milestoneName}`);
170
+ if (options.taskId) lines.push(`task_id: ${options.taskId}`);
171
+ if (options.taskName) lines.push(`task_name: ${options.taskName}`);
172
+ if (options.score !== undefined) lines.push(`consensus_score: ${options.score}`);
173
+ lines.push(`updated_at: ${new Date().toISOString()}`);
174
+ lines.push('---');
175
+ return lines.join('\n');
176
+ }
177
+
178
+ /**
179
+ * Save metadata to JSON file
180
+ */
181
+ private async saveMetadata(
182
+ type: 'master' | 'milestone' | 'task',
183
+ options: {
184
+ milestoneId?: string;
185
+ milestoneName?: string;
186
+ taskId?: string;
187
+ taskName?: string;
188
+ score?: number;
189
+ }
190
+ ): Promise<void> {
191
+ if (!options.milestoneId) return;
192
+
193
+ const metadataPath = this.getMetadataPath(options.milestoneId, options.taskId);
194
+
195
+ let metadata: PlanMetadata;
196
+ try {
197
+ const existing = await fs.readFile(metadataPath, 'utf-8');
198
+ metadata = JSON.parse(existing);
199
+ metadata.version += 1;
200
+ metadata.updatedAt = new Date().toISOString();
201
+ if (options.score !== undefined) metadata.consensusScore = options.score;
202
+ } catch {
203
+ metadata = {
204
+ id: options.taskId || options.milestoneId,
205
+ type,
206
+ milestoneId: options.milestoneId,
207
+ milestoneName: options.milestoneName,
208
+ taskId: options.taskId,
209
+ taskName: options.taskName,
210
+ version: 1,
211
+ createdAt: new Date().toISOString(),
212
+ updatedAt: new Date().toISOString(),
213
+ consensusScore: options.score,
214
+ status: 'draft',
215
+ };
216
+ }
217
+
218
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
219
+ }
220
+
221
+ /**
222
+ * Load a plan from file
223
+ */
224
+ async loadPlan(
225
+ type: 'master' | 'milestone' | 'task',
226
+ milestoneId?: string,
227
+ taskId?: string
228
+ ): Promise<string | null> {
229
+ try {
230
+ const planPath = this.getPlanPath(type, milestoneId, taskId);
231
+ const content = await fs.readFile(planPath, 'utf-8');
232
+
233
+ // Strip the header if present
234
+ const headerMatch = content.match(/^---[\s\S]*?---\n\n/);
235
+ if (headerMatch) {
236
+ return content.slice(headerMatch[0].length);
237
+ }
238
+ return content;
239
+ } catch {
240
+ return null;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Save feedback from a reviewer
246
+ */
247
+ async saveFeedback(
248
+ feedback: ReviewerFeedback,
249
+ milestoneId: string,
250
+ taskId?: string
251
+ ): Promise<void> {
252
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId);
253
+
254
+ // Ensure directory exists
255
+ await fs.mkdir(path.dirname(feedbackPath), { recursive: true });
256
+
257
+ // Load existing feedback
258
+ let existingFeedback: ReviewerFeedback[] = [];
259
+ try {
260
+ const content = await fs.readFile(feedbackPath.replace('.md', '.json'), 'utf-8');
261
+ existingFeedback = JSON.parse(content);
262
+ } catch {
263
+ // No existing feedback
264
+ }
265
+
266
+ // Add new feedback
267
+ existingFeedback.push(feedback);
268
+
269
+ // Save JSON for programmatic access
270
+ await fs.writeFile(
271
+ feedbackPath.replace('.md', '.json'),
272
+ JSON.stringify(existingFeedback, null, 2),
273
+ 'utf-8'
274
+ );
275
+
276
+ // Also save human-readable markdown
277
+ const mdContent = this.formatFeedbackAsMarkdown(existingFeedback);
278
+ await fs.writeFile(feedbackPath, mdContent, 'utf-8');
279
+ }
280
+
281
+ /**
282
+ * Load all feedback for a plan
283
+ */
284
+ async loadFeedback(milestoneId: string, taskId?: string): Promise<ReviewerFeedback[]> {
285
+ try {
286
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId).replace('.md', '.json');
287
+ const content = await fs.readFile(feedbackPath, 'utf-8');
288
+ return JSON.parse(content);
289
+ } catch {
290
+ return [];
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Clear feedback for a new consensus round
296
+ */
297
+ async clearFeedback(milestoneId: string, taskId?: string): Promise<void> {
298
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId);
299
+ try {
300
+ await fs.unlink(feedbackPath);
301
+ await fs.unlink(feedbackPath.replace('.md', '.json'));
302
+ } catch {
303
+ // Files don't exist, that's fine
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Format feedback as readable markdown
309
+ */
310
+ private formatFeedbackAsMarkdown(feedback: ReviewerFeedback[]): string {
311
+ const lines: string[] = [];
312
+ lines.push('# Reviewer Feedback\n');
313
+
314
+ for (const fb of feedback) {
315
+ lines.push(`## ${fb.reviewer.toUpperCase()} Review`);
316
+ lines.push(`- **Score:** ${fb.score}%`);
317
+ lines.push(`- **Timestamp:** ${fb.timestamp}`);
318
+ lines.push('');
319
+
320
+ if (fb.concerns.length > 0) {
321
+ lines.push('### Concerns');
322
+ for (const concern of fb.concerns) {
323
+ lines.push(`- ${concern}`);
324
+ }
325
+ lines.push('');
326
+ }
327
+
328
+ if (fb.recommendations.length > 0) {
329
+ lines.push('### Recommendations');
330
+ for (const rec of fb.recommendations) {
331
+ lines.push(`- ${rec}`);
332
+ }
333
+ lines.push('');
334
+ }
335
+
336
+ if (fb.analysis) {
337
+ lines.push('### Analysis');
338
+ lines.push(fb.analysis);
339
+ lines.push('');
340
+ }
341
+
342
+ lines.push('---\n');
343
+ }
344
+
345
+ return lines.join('\n');
346
+ }
347
+
348
+ /**
349
+ * Get combined feedback summary for revision
350
+ */
351
+ async getCombinedFeedbackForRevision(
352
+ milestoneId: string,
353
+ taskId?: string
354
+ ): Promise<{
355
+ averageScore: number;
356
+ allConcerns: string[];
357
+ allRecommendations: string[];
358
+ combinedAnalysis: string;
359
+ }> {
360
+ const feedback = await this.loadFeedback(milestoneId, taskId);
361
+
362
+ if (feedback.length === 0) {
363
+ return {
364
+ averageScore: 0,
365
+ allConcerns: [],
366
+ allRecommendations: [],
367
+ combinedAnalysis: '',
368
+ };
369
+ }
370
+
371
+ const averageScore = feedback.reduce((sum, f) => sum + f.score, 0) / feedback.length;
372
+
373
+ // Deduplicate concerns and recommendations
374
+ const allConcerns = [...new Set(feedback.flatMap(f => f.concerns))];
375
+ const allRecommendations = [...new Set(feedback.flatMap(f => f.recommendations))];
376
+
377
+ // Combine analysis
378
+ const combinedAnalysis = feedback
379
+ .map(f => `### ${f.reviewer.toUpperCase()} (${f.score}%)\n${f.analysis}`)
380
+ .join('\n\n');
381
+
382
+ return {
383
+ averageScore,
384
+ allConcerns,
385
+ allRecommendations,
386
+ combinedAnalysis,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Update plan status
392
+ */
393
+ async updateStatus(
394
+ status: 'draft' | 'reviewing' | 'approved' | 'implemented',
395
+ milestoneId: string,
396
+ taskId?: string
397
+ ): Promise<void> {
398
+ const metadataPath = this.getMetadataPath(milestoneId, taskId);
399
+
400
+ try {
401
+ const content = await fs.readFile(metadataPath, 'utf-8');
402
+ const metadata: PlanMetadata = JSON.parse(content);
403
+ metadata.status = status;
404
+ metadata.updatedAt = new Date().toISOString();
405
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
406
+ } catch {
407
+ // Metadata doesn't exist yet
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Get plan tracking summary for a milestone
413
+ */
414
+ async getMilestoneTrackingSummary(milestoneId: string): Promise<{
415
+ milestonePlan: { exists: boolean; score?: number; status?: string };
416
+ taskPlans: Array<{
417
+ taskId: string;
418
+ taskName?: string;
419
+ exists: boolean;
420
+ score?: number;
421
+ status?: string;
422
+ }>;
423
+ }> {
424
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
425
+
426
+ // Check milestone plan
427
+ let milestonePlan: { exists: boolean; score?: number; status?: string } = { exists: false };
428
+ try {
429
+ const metadataPath = path.join(milestoneDir, 'metadata.json');
430
+ const content = await fs.readFile(metadataPath, 'utf-8');
431
+ const metadata: PlanMetadata = JSON.parse(content);
432
+ milestonePlan = {
433
+ exists: true,
434
+ score: metadata.consensusScore,
435
+ status: metadata.status,
436
+ };
437
+ } catch {
438
+ // No milestone plan
439
+ }
440
+
441
+ // Find task plans
442
+ const taskPlans: Array<{
443
+ taskId: string;
444
+ taskName?: string;
445
+ exists: boolean;
446
+ score?: number;
447
+ status?: string;
448
+ }> = [];
449
+
450
+ try {
451
+ const files = await fs.readdir(milestoneDir);
452
+ const taskMetadataFiles = files.filter(f => f.startsWith('task-') && f.endsWith('-metadata.json'));
453
+
454
+ for (const file of taskMetadataFiles) {
455
+ try {
456
+ const content = await fs.readFile(path.join(milestoneDir, file), 'utf-8');
457
+ const metadata: PlanMetadata = JSON.parse(content);
458
+ taskPlans.push({
459
+ taskId: metadata.taskId || '',
460
+ taskName: metadata.taskName,
461
+ exists: true,
462
+ score: metadata.consensusScore,
463
+ status: metadata.status,
464
+ });
465
+ } catch {
466
+ // Skip invalid files
467
+ }
468
+ }
469
+ } catch {
470
+ // Directory doesn't exist
471
+ }
472
+
473
+ return { milestonePlan, taskPlans };
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Create a plan storage instance for a project
479
+ */
480
+ export function createPlanStorage(projectDir: string): PlanStorage {
481
+ return new PlanStorage(projectDir);
482
+ }