#!/usr/bin/env node // check-workflow-syntax.mjs — Workflow 脚本的确定性语法门(附录补 18)。 // // 为什么不能直接 `node --check workflows/coding.mjs`:coding.mjs 是 Claude Workflow 运行时脚本, // 运行时会把脚本体包进 async function 后执行(顶层 return 是结果通道)。node v26 见到 // `export const meta` 即按 ESM 解析,顶层 return 必报 `Illegal return statement`——基线恒红。 // 恒红的语法门会让人习惯性忽略红灯(掩盖真语法错),故此处复刻运行时的包裹再 --check。 // // 用法:node lib/check-workflow-syntax.mjs [workflow 文件路径...](缺省 workflows/coding.mjs) // 退出码:透传 node --check(0=语法通过)。 import { readFileSync, writeFileSync, mkdirSync } from 'node:fs' import { spawnSync } from 'node:child_process' import { basename, dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') const targets = process.argv.slice(2).length ? process.argv.slice(2) : [join(repoRoot, 'workflows', 'coding.mjs')] let failed = false for (const target of targets) { const src = readFileSync(resolve(target), 'utf8') // 剥行首 `export `(运行时注入 meta,不走 ESM export),再包进运行时同形的 async 包裹。 const wrapped = [ 'async function __wf(args, agent, parallel, phase, log) {', src.replace(/^export /gm, ''), '}', ].join('\n') // 临时文件名由目标 basename 确定性导出(无时间戳/随机数),落在仓库 .tmp/ 下。 const tmpDir = join(repoRoot, '.tmp', 'check-workflow-syntax') mkdirSync(tmpDir, { recursive: true }) const tmpFile = join(tmpDir, `${basename(target)}.wrapped.mjs`) writeFileSync(tmpFile, wrapped) const r = spawnSync(process.execPath, ['--check', tmpFile], { stdio: 'inherit' }) if (r.status !== 0) { failed = true console.error(`✗ 语法检查失败:${target}(包裹文件 ${tmpFile})`) } else { console.log(`✓ 语法通过:${target}`) } } process.exit(failed ? 1 : 0)