Files
Arcrun/cypher-executor/src/actions/graph-builder.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

73 lines
3.1 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.
import type { ParsedTriplets } from './triplet-parser';
import { toEdgeType } from './triplet-parser';
import type { SearchResult } from './search-nodes';
/** 從 nodeResults + parsed 組成可直接送入 /execute 的 ExecutionGraph
*
* config 格式(來自 workflow YAML 的 config 欄位):
* { node_name: { component: "cmp_xxxxxxxx" | "rec_xxxxxxxx" | canonical_id, ...params } }
*
* 若 config[node].component 存在,以它覆蓋 searchNodes 偵測到的 componentId。
* config[node] 的其他欄位作為節點靜態參數(node.data),合併進執行 context。
*/
export function buildExecutionGraph(
parsed: ParsedTriplets,
nodeResults: SearchResult['nodeResults'],
graphId: string,
graphName: string,
config?: Record<string, Record<string, unknown>>,
) {
const nodes = [...parsed.nodeNames].map(name => {
const nr = nodeResults[name]!;
const id = name.toLowerCase().replace(/\s+/g, '-');
const nodeConfig = config?.[name] ?? {};
// config[name].component 可以是 hash 或 canonical_id,覆蓋自動偵測的 componentId
const componentId = (nodeConfig.component as string | undefined) ?? nr.componentId;
// 其他 config 欄位作為 node.data(靜態參數)
const { component: _component, ...staticParams } = nodeConfig;
const data = Object.keys(staticParams).length > 0 ? staticParams : undefined;
return { id, type: nr.type, componentId, label: name, data };
});
const edges = parsed.edges.map(e => {
// 「對每個 X」label 抽 iteratorcypher binding 表達 FOREACH 的迭代變數
// 例:'A >> 對每個 paragraph >> B' → type=FOREACH, iterator='paragraph'
// getIterableFromContext 會找 ctx.paragraphs(複數)或 ctx.paragraph
let iterator: string | undefined;
let label = e.label;
const foreachMatch = label.match(/^(?:對每個|FOREACH)\s+(\w+)$/i);
if (foreachMatch) {
iterator = foreachMatch[1];
label = '對每個'; // 改回標準 label 走 SEMANTIC_EDGE_MAP
}
// 「ON_BRANCH(標籤)」抽 branch:意圖語法表達具名分支(SDD workflow-discovery 3.11
// 例:'my_switch >> ON_BRANCH(branch_active) >> 處理啟用' → type=ON_BRANCH, branch='branch_active'
// 沒有這段的話,帶括號的 label 會落到 toEdgeType 的預設值 PIPE ⇒ 分支靜默失效
// (即「教了語法但引擎不收」——比沒做更糟,故與 skill 文件同批補上)
let branch: string | undefined;
const branchMatch = label.match(/^(?:ON_BRANCH|分支)\s*[(]\s*([\w-]+)\s*[)]$/i);
if (branchMatch) {
branch = branchMatch[1];
label = 'ON_BRANCH';
}
const edge: {
from: string; to: string; type: ReturnType<typeof toEdgeType>;
iterator?: string; branch?: string;
} = {
from: e.from.toLowerCase().replace(/\s+/g, '-'),
to: e.to.toLowerCase().replace(/\s+/g, '-'),
type: toEdgeType(label),
};
if (iterator) edge.iterator = iterator;
if (branch) edge.branch = branch;
return edge;
});
return { id: graphId, name: graphName, nodes, edges };
}