@winspan/claude-forge 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/autopilot/quality-gate.d.ts +1 -1
  2. package/dist/autopilot/quality-gate.d.ts.map +1 -1
  3. package/dist/autopilot/quality-gate.js +1 -1
  4. package/dist/autopilot/quality-gate.js.map +1 -1
  5. package/dist/daemon/index.js +1 -1
  6. package/dist/daemon/index.js.map +1 -1
  7. package/dist/pipeline/artifact-enforcement.d.ts +2 -2
  8. package/dist/pipeline/artifact-enforcement.js +2 -2
  9. package/dist/pipeline/dynamic-node-executor.d.ts +1 -1
  10. package/dist/pipeline/dynamic-node-executor.js +1 -1
  11. package/dist/pipeline/i-node-executor.d.ts +2 -2
  12. package/dist/pipeline/i-node-executor.js +1 -1
  13. package/dist/pipeline/index.d.ts +1 -1
  14. package/dist/pipeline/index.d.ts.map +1 -1
  15. package/dist/pipeline/index.js +26 -112
  16. package/dist/pipeline/index.js.map +1 -1
  17. package/dist/pipeline/node-type-sync.d.ts.map +1 -1
  18. package/dist/pipeline/node-type-sync.js.map +1 -1
  19. package/dist/pipeline/step-validator.d.ts.map +1 -1
  20. package/dist/pipeline/step-validator.js +1 -2
  21. package/dist/pipeline/step-validator.js.map +1 -1
  22. package/dist/retrospective/index.d.ts +1 -1
  23. package/dist/retrospective/index.js +1 -1
  24. package/dist/retrospective/metrics-extractor.d.ts +1 -1
  25. package/dist/retrospective/metrics-extractor.js +1 -1
  26. package/dist/storage/repositories/maintenance-repository.d.ts +9 -1
  27. package/dist/storage/repositories/maintenance-repository.d.ts.map +1 -1
  28. package/dist/storage/repositories/maintenance-repository.js +84 -77
  29. package/dist/storage/repositories/maintenance-repository.js.map +1 -1
  30. package/dist/web/server.d.ts +1 -1
  31. package/dist/web/server.d.ts.map +1 -1
  32. package/dist/web-static/assets/{index-BcPVkZqZ.js → index-DABYEmUB.js} +1 -1
  33. package/dist/web-static/index.html +1 -1
  34. package/package.json +1 -1
  35. package/dist/pipeline/phase-manager.d.ts +0 -190
  36. package/dist/pipeline/phase-manager.d.ts.map +0 -1
  37. package/dist/pipeline/phase-manager.js +0 -961
  38. package/dist/pipeline/phase-manager.js.map +0 -1
