diff --git a/AGENTS.md b/AGENTS.md index e21b6be..1c7de8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,6 +255,36 @@ config: --- +## 10.5 內建 magic vars(`_` prefix reserved) + +YAML 內可直接用以下變數,cypher-executor 自動展開為當下時間(UTC): + +| 變數 | 範例 | 用途 | +|---|---|---| +| `{{_today}}` | `2026-05-16` | 日 log / page_name | +| `{{_yesterday}}` | `2026-05-15` | digest 取昨日 | +| `{{_now}}` | `2026-05-16T09:30:00.123Z` | ISO 8601 | +| `{{_now_unix}}` | `1778937000123` | unix ms | +| `{{_now_unix_s}}` | `1778937000` | unix sec | +| `{{_iso_week}}` | `2026-W20` | weekly archive (本 doc 推薦) | +| `{{_iso_week_num}}` / `{{_iso_year}}` | `20` / `2026` | 拆開用 | +| `{{_yyyymm}}` / `{{_yyyymmdd}}` | `202605` / `20260516` | 緊湊路徑 | +| `{{_year}}` / `{{_month}}` / `{{_day}}` / `{{_hour}}` / `{{_minute}}` | 各別 zero-padded | 自己拼路徑 | +| `{{_weekday}}` | `0`-`6`(0=日)| if-control | +| `{{_iso_weekday}}` | `1`-`7`(1=一)| ISO 風格 | + +**rule**:`_` prefix reserved for system,**用戶自己 ctx 變數不要用 `_` 開頭**。 + +**範例**:weekly archive +```yaml +publish_roadmap_archive: + component: kbdb_upsert_block + page_name: "roadmap-{{_iso_week}}" # roadmap-2026-W20 + tags_json: '["weekly", "week:{{_iso_week}}"]' +``` + +--- + ## 11. 給寫 LI 的 AI 自己的 meta-規範 你(AI)在寫 arcrun workflow 時,**遵守以下習慣**會少踩坑: diff --git a/cypher-executor/src/graph-executor.ts b/cypher-executor/src/graph-executor.ts index 0f6c51c..c2542a1 100644 --- a/cypher-executor/src/graph-executor.ts +++ b/cypher-executor/src/graph-executor.ts @@ -5,6 +5,7 @@ import { injectCredentials } from './actions/credential-injector'; import { tryAuthDispatch } from './actions/auth-dispatcher'; import { expandPromptRecipe } from './lib/recipe-expander'; import { persistPausedRun, isResumablePending, parseRecipeOutput } from './lib/paused-runs'; +import { buildMagicVars } from './lib/magic-vars'; export type ComponentLoader = (componentId: string) => Promise; export type WorkflowLoader = (workflowId: string) => Promise; @@ -51,12 +52,20 @@ export class GraphExecutor { this.currentGraph = graph; this.currentRunId = kvStore?.runId ?? `${graph.id}-${Date.now()}`; + // Magic vars:注入 _today / _now / _iso_week 等系統變數(LI SDD M2.x) + // initialContext 寫前,magic vars 寫後 → magic vars 永遠 win(防 user accidentally 用 _ prefix) + // 同時保留 user 既有 ctx,magic vars 不破壞既有 workflow(_ prefix reserved) + const ctxWithMagic: Record = { + ...initialContext, + ...buildMagicVars(), + }; + // 找出所有起點(沒有任何邊指向的節點) const hasIncoming = new Set(graph.edges.map(e => e.to)); const startNodes = graph.nodes.filter(n => !hasIncoming.has(n.id)); if (startNodes.length === 0) { - return { data: initialContext, trace }; + return { data: ctxWithMagic, trace }; } // 建立 fan-in 狀態(入度 > 1 的節點需要等所有上游) @@ -64,14 +73,14 @@ export class GraphExecutor { for (const node of graph.nodes) { const inDeg = graph.edges.filter(e => e.to === node.id).length; if (inDeg > 1) { - fanIn.set(node.id, { ctx: { ...initialContext }, remaining: inDeg }); + fanIn.set(node.id, { ctx: { ...ctxWithMagic }, remaining: inDeg }); } } // 並行執行所有起點 const results = await Promise.all( startNodes.map(node => - this.executeNode(node, graph, initialContext, new Set(), trace, fanIn, kvStore) + this.executeNode(node, graph, ctxWithMagic, new Set(), trace, fanIn, kvStore) ) ); diff --git a/cypher-executor/src/lib/magic-vars.ts b/cypher-executor/src/lib/magic-vars.ts new file mode 100644 index 0000000..4ca39ab --- /dev/null +++ b/cypher-executor/src/lib/magic-vars.ts @@ -0,0 +1,89 @@ +/** + * Magic vars — workflow YAML 內建變數 + * + * 對應 LI SDD M2.x improvement(feedback block c47bf70b)。 + * + * 任何以 `_` 開頭的變數名都是 reserved(system)。常見:時間、執行 metadata。 + * 用於 page_name / file path / URL 等需要時間戳的場景。 + * + * 範例 YAML: + * page_name: "roadmap-week-{{_iso_week}}" # roadmap-week-2026-W20 + * page_name: "log-{{_today}}" # log-2026-05-16 + * filename: "snapshot-{{_now_unix}}.json" # snapshot-1778940000123.json + * + * 不違反 §2.2:這是 orchestrator routing 提供的「環境變數」(像 shell 的 $DATE), + * 不涉及 secret / credential / JWT,跟既有 ctx 變數展開同層。 + */ + +/** + * 算 ISO 8601 週數(W01-W53)。 + * 週一為週首,W01 含當年首個週四(ISO 標準)。 + * https://en.wikipedia.org/wiki/ISO_week_date + */ +function isoWeekNumber(d: Date): { year: number; week: number } { + const target = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); + const dayNum = (target.getUTCDay() + 6) % 7; // Mon=0 + target.setUTCDate(target.getUTCDate() - dayNum + 3); + const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4)); + const weekNum = 1 + Math.round( + ((target.getTime() - firstThursday.getTime()) / 86400000 - + 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7 + ); + return { year: target.getUTCFullYear(), week: weekNum }; +} + +function pad2(n: number): string { + return n.toString().padStart(2, '0'); +} + +/** + * 建立 magic vars。每次 workflow 觸發時呼叫一次,貫穿整個執行。 + * + * 設計:UTC 為基準(避免 worker 跨時區誤判)。需要本地時區的場景, + * 用戶可自己組(例如 yaml 寫 `{{_today_utc}}` + 自己處理偏移)。 + */ +export function buildMagicVars(now: Date = new Date()): Record { + const iso = now.toISOString(); // 2026-05-16T09:30:00.123Z + const yyyy = now.getUTCFullYear(); + const mm = pad2(now.getUTCMonth() + 1); + const dd = pad2(now.getUTCDate()); + const hh = pad2(now.getUTCHours()); + const mi = pad2(now.getUTCMinutes()); + const ss = pad2(now.getUTCSeconds()); + + const yesterday = new Date(now.getTime() - 86400000); + const yMm = pad2(yesterday.getUTCMonth() + 1); + const yDd = pad2(yesterday.getUTCDate()); + + const { year: isoYear, week: isoWeek } = isoWeekNumber(now); + + return { + // 日期 / 時間(UTC) + _today: `${yyyy}-${mm}-${dd}`, // 2026-05-16 + _yesterday: `${yesterday.getUTCFullYear()}-${yMm}-${yDd}`, // 2026-05-15 + _now: iso, // ISO 8601 + _now_unix: now.getTime(), // unix ms + _now_unix_s: Math.floor(now.getTime() / 1000), // unix sec + + // 個別欄位(給 path / page_name 拼) + _year: yyyy, + _month: mm, + _day: dd, + _hour: hh, + _minute: mi, + _second: ss, + + // ISO 週(roadmap weekly archive 必備) + _iso_week: `${isoYear}-W${pad2(isoWeek)}`, // 2026-W20 + _iso_week_num: isoWeek, + _iso_year: isoYear, + + // 簡單時間 slot(cron-friendly) + _yyyymm: `${yyyy}${mm}`, // 202605 + _yyyymmdd: `${yyyy}${mm}${dd}`, // 20260516 + + // 週幾(0=週日,1=週一 ... 6=週六;ISO 風格在 _iso_weekday) + _weekday: now.getUTCDay(), + _iso_weekday: ((now.getUTCDay() + 6) % 7) + 1, // 1=Mon...7=Sun + }; +}