Commit af4ac730e6cda76f2d235ee1f234288deb05d5ba

Authored by zichun
1 parent aa1d79fa

lib: 新增 check-workflow-syntax 语法门——node --check 对 Workflow 脚本基线恒红(ESM export + 顶…

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