@ghyper9023/pi-dev-workflow 0.5.0 → 0.6.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/.version/RELEASE-v0.6.0.md +106 -0
- package/README.md +32 -30
- package/agents/git-agent.md +24 -16
- package/agents/grill/dev-doc-grill-agent.md +5 -4
- package/agents/grill/dev-fix-grill-agent.md +5 -3
- package/agents/grill/dev-grill-agent.md +6 -6
- package/agents/grill/dev-perf-grill-agent.md +5 -3
- package/agents/grill/dev-prd-agent.md +1 -1
- package/agents/grill/dev-refactor-grill-agent.md +5 -2
- package/agents/grill/dev-test-grill-agent.md +5 -2
- package/agents/review-agent.md +30 -22
- package/agents/workflow/docWriter-agent.md +35 -22
- package/agents/workflow/planner-agent.md +95 -57
- package/agents/workflow/reviewer-agent.md +91 -33
- package/agents/workflow/trimmer-agent.md +59 -26
- package/agents/workflow/worker-agent.md +51 -25
- package/extensions/dev-prompts.ts +29 -29
- package/extensions/grill-me-agent.ts +16 -16
- package/extensions/sub-agents.ts +59 -14
- package/extensions/workflow-engine.ts +77 -227
- package/package.json +1 -1
- package/tests/test-workflow-engine-bugs.mjs +123 -9
|
@@ -447,13 +447,19 @@ function captureBaseline(cwd: string): void {
|
|
|
447
447
|
*
|
|
448
448
|
* Uses git-format status codes (M, A, D) for display consistency.
|
|
449
449
|
* Deduplicates against existing _workflowFileChanges.
|
|
450
|
+
*
|
|
451
|
+
* The dedup key includes stepIndex (filePath:stepIndex) so the same file
|
|
452
|
+
* can appear across different workflow steps (e.g., created by worker in
|
|
453
|
+
* loop-group, then reviewed by reviewer) without being collapsed into one.
|
|
454
|
+
* Without :stepIndex, the Set dedup would wrongly skip a file modified in
|
|
455
|
+
* a later step just because it was already tracked in an earlier step.
|
|
450
456
|
*/
|
|
451
457
|
function updateToolsFromGit(cwd: string, stepIndex: number, agentName: string): void {
|
|
452
458
|
const currentChanges = getGitDiffChanges(cwd);
|
|
453
|
-
const seen = new Set(_workflowFileChanges.map(c => c.filePath));
|
|
454
|
-
|
|
459
|
+
const seen = new Set(_workflowFileChanges.map(c => `${c.filePath}:${c.stepIndex}`));
|
|
460
|
+
|
|
455
461
|
for (const change of currentChanges) {
|
|
456
|
-
if (seen.has(change.path)) continue;
|
|
462
|
+
if (seen.has(`${change.path}:${stepIndex}`)) continue;
|
|
457
463
|
|
|
458
464
|
// ── Baseline filtering ──────────────────────────────
|
|
459
465
|
// Skip files that were already dirty at workflow start and haven't been touched.
|
|
@@ -470,7 +476,13 @@ function updateToolsFromGit(cwd: string, stepIndex: number, agentName: string):
|
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
|
|
473
|
-
|
|
479
|
+
// Skip files in .pi-dev-output/ — these are workflow-generated artifacts (plans, reviews)
|
|
480
|
+
// and should NOT be reported as user repo changes. Real git clients (VSCode, Zed) also
|
|
481
|
+
// ignore this directory (it's in .gitignore). This filter is defense-in-depth.
|
|
482
|
+
// Note: git always uses forward slashes in paths, even on Windows.
|
|
483
|
+
if (change.path.startsWith(".pi-dev-output/")) continue;
|
|
484
|
+
|
|
485
|
+
seen.add(`${change.path}:${stepIndex}`);
|
|
474
486
|
const type: FileChangeEntry["type"] =
|
|
475
487
|
change.status === "A" ? "new" :
|
|
476
488
|
change.status === "D" ? "delete" :
|
|
@@ -883,47 +895,6 @@ function addWidgetSubStepTool(stepIndex: number, agentName: string, tool: string
|
|
|
883
895
|
if (!sub.tools) sub.tools = [];
|
|
884
896
|
sub.tools.push(tool);
|
|
885
897
|
if (sub.tools.length > 20) sub.tools = sub.tools.slice(-20); // keep last 20
|
|
886
|
-
|
|
887
|
-
// Also track as file change for checkpoint
|
|
888
|
-
// Support both old format ("edit: path") and new git-format ("M path", "A path", "D path")
|
|
889
|
-
const oldMatch = tool.match(/^(edit|new|delete|read):\s*(.+)/i);
|
|
890
|
-
const gitMatch = !oldMatch ? tool.match(/^([MAD])\s{2,}(.+)$/) : null;
|
|
891
|
-
if (oldMatch) {
|
|
892
|
-
const changeType = oldMatch[1]!.toLowerCase() as FileChangeEntry["type"];
|
|
893
|
-
const filePath = oldMatch[2]!.trim();
|
|
894
|
-
const exists = _workflowFileChanges.some(
|
|
895
|
-
c => c.filePath === filePath && c.type === changeType && c.stepIndex === stepIndex && c.agent === agentName,
|
|
896
|
-
);
|
|
897
|
-
if (!exists && filePath.length > 3) {
|
|
898
|
-
_workflowFileChanges.push({
|
|
899
|
-
agent: agentName,
|
|
900
|
-
stepIndex,
|
|
901
|
-
type: changeType,
|
|
902
|
-
filePath,
|
|
903
|
-
timestamp: new Date().toISOString(),
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
} else if (gitMatch) {
|
|
907
|
-
const gitStatus = gitMatch[1]!;
|
|
908
|
-
const changeType: FileChangeEntry["type"] =
|
|
909
|
-
gitStatus === "A" ? "new" :
|
|
910
|
-
gitStatus === "D" ? "delete" :
|
|
911
|
-
"edit";
|
|
912
|
-
const filePath = gitMatch[2]!.trim();
|
|
913
|
-
const exists = _workflowFileChanges.some(
|
|
914
|
-
c => c.filePath === filePath && c.type === changeType && c.stepIndex === stepIndex && c.agent === agentName,
|
|
915
|
-
);
|
|
916
|
-
if (!exists && filePath.length > 3) {
|
|
917
|
-
_workflowFileChanges.push({
|
|
918
|
-
agent: agentName,
|
|
919
|
-
stepIndex,
|
|
920
|
-
type: changeType,
|
|
921
|
-
filePath,
|
|
922
|
-
timestamp: new Date().toISOString(),
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
898
|
refreshWidget();
|
|
928
899
|
}
|
|
929
900
|
}
|
|
@@ -1130,7 +1101,7 @@ async function runAgentWithProgress(
|
|
|
1130
1101
|
const result = await spawnSubagent(agent, task, _workflowCwd, signal, timeoutMs, (progress) => {
|
|
1131
1102
|
// Try to parse tool calls from progress messages
|
|
1132
1103
|
// Only match if it looks like a file path (contains a dot or path separator)
|
|
1133
|
-
const toolMatch = progress.match(/(edit|read|write|new|bash|grep|find|ls|delete|remove)\s*[::]\s*(\
|
|
1104
|
+
const toolMatch = progress.match(/(edit|read|write|new|bash|grep|find|ls|delete|remove)\s*[::]\s*([\w./\\-]+)/i);
|
|
1134
1105
|
if (toolMatch) {
|
|
1135
1106
|
const toolType = toolMatch[1]!.toLowerCase();
|
|
1136
1107
|
const target = toolMatch[2]!;
|
|
@@ -1141,12 +1112,14 @@ async function runAgentWithProgress(
|
|
|
1141
1112
|
_widgetExtraToolCount++;
|
|
1142
1113
|
}
|
|
1143
1114
|
}
|
|
1144
|
-
// Detect output file paths —
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1115
|
+
// Detect output file paths — only match .pi-dev-output paths belonging to current workflow
|
|
1116
|
+
if (_workflowId) {
|
|
1117
|
+
const outputMatch = progress.match(/\.pi-dev-output\/[^\s,;)\]}'"`]{5,}\.\w+/i);
|
|
1118
|
+
if (outputMatch) {
|
|
1119
|
+
const pathCandidate = outputMatch[0]!.trim().replace(/["'`\\]+$/g, '');
|
|
1120
|
+
if (pathCandidate.length > 15 && pathCandidate.length < 300 && pathCandidate.includes(_workflowId)) {
|
|
1121
|
+
addWidgetSubStepOutput(stepIndex, agentName, pathCandidate);
|
|
1122
|
+
}
|
|
1150
1123
|
}
|
|
1151
1124
|
}
|
|
1152
1125
|
}, _workflowId ? { workflowId: _workflowId } : undefined);
|
|
@@ -1163,127 +1136,47 @@ async function runAgentWithProgress(
|
|
|
1163
1136
|
toolCount: _widgetExtraToolCount,
|
|
1164
1137
|
});
|
|
1165
1138
|
|
|
1166
|
-
// ── Post-completion:
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1139
|
+
// ── Post-completion: file change detection via git diff ONLY ──
|
|
1140
|
+
// We do NOT parse agent output text for file paths — that approach is fragile and
|
|
1141
|
+
// produces false positives (e.g., matching JS property names like `.push`, `.length`
|
|
1142
|
+
// from code snippets). Instead, we rely entirely on git diff --name-status, which is
|
|
1143
|
+
// the same approach used by VSCode, Zed, and every other professional git client.
|
|
1144
|
+
// It's deterministic, noise-free, and always produces correct relative paths.
|
|
1169
1145
|
const allOutput = (result.output || "") + "\n" + (result.stderr || "");
|
|
1170
1146
|
const finalOutput = extractFinalOutput(result.output) || result.output;
|
|
1171
1147
|
const searchText = allOutput + "\n" + finalOutput;
|
|
1172
1148
|
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
// Chinese patterns
|
|
1183
|
-
/(?:编写|创建|修改|删除|读取|写入|更新)\s*(?:了|文件)?\s*[::]?\s*[`"']?([^`"'\s,,]+\.[a-zA-Z0-9_]+)[`"']?/gi,
|
|
1184
|
-
// File path with action prefix: "edit: src/file.ts", "new: src/file.ts"
|
|
1185
|
-
/(?:^|\n)\s*(?:edit|new|delete|read|modify|create|update|add|remove)\s*[::]\s*([^\n]+\.[a-zA-Z0-9_]+)/gim,
|
|
1186
|
-
];
|
|
1187
|
-
const seenTools = new Set<string>();
|
|
1188
|
-
for (const pattern of filePatterns) {
|
|
1149
|
+
// Find output file paths (plans, reviews) for display in the "output:" section
|
|
1150
|
+
// These are workflow artifacts (.pi-dev-output/) — only show files belonging to
|
|
1151
|
+
// the CURRENT workflow to avoid cross-contamination from previous runs.
|
|
1152
|
+
// Note: file change detection itself relies exclusively on git diff below, not text parsing.
|
|
1153
|
+
const seenOutputs = new Set<string>();
|
|
1154
|
+
if (_workflowId) {
|
|
1155
|
+
// Only match .pi-dev-output paths containing the current workflow UUID
|
|
1156
|
+
const escapedId = _workflowId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1157
|
+
const outputPattern = new RegExp(`\\.pi-dev-output\\/[^\\s,;)\\]}'"\\`]*${escapedId}[^\\s,;)\\]}'"\\`]*`, 'g');
|
|
1189
1158
|
let m;
|
|
1190
|
-
while ((m =
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
.
|
|
1194
|
-
|
|
1195
|
-
// Validate it's a real file path
|
|
1196
|
-
if (filePath.length > 3 && filePath.length < 300 && !seenTools.has(filePath)) {
|
|
1197
|
-
// Skip common non-file matches
|
|
1198
|
-
if (filePath.match(/^(the|a|an|this|that|it|its|my|your|our|their|some|any|all|each|every|both|few|many|several|most|other|another|such|what|which|whose|whom|when|where|why|how|who|being|having|doing|making|taking|giving|getting|setting|using|running|going|coming|looking|finding|keeping|putting)/i)) continue;
|
|
1199
|
-
if (filePath.startsWith("http")) continue;
|
|
1200
|
-
if (filePath.length < 6 && !filePath.includes("/")) continue;
|
|
1201
|
-
|
|
1202
|
-
// 额外过滤器:排除明显不是文件路径的脏数据
|
|
1203
|
-
if (filePath.includes("${") || filePath.includes("\\n") || filePath.includes("\\t")) continue; // 排除模板字符串和转义字符
|
|
1204
|
-
if (filePath.includes("[]") || filePath.includes("{}")) continue; // 排除数组/对象字面量
|
|
1205
|
-
if (filePath.match(/^[\s,;)\]}]+$/)) continue; // 排除纯符号
|
|
1206
|
-
seenTools.add(filePath);
|
|
1207
|
-
const fullMatch = m[0]!.toLowerCase();
|
|
1208
|
-
// Determine operation type and convert to git status
|
|
1209
|
-
let toolType = "edit";
|
|
1210
|
-
if (fullMatch.includes("write") || fullMatch.includes("创建") || fullMatch.includes("new") || fullMatch.includes("add") || fullMatch.includes("created") || fullMatch.includes("added")) {
|
|
1211
|
-
toolType = "new";
|
|
1212
|
-
} else if (fullMatch.includes("delete") || fullMatch.includes("删除") || fullMatch.includes("remove") || fullMatch.includes("deleted") || fullMatch.includes("removed")) {
|
|
1213
|
-
toolType = "delete";
|
|
1214
|
-
} else if (fullMatch.includes("read") || fullMatch.includes("读取")) {
|
|
1215
|
-
toolType = "read";
|
|
1216
|
-
}
|
|
1217
|
-
if (toolType !== "read") {
|
|
1218
|
-
const gitStatus = toGitStatus(toolType);
|
|
1219
|
-
addWidgetSubStepTool(stepIndex, agentName, `${gitStatus} ${filePath}`);
|
|
1220
|
-
_widgetExtraToolCount++;
|
|
1221
|
-
}
|
|
1159
|
+
while ((m = outputPattern.exec(searchText)) !== null) {
|
|
1160
|
+
const path_ = m[0]!.trim();
|
|
1161
|
+
if (path_.length > 15 && path_.length < 300 && !seenOutputs.has(path_)) {
|
|
1162
|
+
seenOutputs.add(path_);
|
|
1163
|
+
addWidgetSubStepOutput(stepIndex, agentName, path_);
|
|
1222
1164
|
}
|
|
1223
1165
|
}
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
// Look for explicit tool call patterns in the raw JSON output
|
|
1228
|
-
if (seenTools.size === 0) {
|
|
1229
|
-
const jsonLines = (result.output || "").split("\n");
|
|
1230
|
-
for (const line of jsonLines) {
|
|
1231
|
-
try {
|
|
1232
|
-
const event = JSON.parse(line);
|
|
1233
|
-
// Look for tool_use events in the JSON stream
|
|
1234
|
-
if (event.type === "message_update" && event.assistantMessageEvent?.type === "tool_use") {
|
|
1235
|
-
const toolName = event.assistantMessageEvent.name;
|
|
1236
|
-
const args = event.assistantMessageEvent.args || {};
|
|
1237
|
-
// write tool: args contains file_path
|
|
1238
|
-
if (toolName === "write" && args.file_path) {
|
|
1239
|
-
const fp = args.file_path.trim();
|
|
1240
|
-
if (!seenTools.has(fp)) {
|
|
1241
|
-
seenTools.add(fp);
|
|
1242
|
-
addWidgetSubStepTool(stepIndex, agentName, `A ${fp}`);
|
|
1243
|
-
_widgetExtraToolCount++;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
// edit tool: args contains file_path
|
|
1247
|
-
if (toolName === "edit" && args.file_path) {
|
|
1248
|
-
const fp = args.file_path.trim();
|
|
1249
|
-
if (!seenTools.has(fp)) {
|
|
1250
|
-
seenTools.add(fp);
|
|
1251
|
-
addWidgetSubStepTool(stepIndex, agentName, `M ${fp}`);
|
|
1252
|
-
_widgetExtraToolCount++;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
} catch { /* not JSON, skip */ }
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// Find output file paths (.pi-dev-output, review reports, plan files)
|
|
1261
|
-
// NOTE: Only match explicit paths in .pi-dev-output/ or known review/plan file patterns.
|
|
1262
|
-
// The old pattern matching loose "output:" text was the root cause of "output::0.00004508" noise.
|
|
1263
|
-
// File change detection now relies on git diff --name-status (updateToolsFromGit below),
|
|
1264
|
-
// which is deterministic and noise-free.
|
|
1265
|
-
const outputPathPatterns = [
|
|
1266
|
-
// Direct reference to .pi-dev-output paths: ".pi-dev-output/pi-plans/xxx.md"
|
|
1267
|
-
/\.pi-dev-output\/[a-zA-Z0-9_\/-]+\.[a-zA-Z0-9]+/g,
|
|
1268
|
-
// Review file patterns: "review-20260520-162800.md"
|
|
1269
|
-
/review-\d{8}-\d{6}\.md/g,
|
|
1270
|
-
// Plan file patterns: "20260520-1628-*.md"
|
|
1271
|
-
/\d{8}-\d{4,6}-[a-zA-Z0-9_-]+\.md/g,
|
|
1272
|
-
];
|
|
1273
|
-
const seenOutputs = new Set<string>();
|
|
1274
|
-
for (const pattern of outputPathPatterns) {
|
|
1166
|
+
} else {
|
|
1167
|
+
// No workflow ID (unlikely): match .pi-dev-output paths conservatively
|
|
1168
|
+
const outputPattern = /\.pi-dev-output\/[a-zA-Z0-9_\/-]+\.[a-zA-Z0-9]+/g;
|
|
1275
1169
|
let m;
|
|
1276
|
-
while ((m =
|
|
1170
|
+
while ((m = outputPattern.exec(searchText)) !== null) {
|
|
1277
1171
|
const path_ = m[0]!.trim();
|
|
1278
|
-
if (path_.length >
|
|
1172
|
+
if (path_.length > 15 && path_.length < 300 && !seenOutputs.has(path_)) {
|
|
1279
1173
|
seenOutputs.add(path_);
|
|
1280
1174
|
addWidgetSubStepOutput(stepIndex, agentName, path_);
|
|
1281
1175
|
}
|
|
1282
1176
|
}
|
|
1283
1177
|
}
|
|
1284
1178
|
|
|
1285
|
-
|
|
1286
|
-
// ── Update file changes from git diff (more accurate than text scraping) ──
|
|
1179
|
+
// ── Update file changes from git diff (more accurate than text scraping) ──
|
|
1287
1180
|
updateToolsFromGit(_workflowCwd, stepIndex, agentName);
|
|
1288
1181
|
// Update sub-step status based on result
|
|
1289
1182
|
const subStatus: WorkflowSubStepWidgetState["status"] =
|
|
@@ -1350,33 +1243,19 @@ async function executeSingleStep(
|
|
|
1350
1243
|
throw new Error(`Agent 错误 (exit ${result.exitCode}): ${result.stderr.slice(0, 500)}`);
|
|
1351
1244
|
}
|
|
1352
1245
|
|
|
1353
|
-
// ── Capture
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
const delCount = agentChanges.filter(c => c.startsWith("D")).length;
|
|
1367
|
-
const statsParts: string[] = [];
|
|
1368
|
-
if (editCount > 0) statsParts.push(`修改 ${editCount} 个`);
|
|
1369
|
-
if (newCount > 0) statsParts.push(`新增 ${newCount} 个`);
|
|
1370
|
-
if (delCount > 0) statsParts.push(`删除 ${delCount} 个`);
|
|
1371
|
-
const statsLine = statsParts.length > 0 ? `改动统计: ${statsParts.join(",")}\n\n` : "";
|
|
1372
|
-
|
|
1373
|
-
updateChainContext(chainKey,
|
|
1374
|
-
`${agentName} 已完成。\n` +
|
|
1375
|
-
statsLine +
|
|
1376
|
-
`变更文件列表:\n${agentChanges.join("\n")}`
|
|
1377
|
-
);
|
|
1378
|
-
} else {
|
|
1379
|
-
updateChainContext(chainKey, `${agentName} 已完成执行,未检测到文件变更。`);
|
|
1246
|
+
// ── Capture AI work summary as supplementary chain context ──
|
|
1247
|
+
// We intentionally do NOT create chain contexts from _workflowFileChanges here.
|
|
1248
|
+
// File-change-based chain contexts ("计划制定摘要"/"文档更新摘要") were removed in v0.6.0
|
|
1249
|
+
// because their M/A/D counts provided negligible value for downstream agents compared
|
|
1250
|
+
// to the agent's own free-text work summary. The file-change approach also created
|
|
1251
|
+
// tight coupling between executeSingleStep and the _workflowFileChanges data structure.
|
|
1252
|
+
const workSummary = extractFinalOutput(result.output);
|
|
1253
|
+
if (workSummary) {
|
|
1254
|
+
updateChainContext(`${agentName} 工作总结`, `${agentName} 已完成工作,以下是其工作总结:\n\n${workSummary}`);
|
|
1255
|
+
} else if (result.output) {
|
|
1256
|
+
// 保底:使用输出前 200 字符作为简略摘要
|
|
1257
|
+
const fallback = result.output.slice(0, 200).trim();
|
|
1258
|
+
if (fallback) updateChainContext(`${agentName} 工作总结`, fallback);
|
|
1380
1259
|
}
|
|
1381
1260
|
}
|
|
1382
1261
|
|
|
@@ -1440,34 +1319,11 @@ async function executeLoopGroup(
|
|
|
1440
1319
|
}
|
|
1441
1320
|
}
|
|
1442
1321
|
|
|
1443
|
-
// ──
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
// Use agent-specific keys so worker and trimmer changes coexist without overwriting
|
|
1449
|
-
const chainKey = step.loopAgentName === "worker" ? "代码实施摘要" : "代码精简摘要";
|
|
1450
|
-
|
|
1451
|
-
if (loopAgentChanges.length > 0) {
|
|
1452
|
-
const editCount = loopAgentChanges.filter(c => c.startsWith("M")).length;
|
|
1453
|
-
const newCount = loopAgentChanges.filter(c => c.startsWith("A")).length;
|
|
1454
|
-
const delCount = loopAgentChanges.filter(c => c.startsWith("D")).length;
|
|
1455
|
-
const statsParts: string[] = [];
|
|
1456
|
-
if (editCount > 0) statsParts.push(`修改 ${editCount} 个`);
|
|
1457
|
-
if (newCount > 0) statsParts.push(`新增 ${newCount} 个`);
|
|
1458
|
-
if (delCount > 0) statsParts.push(`删除 ${delCount} 个`);
|
|
1459
|
-
const statsLine = statsParts.length > 0 ? `改动统计: ${statsParts.join(",")}\n\n` : "";
|
|
1460
|
-
|
|
1461
|
-
updateChainContext(chainKey,
|
|
1462
|
-
`${step.loopAgentName === "worker" ? "代码实施" : "代码精简"}已完成。\n` +
|
|
1463
|
-
statsLine +
|
|
1464
|
-
`变更文件列表:\n${loopAgentChanges.join("\n")}` +
|
|
1465
|
-
(planFileRelPath ? `\n\n实施计划: ${planFileRelPath}\n(可在 .pi-dev-output/pi-plans/ 中 grep UUID ${_workflowId} 找到)` : "")
|
|
1466
|
-
);
|
|
1467
|
-
} else {
|
|
1468
|
-
updateChainContext(chainKey,
|
|
1469
|
-
`${step.loopAgentName} 已完成执行,未检测到文件变更。\n` +
|
|
1470
|
-
(planFileRelPath ? `实施计划: ${planFileRelPath}\n(可在 .pi-dev-output/pi-plans/ 中 grep UUID ${_workflowId} 找到)` : "")
|
|
1322
|
+
// ── Capture loop agent work summary as supplementary chain context ──
|
|
1323
|
+
const loopFinalText = extractFinalOutput(agentResult.output);
|
|
1324
|
+
if (loopFinalText) {
|
|
1325
|
+
updateChainContext(`${step.loopAgentName} 工作总结`,
|
|
1326
|
+
`${step.loopAgentName} 已完成工作,以下是其工作总结:\n\n${loopFinalText}`
|
|
1471
1327
|
);
|
|
1472
1328
|
}
|
|
1473
1329
|
|
|
@@ -1509,20 +1365,14 @@ async function executeLoopGroup(
|
|
|
1509
1365
|
}
|
|
1510
1366
|
}
|
|
1511
1367
|
|
|
1512
|
-
// ──
|
|
1513
|
-
//
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
const reviewStats = reviewCountParts.length > 0 ? `发现 ${reviewCountParts.join(",")} 问题。` : "未发现问题。";
|
|
1521
|
-
|
|
1522
|
-
updateChainContext(reviewChainKey,
|
|
1523
|
-
`${reviewStats}\n` +
|
|
1524
|
-
`完整审查报告在 .pi-dev-output/pi-review/md/ 目录中,\n` +
|
|
1525
|
-
`请在工作流输出目录中 grep UUID "${_workflowId}" 查找最新报告。`
|
|
1368
|
+
// ── Capture reviewer work summary as supplementary chain context ──
|
|
1369
|
+
// The old review-chain-context entries ("代码审查反馈"/"精简审查反馈") based on
|
|
1370
|
+
// _workflowFileChanges were removed in v0.6.0 because they duplicated information
|
|
1371
|
+
// from the review report file and polluted chain context with file-path-heavy data.
|
|
1372
|
+
// Reuse already-parsed extractedOutput to avoid double parsing.
|
|
1373
|
+
if (extractedOutput) {
|
|
1374
|
+
updateChainContext(`审查工作总结`,
|
|
1375
|
+
`审查者已完成审查,以下是其审查总结:\n\n${extractedOutput}`
|
|
1526
1376
|
);
|
|
1527
1377
|
}
|
|
1528
1378
|
|
package/package.json
CHANGED
|
@@ -495,17 +495,19 @@ assert(singleStepFuncStart !== -1, "找到 executeSingleStep 函数");
|
|
|
495
495
|
const singleStepEndSearch = source.indexOf("async function executeLoopGroup", singleStepFuncStart);
|
|
496
496
|
const fullSingleStep = source.slice(singleStepFuncStart, singleStepEndSearch);
|
|
497
497
|
|
|
498
|
-
|
|
499
|
-
|
|
498
|
+
// Verify 工作总结 entries still exist (preserved - from extractFinalOutput)
|
|
499
|
+
const hasWorkSummaryKey = fullSingleStep.includes('工作总结');
|
|
500
|
+
assertTrue(hasWorkSummaryKey, "executeSingleStep 中保留 '工作总结' 条目");
|
|
500
501
|
|
|
501
|
-
const
|
|
502
|
-
assertTrue(
|
|
502
|
+
const hasWorkSummaryUpdate = fullSingleStep.includes('updateChainContext');
|
|
503
|
+
assertTrue(hasWorkSummaryUpdate, "executeSingleStep 中仍有 updateChainContext 调用(工作总结)");
|
|
503
504
|
|
|
504
|
-
|
|
505
|
-
|
|
505
|
+
// Verify the removed summary keys are no longer present
|
|
506
|
+
const hasPlannerKeyRemoved = !fullSingleStep.includes('"计划制定摘要"');
|
|
507
|
+
assertTrue(hasPlannerKeyRemoved, "executeSingleStep 中已移除 '计划制定摘要' key");
|
|
506
508
|
|
|
507
|
-
const
|
|
508
|
-
assertTrue(
|
|
509
|
+
const hasDocWriterKeyRemoved = !fullSingleStep.includes('"文档更新摘要"');
|
|
510
|
+
assertTrue(hasDocWriterKeyRemoved, "executeSingleStep 中已移除 '文档更新摘要' key");
|
|
509
511
|
|
|
510
512
|
|
|
511
513
|
console.log("\n═══ Bug H 测试 — Agent 前置元数据解析 ═══\n");
|
|
@@ -648,11 +650,123 @@ const reviewHasWorkflowId = reviewTaskParams.includes("workflowId");
|
|
|
648
650
|
assertTrue(reviewHasWorkflowId, "buildReviewTask 接收 workflowId 参数");
|
|
649
651
|
|
|
650
652
|
|
|
653
|
+
console.log("\n");
|
|
654
|
+
|
|
655
|
+
console.log("═══ Bug J 测试 — _workflowFileChanges 仅来自 git diff ═══\n");
|
|
656
|
+
|
|
657
|
+
// ── Test J1: _workflowFileChanges.push 仅出现在 updateToolsFromGit ──
|
|
658
|
+
console.log("📋 测试 J1: _workflowFileChanges.push 仅出现在 updateToolsFromGit\n");
|
|
659
|
+
|
|
660
|
+
// Count all _workflowFileChanges.push occurrences
|
|
661
|
+
const pushMatches = [...source.matchAll(/_workflowFileChanges\.push\(/g)];
|
|
662
|
+
assertEq(pushMatches.length, 1, "_workflowFileChanges.push 仅出现 1 次");
|
|
663
|
+
|
|
664
|
+
// Verify the single push is inside updateToolsFromGit by checking
|
|
665
|
+
// the function body of updateToolsFromGit contains the push.
|
|
666
|
+
const updateToolsStart = source.indexOf("function updateToolsFromGit");
|
|
667
|
+
const nextFuncStart = source.indexOf("function saveCheckpoint", updateToolsStart);
|
|
668
|
+
const updateToolsBody = source.slice(updateToolsStart, nextFuncStart);
|
|
669
|
+
const pushInUpdateTools = updateToolsBody.includes("_workflowFileChanges.push");
|
|
670
|
+
assertTrue(pushInUpdateTools, "updateToolsFromGit 函数体内包含 _workflowFileChanges.push");
|
|
671
|
+
|
|
672
|
+
// ── Test J2: addWidgetSubStepTool 不再污染 _workflowFileChanges ──
|
|
673
|
+
console.log("\n📋 测试 J2: addWidgetSubStepTool 不再污染 _workflowFileChanges\n");
|
|
674
|
+
|
|
675
|
+
const addWidgetFunc = source.slice(
|
|
676
|
+
source.indexOf("function addWidgetSubStepTool"),
|
|
677
|
+
source.indexOf("function addWidgetSubStepOutput")
|
|
678
|
+
);
|
|
679
|
+
const hasNoFileChangesRef = !addWidgetFunc.includes("_workflowFileChanges");
|
|
680
|
+
assertTrue(hasNoFileChangesRef, "addWidgetSubStepTool 中不再引用 _workflowFileChanges");
|
|
681
|
+
|
|
682
|
+
// ── Test J3: executeSingleStep 中不再有基于 _workflowFileChanges 的统计 ──
|
|
683
|
+
console.log("\n📋 测试 J3: executeSingleStep 中不再有 '执行摘要' 类 chain context\n");
|
|
684
|
+
|
|
685
|
+
const hasNoExecutorSummary = !fullSingleStep.includes('执行摘要');
|
|
686
|
+
assertTrue(hasNoExecutorSummary, "executeSingleStep 中不再有 '执行摘要' chain context");
|
|
687
|
+
|
|
688
|
+
// ── Test J4: executeLoopGroup 中不再有 "代码实施/精简摘要" chain context ──
|
|
689
|
+
console.log("\n📋 测试 J4: executeLoopGroup 中不再有 '代码实施/精简摘要' chain context\n");
|
|
690
|
+
|
|
691
|
+
const loopGroupStart = source.indexOf("async function executeLoopGroup");
|
|
692
|
+
assert(loopGroupStart !== -1, "找到 executeLoopGroup 函数");
|
|
693
|
+
const loopGroupBody = source.slice(loopGroupStart);
|
|
694
|
+
const hasNoCodeImplSummary = !loopGroupBody.includes('"代码实施摘要"') && !loopGroupBody.includes('"代码精简摘要"');
|
|
695
|
+
assertTrue(hasNoCodeImplSummary, "executeLoopGroup 中不再有 '代码实施摘要' 或 '代码精简摘要' chain context");
|
|
696
|
+
|
|
697
|
+
const hasNoReviewFeedbackKey = !loopGroupBody.includes('"代码审查反馈"') && !loopGroupBody.includes('"精简审查反馈"');
|
|
698
|
+
assertTrue(hasNoReviewFeedbackKey, "executeLoopGroup 中不再有 '代码审查反馈' 或 '精简审查反馈' chain context");
|
|
699
|
+
|
|
700
|
+
// ── Test J5: 工作总结条目在所有 agent 函数中仍保留 ──
|
|
701
|
+
console.log("\n📋 测试 J5: '工作总结' 条目在所有 agent 函数中仍保留\n");
|
|
702
|
+
|
|
703
|
+
const hasWorkSummaryInSingleStep = fullSingleStep.includes('工作总结');
|
|
704
|
+
assertTrue(hasWorkSummaryInSingleStep, "executeSingleStep 保留 '工作总结' 条目");
|
|
705
|
+
|
|
706
|
+
const hasWorkSummaryInLoop = loopGroupBody.includes('工作总结');
|
|
707
|
+
assertTrue(hasWorkSummaryInLoop, "executeLoopGroup 保留 '工作总结' 条目");
|
|
708
|
+
|
|
709
|
+
|
|
651
710
|
console.log("\n=== 增强功能测试汇总 ===\n");
|
|
652
711
|
console.log("📋 附加测试覆盖:");
|
|
653
|
-
console.log(" - G: 链上下文传递 (executeSingleStep)");
|
|
712
|
+
console.log(" - G: 链上下文传递 (executeSingleStep) — 已更新验证工作总结");
|
|
654
713
|
console.log(" - H: Agent 前置元数据解析 + MCP/SKILL 移除");
|
|
655
714
|
console.log(" - I: 工作流 UUID 溯源机制");
|
|
715
|
+
console.log(" - J: _workflowFileChanges 仅来自 git diff");
|
|
716
|
+
console.log("\n═══ Bug K 测试 — git diff 精准识别 + .pi-dev-output 过滤 ═══\n");
|
|
717
|
+
|
|
718
|
+
// ── Test K1: filePatterns 已移除 — 不再用 regex 猜文件路径 ──
|
|
719
|
+
console.log("📋 测试 K1: filePatterns 已移除\n");
|
|
720
|
+
|
|
721
|
+
const hasFilePatterns = source.includes("const filePatterns = [");
|
|
722
|
+
assertFalse(hasFilePatterns, "filePatterns 数组已移除(不再用 regex 嗅探文件路径)");
|
|
723
|
+
|
|
724
|
+
// ── Test K2: seenTools 文本解析 fallback 已移除 ──
|
|
725
|
+
console.log("\n📋 测试 K2: seenTools JSON fallback 已移除\n");
|
|
726
|
+
|
|
727
|
+
const hasJsonFallback = source.includes("if (seenTools.size === 0)");
|
|
728
|
+
assertFalse(hasJsonFallback, "seenTools JSON tool_use fallback 已移除");
|
|
729
|
+
|
|
730
|
+
// ── Test K3: outputPathPatterns 统一 inline 模式已移除 ──
|
|
731
|
+
console.log("\n📋 测试 K3: outputPathPatterns 已简化\n");
|
|
732
|
+
|
|
733
|
+
const hasOutputPathPatterns = source.includes("const outputPathPatterns = [");
|
|
734
|
+
assertFalse(hasOutputPathPatterns, "outputPathPatterns 数组已移除(替换为 inline workflowId 过滤)");
|
|
735
|
+
|
|
736
|
+
// ── Test K4: updateToolsFromGit 有 .pi-dev-output/ 过滤 ──
|
|
737
|
+
console.log("\n📋 测试 K4: updateToolsFromGit 过滤 .pi-dev-output/\n");
|
|
738
|
+
|
|
739
|
+
const hasDotPiDevFilter = source.includes('change.path.startsWith(".pi-dev-output/")');
|
|
740
|
+
assertTrue(hasDotPiDevFilter, "updateToolsFromGit 跳过 .pi-dev-output/ 路径");
|
|
741
|
+
|
|
742
|
+
// ── Test K5: output 路径匹配使用 escapedId + workflowId ──
|
|
743
|
+
console.log("\n📋 测试 K5: output 路径使用 workflowId 过滤\n");
|
|
744
|
+
|
|
745
|
+
const hasEscapedId = source.includes("const escapedId = _workflowId.replace(");
|
|
746
|
+
assertTrue(hasEscapedId, "output 路径匹配使用 escapedId");
|
|
747
|
+
|
|
748
|
+
// ── Test K6: progress handler output match 使用 _workflowId ──
|
|
749
|
+
console.log("\n📋 测试 K6: progress handler 使用 workflowId\n");
|
|
750
|
+
|
|
751
|
+
const hasIncludesWorkflowId = source.includes("pathCandidate.includes(_workflowId)");
|
|
752
|
+
assertTrue(hasIncludesWorkflowId, "progress handler 使用 pathCandidate.includes(_workflowId)");
|
|
753
|
+
|
|
754
|
+
// ── Test K7: addWidgetSubStepTool 只来自 git diff + progress handler ──
|
|
755
|
+
console.log("\n📋 测试 K7: addWidgetSubStepTool 只来自必要来源\n");
|
|
756
|
+
|
|
757
|
+
// Verify the two known call sites exist:
|
|
758
|
+
// 1. updateToolsFromGit: calls addWidgetSubStepTool with git status + path
|
|
759
|
+
// 2. runAgentWithProgress progress handler: calls addWidgetSubStepTool with parsed toolMatch
|
|
760
|
+
// Both are runtime call sites (not the function definition itself).
|
|
761
|
+
assertTrue(source.includes("addWidgetSubStepTool(stepIndex, agentName, `${change.status} ${change.path}`)"), "updateToolsFromGit 调用 addWidgetSubStepTool");
|
|
762
|
+
assertTrue(source.includes("addWidgetSubStepTool(stepIndex, agentName, `${gitStatus} ${target}`)"), "progress handler 调用 addWidgetSubStepTool");
|
|
763
|
+
|
|
764
|
+
// ── Test K8: addWidgetSubStepOutput 仍存在 (output 路径展示有用) ──
|
|
765
|
+
console.log("\n📋 测试 K8: addWidgetSubStepOutput 仍保留\n");
|
|
766
|
+
|
|
767
|
+
const hasAddWidgetOutput = source.includes("function addWidgetSubStepOutput");
|
|
768
|
+
assertTrue(hasAddWidgetOutput, "addWidgetSubStepOutput 函数声明仍保留");
|
|
769
|
+
|
|
656
770
|
|
|
657
771
|
|
|
658
772
|
console.log("\n═══════════════════════════════════════════════════════\n");
|