a9a47b7c37
備考 haiku 真考時自查發現的兩個斷點——**若不補,考試必掛,且掛的是我自己的教材**。
形狀=步驟1 那個「世代脫節」的翻版:能力做好了,但教 AI 的地方還停在舊世代。
斷點①:三處教材主動說「沒有條件分支」,還教了正好造成腹語術的替代法
- mcp/src/mcp-handler.ts:44「**沒有** ON_TRUE/ON_FALSE——引擎目前不支援條件分支」
- registry/skills/write_intent_workflow.md:39「不要寫 ON_TRUE/ON_FALSE…
需要判斷時**寫成一個獨立節點**再接 ON_SUCCESS」← 這正是「判斷退回 code」的入口
- registry/skills/INDEX.md:44「已知的坑:引擎沒有條件分支」
⇒ 三處全部更新成現況(三顆零件都輸出 data.branch、引擎依標籤選路、
查零件回應附 branch_hint 照著接即可),並保留「不要寫 ON_FAILURE」(那個真的沒有)。
斷點②(更隱蔽,靜默失效):`graph-builder` 只認得 `對每個 X` 的參數化 label,
`ON_BRANCH(branch_active)` 帶括號會落到 toEdgeType 預設值 **PIPE**
⇒ 我在 skill 教的寫法,編圖收不到,而且**不報錯**——AI 以為分支了、實際全走同一條。
「教了語法但引擎不收」比沒做更糟,故與文件同批補上:比照 FOREACH 抽 iterator 的作法
抽 branch 標籤(半形/全形括號都收),寫進 edge.branch。
新增 tests/intent-branch-syntax.test.ts(7 項綠):守「文件教的寫法,編圖真的收得到」
——ON_TRUE/ON_FALSE 不退化成 PIPE、中文「成立時/否則」、ON_BRANCH(標籤) 抽得出 branch、
全形括號、try/catch 標籤;零變化:ON_SUCCESS 仍是 ON_SUCCESS、對每個 X 的 iterator 不受干擾。
全套 234 passed(前 227 +7),失敗數維持既有 9 筆;tsc 綠。
SDD: workflow-discovery 3.11|CP: arcrun-usable 步驟 5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
105 lines
4.6 KiB
TypeScript
105 lines
4.6 KiB
TypeScript
/**
|
||
* 意圖語法寫得出條件分支 → 編圖帶對邊型與標籤(SDD workflow-discovery 3.11)
|
||
*
|
||
* 為什麼補這一支(08-01 施工中自查發現的斷點,差點漏掉):
|
||
* 引擎支援了 ON_TRUE/ON_FALSE/ON_BRANCH,skill 文件也教了寫法,
|
||
* 但 `graph-builder` 原本**只認得 `對每個 X` 的參數化 label**,
|
||
* `ON_BRANCH(branch_active)` 這種帶括號的 label 會落到 `toEdgeType` 的預設值 **PIPE**
|
||
* ⇒ 「教了語法但引擎不收,而且是靜默的」——比沒做更糟(AI 以為分支了,實際全走同一條)。
|
||
*
|
||
* 本檔守的就是「文件教的寫法,編圖真的收得到」這條線。
|
||
*/
|
||
import { describe, it, expect } from 'vitest';
|
||
import { buildExecutionGraph } from '../src/actions/graph-builder';
|
||
import { parseTriplets, resolveNodeRole } from '../src/actions/triplet-parser';
|
||
|
||
/** 把意圖字串編成圖(走 AI 真正會走的那條路:triplets → graph)。
|
||
* nodeResults 用「全部 found」的最小替身——本檔只驗**邊**的編法,零件解析另有測試。 */
|
||
function build(triplets: string[]) {
|
||
const parsed = parseTriplets(triplets)!;
|
||
const nodeResults: Record<string, { status: 'found'; componentId: string; type: ReturnType<typeof resolveNodeRole> }> = {};
|
||
for (const name of parsed.nodeNames) {
|
||
nodeResults[name] = {
|
||
status: 'found',
|
||
componentId: name.toLowerCase().replace(/\s+/g, '_'),
|
||
type: resolveNodeRole(name, parsed),
|
||
};
|
||
}
|
||
return buildExecutionGraph(parsed, nodeResults as never, 'test-graph', '測試');
|
||
}
|
||
|
||
function edgeBetween(graph: ReturnType<typeof build>, from: string, to: string) {
|
||
return graph.edges.find(e => e.from === from && e.to === to);
|
||
}
|
||
|
||
describe('意圖語法:if_control 兩路(ON_TRUE/ON_FALSE)', () => {
|
||
it('ON_TRUE/ON_FALSE 編成對應邊型,不會退化成 PIPE', () => {
|
||
const g = build([
|
||
'input >> ON_SUCCESS >> 判斷有沒有新資料',
|
||
'判斷有沒有新資料 >> ON_TRUE >> 傳到telegram',
|
||
'判斷有沒有新資料 >> ON_FALSE >> 結束',
|
||
]);
|
||
expect(edgeBetween(g, '判斷有沒有新資料', '傳到telegram')?.type).toBe('ON_TRUE');
|
||
expect(edgeBetween(g, '判斷有沒有新資料', '結束')?.type).toBe('ON_FALSE');
|
||
});
|
||
|
||
it('中文語意詞「成立時」「否則」也編得出來', () => {
|
||
const g = build([
|
||
'判斷有沒有新資料 >> 成立時 >> 傳到telegram',
|
||
'判斷有沒有新資料 >> 否則 >> 結束',
|
||
]);
|
||
expect(edgeBetween(g, '判斷有沒有新資料', '傳到telegram')?.type).toBe('ON_TRUE');
|
||
expect(edgeBetween(g, '判斷有沒有新資料', '結束')?.type).toBe('ON_FALSE');
|
||
});
|
||
});
|
||
|
||
describe('意圖語法:switch 具名分支(ON_BRANCH(標籤))', () => {
|
||
it('括號裡的標籤被抽成 edge.branch,型別是 ON_BRANCH', () => {
|
||
const g = build([
|
||
'my_switch >> ON_BRANCH(branch_active) >> 處理啟用',
|
||
'my_switch >> ON_BRANCH(branch_pending) >> 處理待辦',
|
||
'my_switch >> ON_BRANCH(branch_default) >> 其他',
|
||
]);
|
||
const active = edgeBetween(g, 'my_switch', '處理啟用');
|
||
expect(active?.type).toBe('ON_BRANCH');
|
||
expect(active?.branch).toBe('branch_active');
|
||
|
||
const pending = edgeBetween(g, 'my_switch', '處理待辦');
|
||
expect(pending?.branch).toBe('branch_pending');
|
||
|
||
const dflt = edgeBetween(g, 'my_switch', '其他');
|
||
expect(dflt?.branch).toBe('branch_default');
|
||
});
|
||
|
||
it('全形括號也收(中文輸入法常打出全形)', () => {
|
||
const g = build(['my_switch >> ON_BRANCH(branch_active) >> 處理啟用']);
|
||
const e = edgeBetween(g, 'my_switch', '處理啟用');
|
||
expect(e?.type).toBe('ON_BRANCH');
|
||
expect(e?.branch).toBe('branch_active');
|
||
});
|
||
|
||
it('try_catch 的 try/catch 標籤同樣收得到', () => {
|
||
const g = build([
|
||
'my_try >> ON_BRANCH(try) >> 正常流程',
|
||
'my_try >> ON_BRANCH(catch) >> 補救流程',
|
||
]);
|
||
expect(edgeBetween(g, 'my_try', '正常流程')?.branch).toBe('try');
|
||
expect(edgeBetween(g, 'my_try', '補救流程')?.branch).toBe('catch');
|
||
});
|
||
});
|
||
|
||
describe('零變化:既有語法不受影響', () => {
|
||
it('ON_SUCCESS 仍是 ON_SUCCESS', () => {
|
||
const g = build(['input >> ON_SUCCESS >> prep']);
|
||
expect(edgeBetween(g, 'input', 'prep')?.type).toBe('ON_SUCCESS');
|
||
});
|
||
|
||
it('「對每個 X」仍抽得到 iterator(不被新的 branch 抽取干擾)', () => {
|
||
const g = build(['parse_card >> 對每個 block >> post_block']);
|
||
const e = edgeBetween(g, 'parse_card', 'post_block');
|
||
expect(e?.type).toBe('FOREACH');
|
||
expect(e?.iterator).toBe('block');
|
||
expect(e?.branch).toBeUndefined();
|
||
});
|
||
});
|