popeye-cli 1.0.1 → 1.2.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 (216) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +832 -123
  5. package/dist/adapters/claude.d.ts +19 -4
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +908 -42
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/gemini.d.ts +55 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +318 -0
  12. package/dist/adapters/gemini.js.map +1 -0
  13. package/dist/adapters/grok.d.ts +73 -0
  14. package/dist/adapters/grok.d.ts.map +1 -0
  15. package/dist/adapters/grok.js +430 -0
  16. package/dist/adapters/grok.js.map +1 -0
  17. package/dist/adapters/openai.d.ts +1 -1
  18. package/dist/adapters/openai.d.ts.map +1 -1
  19. package/dist/adapters/openai.js +47 -8
  20. package/dist/adapters/openai.js.map +1 -1
  21. package/dist/auth/claude.d.ts +11 -9
  22. package/dist/auth/claude.d.ts.map +1 -1
  23. package/dist/auth/claude.js +107 -71
  24. package/dist/auth/claude.js.map +1 -1
  25. package/dist/auth/gemini.d.ts +58 -0
  26. package/dist/auth/gemini.d.ts.map +1 -0
  27. package/dist/auth/gemini.js +172 -0
  28. package/dist/auth/gemini.js.map +1 -0
  29. package/dist/auth/grok.d.ts +73 -0
  30. package/dist/auth/grok.d.ts.map +1 -0
  31. package/dist/auth/grok.js +211 -0
  32. package/dist/auth/grok.js.map +1 -0
  33. package/dist/auth/index.d.ts +14 -7
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +41 -6
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/keychain.d.ts +20 -7
  38. package/dist/auth/keychain.d.ts.map +1 -1
  39. package/dist/auth/keychain.js +85 -29
  40. package/dist/auth/keychain.js.map +1 -1
  41. package/dist/auth/openai.d.ts +2 -2
  42. package/dist/auth/openai.d.ts.map +1 -1
  43. package/dist/auth/openai.js +30 -32
  44. package/dist/auth/openai.js.map +1 -1
  45. package/dist/cli/commands/auth.d.ts +1 -1
  46. package/dist/cli/commands/auth.d.ts.map +1 -1
  47. package/dist/cli/commands/auth.js +79 -8
  48. package/dist/cli/commands/auth.js.map +1 -1
  49. package/dist/cli/commands/create.d.ts.map +1 -1
  50. package/dist/cli/commands/create.js +15 -4
  51. package/dist/cli/commands/create.js.map +1 -1
  52. package/dist/cli/interactive.d.ts.map +1 -1
  53. package/dist/cli/interactive.js +1494 -114
  54. package/dist/cli/interactive.js.map +1 -1
  55. package/dist/config/defaults.d.ts +9 -1
  56. package/dist/config/defaults.d.ts.map +1 -1
  57. package/dist/config/defaults.js +19 -2
  58. package/dist/config/defaults.js.map +1 -1
  59. package/dist/config/index.d.ts +19 -0
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +33 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/schema.d.ts +47 -0
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +29 -1
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/generators/fullstack.d.ts +32 -0
  68. package/dist/generators/fullstack.d.ts.map +1 -0
  69. package/dist/generators/fullstack.js +497 -0
  70. package/dist/generators/fullstack.js.map +1 -0
  71. package/dist/generators/index.d.ts +4 -3
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +15 -1
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/python.d.ts +17 -1
  76. package/dist/generators/python.d.ts.map +1 -1
  77. package/dist/generators/python.js +34 -20
  78. package/dist/generators/python.js.map +1 -1
  79. package/dist/generators/templates/fullstack.d.ts +113 -0
  80. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  81. package/dist/generators/templates/fullstack.js +1004 -0
  82. package/dist/generators/templates/fullstack.js.map +1 -0
  83. package/dist/generators/typescript.d.ts +19 -1
  84. package/dist/generators/typescript.d.ts.map +1 -1
  85. package/dist/generators/typescript.js +37 -20
  86. package/dist/generators/typescript.js.map +1 -1
  87. package/dist/state/index.d.ts +108 -0
  88. package/dist/state/index.d.ts.map +1 -1
  89. package/dist/state/index.js +551 -4
  90. package/dist/state/index.js.map +1 -1
  91. package/dist/state/registry.d.ts +52 -0
  92. package/dist/state/registry.d.ts.map +1 -0
  93. package/dist/state/registry.js +215 -0
  94. package/dist/state/registry.js.map +1 -0
  95. package/dist/types/cli.d.ts +8 -0
  96. package/dist/types/cli.d.ts.map +1 -1
  97. package/dist/types/cli.js.map +1 -1
  98. package/dist/types/consensus.d.ts +186 -4
  99. package/dist/types/consensus.d.ts.map +1 -1
  100. package/dist/types/consensus.js +35 -3
  101. package/dist/types/consensus.js.map +1 -1
  102. package/dist/types/project.d.ts +76 -0
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +1 -1
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +217 -16
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +40 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/workflow/auto-fix.d.ts +45 -0
  111. package/dist/workflow/auto-fix.d.ts.map +1 -0
  112. package/dist/workflow/auto-fix.js +274 -0
  113. package/dist/workflow/auto-fix.js.map +1 -0
  114. package/dist/workflow/consensus.d.ts +70 -2
  115. package/dist/workflow/consensus.d.ts.map +1 -1
  116. package/dist/workflow/consensus.js +872 -17
  117. package/dist/workflow/consensus.js.map +1 -1
  118. package/dist/workflow/execution-mode.d.ts +10 -4
  119. package/dist/workflow/execution-mode.d.ts.map +1 -1
  120. package/dist/workflow/execution-mode.js +547 -58
  121. package/dist/workflow/execution-mode.js.map +1 -1
  122. package/dist/workflow/index.d.ts +14 -2
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +69 -6
  125. package/dist/workflow/index.js.map +1 -1
  126. package/dist/workflow/milestone-workflow.d.ts +34 -0
  127. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  128. package/dist/workflow/milestone-workflow.js +414 -0
  129. package/dist/workflow/milestone-workflow.js.map +1 -0
  130. package/dist/workflow/plan-mode.d.ts +80 -3
  131. package/dist/workflow/plan-mode.d.ts.map +1 -1
  132. package/dist/workflow/plan-mode.js +767 -49
  133. package/dist/workflow/plan-mode.js.map +1 -1
  134. package/dist/workflow/plan-storage.d.ts +386 -0
  135. package/dist/workflow/plan-storage.d.ts.map +1 -0
  136. package/dist/workflow/plan-storage.js +878 -0
  137. package/dist/workflow/plan-storage.js.map +1 -0
  138. package/dist/workflow/project-verification.d.ts +37 -0
  139. package/dist/workflow/project-verification.d.ts.map +1 -0
  140. package/dist/workflow/project-verification.js +381 -0
  141. package/dist/workflow/project-verification.js.map +1 -0
  142. package/dist/workflow/task-workflow.d.ts +37 -0
  143. package/dist/workflow/task-workflow.d.ts.map +1 -0
  144. package/dist/workflow/task-workflow.js +386 -0
  145. package/dist/workflow/task-workflow.js.map +1 -0
  146. package/dist/workflow/test-runner.d.ts +9 -0
  147. package/dist/workflow/test-runner.d.ts.map +1 -1
  148. package/dist/workflow/test-runner.js +101 -5
  149. package/dist/workflow/test-runner.js.map +1 -1
  150. package/dist/workflow/ui-designer.d.ts +82 -0
  151. package/dist/workflow/ui-designer.d.ts.map +1 -0
  152. package/dist/workflow/ui-designer.js +234 -0
  153. package/dist/workflow/ui-designer.js.map +1 -0
  154. package/dist/workflow/ui-setup.d.ts +58 -0
  155. package/dist/workflow/ui-setup.d.ts.map +1 -0
  156. package/dist/workflow/ui-setup.js +685 -0
  157. package/dist/workflow/ui-setup.js.map +1 -0
  158. package/dist/workflow/ui-verification.d.ts +114 -0
  159. package/dist/workflow/ui-verification.d.ts.map +1 -0
  160. package/dist/workflow/ui-verification.js +258 -0
  161. package/dist/workflow/ui-verification.js.map +1 -0
  162. package/dist/workflow/workflow-logger.d.ts +110 -0
  163. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  164. package/dist/workflow/workflow-logger.js +267 -0
  165. package/dist/workflow/workflow-logger.js.map +1 -0
  166. package/dist/workflow/workspace-manager.d.ts +342 -0
  167. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  168. package/dist/workflow/workspace-manager.js +733 -0
  169. package/dist/workflow/workspace-manager.js.map +1 -0
  170. package/package.json +2 -2
  171. package/src/adapters/claude.ts +1067 -47
  172. package/src/adapters/gemini.ts +373 -0
  173. package/src/adapters/grok.ts +492 -0
  174. package/src/adapters/openai.ts +48 -9
  175. package/src/auth/claude.ts +120 -78
  176. package/src/auth/gemini.ts +207 -0
  177. package/src/auth/grok.ts +255 -0
  178. package/src/auth/index.ts +47 -9
  179. package/src/auth/keychain.ts +95 -28
  180. package/src/auth/openai.ts +29 -36
  181. package/src/cli/commands/auth.ts +89 -10
  182. package/src/cli/commands/create.ts +13 -4
  183. package/src/cli/interactive.ts +1774 -142
  184. package/src/config/defaults.ts +19 -2
  185. package/src/config/index.ts +36 -1
  186. package/src/config/schema.ts +30 -1
  187. package/src/generators/fullstack.ts +551 -0
  188. package/src/generators/index.ts +25 -1
  189. package/src/generators/python.ts +65 -20
  190. package/src/generators/templates/fullstack.ts +1047 -0
  191. package/src/generators/typescript.ts +69 -20
  192. package/src/state/index.ts +713 -4
  193. package/src/state/registry.ts +278 -0
  194. package/src/types/cli.ts +8 -0
  195. package/src/types/consensus.ts +197 -6
  196. package/src/types/project.ts +82 -1
  197. package/src/types/workflow.ts +90 -1
  198. package/src/workflow/auto-fix.ts +340 -0
  199. package/src/workflow/consensus.ts +1180 -16
  200. package/src/workflow/execution-mode.ts +673 -74
  201. package/src/workflow/index.ts +95 -6
  202. package/src/workflow/milestone-workflow.ts +576 -0
  203. package/src/workflow/plan-mode.ts +924 -50
  204. package/src/workflow/plan-storage.ts +1282 -0
  205. package/src/workflow/project-verification.ts +471 -0
  206. package/src/workflow/task-workflow.ts +528 -0
  207. package/src/workflow/test-runner.ts +120 -5
  208. package/src/workflow/ui-designer.ts +337 -0
  209. package/src/workflow/ui-setup.ts +797 -0
  210. package/src/workflow/ui-verification.ts +357 -0
  211. package/src/workflow/workflow-logger.ts +353 -0
  212. package/src/workflow/workspace-manager.ts +912 -0
  213. package/tests/config/config.test.ts +1 -1
  214. package/tests/types/consensus.test.ts +3 -3
  215. package/tests/workflow/plan-mode.test.ts +213 -0
  216. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,878 @@
