/** * 意圖語法寫得出條件分支 → 編圖帶對邊型與標籤(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 }> = {}; 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, 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(); }); });