Skip to content

Commit 7318fa3

Browse files
committed
fix(compose): repairWithLLM 失败时给出明确日志,加 1 项跨 step 同名变量边界测试
深挖 0.6.10 的修复链发现两个细节问题: 1. repairWithLLM 失败时 catch 静默吞错。LLM 因网络/认证/超时失败时只返回 { ok: false },调用方继续走,最终用户看到 "LLM 修复后仍有 X 个变量未解决" 会以为 LLM 修了但不够,实际根本没调通。改为在 stderr 写明失败原因 2. 跨 step 同名 bad var 的已知 limitation: 第一个 step 把 {{review}} 全局 replace 后,第二个 step 即使上游不含该 output 也会被改。这是 autoFix 按 step 处理但用全局正则 replace 的折衷。新增针对性测试明确这个行为, 留给 LLM repair 兜底
1 parent 5968445 commit 7318fa3

3 files changed

Lines changed: 50 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
本项目采用 [语义化版本](https://semver.org/lang/zh-CN/)
44

5+
## [0.6.11] - 2026-04-27
6+
7+
### Fixed
8+
- `repairWithLLM` 失败时静默吞错。LLM 调用因网络/认证/超时失败时不再悄悄返回,会在 stderr 给出失败原因,避免用户看到 "LLM 修复后仍有 X 个变量未解决" 误以为 LLM 修了但不够,实际是根本没调通
9+
10+
### Tests
11+
- 新增 1 项测试覆盖跨 step 同名 bad var 的已知边界行为(全局 replace 只处理一次,靠 LLM repair 兜底)
12+
513
## [0.6.10] - 2026-04-27
614

715
### Fixed

src/cli/compose.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,11 +773,14 @@ inputs: ${inputNames.length > 0 ? inputNames.join('、') : '(无)'}
773773
});
774774
const fixedYaml = extractYamlFromResponse(result.content);
775775
if (!fixedYaml || !fixedYaml.includes('steps:')) {
776+
process.stderr.write(' ⚠️ LLM 二次修复返回的内容不是有效 YAML,保留原文件\n');
776777
return { ok: false, replaced: false };
777778
}
778779
writeFileSync(yamlPath, fixedYaml + '\n', 'utf-8');
779780
return { ok: true, replaced: true };
780-
} catch {
781+
} catch (err) {
782+
const msg = err instanceof Error ? err.message : String(err);
783+
process.stderr.write(` ⚠️ LLM 二次修复调用失败: ${msg.slice(0, 120)}\n`);
781784
return { ok: false, replaced: false };
782785
}
783786
}

test/compose.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,44 @@ steps:
348348
assert(r.fixed === 0, `没有上游应不修,实际 ${r.fixed}`);
349349
});
350350

351+
await test('autoFix: 跨 step 同名 bad var 只全局处理一次(已知 limitation)', async () => {
352+
// 边角 case:两个 step 都引用 {{review}},但上游不同。
353+
// 当前实现用全局 replace + globallyHandled,所以两个 step 的 {{review}} 都
354+
// 被改成同一个 output(第一个匹配到的)。这在罕见的"同名变量不同语义"场景
355+
// 下会让第二个 step 出现新的未定义变量,留给 LLM repair 兜底。
356+
const p = makeYamlFile(validYamlBase + `
357+
steps:
358+
- id: a_step
359+
role: engineering/engineering-sre
360+
task: "A"
361+
output: data_a
362+
363+
- id: b_step
364+
role: engineering/engineering-sre
365+
task: "B"
366+
output: data_b
367+
368+
- id: review_a
369+
role: engineering/engineering-sre
370+
task: "review {{review}}"
371+
output: review_a_out
372+
depends_on: [a_step]
373+
374+
- id: review_b
375+
role: engineering/engineering-sre
376+
task: "review {{review}}"
377+
output: review_b_out
378+
depends_on: [b_step]
379+
`);
380+
const r = await autoFixVariableRefs(p);
381+
// 期望:第一个 step (review_a) 把 {{review}} → {{data_a}}(上游唯一 output)
382+
// 第二个 step (review_b) 的 {{review}} 也被全局 replace 改成 data_a
383+
// 但 review_b 的上游是 b_step,data_a 不在它的 depends_on 闭包里
384+
// 这是已知 limitation,由 LLM repair 兜底。autoFix 自身只确保不指向"下游"
385+
assert(r.fixed === 1, `应仅记录 1 次替换(全局),实际 ${r.fixed}`);
386+
assert(r.details[0].from === 'review', `期望 from=review,实际 ${r.details[0].from}`);
387+
});
388+
351389
// ─── 汇总 ───
352390
console.log(`\n 结果: ${passed} 通过, ${failed} 失败\n`);
353391
if (failed > 0) process.exit(1);

0 commit comments

Comments
 (0)