@ghyper9023/pi-dev-workflow 0.5.1 → 0.6.1
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 +97 -59
- package/agents/workflow/reviewer-agent.md +100 -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/workflow-engine.ts +149 -271
- package/package.json +1 -1
- package/tests/test-workflow-engine-bugs.mjs +252 -10
|
@@ -389,20 +389,16 @@ function getGitDiffChanges(cwd: string): GitFileChange[] {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
392
|
+
* 检查路径是否为工作流生成的产物路径(.pi-dev-output/),
|
|
393
|
+
* 若是则不应出现在 A: M: D: 文件变更列表中。
|
|
394
|
+
* 该目录已加入 .gitignore,git diff 本身已忽略,
|
|
395
|
+
* 此函数作为防御性过滤。
|
|
394
396
|
*/
|
|
395
|
-
function
|
|
396
|
-
|
|
397
|
-
case "new": case "write": case "create": case "add": case "created": case "added":
|
|
398
|
-
return "A";
|
|
399
|
-
case "delete": case "remove": case "deleted": case "removed":
|
|
400
|
-
return "D";
|
|
401
|
-
default:
|
|
402
|
-
return "M";
|
|
403
|
-
}
|
|
397
|
+
function isWorkflowArtifactPath(filePath: string): boolean {
|
|
398
|
+
return filePath.startsWith(".pi-dev-output/");
|
|
404
399
|
}
|
|
405
400
|
|
|
401
|
+
|
|
406
402
|
/**
|
|
407
403
|
* Check whether a file's current content hash differs from its baseline.
|
|
408
404
|
* Returns true if the hash changed or the file is no longer accessible.
|
|
@@ -447,13 +443,20 @@ function captureBaseline(cwd: string): void {
|
|
|
447
443
|
*
|
|
448
444
|
* Uses git-format status codes (M, A, D) for display consistency.
|
|
449
445
|
* Deduplicates against existing _workflowFileChanges.
|
|
446
|
+
*
|
|
447
|
+
* The dedup key includes stepIndex (filePath:stepIndex) so the same file
|
|
448
|
+
* can appear across different workflow steps (e.g., created by worker in
|
|
449
|
+
* loop-group, then reviewed by reviewer) without being collapsed into one.
|
|
450
|
+
* Without :stepIndex, the Set dedup would wrongly skip a file modified in
|
|
451
|
+
* a later step just because it was already tracked in an earlier step.
|
|
452
|
+
*
|
|
450
453
|
*/
|
|
451
454
|
function updateToolsFromGit(cwd: string, stepIndex: number, agentName: string): void {
|
|
452
455
|
const currentChanges = getGitDiffChanges(cwd);
|
|
453
|
-
const seen = new Set(_workflowFileChanges.map(c => c.filePath));
|
|
454
|
-
|
|
456
|
+
const seen = new Set(_workflowFileChanges.map(c => `${c.filePath}:${c.stepIndex}`));
|
|
457
|
+
|
|
455
458
|
for (const change of currentChanges) {
|
|
456
|
-
if (seen.has(change.path)) continue;
|
|
459
|
+
if (seen.has(`${change.path}:${stepIndex}`)) continue;
|
|
457
460
|
|
|
458
461
|
// ── Baseline filtering ──────────────────────────────
|
|
459
462
|
// Skip files that were already dirty at workflow start and haven't been touched.
|
|
@@ -470,7 +473,13 @@ function updateToolsFromGit(cwd: string, stepIndex: number, agentName: string):
|
|
|
470
473
|
}
|
|
471
474
|
}
|
|
472
475
|
|
|
473
|
-
|
|
476
|
+
// Skip files in .pi-dev-output/ — these are workflow-generated artifacts (plans, reviews)
|
|
477
|
+
// and should NOT be reported as user repo changes. Real git clients (VSCode, Zed) also
|
|
478
|
+
// ignore this directory (it's in .gitignore). This filter is defense-in-depth.
|
|
479
|
+
// Note: git always uses forward slashes in paths, even on Windows.
|
|
480
|
+
if (isWorkflowArtifactPath(change.path)) continue;
|
|
481
|
+
|
|
482
|
+
seen.add(`${change.path}:${stepIndex}`);
|
|
474
483
|
const type: FileChangeEntry["type"] =
|
|
475
484
|
change.status === "A" ? "new" :
|
|
476
485
|
change.status === "D" ? "delete" :
|
|
@@ -883,47 +892,6 @@ function addWidgetSubStepTool(stepIndex: number, agentName: string, tool: string
|
|
|
883
892
|
if (!sub.tools) sub.tools = [];
|
|
884
893
|
sub.tools.push(tool);
|
|
885
894
|
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
895
|
refreshWidget();
|
|
928
896
|
}
|
|
929
897
|
}
|
|
@@ -1123,33 +1091,81 @@ async function runAgentWithProgress(
|
|
|
1123
1091
|
}
|
|
1124
1092
|
}
|
|
1125
1093
|
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1128
|
-
//
|
|
1129
|
-
//
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1094
|
+
// ── Start git diff polling for real file changes ──
|
|
1095
|
+
// This replaces the unreliable regex-based tool call sniffing that was removed.
|
|
1096
|
+
// git diff --name-status is the same approach used by VSCode, Zed, and every
|
|
1097
|
+
// professional git client — it's deterministic, noise-free, and 100% accurate.
|
|
1098
|
+
let _gitPollTimer: ReturnType<typeof setInterval> | null = null;
|
|
1099
|
+
const _pollSeen = new Set<string>();
|
|
1100
|
+
if (_workflowCwd) {
|
|
1101
|
+
_gitPollTimer = setInterval(() => {
|
|
1102
|
+
try {
|
|
1103
|
+
const currentChanges = getGitDiffChanges(_workflowCwd);
|
|
1104
|
+
for (const change of currentChanges) {
|
|
1105
|
+
const key = `${change.path}:${stepIndex}`;
|
|
1106
|
+
if (_pollSeen.has(key)) continue;
|
|
1107
|
+
_pollSeen.add(key);
|
|
1108
|
+
|
|
1109
|
+
if (isWorkflowArtifactPath(change.path)) continue;
|
|
1110
|
+
if (_workflowBaseline.length > 0) {
|
|
1111
|
+
const baseEntry = _workflowBaseline.find(b => b.path === change.path);
|
|
1112
|
+
if (baseEntry) {
|
|
1113
|
+
if (!hasContentChanged(_workflowCwd, change.path, baseEntry.hash)) continue;
|
|
1114
|
+
// ⭐ 同步删除已变更的基线条目,与 updateToolsFromGit 行为一致
|
|
1115
|
+
_workflowBaseline = _workflowBaseline.filter(b => b.path !== change.path);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// ⭐ Also record in _workflowFileChanges so the completion report file tree
|
|
1120
|
+
// and revertStepChanges have accurate data. The final updateToolsFromGit call
|
|
1121
|
+
// will skip this via seen, so we must record it here.
|
|
1122
|
+
const type: FileChangeEntry["type"] =
|
|
1123
|
+
change.status === "A" ? "new" :
|
|
1124
|
+
change.status === "D" ? "delete" :
|
|
1125
|
+
"edit";
|
|
1126
|
+
_workflowFileChanges.push({
|
|
1127
|
+
agent: agentName,
|
|
1128
|
+
stepIndex,
|
|
1129
|
+
type,
|
|
1130
|
+
filePath: change.path,
|
|
1131
|
+
timestamp: new Date().toISOString(),
|
|
1132
|
+
});
|
|
1133
|
+
addWidgetSubStepTool(stepIndex, agentName, `${change.status} ${change.path}`);
|
|
1134
|
+
_widgetExtraToolCount++;
|
|
1135
|
+
}
|
|
1136
|
+
} catch { /* 静默忽略 git 错误 */ }
|
|
1137
|
+
}, 5000);
|
|
1138
|
+
_gitPollTimer.unref();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Run the sub-agent with progress reporting
|
|
1142
|
+
// The toolMatch regex sniffing has been removed — it was the primary source of
|
|
1143
|
+
// false positives in the A: M: D: panel (matching AI natural language as tool calls).
|
|
1144
|
+
// File change detection is now handled entirely by git diff polling (above) and
|
|
1145
|
+
// the final updateToolsFromGit call (below), both of which use git's own API.
|
|
1146
|
+
let result;
|
|
1147
|
+
try {
|
|
1148
|
+
result = await spawnSubagent(agent, task, _workflowCwd, signal, timeoutMs, (progress) => {
|
|
1149
|
+
// Detect output file paths — only match .pi-dev-output paths belonging to current workflow
|
|
1150
|
+
if (_workflowId) {
|
|
1151
|
+
const outputMatch = progress.match(/\.pi-dev-output\/[a-zA-Z0-9_\/\.-]+/i);
|
|
1152
|
+
if (outputMatch) {
|
|
1153
|
+
const pathCandidate = outputMatch[0]!.trim();
|
|
1154
|
+
// ⭐ 严格白名单:拒绝中文、引号、括号等非路径字符
|
|
1155
|
+
if (pathCandidate.length > 15 && pathCandidate.length < 300 &&
|
|
1156
|
+
pathCandidate.includes(_workflowId) &&
|
|
1157
|
+
/^[\w.\/-]+$/.test(pathCandidate)) {
|
|
1158
|
+
addWidgetSubStepOutput(stepIndex, agentName, pathCandidate);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1150
1161
|
}
|
|
1162
|
+
}, _workflowId ? { workflowId: _workflowId } : undefined);
|
|
1163
|
+
} finally {
|
|
1164
|
+
if (_gitPollTimer) {
|
|
1165
|
+
clearInterval(_gitPollTimer);
|
|
1166
|
+
_gitPollTimer = null;
|
|
1151
1167
|
}
|
|
1152
|
-
}
|
|
1168
|
+
}
|
|
1153
1169
|
|
|
1154
1170
|
const agentDuration = Date.now() - agentStartTime;
|
|
1155
1171
|
|
|
@@ -1163,127 +1179,56 @@ async function runAgentWithProgress(
|
|
|
1163
1179
|
toolCount: _widgetExtraToolCount,
|
|
1164
1180
|
});
|
|
1165
1181
|
|
|
1166
|
-
// ── Post-completion:
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1169
|
-
|
|
1182
|
+
// ── Post-completion: file change detection via git diff ONLY ──
|
|
1183
|
+
// We do NOT parse agent output text for file paths — that approach is fragile and
|
|
1184
|
+
// produces false positives (e.g., matching JS property names like `.push`, `.length`
|
|
1185
|
+
// from code snippets). Instead, we rely entirely on git diff --name-status, which is
|
|
1186
|
+
// the same approach used by VSCode, Zed, and every other professional git client.
|
|
1187
|
+
// It's deterministic, noise-free, and always produces correct relative paths.
|
|
1170
1188
|
const finalOutput = extractFinalOutput(result.output) || result.output;
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
//
|
|
1185
|
-
/(
|
|
1186
|
-
|
|
1187
|
-
const seenTools = new Set<string>();
|
|
1188
|
-
for (const pattern of filePatterns) {
|
|
1189
|
+
|
|
1190
|
+
// Find output file paths (plans, reviews) for display in the "output:" section
|
|
1191
|
+
// These are workflow artifacts (.pi-dev-output/) — only show files belonging to
|
|
1192
|
+
// the CURRENT workflow to avoid cross-contamination from previous runs.
|
|
1193
|
+
// Note: file change detection itself relies exclusively on git diff below, not text parsing.
|
|
1194
|
+
// ⭐ IMPORTANT: Only search in CLEAN extracted text (finalOutput), NOT raw JSON output.
|
|
1195
|
+
// The old approach concatenated allOutput (raw JSON + stderr) into searchText and
|
|
1196
|
+
// matched against it, producing false positives from JSON stringified content.
|
|
1197
|
+
// By restricting to extractFinalOutput's clean text, we avoid matching against
|
|
1198
|
+
// JSON-escaped paths, code snippets, or natural language in raw agent output.
|
|
1199
|
+
const cleanText = finalOutput || result.output || "";
|
|
1200
|
+
const seenOutputs = new Set<string>();
|
|
1201
|
+
if (_workflowId) {
|
|
1202
|
+
// Only match .pi-dev-output paths containing the current workflow UUID
|
|
1203
|
+
const escapedId = _workflowId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1204
|
+
const outputPattern = new RegExp(`\\.pi-dev-output\\/[a-zA-Z0-9_\\/.-]*${escapedId}[a-zA-Z0-9_\\/.-]*`, 'g');
|
|
1189
1205
|
let m;
|
|
1190
|
-
while ((m =
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
-
}
|
|
1206
|
+
while ((m = outputPattern.exec(cleanText)) !== null) {
|
|
1207
|
+
const path_ = m[0]!.trim();
|
|
1208
|
+
if (path_.length > 15 && path_.length < 300 && !seenOutputs.has(path_)) {
|
|
1209
|
+
// ⭐ 严格白名单:仅允许 [a-zA-Z0-9_./-] 字符
|
|
1210
|
+
// 拒绝包含中文、引号、括号、空格等非路径字符的乱码匹配
|
|
1211
|
+
if (!/^[\w.\/-]+$/.test(path_)) continue;
|
|
1212
|
+
seenOutputs.add(path_);
|
|
1213
|
+
addWidgetSubStepOutput(stepIndex, agentName, path_);
|
|
1222
1214
|
}
|
|
1223
1215
|
}
|
|
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) {
|
|
1216
|
+
} else {
|
|
1217
|
+
// No workflow ID (unlikely): match .pi-dev-output paths conservatively
|
|
1218
|
+
const outputPattern = /\.pi-dev-output\/[a-zA-Z0-9_\/-]+\.[a-zA-Z0-9]+/g;
|
|
1275
1219
|
let m;
|
|
1276
|
-
while ((m =
|
|
1220
|
+
while ((m = outputPattern.exec(cleanText)) !== null) {
|
|
1277
1221
|
const path_ = m[0]!.trim();
|
|
1278
|
-
if (path_.length >
|
|
1222
|
+
if (path_.length > 15 && path_.length < 300 && !seenOutputs.has(path_)) {
|
|
1223
|
+
// ⭐ 严格白名单
|
|
1224
|
+
if (!/^[\w.\/-]+$/.test(path_)) continue;
|
|
1279
1225
|
seenOutputs.add(path_);
|
|
1280
1226
|
addWidgetSubStepOutput(stepIndex, agentName, path_);
|
|
1281
1227
|
}
|
|
1282
1228
|
}
|
|
1283
1229
|
}
|
|
1284
1230
|
|
|
1285
|
-
|
|
1286
|
-
// ── Update file changes from git diff (more accurate than text scraping) ──
|
|
1231
|
+
// ── Update file changes from git diff (more accurate than text scraping) ──
|
|
1287
1232
|
updateToolsFromGit(_workflowCwd, stepIndex, agentName);
|
|
1288
1233
|
// Update sub-step status based on result
|
|
1289
1234
|
const subStatus: WorkflowSubStepWidgetState["status"] =
|
|
@@ -1350,41 +1295,19 @@ async function executeSingleStep(
|
|
|
1350
1295
|
throw new Error(`Agent 错误 (exit ${result.exitCode}): ${result.stderr.slice(0, 500)}`);
|
|
1351
1296
|
}
|
|
1352
1297
|
|
|
1353
|
-
// ── Capture chain context for single-step agents ──
|
|
1354
|
-
const agentChanges = _workflowFileChanges
|
|
1355
|
-
.filter(c => c.stepIndex === stepIndex && c.agent === agentName)
|
|
1356
|
-
.map(c => `${c.type === "new" ? "A" : c.type === "delete" ? "D" : "M"} ${c.filePath}`);
|
|
1357
|
-
|
|
1358
|
-
let chainKey: string;
|
|
1359
|
-
if (agentName === "planner") chainKey = "计划制定摘要";
|
|
1360
|
-
else if (agentName === "docWriter") chainKey = "文档更新摘要";
|
|
1361
|
-
else chainKey = `${agentName} 执行摘要`;
|
|
1362
|
-
|
|
1363
|
-
if (agentChanges.length > 0) {
|
|
1364
|
-
const editCount = agentChanges.filter(c => c.startsWith("M")).length;
|
|
1365
|
-
const newCount = agentChanges.filter(c => c.startsWith("A")).length;
|
|
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} 已完成执行,未检测到文件变更。`);
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
1298
|
// ── Capture AI work summary as supplementary chain context ──
|
|
1299
|
+
// We intentionally do NOT create chain contexts from _workflowFileChanges here.
|
|
1300
|
+
// File-change-based chain contexts ("计划制定摘要"/"文档更新摘要") were removed in v0.6.0
|
|
1301
|
+
// because their M/A/D counts provided negligible value for downstream agents compared
|
|
1302
|
+
// to the agent's own free-text work summary. The file-change approach also created
|
|
1303
|
+
// tight coupling between executeSingleStep and the _workflowFileChanges data structure.
|
|
1383
1304
|
const workSummary = extractFinalOutput(result.output);
|
|
1384
1305
|
if (workSummary) {
|
|
1385
|
-
updateChainContext(`${agentName} 工作总结`,
|
|
1386
|
-
|
|
1387
|
-
|
|
1306
|
+
updateChainContext(`${agentName} 工作总结`, `${agentName} 已完成工作,以下是其工作总结:\n\n${workSummary}`);
|
|
1307
|
+
} else if (result.output) {
|
|
1308
|
+
// 保底:使用输出前 200 字符作为简略摘要
|
|
1309
|
+
const fallback = result.output.slice(0, 200).trim();
|
|
1310
|
+
if (fallback) updateChainContext(`${agentName} 工作总结`, fallback);
|
|
1388
1311
|
}
|
|
1389
1312
|
}
|
|
1390
1313
|
|
|
@@ -1448,37 +1371,6 @@ async function executeLoopGroup(
|
|
|
1448
1371
|
}
|
|
1449
1372
|
}
|
|
1450
1373
|
|
|
1451
|
-
// ── After loop agent completes: capture changes + stats for chain context ──
|
|
1452
|
-
const loopAgentChanges = _workflowFileChanges
|
|
1453
|
-
.filter(c => c.stepIndex === stepIndex && c.agent === step.loopAgentName)
|
|
1454
|
-
.map(c => `${c.type === "new" ? "A" : c.type === "delete" ? "D" : "M"} ${c.filePath}`);
|
|
1455
|
-
|
|
1456
|
-
// Use agent-specific keys so worker and trimmer changes coexist without overwriting
|
|
1457
|
-
const chainKey = step.loopAgentName === "worker" ? "代码实施摘要" : "代码精简摘要";
|
|
1458
|
-
|
|
1459
|
-
if (loopAgentChanges.length > 0) {
|
|
1460
|
-
const editCount = loopAgentChanges.filter(c => c.startsWith("M")).length;
|
|
1461
|
-
const newCount = loopAgentChanges.filter(c => c.startsWith("A")).length;
|
|
1462
|
-
const delCount = loopAgentChanges.filter(c => c.startsWith("D")).length;
|
|
1463
|
-
const statsParts: string[] = [];
|
|
1464
|
-
if (editCount > 0) statsParts.push(`修改 ${editCount} 个`);
|
|
1465
|
-
if (newCount > 0) statsParts.push(`新增 ${newCount} 个`);
|
|
1466
|
-
if (delCount > 0) statsParts.push(`删除 ${delCount} 个`);
|
|
1467
|
-
const statsLine = statsParts.length > 0 ? `改动统计: ${statsParts.join(",")}\n\n` : "";
|
|
1468
|
-
|
|
1469
|
-
updateChainContext(chainKey,
|
|
1470
|
-
`${step.loopAgentName === "worker" ? "代码实施" : "代码精简"}已完成。\n` +
|
|
1471
|
-
statsLine +
|
|
1472
|
-
`变更文件列表:\n${loopAgentChanges.join("\n")}` +
|
|
1473
|
-
(planFileRelPath ? `\n\n实施计划: ${planFileRelPath}\n(可在 .pi-dev-output/pi-plans/ 中 grep UUID ${_workflowId} 找到)` : "")
|
|
1474
|
-
);
|
|
1475
|
-
} else {
|
|
1476
|
-
updateChainContext(chainKey,
|
|
1477
|
-
`${step.loopAgentName} 已完成执行,未检测到文件变更。\n` +
|
|
1478
|
-
(planFileRelPath ? `实施计划: ${planFileRelPath}\n(可在 .pi-dev-output/pi-plans/ 中 grep UUID ${_workflowId} 找到)` : "")
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
1374
|
// ── Capture loop agent work summary as supplementary chain context ──
|
|
1483
1375
|
const loopFinalText = extractFinalOutput(agentResult.output);
|
|
1484
1376
|
if (loopFinalText) {
|
|
@@ -1525,25 +1417,11 @@ async function executeLoopGroup(
|
|
|
1525
1417
|
}
|
|
1526
1418
|
}
|
|
1527
1419
|
|
|
1528
|
-
// ── After reviewer: capture review context for next loop ──
|
|
1529
|
-
// Use agent-specific key so worker-reviewer and trimmer-reviewer feedback don't interfere
|
|
1530
|
-
const reviewChainKey = step.loopAgentName === "worker" ? "代码审查反馈" : "精简审查反馈";
|
|
1531
|
-
if (reviewSummary) {
|
|
1532
|
-
const reviewCountParts: string[] = [];
|
|
1533
|
-
if (reviewSummary.critical > 0) reviewCountParts.push(`严重 ${reviewSummary.critical} 个`);
|
|
1534
|
-
if (reviewSummary.medium > 0) reviewCountParts.push(`中等 ${reviewSummary.medium} 个`);
|
|
1535
|
-
if (reviewSummary.low > 0) reviewCountParts.push(`低 ${reviewSummary.low} 个`);
|
|
1536
|
-
const reviewStats = reviewCountParts.length > 0 ? `发现 ${reviewCountParts.join(",")} 问题。` : "未发现问题。";
|
|
1537
|
-
|
|
1538
|
-
updateChainContext(reviewChainKey,
|
|
1539
|
-
`${reviewStats}\n` +
|
|
1540
|
-
`完整审查报告在 .pi-dev-output/pi-review/md/ 目录中,\n` +
|
|
1541
|
-
`请在工作流输出目录中 grep UUID "${_workflowId}" 查找最新报告。`
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
1420
|
// ── Capture reviewer work summary as supplementary chain context ──
|
|
1546
|
-
//
|
|
1421
|
+
// The old review-chain-context entries ("代码审查反馈"/"精简审查反馈") based on
|
|
1422
|
+
// _workflowFileChanges were removed in v0.6.0 because they duplicated information
|
|
1423
|
+
// from the review report file and polluted chain context with file-path-heavy data.
|
|
1424
|
+
// Reuse already-parsed extractedOutput to avoid double parsing.
|
|
1547
1425
|
if (extractedOutput) {
|
|
1548
1426
|
updateChainContext(`审查工作总结`,
|
|
1549
1427
|
`审查者已完成审查,以下是其审查总结:\n\n${extractedOutput}`
|