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.
- package/README.md +521 -125
- package/dist/adapters/claude.d.ts +16 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +679 -33
- 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/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +41 -7
- 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/index.d.ts +11 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +23 -5
- 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/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts +2 -2
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1380 -183
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +6 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +10 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +20 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +1 -0
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +1 -0
- 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 +4 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +69 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +24 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/workflow.d.ts +55 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +16 -0
- 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 +44 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +565 -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 +14 -1
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +589 -47
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +142 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +331 -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 +383 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +1 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +9 -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/package.json +2 -2
- package/src/adapters/claude.ts +815 -34
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/openai.ts +40 -7
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/index.ts +28 -8
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/index.ts +4 -7
- package/src/cli/interactive.ts +1641 -216
- package/src/config/defaults.ts +10 -2
- package/src/config/index.ts +21 -0
- package/src/config/schema.ts +7 -0
- package/src/generators/python.ts +1 -0
- package/src/generators/typescript.ts +1 -0
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +4 -0
- package/src/types/consensus.ts +65 -6
- package/src/types/workflow.ts +35 -0
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +750 -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 +696 -50
- package/src/workflow/plan-storage.ts +482 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +525 -0
- package/src/workflow/test-runner.ts +10 -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/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,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
|
+
}
|