1
+ /**
2
+ * Plan Storage System
3
+ * Manages plans in markdown files to reduce API calls and maintain tracking
4
+ *
5
+ * Directory Structure for Fullstack Projects:
6
+ * docs/plans/
7
+ * ├── master/
8
+ * │ ├── plan.md
9
+ * │ ├── metadata.json
10
+ * │ ├── unified/
11
+ * │ │ ├── feedback.json
12
+ * │ │ └── feedback.md
13
+ * │ ├── frontend/
14
+ * │ │ ├── feedback.json
15
+ * │ │ └── feedback.md
16
+ * │ └── backend/
17
+ * │ ├── feedback.json
18
+ * │ └── feedback.md
19
+ * ├── milestone-1/
20
+ * │ ├── plan.md
21
+ * │ ├── metadata.json
22
+ * │ ├── unified/
23
+ * │ ├── frontend/
24
+ * │ ├── backend/
25
+ * │ └── tasks/
26
+ * │ └── task-1/
27
+ * │ ├── plan.md
28
+ * │ ├── metadata.json
29
+ * │ ├── unified/
30
+ * │ ├── frontend/
31
+ * │ └── backend/
32
+ */
33
+ import { promises as fs } from 'node:fs';
34
+ import path from 'node:path';
35
+ /**
36
+ * Plan Storage Manager
37
+ */
38
+ export class PlanStorage {
39
+ projectDir;
40
+ plansDir;
41
+ isFullstack;
42
+ constructor(projectDir, isFullstack = false) {
43
+ this.projectDir = projectDir;
44
+ this.plansDir = path.join(projectDir, 'docs', 'plans');
45
+ this.isFullstack = isFullstack;
46
+ }
47
+ /**
48
+ * Set fullstack mode
49
+ */
50
+ setFullstack(isFullstack) {
51
+ this.isFullstack = isFullstack;
52
+ }
53
+ /**
54
+ * Initialize the plans directory structure
55
+ */
56
+ async initialize() {
57
+ await fs.mkdir(this.plansDir, { recursive: true });
58
+ // Create master directory with app subdirectories for fullstack
59
+ if (this.isFullstack) {
60
+ await this.initializeAppDirectories(path.join(this.plansDir, 'master'));
61
+ }
62
+ }
63
+ /**
64
+ * Initialize app subdirectories (frontend/backend/unified)
65
+ */
66
+ async initializeAppDirectories(baseDir) {
67
+ await fs.mkdir(baseDir, { recursive: true });
68
+ await fs.mkdir(path.join(baseDir, 'unified'), { recursive: true });
69
+ await fs.mkdir(path.join(baseDir, 'frontend'), { recursive: true });
70
+ await fs.mkdir(path.join(baseDir, 'backend'), { recursive: true });
71
+ }
72
+ /**
73
+ * Get the path for a plan file
74
+ *
75
+ * New structure for fullstack:
76
+ * - master: docs/plans/master/plan.md
77
+ * - milestone: docs/plans/milestone-N/plan.md
78
+ * - task: docs/plans/milestone-N/tasks/task-N/plan.md
79
+ */
80
+ getPlanPath(type, milestoneId, taskId) {
81
+ if (type === 'master') {
82
+ if (this.isFullstack) {
83
+ return path.join(this.plansDir, 'master', 'plan.md');
84
+ }
85
+ return path.join(this.projectDir, 'docs', 'PLAN.md');
86
+ }
87
+ if (type === 'milestone' && milestoneId) {
88
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
89
+ return path.join(milestoneDir, 'plan.md');
90
+ }
91
+ if (type === 'task' && milestoneId && taskId) {
92
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
93
+ if (this.isFullstack) {
94
+ return path.join(milestoneDir, 'tasks', `task-${taskId}`, 'plan.md');
95
+ }
96
+ return path.join(milestoneDir, `task-${taskId}-plan.md`);
97
+ }
98
+ throw new Error(`Invalid plan type or missing IDs: ${type}`);
99
+ }
100
+ /**
101
+ * Get the base directory for a plan level
102
+ */
103
+ getPlanBaseDir(type, milestoneId, taskId) {
104
+ if (type === 'master') {
105
+ return path.join(this.plansDir, 'master');
106
+ }
107
+ if (type === 'milestone' && milestoneId) {
108
+ return path.join(this.plansDir, `milestone-${milestoneId}`);
109
+ }
110
+ if (type === 'task' && milestoneId && taskId) {
111
+ return path.join(this.plansDir, `milestone-${milestoneId}`, 'tasks', `task-${taskId}`);
112
+ }
113
+ throw new Error(`Invalid plan type or missing IDs: ${type}`);
114
+ }
115
+ /**
116
+ * Get the path for feedback file
117
+ *
118
+ * For fullstack projects, feedback is stored per-app:
119
+ * - unified/feedback.md, frontend/feedback.md, backend/feedback.md
120
+ */
121
+ getFeedbackPath(milestoneId, taskId, appTarget) {
122
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
123
+ if (this.isFullstack && appTarget) {
124
+ if (taskId) {
125
+ return path.join(milestoneDir, 'tasks', `task-${taskId}`, appTarget, 'feedback.md');
126
+ }
127
+ return path.join(milestoneDir, appTarget, 'feedback.md');
128
+ }
129
+ // Legacy non-fullstack path
130
+ if (taskId) {
131
+ return path.join(milestoneDir, `task-${taskId}-feedback.md`);
132
+ }
133
+ return path.join(milestoneDir, 'feedback.md');
134
+ }
135
+ /**
136
+ * Get feedback path for master plan
137
+ */
138
+ getMasterFeedbackPath(appTarget) {
139
+ if (this.isFullstack && appTarget) {
140
+ return path.join(this.plansDir, 'master', appTarget, 'feedback.md');
141
+ }
142
+ return path.join(this.plansDir, 'master', 'feedback.md');
143
+ }
144
+ /**
145
+ * Get the path for metadata file
146
+ */
147
+ getMetadataPath(type, milestoneId, taskId) {
148
+ if (type === 'master') {
149
+ return path.join(this.plansDir, 'master', 'metadata.json');
150
+ }
151
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
152
+ if (taskId) {
153
+ if (this.isFullstack) {
154
+ return path.join(milestoneDir, 'tasks', `task-${taskId}`, 'metadata.json');
155
+ }
156
+ return path.join(milestoneDir, `task-${taskId}-metadata.json`);
157
+ }
158
+ return path.join(milestoneDir, 'metadata.json');
159
+ }
160
+ /**
161
+ * Save a plan to file
162
+ */
163
+ async savePlan(content, type, options = {}) {
164
+ const planPath = this.getPlanPath(type, options.milestoneId, options.taskId);
165
+ // Ensure directory exists
166
+ await fs.mkdir(path.dirname(planPath), { recursive: true });
167
+ // For fullstack projects, also create app subdirectories
168
+ if (this.isFullstack) {
169
+ const baseDir = this.getPlanBaseDir(type, options.milestoneId, options.taskId);
170
+ await this.initializeAppDirectories(baseDir);
171
+ }
172
+ // Add header with metadata
173
+ const header = this.generatePlanHeader(type, options);
174
+ const fullContent = `${header}\n\n${content}`;
175
+ await fs.writeFile(planPath, fullContent, 'utf-8');
176
+ // Save metadata separately for easy parsing
177
+ await this.saveMetadata(type, options);
178
+ return planPath;
179
+ }
180
+ /**
181
+ * Generate plan header with tracking info
182
+ */
183
+ generatePlanHeader(type, options) {
184
+ const lines = [];
185
+ lines.push('---');
186
+ lines.push(`type: ${type}`);
187
+ if (options.milestoneId)
188
+ lines.push(`milestone_id: ${options.milestoneId}`);
189
+ if (options.milestoneName)
190
+ lines.push(`milestone_name: ${options.milestoneName}`);
191
+ if (options.taskId)
192
+ lines.push(`task_id: ${options.taskId}`);
193
+ if (options.taskName)
194
+ lines.push(`task_name: ${options.taskName}`);
195
+ if (options.score !== undefined)
196
+ lines.push(`consensus_score: ${options.score}`);
197
+ // Fullstack-specific scores
198
+ if (this.isFullstack) {
199
+ lines.push(`is_fullstack: true`);
200
+ if (options.frontendScore !== undefined)
201
+ lines.push(`frontend_score: ${options.frontendScore}`);
202
+ if (options.backendScore !== undefined)
203
+ lines.push(`backend_score: ${options.backendScore}`);
204
+ if (options.unifiedScore !== undefined)
205
+ lines.push(`unified_score: ${options.unifiedScore}`);
206
+ }
207
+ lines.push(`updated_at: ${new Date().toISOString()}`);
208
+ lines.push('---');
209
+ return lines.join('\n');
210
+ }
211
+ /**
212
+ * Save metadata to JSON file
213
+ */
214
+ async saveMetadata(type, options) {
215
+ const metadataPath = this.getMetadataPath(type, options.milestoneId, options.taskId);
216
+ // Ensure directory exists
217
+ await fs.mkdir(path.dirname(metadataPath), { recursive: true });
218
+ let metadata;
219
+ try {
220
+ const existing = await fs.readFile(metadataPath, 'utf-8');
221
+ metadata = JSON.parse(existing);
222
+ metadata.version += 1;
223
+ metadata.updatedAt = new Date().toISOString();
224
+ if (options.score !== undefined)
225
+ metadata.consensusScore = options.score;
226
+ // Update fullstack scores
227
+ if (this.isFullstack) {
228
+ if (options.frontendScore !== undefined)
229
+ metadata.frontendScore = options.frontendScore;
230
+ if (options.backendScore !== undefined)
231
+ metadata.backendScore = options.backendScore;
232
+ if (options.unifiedScore !== undefined)
233
+ metadata.unifiedScore = options.unifiedScore;
234
+ }
235
+ }
236
+ catch {
237
+ metadata = {
238
+ id: options.taskId || options.milestoneId || 'master',
239
+ type,
240
+ milestoneId: options.milestoneId,
241
+ milestoneName: options.milestoneName,
242
+ taskId: options.taskId,
243
+ taskName: options.taskName,
244
+ version: 1,
245
+ createdAt: new Date().toISOString(),
246
+ updatedAt: new Date().toISOString(),
247
+ consensusScore: options.score,
248
+ status: 'draft',
249
+ isFullstack: this.isFullstack,
250
+ frontendScore: options.frontendScore,
251
+ backendScore: options.backendScore,
252
+ unifiedScore: options.unifiedScore,
253
+ totalIterations: 0,
254
+ corrections: [],
255
+ };
256
+ }
257
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
258
+ }
259
+ /**
260
+ * Load a plan from file
261
+ */
262
+ async loadPlan(type, milestoneId, taskId) {
263
+ try {
264
+ const planPath = this.getPlanPath(type, milestoneId, taskId);
265
+ const content = await fs.readFile(planPath, 'utf-8');
266
+ // Strip the header if present
267
+ const headerMatch = content.match(/^---[\s\S]*?---\n\n/);
268
+ if (headerMatch) {
269
+ return content.slice(headerMatch[0].length);
270
+ }
271
+ return content;
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
277
+ /**
278
+ * Save feedback from a reviewer
279
+ *
280
+ * For fullstack projects, appTarget determines which subdirectory:
281
+ * - 'frontend': milestone-N/frontend/feedback.json
282
+ * - 'backend': milestone-N/backend/feedback.json
283
+ * - 'unified': milestone-N/unified/feedback.json
284
+ */
285
+ async saveFeedback(feedback, milestoneId, taskId, appTarget) {
286
+ const effectiveAppTarget = this.isFullstack ? (appTarget || 'unified') : undefined;
287
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId, effectiveAppTarget);
288
+ // Ensure directory exists
289
+ await fs.mkdir(path.dirname(feedbackPath), { recursive: true });
290
+ // Load existing feedback
291
+ let existingFeedback = [];
292
+ try {
293
+ const content = await fs.readFile(feedbackPath.replace('.md', '.json'), 'utf-8');
294
+ existingFeedback = JSON.parse(content);
295
+ }
296
+ catch {
297
+ // No existing feedback
298
+ }
299
+ // Tag feedback with app target
300
+ const taggedFeedback = {
301
+ ...feedback,
302
+ appTarget: effectiveAppTarget,
303
+ };
304
+ // Add new feedback
305
+ existingFeedback.push(taggedFeedback);
306
+ // Save JSON for programmatic access
307
+ await fs.writeFile(feedbackPath.replace('.md', '.json'), JSON.stringify(existingFeedback, null, 2), 'utf-8');
308
+ // Also save human-readable markdown
309
+ const mdContent = this.formatFeedbackAsMarkdown(existingFeedback, effectiveAppTarget);
310
+ await fs.writeFile(feedbackPath, mdContent, 'utf-8');
311
+ }
312
+ /**
313
+ * Save fullstack feedback with per-app breakdown
314
+ *
315
+ * Saves feedback to all three directories (unified, frontend, backend)
316
+ */
317
+ async saveFullstackFeedback(feedback, type, milestoneId, taskId) {
318
+ if (!this.isFullstack) {
319
+ // Fall back to unified storage
320
+ await this.saveFeedback(feedback, milestoneId || 'master', taskId);
321
+ return;
322
+ }
323
+ const apps = ['unified', 'frontend', 'backend'];
324
+ for (const app of apps) {
325
+ // Extract app-specific concerns and recommendations
326
+ const appConcerns = feedback.taggedConcerns
327
+ .filter(c => c.app === app)
328
+ .map(c => c.content);
329
+ const appRecommendations = feedback.taggedRecommendations
330
+ .filter(r => r.app === app)
331
+ .map(r => r.content);
332
+ // Get app-specific score
333
+ const appScore = app === 'frontend'
334
+ ? feedback.appScores.frontend
335
+ : app === 'backend'
336
+ ? feedback.appScores.backend
337
+ : feedback.appScores.unified;
338
+ const appFeedback = {
339
+ reviewer: feedback.reviewer,
340
+ score: appScore || feedback.score,
341
+ timestamp: feedback.timestamp,
342
+ concerns: appConcerns.length > 0 ? appConcerns : feedback.concerns,
343
+ recommendations: appRecommendations.length > 0 ? appRecommendations : feedback.recommendations,
344
+ analysis: feedback.analysis,
345
+ appTarget: app,
346
+ };
347
+ if (type === 'master') {
348
+ await this.saveMasterFeedback(appFeedback, app);
349
+ }
350
+ else {
351
+ await this.saveFeedback(appFeedback, milestoneId, taskId, app);
352
+ }
353
+ }
354
+ }
355
+ /**
356
+ * Save feedback for master plan
357
+ */
358
+ async saveMasterFeedback(feedback, appTarget) {
359
+ const effectiveAppTarget = this.isFullstack ? (appTarget || 'unified') : undefined;
360
+ const feedbackPath = this.getMasterFeedbackPath(effectiveAppTarget);
361
+ // Ensure directory exists
362
+ await fs.mkdir(path.dirname(feedbackPath), { recursive: true });
363
+ // Load existing feedback
364
+ let existingFeedback = [];
365
+ try {
366
+ const content = await fs.readFile(feedbackPath.replace('.md', '.json'), 'utf-8');
367
+ existingFeedback = JSON.parse(content);
368
+ }
369
+ catch {
370
+ // No existing feedback
371
+ }
372
+ // Tag feedback with app target
373
+ const taggedFeedback = {
374
+ ...feedback,
375
+ appTarget: effectiveAppTarget,
376
+ };
377
+ existingFeedback.push(taggedFeedback);
378
+ // Save JSON
379
+ await fs.writeFile(feedbackPath.replace('.md', '.json'), JSON.stringify(existingFeedback, null, 2), 'utf-8');
380
+ // Save markdown
381
+ const mdContent = this.formatFeedbackAsMarkdown(existingFeedback, effectiveAppTarget);
382
+ await fs.writeFile(feedbackPath, mdContent, 'utf-8');
383
+ }
384
+ /**
385
+ * Load all feedback for a plan
386
+ */
387
+ async loadFeedback(milestoneId, taskId, appTarget) {
388
+ try {
389
+ const effectiveAppTarget = this.isFullstack ? (appTarget || 'unified') : undefined;
390
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId, effectiveAppTarget).replace('.md', '.json');
391
+ const content = await fs.readFile(feedbackPath, 'utf-8');
392
+ return JSON.parse(content);
393
+ }
394
+ catch {
395
+ return [];
396
+ }
397
+ }
398
+ /**
399
+ * Load all feedback for all apps (fullstack)
400
+ */
401
+ async loadAllAppFeedback(milestoneId, taskId) {
402
+ if (!this.isFullstack) {
403
+ const unified = await this.loadFeedback(milestoneId, taskId);
404
+ return { unified, frontend: [], backend: [] };
405
+ }
406
+ const [unified, frontend, backend] = await Promise.all([
407
+ this.loadFeedback(milestoneId, taskId, 'unified'),
408
+ this.loadFeedback(milestoneId, taskId, 'frontend'),
409
+ this.loadFeedback(milestoneId, taskId, 'backend'),
410
+ ]);
411
+ return { unified, frontend, backend };
412
+ }
413
+ /**
414
+ * Load master plan feedback
415
+ */
416
+ async loadMasterFeedback(appTarget) {
417
+ try {
418
+ const effectiveAppTarget = this.isFullstack ? (appTarget || 'unified') : undefined;
419
+ const feedbackPath = this.getMasterFeedbackPath(effectiveAppTarget).replace('.md', '.json');
420
+ const content = await fs.readFile(feedbackPath, 'utf-8');
421
+ return JSON.parse(content);
422
+ }
423
+ catch {
424
+ return [];
425
+ }
426
+ }
427
+ /**
428
+ * Load all master plan feedback (fullstack)
429
+ */
430
+ async loadAllMasterFeedback() {
431
+ if (!this.isFullstack) {
432
+ const unified = await this.loadMasterFeedback();
433
+ return { unified, frontend: [], backend: [] };
434
+ }
435
+ const [unified, frontend, backend] = await Promise.all([
436
+ this.loadMasterFeedback('unified'),
437
+ this.loadMasterFeedback('frontend'),
438
+ this.loadMasterFeedback('backend'),
439
+ ]);
440
+ return { unified, frontend, backend };
441
+ }
442
+ /**
443
+ * Clear feedback for a new consensus round
444
+ */
445
+ async clearFeedback(milestoneId, taskId, appTarget) {
446
+ if (this.isFullstack && !appTarget) {
447
+ // Clear all app feedback
448
+ await Promise.all([
449
+ this.clearFeedback(milestoneId, taskId, 'unified'),
450
+ this.clearFeedback(milestoneId, taskId, 'frontend'),
451
+ this.clearFeedback(milestoneId, taskId, 'backend'),
452
+ ]);
453
+ return;
454
+ }
455
+ const feedbackPath = this.getFeedbackPath(milestoneId, taskId, appTarget);
456
+ try {
457
+ await fs.unlink(feedbackPath);
458
+ await fs.unlink(feedbackPath.replace('.md', '.json'));
459
+ }
460
+ catch {
461
+ // Files don't exist, that's fine
462
+ }
463
+ }
464
+ /**
465
+ * Clear master plan feedback
466
+ */
467
+ async clearMasterFeedback(appTarget) {
468
+ if (this.isFullstack && !appTarget) {
469
+ // Clear all app feedback
470
+ await Promise.all([
471
+ this.clearMasterFeedback('unified'),
472
+ this.clearMasterFeedback('frontend'),
473
+ this.clearMasterFeedback('backend'),
474
+ ]);
475
+ return;
476
+ }
477
+ const feedbackPath = this.getMasterFeedbackPath(appTarget);
478
+ try {
479
+ await fs.unlink(feedbackPath);
480
+ await fs.unlink(feedbackPath.replace('.md', '.json'));
481
+ }
482
+ catch {
483
+ // Files don't exist, that's fine
484
+ }
485
+ }
486
+ /**
487
+ * Format feedback as readable markdown
488
+ */
489
+ formatFeedbackAsMarkdown(feedback, appTarget) {
490
+ const lines = [];
491
+ // Header with app target for fullstack
492
+ if (appTarget && this.isFullstack) {
493
+ const appLabel = appTarget.charAt(0).toUpperCase() + appTarget.slice(1);
494
+ lines.push(`# ${appLabel} Reviewer Feedback\n`);
495
+ }
496
+ else {
497
+ lines.push('# Reviewer Feedback\n');
498
+ }
499
+ for (const fb of feedback) {
500
+ lines.push(`## ${fb.reviewer.toUpperCase()} Review`);
501
+ lines.push(`- **Score:** ${fb.score}%`);
502
+ lines.push(`- **Timestamp:** ${fb.timestamp}`);
503
+ if (fb.appTarget) {
504
+ lines.push(`- **App Target:** ${fb.appTarget}`);
505
+ }
506
+ lines.push('');
507
+ if (fb.concerns.length > 0) {
508
+ lines.push('### Concerns');
509
+ for (const concern of fb.concerns) {
510
+ lines.push(`- ${concern}`);
511
+ }
512
+ lines.push('');
513
+ }
514
+ if (fb.recommendations.length > 0) {
515
+ lines.push('### Recommendations');
516
+ for (const rec of fb.recommendations) {
517
+ lines.push(`- ${rec}`);
518
+ }
519
+ lines.push('');
520
+ }
521
+ if (fb.analysis) {
522
+ lines.push('### Analysis');
523
+ lines.push(fb.analysis);
524
+ lines.push('');
525
+ }
526
+ lines.push('---\n');
527
+ }
528
+ return lines.join('\n');
529
+ }
530
+ /**
531
+ * Get combined feedback summary for revision
532
+ */
533
+ async getCombinedFeedbackForRevision(milestoneId, taskId, appTarget) {
534
+ const feedback = await this.loadFeedback(milestoneId, taskId, appTarget);
535
+ if (feedback.length === 0) {
536
+ return {
537
+ averageScore: 0,
538
+ allConcerns: [],
539
+ allRecommendations: [],
540
+ combinedAnalysis: '',
541
+ };
542
+ }
543
+ const averageScore = feedback.reduce((sum, f) => sum + f.score, 0) / feedback.length;
544
+ // Deduplicate concerns and recommendations
545
+ const allConcerns = [...new Set(feedback.flatMap(f => f.concerns))];
546
+ const allRecommendations = [...new Set(feedback.flatMap(f => f.recommendations))];
547
+ // Combine analysis
548
+ const combinedAnalysis = feedback
549
+ .map(f => `### ${f.reviewer.toUpperCase()} (${f.score}%)\n${f.analysis}`)
550
+ .join('\n\n');
551
+ return {
552
+ averageScore,
553
+ allConcerns,
554
+ allRecommendations,
555
+ combinedAnalysis,
556
+ };
557
+ }
558
+ /**
559
+ * Get combined feedback for all apps (fullstack)
560
+ */
561
+ async getFullstackCombinedFeedback(milestoneId, taskId) {
562
+ const [unified, frontend, backend] = await Promise.all([
563
+ this.getCombinedFeedbackForRevision(milestoneId, taskId, 'unified'),
564
+ this.getCombinedFeedbackForRevision(milestoneId, taskId, 'frontend'),
565
+ this.getCombinedFeedbackForRevision(milestoneId, taskId, 'backend'),
566
+ ]);
567
+ // Calculate overall score (weighted average - unified counts more)
568
+ const scores = [unified.averageScore, frontend.averageScore, backend.averageScore].filter(s => s > 0);
569
+ const overallScore = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0;
570
+ // Create tagged concerns and recommendations
571
+ const allTaggedConcerns = [
572
+ ...unified.allConcerns.map(c => ({ app: 'unified', content: c })),
573
+ ...frontend.allConcerns.map(c => ({ app: 'frontend', content: c })),
574
+ ...backend.allConcerns.map(c => ({ app: 'backend', content: c })),
575
+ ];
576
+ const allTaggedRecommendations = [
577
+ ...unified.allRecommendations.map(r => ({ app: 'unified', content: r })),
578
+ ...frontend.allRecommendations.map(r => ({ app: 'frontend', content: r })),
579
+ ...backend.allRecommendations.map(r => ({ app: 'backend', content: r })),
580
+ ];
581
+ return {
582
+ unified,
583
+ frontend,
584
+ backend,
585
+ overallScore,
586
+ allTaggedConcerns,
587
+ allTaggedRecommendations,
588
+ };
589
+ }
590
+ /**
591
+ * Update plan status
592
+ */
593
+ async updateStatus(status, type, milestoneId, taskId) {
594
+ const metadataPath = this.getMetadataPath(type, milestoneId, taskId);
595
+ try {
596
+ const content = await fs.readFile(metadataPath, 'utf-8');
597
+ const metadata = JSON.parse(content);
598
+ metadata.status = status;
599
+ metadata.updatedAt = new Date().toISOString();
600
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
601
+ }
602
+ catch {
603
+ // Metadata doesn't exist yet
604
+ }
605
+ }
606
+ /**
607
+ * Update per-app approval status (fullstack)
608
+ */
609
+ async updateAppApproval(type, appTarget, approved, score, milestoneId, taskId) {
610
+ const metadataPath = this.getMetadataPath(type, milestoneId, taskId);
611
+ try {
612
+ const content = await fs.readFile(metadataPath, 'utf-8');
613
+ const metadata = JSON.parse(content);
614
+ if (appTarget === 'frontend') {
615
+ metadata.frontendApproved = approved;
616
+ metadata.frontendScore = score;
617
+ }
618
+ else if (appTarget === 'backend') {
619
+ metadata.backendApproved = approved;
620
+ metadata.backendScore = score;
621
+ }
622
+ else {
623
+ metadata.unifiedApproved = approved;
624
+ metadata.unifiedScore = score;
625
+ }
626
+ metadata.updatedAt = new Date().toISOString();
627
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
628
+ }
629
+ catch {
630
+ // Metadata doesn't exist yet
631
+ }
632
+ }
633
+ /**
634
+ * Record a correction/revision
635
+ */
636
+ async recordCorrection(type, correction, milestoneId, taskId) {
637
+ const metadataPath = this.getMetadataPath(type, milestoneId, taskId);
638
+ try {
639
+ const content = await fs.readFile(metadataPath, 'utf-8');
640
+ const metadata = JSON.parse(content);
641
+ if (!metadata.corrections) {
642
+ metadata.corrections = [];
643
+ }
644
+ metadata.corrections.push(correction);
645
+ metadata.totalIterations = (metadata.totalIterations || 0) + 1;
646
+ metadata.updatedAt = new Date().toISOString();
647
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
648
+ }
649
+ catch {
650
+ // Metadata doesn't exist yet
651
+ }
652
+ }
653
+ /**
654
+ * Load metadata for a plan
655
+ */
656
+ async loadMetadata(type, milestoneId, taskId) {
657
+ try {
658
+ const metadataPath = this.getMetadataPath(type, milestoneId, taskId);
659
+ const content = await fs.readFile(metadataPath, 'utf-8');
660
+ return JSON.parse(content);
661
+ }
662
+ catch {
663
+ return null;
664
+ }
665
+ }
666
+ /**
667
+ * Get plan tracking summary for a milestone
668
+ */
669
+ async getMilestoneTrackingSummary(milestoneId) {
670
+ const milestoneDir = path.join(this.plansDir, `milestone-${milestoneId}`);
671
+ // Check milestone plan
672
+ let milestonePlan = { exists: false };
673
+ try {
674
+ const metadataPath = path.join(milestoneDir, 'metadata.json');
675
+ const content = await fs.readFile(metadataPath, 'utf-8');
676
+ const metadata = JSON.parse(content);
677
+ milestonePlan = {
678
+ exists: true,
679
+ score: metadata.consensusScore,
680
+ status: metadata.status,
681
+ frontendScore: metadata.frontendScore,
682
+ backendScore: metadata.backendScore,
683
+ unifiedScore: metadata.unifiedScore,
684
+ frontendApproved: metadata.frontendApproved,
685
+ backendApproved: metadata.backendApproved,
686
+ unifiedApproved: metadata.unifiedApproved,
687
+ };
688
+ }
689
+ catch {
690
+ // No milestone plan
691
+ }
692
+ // Find task plans
693
+ const taskPlans = [];
694
+ try {
695
+ // Check for new structure (tasks/ subdirectory)
696
+ if (this.isFullstack) {
697
+ const tasksDir = path.join(milestoneDir, 'tasks');
698
+ try {
699
+ const taskDirs = await fs.readdir(tasksDir);
700
+ for (const taskDir of taskDirs) {
701
+ if (taskDir.startsWith('task-')) {
702
+ const metadataPath = path.join(tasksDir, taskDir, 'metadata.json');
703
+ try {
704
+ const content = await fs.readFile(metadataPath, 'utf-8');
705
+ const metadata = JSON.parse(content);
706
+ taskPlans.push({
707
+ taskId: metadata.taskId || taskDir.replace('task-', ''),
708
+ taskName: metadata.taskName,
709
+ exists: true,
710
+ score: metadata.consensusScore,
711
+ status: metadata.status,
712
+ frontendScore: metadata.frontendScore,
713
+ backendScore: metadata.backendScore,
714
+ unifiedScore: metadata.unifiedScore,
715
+ });
716
+ }
717
+ catch {
718
+ // Skip invalid files
719
+ }
720
+ }
721
+ }
722
+ }
723
+ catch {
724
+ // tasks directory doesn't exist
725
+ }
726
+ }
727
+ // Also check legacy structure
728
+ const files = await fs.readdir(milestoneDir);
729
+ const taskMetadataFiles = files.filter(f => f.startsWith('task-') && f.endsWith('-metadata.json'));
730
+ for (const file of taskMetadataFiles) {
731
+ try {
732
+ const content = await fs.readFile(path.join(milestoneDir, file), 'utf-8');
733
+ const metadata = JSON.parse(content);
734
+ // Avoid duplicates
735
+ if (!taskPlans.find(t => t.taskId === metadata.taskId)) {
736
+ taskPlans.push({
737
+ taskId: metadata.taskId || '',
738
+ taskName: metadata.taskName,
739
+ exists: true,
740
+ score: metadata.consensusScore,
741
+ status: metadata.status,
742
+ frontendScore: metadata.frontendScore,
743
+ backendScore: metadata.backendScore,
744
+ unifiedScore: metadata.unifiedScore,
745
+ });
746
+ }
747
+ }
748
+ catch {
749
+ // Skip invalid files
750
+ }
751
+ }
752
+ }
753
+ catch {
754
+ // Directory doesn't exist
755
+ }
756
+ return { milestonePlan, taskPlans };
757
+ }
758
+ /**
759
+ * Get comprehensive tracking record for the entire project
760
+ */
761
+ async getProjectTrackingRecord() {
762
+ // Load master plan metadata
763
+ const masterPlan = await this.loadMetadata('master');
764
+ // Find all milestone directories
765
+ const milestones = [];
766
+ try {
767
+ const entries = await fs.readdir(this.plansDir, { withFileTypes: true });
768
+ const milestoneDirs = entries
769
+ .filter(e => e.isDirectory() && e.name.startsWith('milestone-'))
770
+ .map(e => e.name);
771
+ for (const milestoneDir of milestoneDirs) {
772
+ const milestoneId = milestoneDir.replace('milestone-', '');
773
+ const milestoneMetadata = await this.loadMetadata('milestone', milestoneId);
774
+ // Get tasks for this milestone
775
+ const { taskPlans } = await this.getMilestoneTrackingSummary(milestoneId);
776
+ const tasks = await Promise.all(taskPlans.map(async (tp) => ({
777
+ metadata: await this.loadMetadata('task', milestoneId, tp.taskId),
778
+ })));
779
+ milestones.push({ metadata: milestoneMetadata, tasks });
780
+ }
781
+ }
782
+ catch {
783
+ // Plans directory doesn't exist
784
+ }
785
+ // Calculate totals
786
+ let totalCorrections = 0;
787
+ let totalIterations = 0;
788
+ if (masterPlan) {
789
+ totalCorrections += masterPlan.corrections?.length || 0;
790
+ totalIterations += masterPlan.totalIterations || 0;
791
+ }
792
+ for (const m of milestones) {
793
+ if (m.metadata) {
794
+ totalCorrections += m.metadata.corrections?.length || 0;
795
+ totalIterations += m.metadata.totalIterations || 0;
796
+ }
797
+ for (const t of m.tasks) {
798
+ if (t.metadata) {
799
+ totalCorrections += t.metadata.corrections?.length || 0;
800
+ totalIterations += t.metadata.totalIterations || 0;
801
+ }
802
+ }
803
+ }
804
+ return {
805
+ masterPlan,
806
+ milestones,
807
+ totalCorrections,
808
+ totalIterations,
809
+ };
810
+ }
811
+ /**
812
+ * Get all feedback file paths for the project
813
+ */
814
+ async getAllFeedbackPaths() {
815
+ const result = {
816
+ master: {},
817
+ milestones: [],
818
+ };
819
+ // Master plan paths
820
+ if (this.isFullstack) {
821
+ result.master = {
822
+ unified: this.getMasterFeedbackPath('unified'),
823
+ frontend: this.getMasterFeedbackPath('frontend'),
824
+ backend: this.getMasterFeedbackPath('backend'),
825
+ };
826
+ }
827
+ else {
828
+ result.master = {
829
+ unified: this.getMasterFeedbackPath(),
830
+ };
831
+ }
832
+ // Find milestone directories
833
+ try {
834
+ const entries = await fs.readdir(this.plansDir, { withFileTypes: true });
835
+ const milestoneDirs = entries
836
+ .filter(e => e.isDirectory() && e.name.startsWith('milestone-'))
837
+ .map(e => e.name);
838
+ for (const dir of milestoneDirs) {
839
+ const milestoneId = dir.replace('milestone-', '');
840
+ const milestonePaths = {};
841
+ if (this.isFullstack) {
842
+ milestonePaths.unified = this.getFeedbackPath(milestoneId, undefined, 'unified');
843
+ milestonePaths.frontend = this.getFeedbackPath(milestoneId, undefined, 'frontend');
844
+ milestonePaths.backend = this.getFeedbackPath(milestoneId, undefined, 'backend');
845
+ }
846
+ else {
847
+ milestonePaths.unified = this.getFeedbackPath(milestoneId);
848
+ }
849
+ // Get task paths
850
+ const { taskPlans } = await this.getMilestoneTrackingSummary(milestoneId);
851
+ const tasks = taskPlans.map(tp => {
852
+ const taskPaths = {};
853
+ if (this.isFullstack) {
854
+ taskPaths.unified = this.getFeedbackPath(milestoneId, tp.taskId, 'unified');
855
+ taskPaths.frontend = this.getFeedbackPath(milestoneId, tp.taskId, 'frontend');
856
+ taskPaths.backend = this.getFeedbackPath(milestoneId, tp.taskId, 'backend');
857
+ }
858
+ else {
859
+ taskPaths.unified = this.getFeedbackPath(milestoneId, tp.taskId);
860
+ }
861
+ return { taskId: tp.taskId, paths: taskPaths };
862
+ });
863
+ result.milestones.push({ milestoneId, paths: milestonePaths, tasks });
864
+ }
865
+ }
866
+ catch {
867
+ // Plans directory doesn't exist
868
+ }
869
+ return result;
870
+ }
871
+ }
872
+ /**
873
+ * Create a plan storage instance for a project
874
+ */
875
+ export function createPlanStorage(projectDir, isFullstack = false) {
876
+ return new PlanStorage(projectDir, isFullstack);
877
+ }
878
+ //# sourceMappingURL=plan-storage.js.map