Files
Arcrun/cli/src/lib/yaml-parser.ts
T
uncle6me-web 922a57fe34 arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。

此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在
richblack/arcrun 與本地 backup 分支)。含:
- acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe)
- recipe push 把關(資料外流提醒 + 打通檢查)
- 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1)
- CLI / cypher-executor / registry / 完整 SDD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:52:38 +08:00

89 lines
2.5 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.
/**
* workflow.yaml 解析與三元組驗證
*/
import yaml from 'js-yaml';
import { readFileSync } from 'node:fs';
export interface WorkflowYaml {
name: string;
description?: string;
flow: string[];
config?: Record<string, Record<string, unknown>>;
}
export interface ParsedTriplet {
subject: string;
relation: string;
object: string;
}
/** 合法關係詞(拒絕 PIPE)*/
const VALID_RELATIONS = new Set([
'完成後', '失敗時', '對每個', '條件滿足時',
'ON_SUCCESS', 'ON_FAIL', 'FOREACH', 'IF', 'ON_CLICK', 'CALLS_SUBFLOW',
]);
const BANNED_RELATIONS = new Set(['PIPE']);
export function loadWorkflowYaml(filePath: string): WorkflowYaml {
const raw = readFileSync(filePath, 'utf8');
const doc = yaml.load(raw) as WorkflowYaml;
if (!doc.name) throw new Error('workflow.yaml 缺少 name 欄位');
if (!Array.isArray(doc.flow) || doc.flow.length === 0) {
throw new Error('workflow.yaml 的 flow 欄位必須為非空陣列');
}
return doc;
}
export function parseTriplets(flow: string[]): ParsedTriplet[] {
const triplets: ParsedTriplet[] = [];
for (const line of flow) {
const parts = line.split('>>').map(s => s.trim());
if (parts.length !== 3) {
throw new Error(
`三元組格式錯誤:「${line}\n` +
`正確格式:「A >> 關係詞 >> B」`
);
}
const [subject, relation, object] = parts;
triplets.push({ subject, relation, object });
}
return triplets;
}
export function validateRelations(triplets: ParsedTriplet[]): void {
for (const t of triplets) {
if (BANNED_RELATIONS.has(t.relation)) {
throw new Error(
`不允許使用關係詞「${t.relation}」。\n` +
`「PIPE」已棄用,請改用「完成後」或「ON_SUCCESS」。`
);
}
// 容許 FOREACH iterator 命名變體:「對每個 paragraph」/「FOREACH item」
// graph-builder.ts 已支援這個 regexcommit e8fca33 2026-05-07
const foreachMatch = t.relation.match(/^(?:對每個|FOREACH)\s+\w+$/i);
if (foreachMatch) continue;
if (!VALID_RELATIONS.has(t.relation)) {
throw new Error(
`未知關係詞「${t.relation}」。\n` +
`合法關係詞:${[...VALID_RELATIONS].join('、')}\n` +
`FOREACH 支援 iterator 命名:「對每個 X」/「FOREACH X」)`
);
}
}
}
export function getNodeNames(triplets: ParsedTriplet[]): string[] {
const nodes = new Set<string>();
for (const t of triplets) {
nodes.add(t.subject);
nodes.add(t.object);
}
return [...nodes];
}