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.
- package/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +832 -123
- package/dist/adapters/claude.d.ts +19 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +908 -42
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +55 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +318 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +47 -8
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/claude.d.ts +11 -9
- package/dist/auth/claude.d.ts.map +1 -1
- package/dist/auth/claude.js +107 -71
- package/dist/auth/claude.js.map +1 -1
- package/dist/auth/gemini.d.ts +58 -0
- package/dist/auth/gemini.d.ts.map +1 -0
- package/dist/auth/gemini.js +172 -0
- package/dist/auth/gemini.js.map +1 -0
- package/dist/auth/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +14 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +41 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +20 -7
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +85 -29
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/openai.d.ts +2 -2
- package/dist/auth/openai.d.ts.map +1 -1
- package/dist/auth/openai.js +30 -32
- package/dist/auth/openai.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1494 -114
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +33 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +47 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +29 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -20
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -20
- package/dist/generators/typescript.js.map +1 -1
- package/dist/state/index.d.ts +108 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +551 -4
- package/dist/state/index.js.map +1 -1
- package/dist/state/registry.d.ts +52 -0
- package/dist/state/registry.d.ts.map +1 -0
- package/dist/state/registry.js +215 -0
- package/dist/state/registry.js.map +1 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +186 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +35 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +217 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +40 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +45 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -0
- package/dist/workflow/auto-fix.js +274 -0
- package/dist/workflow/auto-fix.js.map +1 -0
- package/dist/workflow/consensus.d.ts +70 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +872 -17
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +10 -4
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +547 -58
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +14 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +69 -6
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +34 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -0
- package/dist/workflow/milestone-workflow.js +414 -0
- package/dist/workflow/milestone-workflow.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +80 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +767 -49
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +386 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +878 -0
- package/dist/workflow/plan-storage.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +37 -0
- package/dist/workflow/project-verification.d.ts.map +1 -0
- package/dist/workflow/project-verification.js +381 -0
- package/dist/workflow/project-verification.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +37 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -0
- package/dist/workflow/task-workflow.js +386 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +9 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +101 -5
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/ui-designer.d.ts +82 -0
- package/dist/workflow/ui-designer.d.ts.map +1 -0
- package/dist/workflow/ui-designer.js +234 -0
- package/dist/workflow/ui-designer.js.map +1 -0
- package/dist/workflow/ui-setup.d.ts +58 -0
- package/dist/workflow/ui-setup.d.ts.map +1 -0
- package/dist/workflow/ui-setup.js +685 -0
- package/dist/workflow/ui-setup.js.map +1 -0
- package/dist/workflow/ui-verification.d.ts +114 -0
- package/dist/workflow/ui-verification.d.ts.map +1 -0
- package/dist/workflow/ui-verification.js +258 -0
- package/dist/workflow/ui-verification.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +110 -0
- package/dist/workflow/workflow-logger.d.ts.map +1 -0
- package/dist/workflow/workflow-logger.js +267 -0
- package/dist/workflow/workflow-logger.js.map +1 -0
- package/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +1067 -47
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +48 -9
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +47 -9
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +1774 -142
- package/src/config/defaults.ts +19 -2
- package/src/config/index.ts +36 -1
- package/src/config/schema.ts +30 -1
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -20
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -20
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +8 -0
- package/src/types/consensus.ts +197 -6
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +90 -1
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +1180 -16
- package/src/workflow/execution-mode.ts +673 -74
- package/src/workflow/index.ts +95 -6
- package/src/workflow/milestone-workflow.ts +576 -0
- package/src/workflow/plan-mode.ts +924 -50
- package/src/workflow/plan-storage.ts +1282 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +528 -0
- package/src/workflow/test-runner.ts +120 -5
- package/src/workflow/ui-designer.ts +337 -0
- package/src/workflow/ui-setup.ts +797 -0
- package/src/workflow/ui-verification.ts +357 -0
- package/src/workflow/workflow-logger.ts +353 -0
- package/src/workflow/workspace-manager.ts +912 -0
- package/tests/config/config.test.ts +1 -1
- package/tests/types/consensus.test.ts +3 -3
- package/tests/workflow/plan-mode.test.ts +213 -0
- 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
|