@@ -1,961 +0,0 @@
1
- import { PHASE_LABELS, nextPhase } from './types.js';
2
- import { logger } from '../utils/logger.js';
3
- import { cacheRegistry } from '../core/cache-registry.js';
4
- import { CheckpointManager } from './checkpoint.js';
5
- import { ArtifactEnforcement } from './artifact-enforcement.js';
6
- import fsPromises from 'fs/promises';
7
- import path from 'path';
8
- import { TIME, CACHE, PHASE_DETECTION } from '../constants.js';
9
- export class PhaseManager {
10
- store;
11
- checkpointManager;
12
- artifactEnforcement;
13
- knowledgeContext = cacheRegistry.register('phase:knowledge', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
14
- totalPhaseEventCounts = cacheRegistry.register('phase:eventCounts', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
15
- /** 追踪每个 pipeline:phase 已见过的工具类别(逗号分隔字符串,节省内存) */
16
- phaseToolsSeen = cacheRegistry.register('phase:toolsSeen', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
17
- phaseFixAttempts = cacheRegistry.register('phase:fixAttempts', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
18
- /** 追踪每个 pipeline:phase 的 Write/Edit 调用次数 */
19
- phaseWriteEditCounts = cacheRegistry.register('phase:writeEditCounts', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
20
- /** 追踪每个 pipeline:phase 最近 5 次工具调用 */
21
- recentTools = cacheRegistry.register('phase:recentTools', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
22
- /** 质量门禁去重缓存:已通过的 pipeline:phase 不再重复检查(24h TTL) */
23
- gatePassedCache = cacheRegistry.register('phase:gatePassed', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
24
- /** 活动阈值触发标记:标记为 true 的 pipeline:phase 跳过质量门禁直接推进 */
25
- activityThresholdTriggered = cacheRegistry.register('phase:thresholdTriggered', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.DAY });
26
- /** 产物门禁阻断缓存:记录被阻断的 pipeline:phase,用于注入提示 */
27
- artifactGateBlocked = cacheRegistry.register('phase:artifactBlocked', { max: CACHE.PHASE_MANAGER_MAX, ttl: TIME.HOUR });
28
- static MAX_FIX_ATTEMPTS = 2;
29
- static EMPTY_PHASE_ADVANCE_THRESHOLD = 3;
30
- constructor(store) {
31
- this.store = store;
32
- this.checkpointManager = new CheckpointManager();
33
- this.artifactEnforcement = new ArtifactEnforcement();
34
- }
35
- /**
36
- * Pipeline 关闭时清理所有相关缓存,防止内存泄漏
37
- */
38
- cleanup(pipelineId) {
39
- const phases = ['analyze', 'design', 'profile', 'code', 'test', 'review'];
40
- for (const phase of phases) {
41
- const key = `${pipelineId}:${phase}`;
42
- this.phaseToolsSeen.delete(key);
43
- this.totalPhaseEventCounts.delete(key);
44
- this.phaseFixAttempts.delete(key);
45
- this.phaseWriteEditCounts.delete(key);
46
- this.recentTools.delete(key);
47
- this.gatePassedCache.delete(key);
48
- this.activityThresholdTriggered.delete(key);
49
- this.artifactGateBlocked.delete(key);
50
- // 清理阶段 checkpoint
51
- this.checkpointManager.cleanup(pipelineId, phase).catch(err => {
52
- logger.debug(`[Pipeline] checkpoint 清理失败 ${pipelineId}:${phase}:${err}`);
53
- });
54
- }
55
- this.knowledgeContext.delete(pipelineId);
56
- this.artifactEnforcement.cleanup(pipelineId);
57
- logger.debug(`[Pipeline] 已清理 PhaseManager 缓存:${pipelineId.slice(0, 8)}`);
58
- }
59
- /**
60
- * 注入知识库内容到特定 Pipeline
61
- */
62
- setKnowledgeContext(pipelineId, knowledge) {
63
- this.knowledgeContext.set(pipelineId, knowledge);
64
- }
65
- /**
66
- * 获取产物门禁提示(供 stage-scheduler 注入到 AI 上下文)
67
- */
68
- getArtifactGateReminder(pipelineId, phase) {
69
- const key = `${pipelineId}:${phase}`;
70
- return this.artifactGateBlocked.get(key) || null;
71
- }
72
- /**
73
- * 清除产物门禁提示(AI 看到提示后清除,避免重复注入)
74
- */
75
- clearArtifactGateReminder(pipelineId, phase) {
76
- const key = `${pipelineId}:${phase}`;
77
- this.artifactGateBlocked.delete(key);
78
- }
79
- /**
80
- * ESC 恢复:恢复指定 key 的 phaseToolsSeen(由 PipelineEngine 在从 DB 加载时调用)
81
- */
82
- restoreToolsSeen(key, tools) {
83
- this.phaseToolsSeen.set(key, [...tools].join(','));
84
- }
85
- /**
86
- * ESC 恢复:恢复指定 key 的 totalPhaseEventCounts(由 PipelineEngine 在从 DB 加载时调用)
87
- */
88
- restoreEventCount(key, count) {
89
- this.totalPhaseEventCounts.set(key, count);
90
- }
91
- /**
92
- * 恢复指定 key 的阶段推进状态(daemon_state 持久化)
93
- */
94
- restorePhaseState(key, state) {
95
- if (state.toolsSeen && state.toolsSeen.length > 0) {
96
- this.phaseToolsSeen.set(key, state.toolsSeen.join(','));
97
- }
98
- if (typeof state.eventCount === 'number') {
99
- this.totalPhaseEventCounts.set(key, state.eventCount);
100
- }
101
- if (typeof state.fixAttempts === 'number') {
102
- this.phaseFixAttempts.set(key, state.fixAttempts);
103
- }
104
- if (typeof state.writeEditCount === 'number') {
105
- this.phaseWriteEditCounts.set(key, state.writeEditCount);
106
- }
107
- if (state.recentTools && state.recentTools.length > 0) {
108
- this.recentTools.set(key, state.recentTools.slice(-5));
109
- }
110
- }
111
- /**
112
- * 导出指定 key 的阶段推进状态(用于持久化到 daemon_state)
113
- */
114
- exportPhaseState(key) {
115
- const toolsStr = this.phaseToolsSeen.get(key) ?? '';
116
- return {
117
- toolsSeen: toolsStr ? toolsStr.split(',') : [],
118
- eventCount: this.totalPhaseEventCounts.get(key) ?? 0,
119
- fixAttempts: this.phaseFixAttempts.get(key) ?? 0,
120
- writeEditCount: this.phaseWriteEditCounts.get(key) ?? 0,
121
- recentTools: this.recentTools.get(key) ?? [],
122
- };
123
- }
124
- /**
125
- * 重置指定 Pipeline 的质量门禁修复计数(新 Pipeline 启动或用户手动重试时调用)
126
- */
127
- resetFixAttempts(pipelineId) {
128
- for (const key of [...this.phaseFixAttempts.keys()]) {
129
- if (key.startsWith(`${pipelineId}:`)) {
130
- this.phaseFixAttempts.delete(key);
131
- }
132
- }
133
- }
134
- // ── INodeExecutor 接口实现(别名方法) ──────────────────────────────────────
135
- /**
136
- * 获取当前节点指令(INodeExecutor 接口)
137
- */
138
- getInstruction(pipeline) {
139
- return this.getPhaseInstruction(pipeline);
140
- }
141
- /**
142
- * 恢复节点状态(INodeExecutor 接口)
143
- */
144
- restoreState(key, state) {
145
- this.restorePhaseState(key, state);
146
- }
147
- /**
148
- * 导出节点状态(INodeExecutor 接口)
149
- */
150
- exportState(key) {
151
- return this.exportPhaseState(key);
152
- }
153
- /**
154
- * 生成当前阶段的执行指令(注入 additionalContext)
155
- */
156
- getPhaseInstruction(pipeline) {
157
- const phase = pipeline.phase;
158
- if (phase === 'done')
159
- return '';
160
- const progress = this.store.getPhaseProgress(pipeline.id, phase);
161
- // 空阶段(无任务)不注入指令,等待自愈推进跳过
162
- if (progress.total === 0) {
163
- logger.warn(`[Pipeline] 阶段 ${phase} 无任务,跳过指令注入,等待自愈推进`);
164
- return '';
165
- }
166
- const phaseTasks = pipeline.tasks.filter(t => t.phase === phase);
167
- const currentTask = phaseTasks.find(t => t.status === 'pending' || t.status === 'in_progress');
168
- // 标记当前任务为进行中
169
- if (currentTask && currentTask.status === 'pending') {
170
- this.store.updateTaskStatus(currentTask.id, 'in_progress');
171
- }
172
- const parts = [];
173
- // 状态头(单行,精简需求显示)
174
- const reqShort = pipeline.requirement.length > 80
175
- ? pipeline.requirement.substring(0, 80) + '…'
176
- : pipeline.requirement;
177
- parts.push(`[Forge] ${PHASE_LABELS[phase]}(${progress.completed}/${progress.total})| ${reqShort}`);
178
- // 当前任务(仅显示标题,不展开详情)
179
- if (currentTask) {
180
- parts.push(`→ 当前任务:${currentTask.title}`);
181
- // 强化任务完成信号注入:必须主动执行,不可跳过
182
- parts.push(`⚠️ 任务完成后必须立即执行(不可跳过):\`echo "FORGE_TASK_DONE: ${currentTask.id}"\`\n 此命令是 Forge 进度追踪的唯一可靠来源,未执行将导致进度卡死。`);
183
- }
184
- // 阶段指导:注入当前阶段的具体工作指导(让 Claude 知道该做什么)
185
- const directive = this.getPhaseDirective(phase, currentTask, pipeline);
186
- if (directive) {
187
- parts.push(`\n${directive}`);
188
- }
189
- // 待完成任务(仅显示前 3 个,超过则省略)
190
- const pendingTasks = phaseTasks.filter(t => t.status !== 'completed');
191
- if (pendingTasks.length > 1) {
192
- const toShow = pendingTasks.slice(0, 3);
193
- const remaining = pendingTasks.length - toShow.length;
194
- parts.push(`待办:${toShow.map(t => t.title).join('、')}${remaining > 0 ? ` 等${remaining}项` : ''}`);
195
- }
196
- return parts.join(' ');
197
- }
198
- /**
199
- * 根据 PostToolUse 事件判断是否推进阶段(重构版:职责分离 + 信号优先)
200
- */
201
- async checkAndAdvance(pipeline, event, qualityGate) {
202
- const phase = pipeline.phase;
203
- // 1. 工具追踪
204
- this.trackToolUsage(pipeline, event);
205
- // 1.5. 产物追踪(第二层防护)
206
- this.artifactEnforcement.trackToolCall(pipeline.id, phase, event.tool_name || '', event.tool_input || {});
207
- // 2. 空阶段自愈
208
- const emptyPhaseResult = this.handleEmptyPhase(pipeline);
209
- if (emptyPhaseResult)
210
- return { ...emptyPhaseResult, advanceReason: 'empty_phase' };
211
- // 3. 任务完成检测
212
- this.detectAndMarkCompletion(pipeline, event);
213
- // 4. 活动阈值检测
214
- const thresholdResult = this.handleActivityThreshold(pipeline);
215
- // 5. 检查阶段是否全部完成
216
- const progress = this.store.getPhaseProgress(pipeline.id, phase);
217
- if (progress.total === 0 || progress.completed < progress.total) {
218
- return { advanced: false };
219
- }
220
- // 6. 产物门禁检查(第三层防护)
221
- const artifactGateResult = this.artifactEnforcement.checkGate(pipeline.id, phase, pipeline.taskType, pipeline.complexity);
222
- if (!artifactGateResult.passed) {
223
- // 产物不足,阻止推进
224
- logger.warn(`[产物门禁] ${phase} 阶段被阻断:${artifactGateResult.reason}`);
225
- logger.info(`[产物门禁] 缺失类型:${artifactGateResult.missingTypes.join(', ')},已有产物:${artifactGateResult.artifactCount} 个`);
226
- // 缓存提示,供下次 AI 调用时注入
227
- const key = `${pipeline.id}:${phase}`;
228
- this.artifactGateBlocked.set(key, artifactGateResult.reminder || '');
229
- return {
230
- advanced: false,
231
- artifactBlocked: true,
232
- artifactReminder: artifactGateResult.reminder,
233
- };
234
- }
235
- // 产物门禁通过
236
- logger.info(`[产物门禁] ${phase} 阶段通过,产物 ${artifactGateResult.artifactCount} 个`);
237
- // 7. 质量门禁(仅首次完成时触发,活动阈值触发时跳过)
238
- if (qualityGate) {
239
- const gateResult = await this.runQualityGateOnce(pipeline, event, qualityGate);
240
- if (gateResult.blocked) {
241
- return {
242
- advanced: false,
243
- qualityBlocked: true,
244
- qualityIssues: gateResult.issues,
245
- qualityLevel: gateResult.level,
246
- fixAttempts: gateResult.fixAttempts,
247
- blocking: gateResult.blocking, // ✅ Propagate hard blocking (must rule violation)
248
- };
249
- }
250
- }
251
- // 8. 阶段推进
252
- const advanceReason = thresholdResult ? 'activity_threshold' : 'task_complete';
253
- const advanceResult = await this.advancePhase(pipeline, thresholdResult?.message);
254
- // 清理产物追踪状态
255
- this.artifactEnforcement.resetPhase(pipeline.id, phase);
256
- return {
257
- ...advanceResult,
258
- advanceReason,
259
- // 活动阈值触发时,将 systemMessage 传递给调用方注入给 Claude
260
- systemMessage: thresholdResult?.systemMessage,
261
- };
262
- }
263
- /**
264
- * 1. 工具追踪:记录工具类别、调用次数、最近工具
265
- */
266
- trackToolUsage(pipeline, event) {
267
- const phase = pipeline.phase;
268
- const toolName = event.tool_name || '';
269
- const inputStr = event.tool_input ? JSON.stringify(event.tool_input) : '';
270
- const key = `${pipeline.id}:${phase}`;
271
- // 工具类别追踪
272
- const toolsStr = this.phaseToolsSeen.get(key) ?? '';
273
- const toolsSeenSet = toolsStr ? new Set(toolsStr.split(',')) : new Set();
274
- const toolCategory = this.categorizeToolCall(toolName, inputStr);
275
- if (toolCategory)
276
- toolsSeenSet.add(toolCategory);
277
- this.phaseToolsSeen.set(key, [...toolsSeenSet].join(','));
278
- // 调用次数追踪
279
- const eventCount = this.totalPhaseEventCounts.get(key) || 0;
280
- this.totalPhaseEventCounts.set(key, eventCount + 1);
281
- // Write/Edit 次数追踪
282
- if (toolName === 'Write' || toolName === 'Edit') {
283
- const writeEditCount = this.phaseWriteEditCounts.get(key) || 0;
284
- this.phaseWriteEditCounts.set(key, writeEditCount + 1);
285
- }
286
- // 最近 5 次工具追踪
287
- const recent = this.recentTools.get(key) || [];
288
- recent.push(toolName);
289
- if (recent.length > 5)
290
- recent.shift();
291
- this.recentTools.set(key, recent);
292
- }
293
- /**
294
- * 2. 空阶段自愈:当前阶段无任务时自动推进
295
- */
296
- handleEmptyPhase(pipeline) {
297
- const phase = pipeline.phase;
298
- const progress = this.store.getPhaseProgress(pipeline.id, phase);
299
- const key = `${pipeline.id}:${phase}`;
300
- const eventCount = this.totalPhaseEventCounts.get(key) || 0;
301
- if (progress.total === 0 && eventCount >= PhaseManager.EMPTY_PHASE_ADVANCE_THRESHOLD) {
302
- logger.warn(`[Pipeline] 阶段 ${PHASE_LABELS[phase]} 无任务(phase 标签可能无效),自动推进到下一阶段`);
303
- const newPh = nextPhase(phase, pipeline.plannedPhases);
304
- this.store.updatePhase(pipeline.id, newPh);
305
- this.phaseToolsSeen.delete(key);
306
- this.gatePassedCache.delete(key);
307
- this.activityThresholdTriggered.delete(key);
308
- return { advanced: true, newPhase: newPh };
309
- }
310
- return null;
311
- }
312
- /**
313
- * 3. 任务完成检测:优先显式信号,回退到启发式
314
- */
315
- detectAndMarkCompletion(pipeline, event) {
316
- const phase = pipeline.phase;
317
- const toolName = event.tool_name || '';
318
- const inputStr = event.tool_input ? JSON.stringify(event.tool_input) : '';
319
- const pendingTasks = pipeline.tasks.filter(t => t.phase === phase && (t.status === 'in_progress' || t.status === 'pending'));
320
- for (const currentTask of pendingTasks) {
321
- const completed = this.detectTaskCompletion(phase, toolName, inputStr, event, currentTask, pipeline.id);
322
- if (completed) {
323
- const artifact = this.summarizeArtifact(toolName, inputStr, event);
324
- this.store.updateTaskStatus(currentTask.id, 'completed', artifact);
325
- logger.info(`[Pipeline] 任务已完成:${currentTask.title} | 成果:${artifact}`);
326
- break; // 每次事件只完成一个任务
327
- }
328
- }
329
- }
330
- /**
331
- * 4. 活动阈值检测:达到阈值且检测到停滞后才强制完成剩余任务
332
- *
333
- * 改进逻辑(v1.2.4):
334
- * - 仅达到阈值不再直接强制完成,需同时满足"停滞"条件
335
- * - 停滞定义:最近工具调用全为只读操作,或 Write/Edit 占比极低
336
- * - 达到阈值但仍有进展时,注入提醒而非强制完成
337
- */
338
- handleActivityThreshold(pipeline) {
339
- const phase = pipeline.phase;
340
- const progress = this.store.getPhaseProgress(pipeline.id, phase);
341
- const key = `${pipeline.id}:${phase}`;
342
- const eventCount = this.totalPhaseEventCounts.get(key) || 0;
343
- const threshold = this.getPhaseActivityThreshold(phase, pipeline.complexity);
344
- // 未达到阈值或已全部完成,不处理
345
- if (progress.completed >= progress.total || eventCount < threshold) {
346
- return null;
347
- }
348
- // 达到阈值但仍有实质性进展 → 仅注入提醒,不强制完成
349
- if (!this.isStagnant(pipeline)) {
350
- // 仅在首次达到阈值时提醒一次(避免每次事件都重复注入)
351
- const nudgeKey = `${key}:nudged`;
352
- if (!this.activityThresholdTriggered.get(nudgeKey)) {
353
- this.activityThresholdTriggered.set(nudgeKey, true);
354
- logger.info(`[Pipeline] 活动阈值已达到(${eventCount}/${threshold}),但检测到持续进展,注入完成提醒`);
355
- }
356
- return null;
357
- }
358
- // 达到阈值 + 停滞 → 强制完成
359
- logger.warn(`[Pipeline] ⚠️ 活动阈值(${threshold})已达到且检测到停滞,强制完成 ${PHASE_LABELS[phase]} 阶段剩余任务`);
360
- const remaining = pipeline.tasks.filter(t => t.phase === phase && t.status !== 'completed');
361
- for (const t of remaining) {
362
- this.store.updateTaskStatus(t.id, 'completed', `活动阈值+停滞检测触发(${eventCount}次工具调用,无实质性进展)`);
363
- }
364
- // 标记活动阈值触发,跳过质量门禁
365
- this.activityThresholdTriggered.set(key, true);
366
- const remainingTitles = remaining.map(t => `- ${t.title}`).join('\n');
367
- const message = `[Forge 自动推进] ℹ️ ${PHASE_LABELS[phase]}阶段活动充分且检测到停滞(${eventCount} 次工具调用,近期无写入操作),自动完成剩余任务:
368
-
369
- ${remainingTitles}
370
-
371
- 继续下一阶段工作。`;
372
- const systemMessage = `[Forge 活动阈值触发] ⚠️ ${PHASE_LABELS[phase]}阶段已达到活动阈值(${eventCount}/${threshold} 次工具调用)且检测到工作停滞(近期无 Write/Edit 操作),系统已自动完成剩余 ${remaining.length} 个任务并推进到下一阶段。
373
-
374
- 已完成任务:
375
- ${remainingTitles}
376
-
377
- 请继续下一阶段的工作。如果上述任务尚未真正完成,请在新阶段中补充。`;
378
- return { message, systemMessage };
379
- }
380
- /**
381
- * 判断当前阶段是否处于停滞状态(无实质性进展)
382
- *
383
- * 按阶段差异化策略:
384
- * - analyze/design:以阅读/思考为主,只有「超过阈值 1.5 倍且仍无任何文档写入」才算停滞
385
- * - profile:以命令执行和报告产出为主,只有「超过阈值后最近连续 5 次都无 Bash/Write/Edit」才算停滞
386
- * - code/test/review:以写入产出为主,只有「超过阈值后最近连续 5 次只读」或「超过阈值 1.5 倍且写入占比极低」才算停滞
387
- *
388
- * 通用兜底:事件数超过阈值 2 倍时无条件视为停滞(防止无限循环)
389
- */
390
- isStagnant(pipeline) {
391
- const phase = pipeline.phase;
392
- const key = `${pipeline.id}:${phase}`;
393
- const recent = this.recentTools.get(key) || [];
394
- const writeEditCount = this.phaseWriteEditCounts.get(key) || 0;
395
- const eventCount = this.totalPhaseEventCounts.get(key) || 0;
396
- const threshold = this.getPhaseActivityThreshold(phase, pipeline.complexity);
397
- // 通用兜底:超过阈值 1.5 倍无条件停滞(防止无限循环)
398
- if (eventCount >= threshold * 1.5) {
399
- logger.info(`[Pipeline] 事件数已超过阈值 1.5 倍(${eventCount}/${threshold * 1.5}),判定为停滞`);
400
- return true;
401
- }
402
- if (phase === 'analyze' || phase === 'design') {
403
- return this.isStagnantForThinkingPhase(recent, writeEditCount, eventCount, threshold);
404
- }
405
- if (phase === 'profile') {
406
- return this.isStagnantForProfilePhase(recent, eventCount, threshold);
407
- }
408
- return this.isStagnantForWritingPhase(recent, writeEditCount, eventCount, threshold);
409
- }
410
- /**
411
- * analyze/design:允许长时间阅读和思考。
412
- * 只有在完全没有任何写入产出且明显超时后,才视为停滞。
413
- *
414
- * 修复:如果写入量很大(超过阈值 1.5 倍),也应视为可能完成,触发推进检查
415
- */
416
- isStagnantForThinkingPhase(recent, writeEditCount, eventCount, threshold) {
417
- // 如果有大量写入(超过阈值 1.5 倍),可能已完成但未显式确认
418
- if (writeEditCount > 0 && eventCount >= Math.floor(threshold * 1.5)) {
419
- logger.info(`[Pipeline] 思考阶段有大量写入(${writeEditCount} 次)且事件数超过阈值 1.5 倍,判定为可能完成`);
420
- return true;
421
- }
422
- if (writeEditCount > 0) {
423
- return false;
424
- }
425
- if (eventCount >= Math.floor(threshold * 1.5)) {
426
- return true;
427
- }
428
- if (eventCount >= threshold && recent.length >= 5) {
429
- const readOnlyTools = new Set(['Read', 'Grep', 'Glob']);
430
- if (recent.slice(-5).every(t => readOnlyTools.has(t))) {
431
- return true;
432
- }
433
- }
434
- return false;
435
- }
436
- /**
437
- * profile:允许 Bash 执行、报告写入混合工作流。
438
- */
439
- isStagnantForProfilePhase(recent, eventCount, threshold) {
440
- if (eventCount < threshold || recent.length < 5) {
441
- return false;
442
- }
443
- const productiveTools = new Set(['Bash', 'Write', 'Edit']);
444
- return recent.slice(-5).every(t => !productiveTools.has(t));
445
- }
446
- /**
447
- * code/test/review:应有持续写入产出。
448
- */
449
- isStagnantForWritingPhase(recent, writeEditCount, eventCount, threshold) {
450
- if (eventCount >= threshold && recent.length >= 5) {
451
- const readOnlyTools = new Set(['Read', 'Grep', 'Glob']);
452
- if (recent.slice(-5).every(t => readOnlyTools.has(t))) {
453
- return true;
454
- }
455
- }
456
- if (eventCount >= Math.floor(threshold * 1.5) && eventCount > 0) {
457
- if (writeEditCount / eventCount < PHASE_DETECTION.WRITE_EDIT_RATIO_MIN) {
458
- return true;
459
- }
460
- }
461
- return false;
462
- }
463
- /**
464
- * 5. 质量门禁(去重版):已通过的阶段不再重复检查,活动阈值触发时跳过
465
- */
466
- async runQualityGateOnce(pipeline, event, qualityGate) {
467
- const phase = pipeline.phase;
468
- const cacheKey = `${pipeline.id}:${phase}`;
469
- // 活动阈值触发时跳过质量门禁
470
- if (this.activityThresholdTriggered.get(cacheKey)) {
471
- logger.info(`[Pipeline] 活动阈值触发,跳过质量门禁,直接推进`);
472
- return { blocked: false, level: 'pass' };
473
- }
474
- // 已通过,跳过
475
- if (this.gatePassedCache.has(cacheKey)) {
476
- return { blocked: false, level: 'pass' };
477
- }
478
- // 首次检查
479
- const fixKey = `${pipeline.id}:${phase}`;
480
- const fixAttempts = this.phaseFixAttempts.get(fixKey) || 0;
481
- const phaseTasks = pipeline.tasks.filter(t => t.phase === phase);
482
- const taskContext = phaseTasks
483
- .map(t => `- ${t.title}: ${t.output || '(进行中)'}`)
484
- .join('\n');
485
- const recentFiles = await this.getRecentlyModifiedFiles(pipeline.projectPath, 30);
486
- const recentFilesNote = recentFiles.length > 0
487
- ? `\n\n## 最近修改的文件(30分钟内)\n${recentFiles.map(f => `- ${f}`).join('\n')}`
488
- : '';
489
- const phaseContext = taskContext + recentFilesNote;
490
- let gateResult = (phase === 'analyze' || phase === 'design')
491
- ? await qualityGate.reviewPhaseCompletion(phase, pipeline.requirement, phaseContext)
492
- : await qualityGate.reviewForPhase(event, phase);
493
- if (gateResult === null && phase !== 'analyze' && phase !== 'design') {
494
- gateResult = await qualityGate.reviewPhaseCompletion(phase, pipeline.requirement, phaseContext);
495
- }
496
- if (gateResult && gateResult.level === 'fail') {
497
- const newAttempts = fixAttempts + 1;
498
- this.phaseFixAttempts.set(fixKey, newAttempts);
499
- const failIssues = gateResult.checks
500
- .filter(c => c.level === 'fail')
501
- .map(c => `[${c.category}] ${c.message}${c.suggestion ? `\n 修复建议:${c.suggestion}` : ''}`);
502
- if (newAttempts >= PhaseManager.MAX_FIX_ATTEMPTS) {
503
- // must 规范违反:即使达到最大修复次数,仍必须硬阻断,不得强制放行
504
- if (gateResult.blocking) {
505
- logger.error(`[Pipeline] 质量门禁阶段 ${PHASE_LABELS[phase]} 达到最大修复次数,但存在 must 规范违反,继续硬阻断`);
506
- // 触发 checkpoint 回滚(best-effort,失败不影响阻断)
507
- this.checkpointManager.rollback(pipeline.id, phase, pipeline.projectPath).then(() => {
508
- logger.info(`[Pipeline] 已回滚到阶段 ${PHASE_LABELS[phase]} 起点`);
509
- }).catch(err => {
510
- logger.warn(`[Pipeline] checkpoint 回滚失败(非致命):${err}`);
511
- });
512
- return {
513
- blocked: true,
514
- issues: failIssues,
515
- level: 'fail',
516
- fixAttempts: newAttempts,
517
- blocking: true,
518
- };
519
- }
520
- // should/may 规范:达到最大修复次数后强制放行
521
- logger.warn(`[Pipeline] 质量门禁阶段 ${PHASE_LABELS[phase]} 已达最大修复次数(${PhaseManager.MAX_FIX_ATTEMPTS}),强制放行`);
522
- this.phaseFixAttempts.delete(fixKey);
523
- this.gatePassedCache.set(cacheKey, true);
524
- return { blocked: false, level: 'warn' };
525
- }
526
- return {
527
- blocked: true,
528
- issues: failIssues,
529
- level: 'fail',
530
- fixAttempts: newAttempts,
531
- blocking: gateResult.blocking, // ✅ Propagate hard blocking flag
532
- };
533
- }
534
- // 通过或警告,标记缓存
535
- if (gateResult) {
536
- this.gatePassedCache.set(cacheKey, true);
537
- this.phaseFixAttempts.delete(fixKey);
538
- }
539
- return { blocked: false, level: gateResult?.level || 'pass' };
540
- }
541
- /**
542
- * 6. 阶段推进
543
- */
544
- async advancePhase(pipeline, forcedMessage) {
545
- const phase = pipeline.phase;
546
- const newPh = nextPhase(phase, pipeline.plannedPhases);
547
- // 跨阶段补充检测
548
- let gapMessage;
549
- const nextProgress = this.store.getPhaseProgress(pipeline.id, newPh);
550
- if (nextProgress.total === 0 && newPh !== 'done') {
551
- gapMessage = `[Forge 阶段间隙] ⚠️ ${PHASE_LABELS[newPh]}阶段暂无任务,可能需要手动创建任务或等待自动推进。`;
552
- logger.warn(`[Pipeline] 阶段间隙:${PHASE_LABELS[newPh]} 无任务`);
553
- }
554
- this.store.updatePhase(pipeline.id, newPh);
555
- const oldKey = `${pipeline.id}:${phase}`;
556
- this.phaseToolsSeen.delete(oldKey);
557
- this.gatePassedCache.delete(oldKey);
558
- this.activityThresholdTriggered.delete(oldKey);
559
- // 清理旧阶段 checkpoint(阶段成功完成)
560
- this.checkpointManager.cleanup(pipeline.id, phase).catch(err => {
561
- logger.debug(`[Pipeline] 旧阶段 checkpoint 清理失败 ${pipeline.id}:${phase}:${err}`);
562
- });
563
- // 为新阶段创建 checkpoint(非阻塞,失败不影响推进)
564
- if (newPh !== 'done') {
565
- const newKey = `${pipeline.id}:${newPh}`;
566
- this.checkpointManager.create(pipeline.id, newPh, pipeline.projectPath).then(() => {
567
- logger.debug(`[Pipeline] 新阶段 checkpoint 已创建:${newKey}`);
568
- }).catch(err => {
569
- logger.debug(`[Pipeline] 新阶段 checkpoint 创建失败(非致命):${err}`);
570
- });
571
- }
572
- const phaseTasks = pipeline.tasks.filter(t => t.phase === phase);
573
- const artifacts = phaseTasks
574
- .map(t => ` - ${t.title}: ${t.output || '(无记录)'}`)
575
- .join('\n');
576
- logger.info(`[Pipeline] 阶段推进:${PHASE_LABELS[phase]} → ${PHASE_LABELS[newPh]}\n` +
577
- ` 完成 ${phaseTasks.length} 个任务,成果汇总:\n${artifacts}`);
578
- return { advanced: true, newPhase: newPh, gapMessage: gapMessage ?? forcedMessage };
579
- }
580
- /**
581
- * 获取项目目录中最近 N 分钟内修改的文件列表(相对路径)
582
- * 用于增强质量门禁的 phaseContext,让 AI 有实际依据
583
- */
584
- async getRecentlyModifiedFiles(projectPath, minutes) {
585
- const cutoff = Date.now() - minutes * TIME.MINUTE;
586
- const result = [];
587
- const ignoreDirs = new Set(['.git', 'node_modules', 'dist', '.claude-forge', '.claude']);
588
- const walk = async (dir, depth) => {
589
- if (depth > 4)
590
- return;
591
- try {
592
- const entries = await fsPromises.readdir(dir, { withFileTypes: true });
593
- for (const entry of entries) {
594
- if (ignoreDirs.has(entry.name))
595
- continue;
596
- const fullPath = path.join(dir, entry.name);
597
- if (entry.isDirectory()) {
598
- await walk(fullPath, depth + 1);
599
- }
600
- else if (entry.isFile()) {
601
- try {
602
- const stat = await fsPromises.stat(fullPath);
603
- if (stat.mtimeMs >= cutoff) {
604
- result.push(path.relative(projectPath, fullPath));
605
- }
606
- }
607
- catch (err) {
608
- logger.debug(`[PhaseManager] 文件状态检查失败 ${fullPath}:${err}`);
609
- }
610
- }
611
- }
612
- }
613
- catch (err) {
614
- logger.debug(`[PhaseManager] 目录读取失败 ${dir}:${err}`);
615
- }
616
- };
617
- try {
618
- await walk(projectPath, 0);
619
- }
620
- catch (err) {
621
- logger.debug(`[PhaseManager] 文件扫描整体失败:${err}`);
622
- }
623
- return result.slice(0, 20); // 最多返回 20 个文件,避免 prompt 过长
624
- }
625
- /** 每个阶段兜底推进所需的最少工具调用次数(按复杂度动态调整) */
626
- getPhaseActivityThreshold(phase, complexity) {
627
- // 复杂度系数:simple 更快推进,complex 需要更多工作量
628
- const multiplier = complexity === 'simple' ? 0.7 : complexity === 'complex' ? 1.8 : 1.0;
629
- const base = {
630
- analyze: 25, // 分析阶段以阅读为主,给予充足空间
631
- design: 30, // 设计阶段需要深度思考
632
- profile: 20, // 性能分析相对快速
633
- code: 50, // 编码阶段工作量最大
634
- test: 25, // 测试阶段需要写+执行
635
- review: 25, // 审查阶段需要阅读+修改
636
- done: 0,
637
- };
638
- return Math.round((base[phase] ?? 30) * multiplier);
639
- }
640
- getPhaseDirective(phase, currentTask, pipeline) {
641
- const taskDesc = currentTask?.description || '';
642
- const taskTitle = currentTask?.title || '';
643
- switch (phase) {
644
- case 'analyze':
645
- return `📋 **需求分析阶段**
646
-
647
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
648
- ${taskDesc}
649
-
650
- ⚠️ **必须产出以下产物**:
651
- 1. 需求分析文档(Markdown 格式)
652
- - 建议路径:docs/需求分析.md 或 docs/ANALYSIS.md
653
- - 必须包含:需求背景、功能点列表、技术约束、验收标准
654
-
655
- ✅ **完成标准**:
656
- - 使用 Write 工具创建需求分析文档
657
- - 文档内容完整(至少包含需求背景和功能点)
658
- - 明确列出技术方案
659
-
660
- 💡 **建议流程**:
661
- 1. 先阅读相关代码和文档理解需求
662
- 2. 整理需求要点和技术约束
663
- 3. 使用 Write 工具创建文档
664
- 4. 在文档中详细描述需求和方案`;
665
- case 'design':
666
- return `🏗️ **架构设计阶段**
667
-
668
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
669
- ${taskDesc}
670
-
671
- ⚠️ **必须产出以下产物**:
672
- 1. 设计文档(Markdown 格式)
673
- - 建议路径:docs/设计文档.md 或 docs/DESIGN.md
674
- - 必须包含:系统架构、模块划分、接口设计、数据流
675
-
676
- 2. 架构图(推荐)
677
- - 使用 Mermaid 语法或 ASCII 图
678
- - 清晰展示模块关系和数据流
679
-
680
- ✅ **完成标准**:
681
- - 使用 Write 工具创建设计文档
682
- - 文档包含架构设计和接口定义
683
- - 设计方案清晰可落地
684
-
685
- 💡 **建议流程**:
686
- 1. 分析需求文档确定设计目标
687
- 2. 设计系统架构和模块划分
688
- 3. 使用 Write 工具创建设计文档
689
- 4. 在文档中绘制架构图(Mermaid 或 ASCII)`;
690
- case 'profile':
691
- return `⚡ **性能分析阶段**
692
-
693
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
694
- ${taskDesc}
695
-
696
- ⚠️ **必须产出以下产物**:
697
- 1. 性能分析报告(Markdown 格式)
698
- - 建议路径:docs/性能分析.md 或 docs/PROFILE.md
699
- - 必须包含:性能基线、瓶颈分析、优化方案
700
-
701
- ✅ **完成标准**:
702
- - 使用 Write 工具创建性能分析报告
703
- - 包含具体的性能数据和分析
704
- - 给出明确的优化建议
705
-
706
- 💡 **建议流程**:
707
- 1. 建立性能基线(执行 profiling)
708
- 2. 定位性能瓶颈
709
- 3. 使用 Write 工具创建分析报告
710
- 4. 在报告中记录数据和优化方案`;
711
- case 'code':
712
- return `💻 **编码实现阶段**
713
-
714
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
715
- ${taskDesc}
716
-
717
- ⚠️ **必须产出以下产物**:
718
- 1. 源代码文件(.ts/.py/.go 等)
719
- - 实现设计文档中的功能
720
- - 代码规范、注释清晰
721
-
722
- ✅ **完成标准**:
723
- - 使用 Write/Edit 工具创建或修改源代码
724
- - 代码可编译/运行
725
- - 实现了设计的功能
726
-
727
- 💡 **建议流程**:
728
- 1. 阅读设计文档理解实现目标
729
- 2. 创建必要的文件和目录结构
730
- 3. 使用 Write/Edit 工具编写代码
731
- 4. 确保代码符合规范和最佳实践`;
732
- case 'test':
733
- return `🧪 **测试验证阶段**
734
-
735
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
736
- ${taskDesc}
737
-
738
- ⚠️ **必须产出以下产物**:
739
- 1. 测试文件(.test.ts/.spec.py 等)
740
- - 覆盖核心功能和边界情况
741
- - 包含正常和异常场景
742
-
743
- 2. 测试执行结果
744
- - 使用 Bash 工具运行测试命令
745
- - 确保所有测试通过
746
-
747
- ✅ **完成标准**:
748
- - 使用 Write 工具创建测试文件
749
- - 使用 Bash 执行测试(${pipeline.techStack?.includes('Vitest') ? 'npm test 或 vitest' : pipeline.techStack?.includes('Jest') ? 'npm test 或 jest' : 'npm test'})
750
- - 所有测试通过
751
-
752
- 💡 **建议流程**:
753
- 1. 分析需要测试的代码和功能
754
- 2. 使用 Write 工具创建测试文件
755
- 3. 编写测试用例(正常+异常场景)
756
- 4. 使用 Bash 运行测试并确认通过`;
757
- case 'review':
758
- return `🔍 **代码审查阶段**
759
-
760
- ${taskTitle ? `当前任务:${taskTitle}` : ''}
761
- ${taskDesc}
762
-
763
- ⚠️ **必须产出以下产物**:
764
- 1. 审查报告(Markdown 格式)
765
- - 建议路径:docs/审查报告.md 或 docs/REVIEW.md
766
- - 必须包含:代码质量评估、安全检查、改进建议
767
-
768
- ✅ **完成标准**:
769
- - 使用 Write 工具创建审查报告
770
- - 报告包含具体的问题和建议
771
- - 给出了改进方案和优先级
772
-
773
- 💡 **建议流程**:
774
- 1. 阅读所有修改的代码
775
- 2. 检查安全性、规范性、性能
776
- 3. 使用 Write 工具创建审查报告
777
- 4. 在报告中列出问题、建议和改进方案
778
-
779
- 📋 **审查清单**:
780
- - 安全性:注入、XSS、敏感信息泄露
781
- - 代码规范:命名、格式、注释
782
- - 性能:算法复杂度、资源使用`;
783
- default:
784
- return '';
785
- }
786
- }
787
- /**
788
- * 检测任务是否完成(重构版:信号优先 + 启发式回退)
789
- */
790
- detectTaskCompletion(phase, toolName, inputStr, event, currentTask, pipelineId) {
791
- // 优先检测显式完成信号(FORGE_TASK_DONE),100% 准确,无需模式匹配
792
- // 信号格式:echo "FORGE_TASK_DONE: <task_id>" 或文件注释 // FORGE: task_done <task_id>
793
- if (toolName === 'Bash' && inputStr.includes('FORGE_TASK_DONE:')) {
794
- const match = inputStr.match(/FORGE_TASK_DONE:\s*(\S+)/);
795
- if (match && match[1] === currentTask.id) {
796
- logger.info(`[Pipeline] 检测到显式完成信号:${currentTask.id}`);
797
- return true;
798
- }
799
- }
800
- if ((toolName === 'Write' || toolName === 'Edit') && inputStr.includes('FORGE: task_done')) {
801
- const match = inputStr.match(/FORGE:\s*task_done\s+(\S+)/);
802
- if (match && match[1] === currentTask.id) {
803
- logger.info(`[Pipeline] 检测到显式完成信号:${currentTask.id}`);
804
- return true;
805
- }
806
- }
807
- // 回退到启发式检测(保留向后兼容)
808
- switch (phase) {
809
- case 'analyze':
810
- // 分析阶段:有输出类工具(Write/Edit)即可,或明确的确认信号
811
- if (inputStr.includes('分析完成') || inputStr.includes('需求明确') ||
812
- inputStr.includes('已分析') || inputStr.includes('analysis complete')) {
813
- return true;
814
- }
815
- return toolName === 'Write' || toolName === 'Edit';
816
- case 'design':
817
- // 设计阶段:Write/Edit 任何文件都算(放宽关键词限制)
818
- // 原来要求必须包含 design/architecture 等关键词,导致实际完成了架构改动但文件名不匹配时卡死
819
- if (toolName !== 'Write' && toolName !== 'Edit')
820
- return false;
821
- // 宽松匹配:任何文档/配置/代码文件的写入都视为设计产出
822
- const designKeywords = [
823
- 'design', 'architecture', 'DECISIONS', 'README',
824
- '设计', '架构', '方案', '接口', '系统',
825
- '.md', '.yaml', '.yml', '.json', 'CLAUDE',
826
- 'config', 'schema', 'plan', 'spec', 'docs/',
827
- 'optimization', '优化', '计划', '文档',
828
- ];
829
- return designKeywords.some(kw => inputStr.includes(kw));
830
- case 'profile':
831
- // 性能分析阶段:执行 profiling/benchmark 或写入性能报告
832
- return (toolName === 'Bash' && (inputStr.includes('profile') || inputStr.includes('benchmark') || inputStr.includes('perf'))) || (toolName === 'Write' && (inputStr.includes('profiling') || inputStr.includes('benchmark') || inputStr.includes('performance')));
833
- case 'code': {
834
- // 编码阶段:创建或修改了源代码文件(Write 或 Edit 均算)
835
- if (toolName !== 'Write' && toolName !== 'Edit')
836
- return false;
837
- const codeFilePath = event.tool_input.file_path || '';
838
- // 排除测试文件、文档、构建产物
839
- if (codeFilePath.includes('.test.') || codeFilePath.includes('.spec.') ||
840
- codeFilePath.includes('/node_modules/') || codeFilePath.includes('/dist/') ||
841
- codeFilePath.endsWith('.md') || codeFilePath.endsWith('.lock') ||
842
- codeFilePath.endsWith('.log')) {
843
- return false;
844
- }
845
- // 源代码文件(扩展后的列表,覆盖前端/后端/脚本/样式)
846
- const codeExts = [
847
- '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
848
- '.py', '.go', '.java', '.rs', '.rb', '.php',
849
- '.vue', '.svelte', '.astro',
850
- '.css', '.scss', '.sass', '.less',
851
- '.html', '.htm',
852
- '.sh', '.bash', '.zsh',
853
- '.c', '.cpp', '.h', '.hpp',
854
- '.cs', '.swift', '.kt',
855
- '.sql', '.graphql', '.proto',
856
- ];
857
- return codeExts.some(ext => codeFilePath.endsWith(ext));
858
- }
859
- case 'test': {
860
- // 测试阶段:必须同时有测试文件写入 AND 测试执行(非 watch 模式)
861
- const key = `${pipelineId}:${phase}`;
862
- const toolsStr = this.phaseToolsSeen.get(key) ?? '';
863
- const toolsSeenSet = toolsStr ? new Set(toolsStr.split(',')) : new Set();
864
- const hasTestFile = (toolName === 'Write' || toolName === 'Edit') &&
865
- (inputStr.includes('test') || inputStr.includes('spec'));
866
- const hasTestRun = toolName === 'Bash' &&
867
- (inputStr.includes('test') || inputStr.includes('vitest') || inputStr.includes('jest')) &&
868
- !inputStr.includes('--watch');
869
- // 持久化标记(trackToolUsage 已更新 phaseToolsSeen)
870
- if (hasTestFile)
871
- toolsSeenSet.add('test:file');
872
- if (hasTestRun)
873
- toolsSeenSet.add('test:run');
874
- this.phaseToolsSeen.set(key, [...toolsSeenSet].join(','));
875
- return toolsSeenSet.has('test:file') && toolsSeenSet.has('test:run');
876
- }
877
- case 'review':
878
- // 审查阶段:有代码修改(Edit)即可(质量门禁已在 checkAndAdvance 中把关)
879
- return toolName === 'Edit';
880
- default:
881
- return false;
882
- }
883
- }
884
- /**
885
- * 将工具调用归类为粗粒度的工具类别(用于多样性判定)
886
- */
887
- categorizeToolCall(toolName, _inputStr) {
888
- switch (toolName) {
889
- case 'Read':
890
- case 'Glob':
891
- case 'Grep':
892
- return 'explore';
893
- case 'Write':
894
- case 'Edit':
895
- return 'write';
896
- case 'Bash':
897
- return 'bash';
898
- case 'Agent':
899
- return 'agent';
900
- default:
901
- return null;
902
- }
903
- }
904
- /**
905
- * 从工具调用中提取成果摘要(文件路径、命令、操作类型)
906
- */
907
- summarizeArtifact(toolName, inputStr, event) {
908
- const input = event.tool_input;
909
- switch (toolName) {
910
- case 'Write':
911
- case 'Read':
912
- case 'Edit': {
913
- const filePath = input?.file_path;
914
- if (filePath) {
915
- const shortPath = filePath.split('/').slice(-3).join('/');
916
- return `${toolName} ${shortPath}`;
917
- }
918
- return toolName;
919
- }
920
- case 'Bash': {
921
- const cmd = (input?.command || '').substring(0, 80);
922
- return `执行命令: ${cmd}`;
923
- }
924
- case 'Glob':
925
- case 'Grep': {
926
- const pattern = input?.pattern || '';
927
- return `${toolName} "${pattern.substring(0, 50)}"`;
928
- }
929
- case 'Agent': {
930
- const desc = input?.description || input?.prompt || '';
931
- return `Agent: ${desc.substring(0, 60)}`;
932
- }
933
- default:
934
- return `${toolName}: ${inputStr.substring(0, 80)}`;
935
- }
936
- }
937
- /**
938
- * 获取 Pipeline 的阶段快照(用于复盘分析)
939
- */
940
- getSessionSnapshot(pipelineId) {
941
- const snapshots = [];
942
- const phases = ['analyze', 'design', 'profile', 'code', 'test', 'review'];
943
- for (const phase of phases) {
944
- const key = `${pipelineId}:${phase}`;
945
- const toolCallCount = this.totalPhaseEventCounts.get(key) || 0;
946
- const fixAttemptCount = this.phaseFixAttempts.get(key) || 0;
947
- const writeEditCount = this.phaseWriteEditCounts.get(key) || 0;
948
- // 只记录有活动的阶段
949
- if (toolCallCount > 0 || fixAttemptCount > 0) {
950
- snapshots.push({
951
- phase,
952
- tool_call_count: toolCallCount,
953
- write_edit_count: writeEditCount,
954
- fix_attempt_count: fixAttemptCount,
955
- });
956
- }
957
- }
958
- return snapshots;
959
- }
960
- }
961
- //# sourceMappingURL=phase-manager.js.map