claude-code-workflow 6.3.26 → 6.3.28
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/.claude/CLAUDE.md +7 -1
- package/.claude/agents/action-planning-agent.md +1 -0
- package/.claude/agents/cli-discuss-agent.md +391 -0
- package/.claude/agents/cli-execution-agent.md +2 -0
- package/.claude/agents/cli-explore-agent.md +2 -1
- package/.claude/agents/cli-lite-planning-agent.md +1 -0
- package/.claude/agents/cli-planning-agent.md +1 -0
- package/.claude/agents/code-developer.md +1 -0
- package/.claude/agents/conceptual-planning-agent.md +2 -0
- package/.claude/agents/context-search-agent.md +1 -0
- package/.claude/agents/debug-explore-agent.md +2 -0
- package/.claude/agents/doc-generator.md +1 -0
- package/.claude/agents/issue-plan-agent.md +2 -1
- package/.claude/agents/issue-queue-agent.md +2 -1
- package/.claude/agents/memory-bridge.md +2 -0
- package/.claude/agents/test-context-search-agent.md +2 -0
- package/.claude/agents/test-fix-agent.md +1 -0
- package/.claude/agents/ui-design-agent.md +2 -0
- package/.claude/agents/universal-executor.md +1 -0
- package/.claude/commands/issue/execute.md +141 -163
- package/.claude/commands/workflow/lite-lite-lite.md +798 -0
- package/.claude/commands/workflow/multi-cli-plan.md +510 -0
- package/.claude/skills/ccw/SKILL.md +262 -372
- package/.claude/skills/ccw/command.json +547 -0
- package/.claude/skills/ccw-help/SKILL.md +46 -107
- package/.claude/skills/ccw-help/command.json +511 -0
- package/.claude/skills/skill-tuning/SKILL.md +303 -0
- package/.claude/skills/skill-tuning/phases/actions/action-abort.md +164 -0
- package/.claude/skills/skill-tuning/phases/actions/action-analyze-requirements.md +406 -0
- package/.claude/skills/skill-tuning/phases/actions/action-apply-fix.md +206 -0
- package/.claude/skills/skill-tuning/phases/actions/action-complete.md +195 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-agent.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-context.md +243 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-dataflow.md +318 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-docs.md +299 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-memory.md +269 -0
- package/.claude/skills/skill-tuning/phases/actions/action-diagnose-token-consumption.md +200 -0
- package/.claude/skills/skill-tuning/phases/actions/action-gemini-analysis.md +322 -0
- package/.claude/skills/skill-tuning/phases/actions/action-generate-report.md +228 -0
- package/.claude/skills/skill-tuning/phases/actions/action-init.md +149 -0
- package/.claude/skills/skill-tuning/phases/actions/action-propose-fixes.md +317 -0
- package/.claude/skills/skill-tuning/phases/actions/action-verify.md +222 -0
- package/.claude/skills/skill-tuning/phases/orchestrator.md +377 -0
- package/.claude/skills/skill-tuning/phases/state-schema.md +378 -0
- package/.claude/skills/skill-tuning/specs/category-mappings.json +284 -0
- package/.claude/skills/skill-tuning/specs/dimension-mapping.md +212 -0
- package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +318 -0
- package/.claude/skills/skill-tuning/specs/quality-gates.md +263 -0
- package/.claude/skills/skill-tuning/specs/skill-authoring-principles.md +189 -0
- package/.claude/skills/skill-tuning/specs/tuning-strategies.md +1537 -0
- package/.claude/skills/skill-tuning/templates/diagnosis-report.md +153 -0
- package/.claude/skills/skill-tuning/templates/fix-proposal.md +204 -0
- package/.claude/workflows/cli-templates/schemas/multi-cli-discussion-schema.json +421 -0
- package/.claude/workflows/cli-tools-usage.md +0 -41
- package/ccw/dist/core/auth/csrf-middleware.d.ts.map +1 -1
- package/ccw/dist/core/auth/csrf-middleware.js +3 -1
- package/ccw/dist/core/auth/csrf-middleware.js.map +1 -1
- package/ccw/dist/core/data-aggregator.d.ts +2 -0
- package/ccw/dist/core/data-aggregator.d.ts.map +1 -1
- package/ccw/dist/core/data-aggregator.js +5 -2
- package/ccw/dist/core/data-aggregator.js.map +1 -1
- package/ccw/dist/core/lite-scanner.d.ts +2 -1
- package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
- package/ccw/dist/core/lite-scanner.js +295 -6
- package/ccw/dist/core/lite-scanner.js.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens/config-handlers.js +5 -5
- package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
- package/ccw/dist/core/routes/session-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/session-routes.js +166 -48
- package/ccw/dist/core/routes/session-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +87 -0
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.js +2 -2
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +226 -0
- package/ccw/scripts/QUICK-REFERENCE.md +135 -0
- package/ccw/scripts/README-memory-embedder.md +157 -0
- package/ccw/scripts/__pycache__/memory_embedder.cpython-313.pyc +0 -0
- package/ccw/scripts/__pycache__/test_memory_embedder.cpython-313-pytest-8.4.2.pyc +0 -0
- package/ccw/scripts/memory-embedder-example.ts +184 -0
- package/ccw/scripts/memory_embedder.py +428 -0
- package/ccw/scripts/test_memory_embedder.py +245 -0
- package/ccw/src/core/auth/csrf-middleware.ts +3 -1
- package/ccw/src/core/data-aggregator.ts +7 -2
- package/ccw/src/core/lite-scanner.ts +440 -6
- package/ccw/src/core/routes/codexlens/config-handlers.ts +12 -9
- package/ccw/src/core/routes/session-routes.ts +201 -48
- package/ccw/src/core/routes/system-routes.ts +102 -0
- package/ccw/src/core/server.ts +2 -2
- package/ccw/src/templates/dashboard-css/01-base.css +8 -0
- package/ccw/src/templates/dashboard-css/02-session.css +81 -0
- package/ccw/src/templates/dashboard-css/04-lite-tasks.css +2442 -0
- package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +157 -0
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +23 -0
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +38 -4
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +38 -13
- package/ccw/src/templates/dashboard-js/components/navigation.js +24 -4
- package/ccw/src/templates/dashboard-js/i18n.js +194 -6
- package/ccw/src/templates/dashboard-js/views/api-settings.js +32 -0
- package/ccw/src/templates/dashboard-js/views/claude-manager.js +44 -3
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +303 -31
- package/ccw/src/templates/dashboard-js/views/history.js +44 -6
- package/ccw/src/templates/dashboard-js/views/home.js +1 -0
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +54 -7
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +1817 -4
- package/ccw/src/templates/dashboard.html +5 -0
- package/package.json +2 -1
- package/.claude/skills/ccw/index/command-capabilities.json +0 -127
- package/.claude/skills/ccw/index/intent-rules.json +0 -136
- package/.claude/skills/ccw/index/workflow-chains.json +0 -451
- package/.claude/skills/ccw/phases/actions/bugfix.md +0 -218
- package/.claude/skills/ccw/phases/actions/coupled.md +0 -194
- package/.claude/skills/ccw/phases/actions/docs.md +0 -93
- package/.claude/skills/ccw/phases/actions/full.md +0 -154
- package/.claude/skills/ccw/phases/actions/issue.md +0 -201
- package/.claude/skills/ccw/phases/actions/rapid.md +0 -104
- package/.claude/skills/ccw/phases/actions/review-fix.md +0 -84
- package/.claude/skills/ccw/phases/actions/tdd.md +0 -66
- package/.claude/skills/ccw/phases/actions/ui.md +0 -79
- package/.claude/skills/ccw/phases/orchestrator.md +0 -435
- package/.claude/skills/ccw/specs/intent-classification.md +0 -336
- package/.claude/skills/ccw-help/index/all-agents.json +0 -82
- package/.claude/skills/ccw-help/index/all-commands.json +0 -882
- package/.claude/skills/ccw-help/index/by-category.json +0 -914
- package/.claude/skills/ccw-help/index/by-use-case.json +0 -896
- package/.claude/skills/ccw-help/index/command-relationships.json +0 -160
- package/.claude/skills/ccw-help/index/essential-commands.json +0 -112
|
@@ -77,6 +77,7 @@ interface DashboardData {
|
|
|
77
77
|
liteTasks: {
|
|
78
78
|
litePlan: unknown[];
|
|
79
79
|
liteFix: unknown[];
|
|
80
|
+
multiCliPlan: unknown[];
|
|
80
81
|
};
|
|
81
82
|
reviewData: ReviewData | null;
|
|
82
83
|
projectOverview: ProjectOverview | null;
|
|
@@ -88,6 +89,7 @@ interface DashboardData {
|
|
|
88
89
|
reviewFindings: number;
|
|
89
90
|
litePlanCount: number;
|
|
90
91
|
liteFixCount: number;
|
|
92
|
+
multiCliPlanCount: number;
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -211,7 +213,8 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
|
|
|
211
213
|
archivedSessions: [],
|
|
212
214
|
liteTasks: {
|
|
213
215
|
litePlan: [],
|
|
214
|
-
liteFix: []
|
|
216
|
+
liteFix: [],
|
|
217
|
+
multiCliPlan: []
|
|
215
218
|
},
|
|
216
219
|
reviewData: null,
|
|
217
220
|
projectOverview: null,
|
|
@@ -222,7 +225,8 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
|
|
|
222
225
|
completedTasks: 0,
|
|
223
226
|
reviewFindings: 0,
|
|
224
227
|
litePlanCount: 0,
|
|
225
|
-
liteFixCount: 0
|
|
228
|
+
liteFixCount: 0,
|
|
229
|
+
multiCliPlanCount: 0
|
|
226
230
|
}
|
|
227
231
|
};
|
|
228
232
|
|
|
@@ -257,6 +261,7 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
|
|
|
257
261
|
data.liteTasks = liteTasks;
|
|
258
262
|
data.statistics.litePlanCount = liteTasks.litePlan.length;
|
|
259
263
|
data.statistics.liteFixCount = liteTasks.liteFix.length;
|
|
264
|
+
data.statistics.multiCliPlanCount = liteTasks.multiCliPlan.length;
|
|
260
265
|
} catch (err) {
|
|
261
266
|
console.error('Error scanning lite tasks:', (err as Error).message);
|
|
262
267
|
}
|
|
@@ -60,9 +60,47 @@ interface LiteSession {
|
|
|
60
60
|
progress: Progress;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// Multi-CLI specific session state from session-state.json
|
|
64
|
+
interface MultiCliSessionState {
|
|
65
|
+
session_id: string;
|
|
66
|
+
task_description: string;
|
|
67
|
+
status: string;
|
|
68
|
+
current_phase: number;
|
|
69
|
+
phases: Record<string, { status: string; rounds_completed?: number }>;
|
|
70
|
+
ace_context?: { relevant_files: string[]; detected_patterns: string[] };
|
|
71
|
+
user_decisions?: Array<{ round: number; decision: string; selected: string }>;
|
|
72
|
+
updated_at?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Discussion topic structure for frontend rendering
|
|
76
|
+
interface DiscussionTopic {
|
|
77
|
+
title: string;
|
|
78
|
+
description: string;
|
|
79
|
+
scope: { included: string[]; excluded: string[] };
|
|
80
|
+
keyQuestions: string[];
|
|
81
|
+
status: string;
|
|
82
|
+
tags: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Extended session interface for multi-cli-plan
|
|
86
|
+
interface MultiCliSession extends LiteSession {
|
|
87
|
+
roundCount: number;
|
|
88
|
+
topicTitle: string;
|
|
89
|
+
status: string;
|
|
90
|
+
metadata: {
|
|
91
|
+
roundId: number;
|
|
92
|
+
timestamp: string;
|
|
93
|
+
currentPhase: number;
|
|
94
|
+
};
|
|
95
|
+
discussionTopic: DiscussionTopic;
|
|
96
|
+
rounds: RoundSynthesis[];
|
|
97
|
+
latestSynthesis: RoundSynthesis | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
interface LiteTasks {
|
|
64
101
|
litePlan: LiteSession[];
|
|
65
102
|
liteFix: LiteSession[];
|
|
103
|
+
multiCliPlan: LiteSession[];
|
|
66
104
|
}
|
|
67
105
|
|
|
68
106
|
interface LiteTaskDetail {
|
|
@@ -84,13 +122,15 @@ interface LiteTaskDetail {
|
|
|
84
122
|
export async function scanLiteTasks(workflowDir: string): Promise<LiteTasks> {
|
|
85
123
|
const litePlanDir = join(workflowDir, '.lite-plan');
|
|
86
124
|
const liteFixDir = join(workflowDir, '.lite-fix');
|
|
125
|
+
const multiCliDir = join(workflowDir, '.multi-cli-plan');
|
|
87
126
|
|
|
88
|
-
const [litePlan, liteFix] = await Promise.all([
|
|
127
|
+
const [litePlan, liteFix, multiCliPlan] = await Promise.all([
|
|
89
128
|
scanLiteDir(litePlanDir, 'lite-plan'),
|
|
90
129
|
scanLiteDir(liteFixDir, 'lite-fix'),
|
|
130
|
+
scanMultiCliDir(multiCliDir),
|
|
91
131
|
]);
|
|
92
132
|
|
|
93
|
-
return { litePlan, liteFix };
|
|
133
|
+
return { litePlan, liteFix, multiCliPlan };
|
|
94
134
|
}
|
|
95
135
|
|
|
96
136
|
/**
|
|
@@ -142,6 +182,372 @@ async function scanLiteDir(dir: string, type: string): Promise<LiteSession[]> {
|
|
|
142
182
|
}
|
|
143
183
|
}
|
|
144
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Load session-state.json from multi-cli session directory
|
|
187
|
+
* @param sessionPath - Session directory path
|
|
188
|
+
* @returns Session state or null if not found
|
|
189
|
+
*/
|
|
190
|
+
async function loadSessionState(sessionPath: string): Promise<MultiCliSessionState | null> {
|
|
191
|
+
const statePath = join(sessionPath, 'session-state.json');
|
|
192
|
+
try {
|
|
193
|
+
const content = await readFile(statePath, 'utf8');
|
|
194
|
+
return JSON.parse(content);
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build discussion topic structure from session state and synthesis
|
|
202
|
+
* @param state - Session state from session-state.json
|
|
203
|
+
* @param synthesis - Latest round synthesis
|
|
204
|
+
* @returns Discussion topic for frontend rendering
|
|
205
|
+
*/
|
|
206
|
+
function buildDiscussionTopic(
|
|
207
|
+
state: MultiCliSessionState | null,
|
|
208
|
+
synthesis: RoundSynthesis | null
|
|
209
|
+
): DiscussionTopic {
|
|
210
|
+
const keyQuestions = synthesis?.clarification_questions || [];
|
|
211
|
+
const solutions = synthesis?.solutions || [];
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
title: state?.task_description || 'Discussion Topic',
|
|
215
|
+
description: solutions[0]?.summary || '',
|
|
216
|
+
scope: {
|
|
217
|
+
included: state?.ace_context?.relevant_files || [],
|
|
218
|
+
excluded: [],
|
|
219
|
+
},
|
|
220
|
+
keyQuestions,
|
|
221
|
+
status: state?.status || 'analyzing',
|
|
222
|
+
tags: solutions.map((s) => s.name).slice(0, 3),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Scan multi-cli-plan directory for sessions
|
|
228
|
+
* @param dir - Directory path to .multi-cli-plan
|
|
229
|
+
* @returns Array of multi-cli sessions with extended metadata
|
|
230
|
+
*/
|
|
231
|
+
async function scanMultiCliDir(dir: string): Promise<MultiCliSession[]> {
|
|
232
|
+
try {
|
|
233
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
234
|
+
|
|
235
|
+
const sessions = (await Promise.all(
|
|
236
|
+
entries
|
|
237
|
+
.filter((entry) => entry.isDirectory())
|
|
238
|
+
.map(async (entry) => {
|
|
239
|
+
const sessionPath = join(dir, entry.name);
|
|
240
|
+
|
|
241
|
+
const [createdAt, syntheses, sessionState] = await Promise.all([
|
|
242
|
+
getCreatedTime(sessionPath),
|
|
243
|
+
loadRoundSyntheses(sessionPath),
|
|
244
|
+
loadSessionState(sessionPath),
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
// Extract data from syntheses
|
|
248
|
+
const roundCount = syntheses.length;
|
|
249
|
+
const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
|
|
250
|
+
|
|
251
|
+
// Calculate progress based on round count and convergence
|
|
252
|
+
const progress = calculateMultiCliProgress(syntheses);
|
|
253
|
+
|
|
254
|
+
// Build discussion topic for frontend
|
|
255
|
+
const discussionTopic = buildDiscussionTopic(sessionState, latestSynthesis);
|
|
256
|
+
|
|
257
|
+
// Determine status from session state or synthesis convergence
|
|
258
|
+
const status = sessionState?.status ||
|
|
259
|
+
(latestSynthesis?.convergence?.recommendation === 'converged' ? 'converged' : 'analyzing');
|
|
260
|
+
|
|
261
|
+
const session: MultiCliSession = {
|
|
262
|
+
id: entry.name,
|
|
263
|
+
type: 'multi-cli-plan',
|
|
264
|
+
path: sessionPath,
|
|
265
|
+
createdAt,
|
|
266
|
+
plan: latestSynthesis,
|
|
267
|
+
tasks: extractTasksFromSyntheses(syntheses),
|
|
268
|
+
progress,
|
|
269
|
+
// Extended multi-cli specific fields
|
|
270
|
+
roundCount,
|
|
271
|
+
topicTitle: sessionState?.task_description || 'Discussion Topic',
|
|
272
|
+
status,
|
|
273
|
+
metadata: {
|
|
274
|
+
roundId: roundCount,
|
|
275
|
+
timestamp: sessionState?.updated_at || createdAt,
|
|
276
|
+
currentPhase: sessionState?.current_phase || 1,
|
|
277
|
+
},
|
|
278
|
+
discussionTopic,
|
|
279
|
+
rounds: syntheses,
|
|
280
|
+
latestSynthesis,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return session;
|
|
284
|
+
}),
|
|
285
|
+
))
|
|
286
|
+
.filter((session): session is MultiCliSession => session !== null)
|
|
287
|
+
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
288
|
+
|
|
289
|
+
return sessions;
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
if (err?.code === 'ENOENT') return [];
|
|
292
|
+
console.error(`Error scanning ${dir}:`, err?.message || String(err));
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// NEW Schema types for multi-cli synthesis
|
|
298
|
+
interface SolutionFileAction {
|
|
299
|
+
file: string;
|
|
300
|
+
line: number;
|
|
301
|
+
action: 'modify' | 'create' | 'delete';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
interface SolutionTask {
|
|
305
|
+
id: string;
|
|
306
|
+
name: string;
|
|
307
|
+
depends_on: string[];
|
|
308
|
+
files: SolutionFileAction[];
|
|
309
|
+
key_point: string | null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
interface SolutionImplementationPlan {
|
|
313
|
+
approach: string;
|
|
314
|
+
tasks: SolutionTask[];
|
|
315
|
+
execution_flow: string;
|
|
316
|
+
milestones: string[];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
interface SolutionDependencies {
|
|
320
|
+
internal: string[];
|
|
321
|
+
external: string[];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
interface Solution {
|
|
325
|
+
name: string;
|
|
326
|
+
source_cli: string[];
|
|
327
|
+
feasibility: number; // 0-1
|
|
328
|
+
effort: 'low' | 'medium' | 'high';
|
|
329
|
+
risk: 'low' | 'medium' | 'high';
|
|
330
|
+
summary: string;
|
|
331
|
+
implementation_plan: SolutionImplementationPlan;
|
|
332
|
+
dependencies: SolutionDependencies;
|
|
333
|
+
technical_concerns: string[];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
interface SynthesisConvergence {
|
|
337
|
+
score: number;
|
|
338
|
+
new_insights: boolean;
|
|
339
|
+
recommendation: 'converged' | 'continue' | 'user_input_needed';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
interface SynthesisCrossVerification {
|
|
343
|
+
agreements: string[];
|
|
344
|
+
disagreements: string[];
|
|
345
|
+
resolution: string;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
interface RoundSynthesis {
|
|
349
|
+
round: number;
|
|
350
|
+
// NEW schema fields
|
|
351
|
+
solutions?: Solution[];
|
|
352
|
+
convergence?: SynthesisConvergence;
|
|
353
|
+
cross_verification?: SynthesisCrossVerification;
|
|
354
|
+
clarification_questions?: string[];
|
|
355
|
+
// OLD schema fields (backward compatibility)
|
|
356
|
+
converged?: boolean;
|
|
357
|
+
tasks?: unknown[];
|
|
358
|
+
synthesis?: unknown;
|
|
359
|
+
[key: string]: unknown;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Load all synthesis.json files from rounds subdirectories
|
|
364
|
+
* @param sessionPath - Session directory path
|
|
365
|
+
* @returns Array of synthesis objects sorted by round number
|
|
366
|
+
*/
|
|
367
|
+
async function loadRoundSyntheses(sessionPath: string): Promise<RoundSynthesis[]> {
|
|
368
|
+
const roundsDir = join(sessionPath, 'rounds');
|
|
369
|
+
const syntheses: RoundSynthesis[] = [];
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const roundEntries = await readdir(roundsDir, { withFileTypes: true });
|
|
373
|
+
|
|
374
|
+
const roundDirs = roundEntries
|
|
375
|
+
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
|
|
376
|
+
.map((entry) => ({
|
|
377
|
+
name: entry.name,
|
|
378
|
+
num: parseInt(entry.name, 10),
|
|
379
|
+
}))
|
|
380
|
+
.sort((a, b) => a.num - b.num);
|
|
381
|
+
|
|
382
|
+
for (const roundDir of roundDirs) {
|
|
383
|
+
const synthesisPath = join(roundsDir, roundDir.name, 'synthesis.json');
|
|
384
|
+
try {
|
|
385
|
+
const content = await readFile(synthesisPath, 'utf8');
|
|
386
|
+
const synthesis = JSON.parse(content) as RoundSynthesis;
|
|
387
|
+
synthesis.round = roundDir.num;
|
|
388
|
+
syntheses.push(synthesis);
|
|
389
|
+
} catch (e) {
|
|
390
|
+
console.warn('Failed to parse synthesis file:', synthesisPath, (e as Error).message);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch (e) {
|
|
394
|
+
// Ignore ENOENT errors (directory doesn't exist), warn on others
|
|
395
|
+
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
396
|
+
console.warn('Failed to read rounds directory:', roundsDir, (e as Error).message);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return syntheses;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Extended Progress interface for multi-cli sessions
|
|
404
|
+
interface MultiCliProgress extends Progress {
|
|
405
|
+
convergenceScore?: number;
|
|
406
|
+
recommendation?: 'converged' | 'continue' | 'user_input_needed';
|
|
407
|
+
solutionsCount?: number;
|
|
408
|
+
avgFeasibility?: number;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Calculate progress for multi-cli-plan sessions
|
|
413
|
+
* Uses new convergence.score and convergence.recommendation when available
|
|
414
|
+
* Falls back to old converged boolean for backward compatibility
|
|
415
|
+
* @param syntheses - Array of round syntheses
|
|
416
|
+
* @returns Progress info with convergence metrics
|
|
417
|
+
*/
|
|
418
|
+
function calculateMultiCliProgress(syntheses: RoundSynthesis[]): MultiCliProgress {
|
|
419
|
+
if (syntheses.length === 0) {
|
|
420
|
+
return { total: 0, completed: 0, percentage: 0 };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const latestSynthesis = syntheses[syntheses.length - 1];
|
|
424
|
+
|
|
425
|
+
// NEW schema: Use convergence object
|
|
426
|
+
if (latestSynthesis.convergence) {
|
|
427
|
+
const { score, recommendation } = latestSynthesis.convergence;
|
|
428
|
+
const isConverged = recommendation === 'converged';
|
|
429
|
+
|
|
430
|
+
// Calculate solutions metrics
|
|
431
|
+
const solutions = latestSynthesis.solutions || [];
|
|
432
|
+
const solutionsCount = solutions.length;
|
|
433
|
+
const avgFeasibility = solutionsCount > 0
|
|
434
|
+
? solutions.reduce((sum, s) => sum + (s.feasibility || 0), 0) / solutionsCount
|
|
435
|
+
: 0;
|
|
436
|
+
|
|
437
|
+
// Total is based on rounds, percentage derived from convergence score
|
|
438
|
+
const total = syntheses.length;
|
|
439
|
+
const completed = isConverged ? total : Math.max(0, total - 1);
|
|
440
|
+
const percentage = isConverged ? 100 : Math.round(score * 100);
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
total,
|
|
444
|
+
completed,
|
|
445
|
+
percentage,
|
|
446
|
+
convergenceScore: score,
|
|
447
|
+
recommendation,
|
|
448
|
+
solutionsCount,
|
|
449
|
+
avgFeasibility: Math.round(avgFeasibility * 100) / 100
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// OLD schema: Fallback to converged boolean
|
|
454
|
+
const isConverged = latestSynthesis.converged === true;
|
|
455
|
+
const total = syntheses.length;
|
|
456
|
+
const completed = isConverged ? total : Math.max(0, total - 1);
|
|
457
|
+
const percentage = isConverged ? 100 : Math.round((completed / Math.max(total, 1)) * 100);
|
|
458
|
+
|
|
459
|
+
return { total, completed, percentage };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Extract tasks from synthesis objects
|
|
464
|
+
* NEW schema: Extract from solutions[].implementation_plan.tasks
|
|
465
|
+
* OLD schema: Extract from tasks[] array directly
|
|
466
|
+
* @param syntheses - Array of round syntheses
|
|
467
|
+
* @returns Normalized tasks from latest synthesis
|
|
468
|
+
*/
|
|
469
|
+
function extractTasksFromSyntheses(syntheses: RoundSynthesis[]): NormalizedTask[] {
|
|
470
|
+
if (syntheses.length === 0) return [];
|
|
471
|
+
|
|
472
|
+
const latestSynthesis = syntheses[syntheses.length - 1];
|
|
473
|
+
|
|
474
|
+
// NEW schema: Extract tasks from solutions
|
|
475
|
+
if (latestSynthesis.solutions && Array.isArray(latestSynthesis.solutions)) {
|
|
476
|
+
const allTasks: NormalizedTask[] = [];
|
|
477
|
+
|
|
478
|
+
for (const solution of latestSynthesis.solutions) {
|
|
479
|
+
const implPlan = solution.implementation_plan;
|
|
480
|
+
if (!implPlan?.tasks || !Array.isArray(implPlan.tasks)) continue;
|
|
481
|
+
|
|
482
|
+
for (const task of implPlan.tasks) {
|
|
483
|
+
const normalizedTask = normalizeSolutionTask(task, solution);
|
|
484
|
+
if (normalizedTask) {
|
|
485
|
+
allTasks.push(normalizedTask);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Sort by task ID
|
|
491
|
+
return allTasks.sort((a, b) => {
|
|
492
|
+
const aNum = parseInt(a.id?.replace(/\D/g, '') || '0');
|
|
493
|
+
const bNum = parseInt(b.id?.replace(/\D/g, '') || '0');
|
|
494
|
+
return aNum - bNum;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// OLD schema: Extract from tasks array directly
|
|
499
|
+
const tasks = latestSynthesis.tasks;
|
|
500
|
+
if (!Array.isArray(tasks)) return [];
|
|
501
|
+
|
|
502
|
+
return tasks
|
|
503
|
+
.map((task) => normalizeTask(task))
|
|
504
|
+
.filter((task): task is NormalizedTask => task !== null);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Normalize a solution task from NEW schema to NormalizedTask
|
|
509
|
+
* @param task - SolutionTask from new schema
|
|
510
|
+
* @param solution - Parent solution for context
|
|
511
|
+
* @returns Normalized task
|
|
512
|
+
*/
|
|
513
|
+
function normalizeSolutionTask(task: SolutionTask, solution: Solution): NormalizedTask | null {
|
|
514
|
+
if (!task || !task.id) return null;
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
id: task.id,
|
|
518
|
+
title: task.name || 'Untitled Task',
|
|
519
|
+
status: (task as unknown as { status?: string }).status || 'pending',
|
|
520
|
+
meta: {
|
|
521
|
+
type: 'implementation',
|
|
522
|
+
agent: null,
|
|
523
|
+
scope: solution.name || null,
|
|
524
|
+
module: null
|
|
525
|
+
},
|
|
526
|
+
context: {
|
|
527
|
+
requirements: task.key_point ? [task.key_point] : [],
|
|
528
|
+
focus_paths: task.files?.map(f => f.file) || [],
|
|
529
|
+
acceptance: [],
|
|
530
|
+
depends_on: task.depends_on || []
|
|
531
|
+
},
|
|
532
|
+
flow_control: {
|
|
533
|
+
implementation_approach: task.files?.map((f, i) => ({
|
|
534
|
+
step: `Step ${i + 1}`,
|
|
535
|
+
action: `${f.action} ${f.file}${f.line ? ` at line ${f.line}` : ''}`
|
|
536
|
+
})) || []
|
|
537
|
+
},
|
|
538
|
+
_raw: {
|
|
539
|
+
task,
|
|
540
|
+
solution: {
|
|
541
|
+
name: solution.name,
|
|
542
|
+
source_cli: solution.source_cli,
|
|
543
|
+
feasibility: solution.feasibility,
|
|
544
|
+
effort: solution.effort,
|
|
545
|
+
risk: solution.risk
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
145
551
|
/**
|
|
146
552
|
* Load plan.json or fix-plan.json from session directory
|
|
147
553
|
* @param sessionPath - Session directory path
|
|
@@ -368,14 +774,19 @@ function calculateProgress(tasks: NormalizedTask[]): Progress {
|
|
|
368
774
|
/**
|
|
369
775
|
* Get detailed lite task info
|
|
370
776
|
* @param workflowDir - Workflow directory
|
|
371
|
-
* @param type - 'lite-plan'
|
|
777
|
+
* @param type - 'lite-plan', 'lite-fix', or 'multi-cli-plan'
|
|
372
778
|
* @param sessionId - Session ID
|
|
373
779
|
* @returns Detailed task info
|
|
374
780
|
*/
|
|
375
781
|
export async function getLiteTaskDetail(workflowDir: string, type: string, sessionId: string): Promise<LiteTaskDetail | null> {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
782
|
+
let dir: string;
|
|
783
|
+
if (type === 'lite-plan') {
|
|
784
|
+
dir = join(workflowDir, '.lite-plan', sessionId);
|
|
785
|
+
} else if (type === 'multi-cli-plan') {
|
|
786
|
+
dir = join(workflowDir, '.multi-cli-plan', sessionId);
|
|
787
|
+
} else {
|
|
788
|
+
dir = join(workflowDir, '.lite-fix', sessionId);
|
|
789
|
+
}
|
|
379
790
|
|
|
380
791
|
try {
|
|
381
792
|
const stats = await stat(dir);
|
|
@@ -384,6 +795,29 @@ export async function getLiteTaskDetail(workflowDir: string, type: string, sessi
|
|
|
384
795
|
return null;
|
|
385
796
|
}
|
|
386
797
|
|
|
798
|
+
// For multi-cli-plan, use synthesis-based loading
|
|
799
|
+
if (type === 'multi-cli-plan') {
|
|
800
|
+
const [syntheses, explorations, clarifications] = await Promise.all([
|
|
801
|
+
loadRoundSyntheses(dir),
|
|
802
|
+
loadExplorations(dir),
|
|
803
|
+
loadClarifications(dir),
|
|
804
|
+
]);
|
|
805
|
+
|
|
806
|
+
const latestSynthesis = syntheses.length > 0 ? syntheses[syntheses.length - 1] : null;
|
|
807
|
+
|
|
808
|
+
const detail: LiteTaskDetail = {
|
|
809
|
+
id: sessionId,
|
|
810
|
+
type,
|
|
811
|
+
path: dir,
|
|
812
|
+
plan: latestSynthesis,
|
|
813
|
+
tasks: extractTasksFromSyntheses(syntheses),
|
|
814
|
+
explorations,
|
|
815
|
+
clarifications,
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
return detail;
|
|
819
|
+
}
|
|
820
|
+
|
|
387
821
|
const [plan, tasks, explorations, clarifications, diagnoses] = await Promise.all([
|
|
388
822
|
loadPlanJson(dir),
|
|
389
823
|
loadTaskJsons(dir),
|
|
@@ -451,18 +451,21 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
|
|
|
451
451
|
const devices: Array<{ name: string; type: string; index: number }> = [];
|
|
452
452
|
|
|
453
453
|
if (process.platform === 'win32') {
|
|
454
|
-
// Windows: Use
|
|
454
|
+
// Windows: Use PowerShell Get-CimInstance (wmic is deprecated in Windows 11)
|
|
455
455
|
try {
|
|
456
456
|
const { execSync } = await import('child_process');
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
457
|
+
const psOutput = execSync(
|
|
458
|
+
'powershell -NoProfile -Command "(Get-CimInstance Win32_VideoController).Name"',
|
|
459
|
+
{
|
|
460
|
+
encoding: 'utf-8',
|
|
461
|
+
timeout: EXEC_TIMEOUTS.SYSTEM_INFO,
|
|
462
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
463
|
+
}
|
|
464
|
+
);
|
|
462
465
|
|
|
463
|
-
const lines =
|
|
466
|
+
const lines = psOutput.split('\n')
|
|
464
467
|
.map(line => line.trim())
|
|
465
|
-
.filter(line => line
|
|
468
|
+
.filter(line => line);
|
|
466
469
|
|
|
467
470
|
lines.forEach((name, index) => {
|
|
468
471
|
if (name) {
|
|
@@ -476,7 +479,7 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
|
|
|
476
479
|
}
|
|
477
480
|
});
|
|
478
481
|
} catch (e) {
|
|
479
|
-
console.warn('[CodexLens]
|
|
482
|
+
console.warn('[CodexLens] PowerShell GPU detection failed:', (e as Error).message);
|
|
480
483
|
}
|
|
481
484
|
} else {
|
|
482
485
|
// Linux/Mac: Try nvidia-smi for NVIDIA GPUs
|