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>
73 lines
3.1 KiB
TypeScript
73 lines
3.1 KiB
TypeScript
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 抽 iterator:cypher 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 };
|
||
}
|