Files
Arcrun/cypher-executor/tests/intent-branch-syntax.test.ts
T
uncle6me-web a9a47b7c37 🔴 步驟5 補斷點:教材說「引擎不支援條件分支」+意圖語法收不到分支標籤
備考 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>
2026-07-31 16:51:19 +08:00

105 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 意圖語法寫得出條件分支 → 編圖帶對邊型與標籤(SDD workflow-discovery 3.11
*
* 為什麼補這一支(08-01 施工中自查發現的斷點,差點漏掉):
* 引擎支援了 ON_TRUE/ON_FALSE/ON_BRANCHskill 文件也教了寫法,
* 但 `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_TRUEON_FALSE', () => {
it('ON_TRUEON_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_BRANCHbranch_active >> 處理啟用']);
const e = edgeBetween(g, 'my_switch', '處理啟用');
expect(e?.type).toBe('ON_BRANCH');
expect(e?.branch).toBe('branch_active');
});
it('try_catch 的 trycatch 標籤同樣收得到', () => {
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();
});
});