系统四件套 · 判据

x-arch

判据是检测器本身:每条规则写明适用哪些文件、判什么、怎么判。条款说「为什么」,判据说「怎么判」。

规范版本
V1.8.3
闸数
11

真相源:.claude/skills/x-spec-checker/references/x-arch-spec.md · 本页是它的构建产物,改内容改那份文件、重新构建即更新

// X-RULE-BEGIN-STANDARD-x-standard-STD-COMMON-0001-规范简介-P1-X

架构层治理规范(业务架构去漂移与系统架构设计件 V1.8.3)

规范简介:本规范治理 .claude/arch/business/ 业务架构层(DDD bounded context 端到端设计真相,与 x-data 归类层 1:1)。V1.1.0(2026-09-14)起业务架构整体迁入 .claude/arch/business/,与系统架构目录 .claude/arch/system/ 并列,适用文件与注册表根同批迁移。V1.2.0(2026-09-14)起增系统组,治理 .claude/arch/system/ 系统架构层(每份规范一个目录,放系统四件套的设计件:架构页 ARCH.html 与变更历史 CHANGELOG.md):规范与目录一一对应、迁移期由系统组注册表的名册只减不增过渡(NS-COMMON-0007);架构页派生区戳记保鲜(NS-COMMON-0008);四件套版本锁步(NS-COMMON-0009);架构页原件写完、测试床点名验真(NS-COMMON-0010)。业务组 NS-COMMON-0002 同批增约束臂:specRefs 的名字必须是存在的规范。V1.2.1(2026-09-14)起 x-arch 自身迁为第一套系统四件套(规则书改写为条款书,建架构页与变更历史),系统组注册表的迁移名册同批划掉 x-arch。强制注册表完整性(登记域必有文档)+ 文档头完整性(frontmatter 契约 + describes 四端键 + layer=business + specRefs 句法代理防第二真相源)+ srcHash 四端保鲜牙(NS-COMMON-0003,改描述域代码逼回看 arch,活体验真:2026-07-11 抓获 customer/dispatch/coupon 真实漂移)。detector 全域无关(读注册表/frontmatter,不硬编码业务域,落实"框架操作业务但绝不依赖业务"铁律)。纯条件检测规范(全 NS,无 STD 模板)。V1.4.0(2026-09-16)起测试床点名验真加两臂(NS-COMMON-0010):床的种类必须写实 —— 不标 data-bed-kind 的床文件里必须真调用引擎(extractNsRuleDetectorcheck.js),只守不变量、不跑判据的床写 data-bed-kind="invariant"data-guards 兼收「规范名:ARCH-条款号」,让由床承接的无机器闸条款有处登记(条款须在该规范条款书里真实存在)。V1.5.0(2026-09-16)起「真调用引擎」的认定扩到四种令牌:extractNsRuleDetectorcheck.jsbuildDetectorToolsX-RULE-BEGIN-SLOT —— 手抠判据 SLOT 块、经引擎 buildDetectorTools 喂 tools 跑检测器的床同样是真跑判据(V1.4.0 只认前两种,把这类床误判成没跑判据)。V1.6.0(2026-09-18)起令牌认定从「全文出现过这段文字」收紧为「剥掉注释后按调用形态出现」:函数令牌 extractNsRuleDetector / buildDetectorTools / checkStandardFormat(本版新增,跑引擎真 STD 执行器的床)须写成调用 令牌(check.js 须作为路径段出现,X-RULE-BEGIN-SLOT 须出现在注释以外 —— 此前注释里写一句 check.js、或一个无关文件名(access-check.js)里恰好含这段文字都能过闸。V1.7.0(2026-09-20)起增 NS-COMMON-0011:架构页必须遵循《系统架构标准》的可机判面(节名册与顺序、节编号属性、表达层在场),判定依据是同前缀名册 x-arch-standard.json,名册只减不增、pending 里的域整条静默 —— 此前「按模板长成一个样」这件事零机器强制,33 页可以各长各的而检测全绿。V1.8.0(2026-09-20)曾给 NS-COMMON-0008 增臂B(把变更历史渲染成同目录派生件并核对三戳),V1.8.2(2026-09-20)撤销:渲染改由治理站点在构建时做,产物不进仓、每次构建重生成,因而不需要保鲜闸 —— 闸是「产物进仓」的代价,不进仓就不用付。

📖 规范定位(三位一体)

规范文档.claude/skills/x-spec-checker/references/x-arch-spec.md(本文件)
检测脚本.claude/skills/x-spec-checker/scripts/check-spec/check.js(规范检测脚本)
实际代码.claude/arch/business/**/*.md(业务架构文档 + 注册表)、.claude/arch/system/**(系统架构目录)、.claude/skills/x-spec-checker/references/x-arch-system.json(系统组注册表)


// X-RULE-END-STANDARD-x-standard-STD-COMMON-0001-规范简介-P1-X

// X-RULE-BEGIN-STANDARD-x-standard-STD-COMMON-0002-规则汇总表-P1-X

📊 规则汇总表

强制标准(STD)

规则ID规则名称检测阶段检测内容

// X-RULE-MULTILINE-BEGIN-x-standard-STD-COMMON-0002-STD规则行-X

(无)--本规范所有规则均为 NS 类型(条件检测)

// X-RULE-MULTILINE-END-x-standard-STD-COMMON-0002-STD规则行-X

非标兜底(NS)

规则ID规则名称检测阶段检测内容

// X-RULE-MULTILINE-BEGIN-x-standard-STD-COMMON-0002-NS规则行-X

NS-COMMON-0001arch 注册表完整性阶段2注册表登记的每个业务域必须有对应架构文档(存在性闸,防登记↔文档失同步)
NS-COMMON-0002arch 文档头完整性阶段2业务架构文档 frontmatter 七字段 + describes 四端键 + layer=business + status 枚举 + specRefs 非空则正文须引用(防第二真相源)+ specRefs 名字必须是存在的规范(V1.2.0 约束臂)
NS-COMMON-0003arch srcHash 四端保鲜阶段2读 describes 四端描述域代码实算签名(DATA 字段集 / SERVER·ADMIN·CLIENT filelist∪DEEP归一),与 frontmatter srcHash 比对;陈旧即拦(改代码逼回看 arch)。全量点强制(跨文件不变量)
NS-COMMON-0004域目录成套阶段2ARCH.md 的域目录必须有同级 CHANGELOG.md(成套闸,防「只建正文不建日志」致日志回流 frontmatter)
NS-COMMON-0005日志载体分离阶段2双臂。臂A:ARCH.md frontmatter 禁 version-log 键(防 ARCH-SYNC 把日志写回文档头);臂B:CHANGELOG.md frontmatter 禁 describes/srcHash/sourceOfTruth(防日志变第二真相源)
NS-COMMON-0006域目录白名单阶段2域目录下只允许 ARCH.md / CHANGELOG.md / parts/ / adr/ / assets/(穷举白名单,防目录沦为杂物间)
NS-COMMON-0007系统四件套成套阶段2系统组。三臂:规范 → 目录(已迁须有 ARCH.html + CHANGELOG.md 且规则书是条款书,未迁须登记在迁移名册)/ 目录 → 规范(目录名有同名判据、白名单 ARCH.html · CHANGELOG.md · assets/)/ 迁移名册(排序、不重复、只登记存在的规范、登记数等于 ceiling,只减不增)
NS-COMMON-0008架构页派生区保鲜阶段2系统组。逐区核对戳记:必需区齐全、载体对、data-v 等于引擎输出版本、data-out 等于产出指纹(没被直接改)、data-src 来源未变(没过期),普查快照须带日期;系统组注册表必须在场
NS-COMMON-0009四件套版本锁步阶段2系统组(锚在系统组注册表,全量检测)。变更历史 frontmatter 三键、条目严格递减、最新条目等于规范简介版本、已封版且判据指纹等于当前判据指纹(待同步 = 未封版即红;封版后判据变了没升版即红)
NS-COMMON-0010架构页原件完整与测试床验真阶段2系统组。原件区不残留占位符;测试床点名的闸真实存在、本规范每颗闸都被点名(无床写待补床)、点名的床在 x-test 下真实存在且内文写明所守的「规范名:规则号」
NS-COMMON-0011架构页遵循系统架构标准阶段2系统组(锚在标准名册 x-arch-standard.json)。四臂纯形态判定:① 节 id 全部来自冻结名册、出现顺序与名册一致、必填节齐全(条件节可缺)② 每个节带 data-indexdata-kicker 编号属性 ③ 页面挂表达层(body 带页面类 + head 引共用样式表)④ 名册自洽(pending 排序不重复、只登记存在的域、登记数等于 ceiling)。名册登记为 pending 的域整条静默,改造完一份划掉一份并降 ceiling

// X-RULE-MULTILINE-END-x-standard-STD-COMMON-0002-NS规则行-X
// X-RULE-END-STANDARD-x-standard-STD-COMMON-0002-规则汇总表-P1-X


// X-RULE-BEGIN-STANDARD-x-standard-STD-COMMON-0003-STD和NS章节结构-P1-X

🎯 唯一标准格式(STD)

// X-RULE-MULTILINE-BEGIN-x-standard-STD-COMMON-0003-STD规则定义-X

⚠️ 说明:本规范为纯条件检测规范,所有规则均为 NS 类型(检测违规即报错),无 STD 模板。

// X-RULE-MULTILINE-END-x-standard-STD-COMMON-0003-STD规则定义-X


🔍 非标兜底检测(NS)

// X-RULE-MULTILINE-BEGIN-x-standard-STD-COMMON-0003-NS规则定义-X
---

// X-RULE-BEGIN-STANDARD-x-standard-STD-COMMON-0005-NS规则SLOT代码块-P1-X

NS-COMMON-0001:arch 注册表完整性(.claude/arch/business/_index.md 文件)

适用文件

规则:arch 注册表(_index.md)「已固化业务域」表格中,每个登记域第 2 列反引号包裹的架构文档路径必须真实存在。防「登记了域却没建文档」的注册表↔文档失同步(registry opt-in 下,登记即承诺有文档)。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0001-arch注册表完整性-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const lines = content.split(/\r?\n/);
  const archDir = path.join(workspaceRoot, '.claude', 'arch', 'business');
  // 注册表行:markdown 表格行,第 2 列反引号包裹的 .md 文档路径(相对 arch/business/)。
  const rowRe = /^\|[^|]*\|\s*`([^`]+\.md)`\s*\|/;
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(rowRe);
    if (!m) continue;
    const docRel = m[1].trim();
    if (!fs.existsSync(path.join(archDir, docRel))) {
      violations.push({
        level: 'error',
        line: i + 1,
        message: `❌ [NS-COMMON-0001] arch 注册表登记的业务域文档不存在:${docRel}\n\n违规行:${lines[i].trim()}\n\n注册表每个登记域必须有对应架构文档(存在性闸,防登记↔文档失同步)。`,
        lineViolations: [],
      });
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0001-arch注册表完整性-P1-X

组成要素(强制要求):
// X-RULE-MULTILINE-BEGIN-x-standard-STD-COMMON-0005-组成要素列表-X

  1. ✅ 每个NS规则必须包含"检测逻辑代码"代码块(SLOT-X标记包裹的检测函数)
  2. ✅ 检测代码必须是JavaScript函数格式
  3. ✅ 每个NS规则必须包含"适用文件"声明

// X-RULE-MULTILINE-END-x-standard-STD-COMMON-0005-组成要素列表-X
// X-RULE-END-STANDARD-x-standard-STD-COMMON-0005-NS规则SLOT代码块-P1-X


NS-COMMON-0002:arch 文档头完整性(.claude/arch/business/**/ARCH.md 文件)

适用文件

规则:业务架构文档必须有合法 frontmatter:七字段(title/layer/status/version/describes/specRefs/sourceOfTruth)+ describes 四端键(data/server/admin/client,N/A 端显式 null)+ layer 必须 business(框架详图归 harness-engineering.md,不进 arch/business/)+ status 枚举(active/superseded/deprecated)+ specRefs 非空则正文须引用其名(I2 句法代理:arch 引用系统架构 rule 而不复述技术契约,防第二真相源漂移)。V1.2.0 约束臂:specRefs 里的每个名字必须是 references 下真实存在的规范 —— 业务架构对系统四件套只经这一个通道约束,写错名字即红。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0002-arch文档头完整性-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const lines = content.split(/\r?\n/);
  // 基解析定位 frontmatter(首行须 ---,到次个 ---),避免裸 content 匹配(NS-COMMON-0024)。
  let fmStart = -1, fmEnd = -1;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].trim() === '---') {
      if (fmStart === -1) fmStart = i;
      else { fmEnd = i; break; }
    }
  }
  if (fmStart !== 0 || fmEnd === -1) {
    violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] arch 业务架构文档缺合法 frontmatter(首行须 --- ... ---)`, lineViolations: [] });
    return violations;
  }
  const fmLines = lines.slice(fmStart + 1, fmEnd);
  const bodyLines = lines.slice(fmEnd + 1);
  const hasKey = (k) => fmLines.some(l => new RegExp('^' + k + '\\s*:').test(l));
  const getVal = (k) => {
    const l = fmLines.find(x => new RegExp('^' + k + '\\s*:').test(x));
    return l ? l.replace(new RegExp('^' + k + '\\s*:\\s*'), '').trim() : null;
  };
  for (const key of ['title', 'layer', 'status', 'version', 'describes', 'specRefs', 'sourceOfTruth']) {
    if (!hasKey(key)) {
      violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] arch 文档 frontmatter 缺字段:${key}`, lineViolations: [] });
    }
  }
  const layer = getVal('layer');
  if (layer && layer !== 'business') {
    violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] arch 文档 layer 必须 business(实际:${layer});框架详图归 harness-engineering.md,不进 arch/business/`, lineViolations: [] });
  }
  const status = getVal('status');
  if (status && !['active', 'superseded', 'deprecated'].includes(status)) {
    violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] arch 文档 status 非法:${status}(仅 active/superseded/deprecated)`, lineViolations: [] });
  }
  // describes 四端键(缩进子键,直到下一顶层键)
  const descIdx = fmLines.findIndex(l => /^describes\s*:/.test(l));
  if (descIdx !== -1) {
    const sub = [];
    for (let j = descIdx + 1; j < fmLines.length; j++) {
      if (/^\S/.test(fmLines[j])) break;
      sub.push(fmLines[j]);
    }
    for (const end of ['data', 'server', 'admin', 'client']) {
      if (!sub.some(l => new RegExp('^\\s+' + end + '\\s*:').test(l))) {
        violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] arch 文档 describes 缺四端键:${end}(N/A 端显式 ${end}: null)`, lineViolations: [] });
      }
    }
  }
  // I2 句法代理:specRefs 非空 ⇒ 正文含其名(防第二真相源)
  const specRefsLine = fmLines.find(l => /^specRefs\s*:/.test(l));
  if (specRefsLine) {
    const inside = (specRefsLine.match(/\[([^\]]*)\]/) || [null, ''])[1];
    const refs = inside.split(',').map(s => s.replace(/["'\s]/g, '')).filter(Boolean);
    for (const ref of refs) {
      if (!bodyLines.some(l => l.indexOf(ref) !== -1)) {
        violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] specRefs 声明 ${ref} 但正文无引用(第二真相源风险:arch 引用系统架构 rule 不复述其技术契约)`, lineViolations: [] });
      }
    }
    // V1.2.0 约束臂:specRefs 里的名字必须是真实存在的规范(业务架构对系统四件套只经这一个通道约束)
    const fsSR = require('fs');
    const pathSR = require('path');
    const refsDirSR = pathSR.join(workspaceRoot, pathSR.relative(workspaceRoot, filePath).replace(/\\/g, '/').split('/arch/')[0], 'skills', 'x-spec-checker', 'references');
    for (const ref of refs) {
      if (!fsSR.existsSync(pathSR.join(refsDirSR, ref + '-spec.md'))) {
        violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0002] specRefs 声明的 ${ref} 不是存在的规范(references 下没有 ${ref}-spec.md):业务架构只能引用真实存在的系统规范`, lineViolations: [] });
      }
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0002-arch文档头完整性-P1-X

NS-COMMON-0003:arch srcHash 四端保鲜(.claude/arch/business/**/ARCH.md 文件)

适用文件

规则:业务架构文档必须有 srcHash 四端保鲜指纹(srcHash: { data, server, admin, client },N/A 端写 null)。detector 读 frontmatter describes 四端 glob 所指描述域代码,实算签名(DATA=datasource 字段集 djb2;SERVER/ADMIN/CLIENT=filelist∪DEEP归一化 djb2,DEEP 剥注释+空白+import 字典序排序),与声明比对;陈旧即拦。此为跨文件不变量,强制点两层:① 全量检测(check.js 无参 / /x-feature-dev VERIFY 阶段)唯一物理咬合点——Stop --changed 结构性不可达(增量门只见变更文件本侧,跨文件不变量显"不适用";per-turn 锚注入方案经对抗审查否决:闸位错/咬错会话/橡皮图章化,见 harness-engineering ADR-16);② /x-feature-dev 收尾 ARCH-SYNC 硬步(触及已建档域 spawn x-architect 回看+升版+重算,跳过=生命周期未闭环禁入 DEPLOY)。部署 VALIDATE 门2 arch 反查注入为 backlog(待 validate/index.js 他会话落地)。诚实边界:srcHash 证结构/字段/取值/逻辑级漂移,盲于纯运行时行为;且证"被改"非"设计对"——语义正确性靠人评审(三层模型不可消除天花板);多会话共享 working tree 下漂移归因靠 §6.6 判据(本会话是否改过该域)。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0003-archsrcHash四端保鲜-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const lines = content.split(/\r?\n/);
  // frontmatter 定位(基解析,避裸 content 匹配 NS-COMMON-0024)
  let fmS = -1, fmE = -1;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].trim() === '---') { if (fmS === -1) fmS = i; else { fmE = i; break; } }
  }
  if (fmS !== 0 || fmE === -1) return violations; // 无 frontmatter → 归 NS-COMMON-0002
  const fm = lines.slice(fmS + 1, fmE);
  const descIdx = fm.findIndex(l => /^describes\s*:/.test(l));
  if (descIdx === -1) return violations; // 归 NS-COMMON-0002
  const describes = {};
  for (let j = descIdx + 1; j < fm.length; j++) {
    if (/^\S/.test(fm[j])) break;
    const m = fm[j].match(/^\s+(data|server|admin|client)\s*:\s*(.*)$/);
    if (m) describes[m[1]] = (m[2].match(/["']([^"']+)["']/g) || []).map(x => x.replace(/["']/g, ''));
  }
  const shLine = fm.find(l => /^srcHash\s*:/.test(l));
  if (!shLine) {
    violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0003] arch 文档缺 srcHash 四端保鲜指纹(补 srcHash: { data:…, server:…, admin:…, client:… };N/A 端写 null)`, lineViolations: [] });
    return violations;
  }
  const declared = {};
  const inl = shLine.match(/\{([^}]*)\}/);
  if (inl) for (const kv of inl[1].split(',')) { const mm = kv.match(/(\w+)\s*:\s*([a-f0-9]+|null)/); if (mm) declared[mm[1]] = mm[2]; }
  // ===== 算法块(与 compute 脚本逐字节同款)=====
  const djb2 = (str) => { let h = 5381; for (let i = 0; i < str.length; i++) h = ((h << 5) + h + str.charCodeAt(i)) >>> 0; return h.toString(16).padStart(8, '0'); };
  const deepNorm = (code) => {
    const s = code.replace(/\/\*[\s\S]*?\*\//g, '');
    const L = s.split(/\r?\n/).map(l => l.replace(/\/\/.*$/, '').replace(/\s+/g, ' ').trim()).filter(Boolean);
    const isImp = (l) => /^(import |const .*=\s*require\()/.test(l);
    return L.filter(isImp).sort().join('\n') + '\n' + L.filter(l => !isImp(l)).join('\n');
  };
  // 生成物 / 依赖目录不进指纹(拆包 S0.2a):describes 指向 x-core/mobile/** 后,backends 下的 node_modules 与各端 dist / .cache / miniprogram_npm
  // 都是装配或安装产物,进指纹 = pnpm install 或一次装配就让 srcHash 陈旧(假红),与引擎 IGNORED_DIRS 同名对齐
  const GENERATED_DIRS = new Set(['node_modules', 'dist', '.cache', 'miniprogram_npm']);
  const walk = (base, out) => {
    const d = path.join(workspaceRoot, base);
    if (!fs.existsSync(d) || !fs.statSync(d).isDirectory()) return;
    (function rec(dir) { for (const e of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, e.name); if (e.isDirectory()) { if (GENERATED_DIRS.has(e.name)) continue; rec(p); } else out.push(path.relative(workspaceRoot, p).replace(/\\/g, '/')); } })(d);
  };
  const filt = {
    data: (f) => f.endsWith('.datasource.json'),
    server: (f) => f.endsWith('.js') && !f.includes('/sys/'),
    admin: (f) => f.endsWith('.ts') || f.endsWith('.tsx'),
    client: (f) => f.endsWith('.js') || f.endsWith('.wxml'),
  };
  const endSig = (globs, end) => {
    const all = [];
    for (const g of (globs || [])) { const base = g.replace(/\/\*\*.*$/, '').replace(/\/\*[^/]*$/, ''); walk(base, all); }
    const files = [...new Set(all)].filter(filt[end]).sort();
    if (files.length === 0) return 'null';
    if (end === 'data') {
      const parts = [];
      for (const rel of files) { try { const j = JSON.parse(fs.readFileSync(path.join(workspaceRoot, rel), 'utf8')); const props = (j.schema && j.schema.properties) || j.properties || {}; for (const k of Object.keys(props).sort()) parts.push(rel + ':' + k + ':' + (props[k].type || '') + ':' + (props[k].format || '')); } catch (_) {} }
      return djb2(parts.sort().join('|'));
    }
    const bodies = files.map(rel => deepNorm(fs.readFileSync(path.join(workspaceRoot, rel), 'utf8'))).join('\n===\n');
    return djb2(files.join('\n') + '\n@@@\n' + bodies);
  };
  // ===== 算法块结束 =====
  for (const end of ['data', 'server', 'admin', 'client']) {
    if (declared[end] === undefined) {
      violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0003] srcHash 缺端键:${end}(N/A 端显式 ${end}: null)`, lineViolations: [] });
      continue;
    }
    const computed = endSig(describes[end], end);
    if (declared[end] === 'null') {
      if (computed !== 'null') {
        violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0003] srcHash ${end} 声明 null 但描述域有代码(实算 ${computed})——N/A 声明与实际不符,杜绝 null 绕过保鲜闸`, lineViolations: [] });
      }
      continue;
    }
    if (computed !== declared[end]) {
      violations.push({ level: 'error', line: 1, message: `❌ [NS-COMMON-0003] arch srcHash 陈旧(${end} 端描述域代码已变):声明 ${declared[end]} ≠ 实算 ${computed}。改了描述域代码必须回看本 arch 文档、确认设计仍成立、重算 srcHash 并升版。诚实边界:srcHash 证结构/字段/取值/逻辑级漂移,不证设计正确。`, lineViolations: [] });
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0003-archsrcHash四端保鲜-P1-X

NS-COMMON-0004:域目录成套(.claude/arch/business/**/ARCH.md 文件)

适用文件

规则:域目录(含 ARCH.md 的目录,身份锚点)必须同时有同级 CHANGELOG.md。版本日志自 frontmatter 迁出后,日志载体是域目录的必备构件;缺失即意味着「日志无处可去」,下一次 ARCH-SYNC 必然把 version-log 写回 ARCH.md 文档头(与 NS-COMMON-0005 臂A 构成防回流双保险)。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0004-域目录成套-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const dir = path.dirname(path.isAbsolute(filePath) ? filePath : path.join(workspaceRoot, filePath));
  if (!fs.existsSync(path.join(dir, 'CHANGELOG.md'))) {
    violations.push({
      level: 'error',
      line: 1,
      message: `❌ [NS-COMMON-0004] 域目录缺 CHANGELOG.md(有 ARCH.md 必须成套)\n\n域目录:${path.relative(workspaceRoot, dir).replace(/\\/g, '/')}\n\n版本日志载体缺失 ⇒ 下次 ARCH-SYNC 会把 version-log 写回 ARCH.md frontmatter(回流)。补建同级 CHANGELOG.md(frontmatter 只放 title/domain/kind,禁 describes/srcHash/sourceOfTruth,见 NS-COMMON-0005 臂B)。`,
      lineViolations: [],
    });
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0004-域目录成套-P1-X

NS-COMMON-0005:日志载体分离(.claude/arch/business/**/ARCH.md.claude/arch/business/**/CHANGELOG.md 文件)

适用文件

规则:设计正文与版本日志物理分居、互不侵入,双臂强制。臂A(防回流)ARCH.md frontmatter 禁出现 version-log 键——日志已迁 CHANGELOG.md,写回文档头会让读设计者被迫吞下历史流水(该形态曾使 order 域 frontmatter 占全文 56.5%)。臂B(防第二真相源)CHANGELOG.md frontmatter 禁出现 describes / srcHash / sourceOfTruth——这三个键是设计真相源的身份标记,出现在日志上即制造两套账,且会诱使 srcHash 保鲜闸咬错载体。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0005-日志载体分离-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const norm = String(filePath).replace(/\\/g, '/');
  const isChangelog = /\/CHANGELOG\.md$/.test(norm);
  const lines = content.split(/\r?\n/);
  // frontmatter 定位(基解析,避裸 content 匹配)
  let fmS = -1, fmE = -1;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].trim() === '---') { if (fmS === -1) fmS = i; else { fmE = i; break; } }
  }
  if (fmS !== 0 || fmE === -1) return violations; // ARCH.md 归 NS-COMMON-0002;CHANGELOG 无 frontmatter 不设强制
  const fm = lines.slice(fmS + 1, fmE);
  const hit = (k) => fm.findIndex((l) => new RegExp('^' + k + '\\s*:').test(l));
  if (!isChangelog) {
    // 臂A:ARCH.md 禁 version-log
    const i = hit('version-log');
    if (i !== -1) {
      violations.push({
        level: 'error',
        line: fmS + 2 + i,
        message: `❌ [NS-COMMON-0005 臂A] ARCH.md frontmatter 禁出现 version-log 键(日志回流)\n\n版本日志的唯一载体是同级 CHANGELOG.md。写回文档头 ⇒ 读设计被迫吞历史流水。\n把该键下的全部条目迁入 CHANGELOG.md(形如 \`## v{版本} — {YYYY-MM-DD}\` 分节),并删除本键。`,
        lineViolations: [],
      });
    }
  } else {
    // 臂B:CHANGELOG.md 禁真相源身份键
    for (const k of ['describes', 'srcHash', 'sourceOfTruth']) {
      const i = hit(k);
      if (i !== -1) {
        violations.push({
          level: 'error',
          line: fmS + 2 + i,
          message: `❌ [NS-COMMON-0005 臂B] CHANGELOG.md frontmatter 禁出现真相源身份键:${k}\n\n该键是设计真相源(ARCH.md)的身份标记,出现在日志载体上=两套账,且会诱使 srcHash 保鲜闸咬错文件。\nCHANGELOG.md 的 frontmatter 只放 title / domain / kind。`,
          lineViolations: [],
        });
      }
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0005-日志载体分离-P1-X

NS-COMMON-0006:域目录白名单(.claude/arch/business/**/ARCH.md 文件)

适用文件

规则:域目录下只允许穷举白名单内的条目——文件 ARCH.md / CHANGELOG.md,子目录 parts/(分支架构)/ adr/(架构决策记录)/ assets/(可视化产物)。零豁免。防「域目录化」退化成杂物间:任何未登记形态的文件落进域目录,都是无人看管的野文件(不受任何 arch 闸保护,却坐在真相源旁边冒充真相)。新形态需求必须先扩本白名单(走 /x-meta-dev),而非直接落盘。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0006-域目录白名单-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const ALLOW_FILES = ['ARCH.md', 'CHANGELOG.md'];
  const ALLOW_DIRS = ['parts', 'adr', 'assets'];
  const dir = path.dirname(path.isAbsolute(filePath) ? filePath : path.join(workspaceRoot, filePath));
  let entries = [];
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return violations; }
  const rel = path.relative(workspaceRoot, dir).replace(/\\/g, '/');
  for (const e of entries) {
    const ok = e.isDirectory() ? ALLOW_DIRS.indexOf(e.name) !== -1 : ALLOW_FILES.indexOf(e.name) !== -1;
    if (!ok) {
      violations.push({
        level: 'error',
        line: 1,
        message: `❌ [NS-COMMON-0006] 域目录出现白名单外条目:${e.isDirectory() ? e.name + '/' : e.name}\n\n域目录:${rel}\n允许:文件 ARCH.md / CHANGELOG.md,子目录 parts/ / adr/ / assets/\n\n未登记形态=无人看管的野文件(不受任何 arch 闸保护,却坐在真相源旁边)。要新增形态先扩白名单(走 /x-meta-dev),别直接落盘。`,
        lineViolations: [],
      });
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0006-域目录白名单-P1-X

NS-COMMON-0007:系统四件套成套(x-arch-system.json.claude/arch/system/** 文件)

适用文件

规则:系统架构层 .claude/arch/system/ 与规范文件一一对应,三臂强制。臂A(规范 → 目录,锚在系统组注册表,全局核对一次):references 下每份规范(元规范 x-standard 除外,其设计留在 harness)要么已迁 —— 有同名目录、目录里有 ARCH.htmlCHANGELOG.md、规则书是条款书(frontmatter 声明 ruleFormat);要么登记在迁移名册 pending 里 —— 此时不得已有目录,规则书也不得已是条款书(迁了一半)。臂B(目录 → 规范,锚在目录里的文件,改目录当轮咬)arch/system/ 下每个目录名必须有同名判据文件、不得是 x-standard;目录里只允许 ARCH.htmlCHANGELOG.mdassets/,两份文件缺一不可;arch/system/ 根下不许放文件。臂C(迁移名册,锚在注册表)pending 按名称排序、不重复、只登记存在的规范且不含 x-standard;登记数必须等于 ceiling —— 名册只减不增,迁完一份同批划掉并把 ceiling 降到登记数(抬高 ceiling 同样是改判据,走 sync 升版并记变更历史,由人审提交把关)。新建的规范直接走 /x-meta-dev 的 tetrad 建整套,不进名册。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0007-系统四件套成套-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const T = tools.tetradParser;
  const rel = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
  const push = (msg) => violations.push({ level: 'error', line: 1, lineViolations: [], message: '❌ [NS-COMMON-0007] ' + msg });
  const onRegistry = /\/skills\/x-spec-checker\/references\/x-arch-system\.json$/.test('/' + rel);
  const treeRel = onRegistry ? rel.split('/skills/')[0] : rel.split('/arch/system/')[0];
  const refsDir = path.join(workspaceRoot, treeRel, 'skills', 'x-spec-checker', 'references');
  const systemDir = path.join(workspaceRoot, treeRel, 'arch', 'system');
  const rulesDir = path.join(workspaceRoot, treeRel, 'rules');
  const isClauseBook = (name) => {
    const rp = path.join(rulesDir, name + '-rule.md');
    if (!fs.existsSync(rp)) return false;
    const fm = T.parseRuleFrontmatter(fs.readFileSync(rp, 'utf-8'));
    return !!(fm && fm.ruleFormat !== null);
  };
  if (onRegistry) {
    let reg = null;
    try { reg = JSON.parse(tools.filtered.codeOnly); } catch (e) { push('系统组注册表不是合法 JSON:' + e.message); return violations; }
    const pending = reg && Array.isArray(reg.pending) ? reg.pending : null;
    if (!pending) { push('系统组注册表缺迁移名册数组 pending'); return violations; }
    if (typeof reg.ceiling !== 'number') push('系统组注册表缺数字上限 ceiling');
    else if (pending.length !== reg.ceiling) push('迁移名册登记 ' + pending.length + ' 份、上限 ceiling 为 ' + reg.ceiling + ':名册只减不增,划掉一份就同批把 ceiling 降到登记数');
    if (pending.slice().sort().join('\n') !== pending.join('\n')) push('迁移名册须按名称排序(增减一眼可审)');
    const seen = new Set();
    for (const p of pending) {
      if (seen.has(p)) push('迁移名册重复登记 ' + p);
      seen.add(p);
      if (p === 'x-standard') push('迁移名册不得登记元规范 x-standard(其设计留在 harness,不设系统架构目录)');
      else if (!fs.existsSync(path.join(refsDir, p + '-spec.md'))) push('迁移名册登记的 ' + p + ' 没有判据文件:规范已删除就从名册划掉并降 ceiling');
    }
    let specs = [];
    try { specs = fs.readdirSync(refsDir).filter((f) => /^x-[a-z0-9-]+-spec\.md$/.test(f)).map((f) => f.replace(/-spec\.md$/, '')).sort(); } catch (e) { specs = []; }
    for (const name of specs) {
      if (name === 'x-standard') continue;
      const dir = path.join(systemDir, name);
      if (seen.has(name)) {
        if (fs.existsSync(dir)) push(name + ' 已建系统架构目录却仍在迁移名册里:迁完就从名册划掉并同批降 ceiling');
        if (isClauseBook(name)) push(name + ' 的规则书已是条款书却仍在迁移名册里(迁了一半):迁完就从名册划掉并同批降 ceiling');
        continue;
      }
      if (!fs.existsSync(path.join(dir, 'ARCH.html'))) push(name + ' 不在迁移名册、却缺系统架构页 ' + treeRel + '/arch/system/' + name + '/ARCH.html → 走 /x-meta-dev tetrad 建四件套');
      if (!fs.existsSync(path.join(dir, 'CHANGELOG.md'))) push(name + ' 不在迁移名册、却缺变更历史 ' + treeRel + '/arch/system/' + name + '/CHANGELOG.md → 走 /x-meta-dev tetrad 建四件套');
      if (!isClauseBook(name)) push(name + ' 不在迁移名册、规则书却不是条款书:frontmatter 须声明 ruleFormat: "clauses-v1" 并按条款书固定格式改写');
    }
    return violations;
  }
  const parts = rel.split('/arch/system/')[1].split('/');
  if (parts.length === 1) { push('系统架构根目录下只允许规范目录,不许放文件:' + rel); return violations; }
  const name = parts[0];
  const dir = path.join(systemDir, name);
  // 目录级判定只从一个锚点报一次(有 ARCH.html 用它,否则 CHANGELOG.md;两者都没有则目录里每个文件各报一次)
  const anchor = fs.existsSync(path.join(dir, 'ARCH.html')) ? 'ARCH.html' : (fs.existsSync(path.join(dir, 'CHANGELOG.md')) ? 'CHANGELOG.md' : null);
  const reportHere = anchor === null || parts.slice(1).join('/') === anchor;
  if (reportHere) {
    if (name === 'x-standard') push('元规范 x-standard 的设计留在 harness,不建系统架构目录');
    else if (!/^x-[a-z][a-z0-9-]*$/.test(name) || !fs.existsSync(path.join(refsDir, name + '-spec.md'))) push('系统架构目录 ' + name + ' 没有同名判据文件:每个目录必须对应 references 里的一份规范');
    let entries = [];
    try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { entries = []; }
    for (const e of entries) {
      const ok = e.isDirectory() ? e.name === 'assets' : (e.name === 'ARCH.html' || e.name === 'CHANGELOG.md');
      if (!ok) push('系统架构目录 ' + name + ' 出现白名单外条目 ' + e.name + (e.isDirectory() ? '/' : '') + '(只允许 ARCH.html、CHANGELOG.md 与 assets/)');
    }
    for (const must of ['ARCH.html', 'CHANGELOG.md']) {
      if (!fs.existsSync(path.join(dir, must))) push('系统架构目录 ' + name + ' 缺 ' + must);
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0007-系统四件套成套-P1-X

NS-COMMON-0008:架构页派生区保鲜(.claude/arch/system/*/ARCH.html 文件)

适用文件

规则:架构页的派生区(外壳样式、外壳脚本、版本与新鲜度头、条款总表、判据总表、注册表面板、普查快照)只准 x-meta-dev 的派生区引擎产出,检测端逐区核对戳记:① 区的形态合法(三种载体、不嵌套、不重名)② 引擎清单(x-meta-dev 骨架 manifest.json)要求的区一个不缺、不出现清单外的区、载体对得上 ③ data-v 等于引擎当前输出版本(引擎改了输出须升号并全量刷新)④ data-out 等于「声明属性 + 正文」的产出指纹,不等即被直接改过 ⑤ data-src 列出的每个来源文件都存在、内容指纹未变,变了即过期(普查快照不锁代码新鲜,但须带快照日期)⑥ 系统组注册表必须在场(它是成套与版本锁步两颗全局闸的锚点,删掉它两颗闸就失去锚点)。改页面当轮咬;只改条款书、判据或注册表时页面过期由全量检测报出,sync 会顺手刷新。诚实边界:戳记证「没被直接改、来源没变」,不证「引擎没写错」—— 后者由 x-meta-dev validate 第 10 项与测试床的逐字重新生成比对兜住;刻意伪造戳记靠人审提交。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0008-架构页派生区保鲜-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const T = tools.tetradParser;
  const rel = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
  const treeRel = rel.split('/arch/system/')[0];
  const push = (line, msg) => violations.push({ level: 'error', line: line || 1, lineViolations: [], message: '❌ [NS-COMMON-0008] ' + msg });
  if (!fs.existsSync(path.join(workspaceRoot, treeRel, 'skills', 'x-spec-checker', 'references', 'x-arch-system.json'))) {
    push(1, '系统组注册表 x-arch-system.json 缺失:成套闸 NS-COMMON-0007 与版本锁步闸 NS-COMMON-0009 就此失去锚点');
  }
  const manifestRel = treeRel + '/skills/x-meta-dev/assets/arch-skeleton/manifest.json';
  let manifest = null;
  try { manifest = JSON.parse(fs.readFileSync(path.join(workspaceRoot, manifestRel), 'utf-8')); } catch (e) { push(1, '架构页引擎清单读不到:' + manifestRel + '(' + e.message + ')'); return violations; }
  const required = manifest.requiredRegions || {};
  const prefixes = manifest.optionalRegionPrefixes || {};
  // 戳记按原文核对:派生区正文本身就含样式与脚本,任何剥离视图都会改变正文与指纹(结构核对,非业务关键词匹配)
  const parsed = T.parseArchRegions(content);
  for (const e of parsed.errors) push(e.line, e.message);
  for (const name of Object.keys(required)) {
    if (!parsed.regions.some((r) => r.name === name)) push(1, '缺必需派生区「' + name + '」(<' + required[name] + '> 载体)→ 跑 /x-meta-dev sync');
  }
  const hashCache = new Map();
  for (const r of parsed.regions) {
    let carrier = required[r.name];
    let census = false;
    if (!carrier) {
      for (const p of Object.keys(prefixes)) {
        if (r.name.indexOf(p) === 0 && r.name.length > p.length) { carrier = prefixes[p]; census = p === 'census-'; }
      }
    }
    if (!carrier) { push(r.line, '不认识的派生区「' + r.name + '」(引擎清单只认 ' + Object.keys(required).join(' / ') + ' 与前缀 ' + Object.keys(prefixes).join(' / ') + ')'); continue; }
    if (r.carrier !== carrier) push(r.line, '派生区「' + r.name + '」须用 <' + carrier + '> 载体(实为 <' + r.carrier + '>)');
    if (r.attrs['data-v'] !== String(manifest.outputVersion)) push(r.line, '派生区「' + r.name + '」的输出版本 ' + (r.attrs['data-v'] || '(缺)') + ' 不是引擎当前版本 ' + manifest.outputVersion + ' → 跑 /x-meta-dev sync 全量刷新');
    if (r.attrs['data-out'] !== T.regionOutHash(r)) push(r.line, '派生区「' + r.name + '」被直接改过(正文或声明与产出指纹不符,派生区禁直接改)→ 跑 /x-meta-dev sync 重新生成');
    if (census && !/^\d{4}-\d{2}-\d{2}$/.test(r.attrs['data-snapshot'] || '')) push(r.line, '普查快照「' + r.name + '」缺快照日期 data-snapshot');
    if (r.attrs['data-src'] === undefined) { push(r.line, '派生区「' + r.name + '」缺来源戳 data-src'); continue; }
    const st = T.parseSourceStamp(r.attrs['data-src']);
    for (const e of st.errors) push(r.line, '派生区「' + r.name + '」:' + e.message);
    if (!census && !st.entries.length && !st.errors.length) push(r.line, '派生区「' + r.name + '」的来源戳为空');
    for (const e of st.entries) {
      const abs = path.join(workspaceRoot, e.path);
      if (!hashCache.has(abs)) hashCache.set(abs, fs.existsSync(abs) ? T.hashText(fs.readFileSync(abs, 'utf-8')) : null);
      const h = hashCache.get(abs);
      if (h === null) push(r.line, '派生区「' + r.name + '」的来源文件不存在:' + e.path);
      else if (h !== e.hash) push(r.line, '派生区「' + r.name + '」已过期:来源 ' + e.path + ' 变了(戳记 ' + e.hash + ',现为 ' + h + ')→ 跑 /x-meta-dev sync 刷新');
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0008-架构页派生区保鲜-P1-X

NS-COMMON-0009:四件套版本锁步(x-arch-system.json 文件)

适用文件

规则:(锚在系统组注册表,对每份已建系统架构目录的规范全局核对一次)版本号只写在规范简介一处,变更历史必须与它锁步:① 变更历史 frontmatter 恰为 title / spec / kind 三键(spec 等于目录名、kind 为 changelog、title 为「规范名 四件套变更历史」)② 条目标题形如「## V主.次.补 · YYYY-MM-DD」、自上而下版本严格递减、每条标题下一行是判据指纹 ③ 最新条目等于规范简介的当前版本 ④ 最新条目已封版(判据指纹为十六进制),且等于当前判据指纹(判据正文 + 同前缀注册表,与规范指纹闸同口径)—— 「(待同步)」表示本版还在施工,封版前全量检测报红;已封版而判据又变了 = 判据变了没升版,报红。为什么锚在注册表而不锚在变更历史:施工中改变更历史是常态,若每轮收尾都逼封版,会逼出一轮一个版本号;锁步放进全量检测(提交前必跑),施工轮次里由 sync 拒绝「已封版却判据变了」当轮咬住。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0009-四件套版本锁步-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const T = tools.tetradParser;
  const rel = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
  const treeRel = rel.split('/skills/')[0];
  const refsDir = path.join(workspaceRoot, treeRel, 'skills', 'x-spec-checker', 'references');
  const systemDir = path.join(workspaceRoot, treeRel, 'arch', 'system');
  const push = (name, line, msg) => violations.push({ level: 'error', line: 1, lineViolations: [], message: '❌ [NS-COMMON-0009] ' + name + ' 变更历史' + (line ? '第 ' + line + ' 行' : '') + ':' + msg });
  let dirs = [];
  try { dirs = fs.readdirSync(systemDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort(); } catch (e) { dirs = []; }
  for (const name of dirs) {
    const clPath = path.join(systemDir, name, 'CHANGELOG.md');
    const specPath = path.join(refsDir, name + '-spec.md');
    if (!fs.existsSync(clPath) || !fs.existsSync(specPath)) continue;
    const cl = T.parseTetradChangelog(fs.readFileSync(clPath, 'utf-8'));
    for (const e of cl.errors) push(name, e.line, e.message);
    const fm = cl.frontmatter;
    if (!fm) push(name, 1, '缺 frontmatter(只放 title / spec / kind)');
    else {
      if (Object.keys(fm).join(',') !== 'title,spec,kind') push(name, 1, 'frontmatter 只放 title / spec / kind 三个键且按此顺序(实为 ' + Object.keys(fm).join(' / ') + ')');
      if (fm.spec !== name) push(name, 1, 'frontmatter 的 spec 须为目录名 ' + name + '(实为 ' + fm.spec + ')');
      if (fm.kind !== 'changelog') push(name, 1, 'frontmatter 的 kind 须为 changelog');
      if (fm.title !== name + ' 四件套变更历史') push(name, 1, 'frontmatter 的 title 须为「' + name + ' 四件套变更历史」');
    }
    const specText = fs.readFileSync(specPath, 'utf-8');
    const specVer = T.extractSpecVersion(specText);
    // 判据指纹:判据正文 + 同目录同前缀注册表(文件名字典序、前缀大小写不敏感)—— 与 x-standard NS-COMMON-0027、x-meta-dev computeSpecHash 逐字同源,改一处必须同批改另两处
    let joined = specText.replace(/\r\n/g, '\n');
    const pre = (name + '-').toLowerCase();
    let comps = [];
    try { comps = fs.readdirSync(refsDir).filter((f) => { const lower = f.toLowerCase(); return lower.indexOf(pre) === 0 && lower.slice(-5) === '.json'; }).sort(); } catch (e) { comps = []; }
    for (const c of comps) joined += '\n' + c.toLowerCase() + '\n' + fs.readFileSync(path.join(refsDir, c), 'utf-8').replace(/\r\n/g, '\n');
    const specHash = T.hashText(joined);
    const latest = cl.latest;
    if (!latest) { push(name, 0, '没有任何版本条目'); continue; }
    if (latest.version !== specVer) push(name, latest.line, '最新条目 ' + latest.version + ' 不是规范简介的当前版本 ' + specVer + '(版本号只在规范简介一处,变更历史最新条目必须等于它)');
    const at = latest.fingerprintLine || latest.line;
    if (latest.fingerprintState === 'pending') push(name, at, latest.version + ' 还没封版(判据指纹「(待同步)」)→ 本版写完跑 /x-meta-dev sync --seal');
    else if (latest.fingerprintState === 'legacy') push(name, at, '最新条目的判据指纹不能是「(迁入前无记录)」');
    else if (latest.fingerprintState === 'hex' && latest.fingerprint !== specHash) push(name, at, latest.version + ' 已封版(判据指纹 ' + latest.fingerprint + ')但判据已变(现为 ' + specHash + '):判据变了必须升版 —— 规范简介升版本、变更历史顶部加新条目,再 sync');
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0009-四件套版本锁步-P1-X

NS-COMMON-0010:架构页原件完整与测试床验真(.claude/arch/system/*/ARCH.html 文件)

适用文件

规则:架构页原件区(派生区以外的正文)不得残留骨架占位符「【待写】」;测试床点名(带 data-bed 与 data-guards 的元素)逐条核对:① data-guards 里每个「规范名:规则号」真实存在,每个「规范名:ARCH-条款号」在该规范的条款书(rules/<规范名>-rule.md)里真实存在 ② 本规范的每颗闸至少出现在一条点名里 —— 有床写床,没有床写「待补床」(页面上标红,债务一眼可见)③ 点名的床必须是仓根相对、位于 x-test 下的 .test.js 文件且真实存在,床的内文写着它守的每个「规范名:规则号」与「规范名:ARCH-条款号」(点名守护某颗闸或条款,床里就得写明是哪颗)④ 床的种类写实(V1.4.0):不写 data-bed-kind 的床是「跑本规范判据的床」,床文件剥掉注释后必须按调用形态出现引擎令牌之一(V1.6.0)—— 函数令牌 extractNsRuleDetector( / buildDetectorTools( / checkStandardFormat( 写成调用,check.js 作为路径段出现(前面紧挨斜杠、反斜杠或引号,后面紧挨引号、空白或行尾),X-RULE-BEGIN-SLOT(手抠 SLOT 块喂引擎的写法)出现在注释以外;写 data-bed-kind="invariant" 的床是「守不变量、不跑判据的床」,不查引擎调用;data-bed-kind 只许 invariant 一个值。诚实边界:证得了床存在、写了闸号、床的代码里(注释以外)有调用引擎的形态,证不了那次调用真的执行了、更证不了床喂进引擎的样本有没有真测到那颗闸的判据 —— 字符串字面量里写一段调用形态同样被认作调用,反向注入质量归床自己的断言与人审。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0010-架构页原件完整与测试床验真-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const T = tools.tetradParser;
  const rel = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
  const treeRel = rel.split('/arch/system/')[0];
  const name = rel.split('/arch/system/')[1].split('/')[0];
  const push = (line, msg) => violations.push({ level: 'error', line: line || 1, lineViolations: [], message: '❌ [NS-COMMON-0010] ' + msg });
  // 原件区占位符:派生区里的文字不算原件(按原文定位,派生区区间来自引擎原子)
  const src = content.replace(/\r\n/g, '\n');
  const regions = T.parseArchRegions(src).regions;
  let at = src.indexOf(T.CLAUSE_PLACEHOLDER);
  while (at >= 0) {
    const pos = at;
    if (!regions.some((r) => pos >= r.openStart && pos < r.closeEnd)) push(src.slice(0, pos).split('\n').length, '架构页原件区还有占位符「' + T.CLAUSE_PLACEHOLDER + '」,原件没有写完');
    at = src.indexOf(T.CLAUSE_PLACEHOLDER, pos + 1);
  }
  const cites = T.parseBedCitations(src);
  if (!cites.length) push(1, '架构页没有点名任何测试床(每颗闸都要出现在测试床点名里,没有床写 data-bed="待补床")');
  const refsDir = path.join(workspaceRoot, treeRel, 'skills', 'x-spec-checker', 'references');
  const gateCache = new Map();
  const gatesOf = (spec) => {
    if (!gateCache.has(spec)) {
      const sp = path.join(refsDir, spec + '-spec.md');
      gateCache.set(spec, fs.existsSync(sp) ? new Set(T.extractGateIds(fs.readFileSync(sp, 'utf-8')).map((g) => g.id)) : null);
    }
    return gateCache.get(spec);
  };
  const covered = new Set();
  const bedText = new Map();
  // 条款号点名(V1.4.0):只有条款书里真实存在的条款才能被床认领
  const clauseCache = new Map();
  const clausesOf = (spec) => {
    if (!clauseCache.has(spec)) {
      const rp = path.join(workspaceRoot, treeRel, 'rules', spec + '-rule.md');
      clauseCache.set(spec, fs.existsSync(rp) ? new Set(T.parseClauseBook(fs.readFileSync(rp, 'utf-8')).clauses.map((k) => k.id)) : null);
    }
    return clauseCache.get(spec);
  };
  // 床种类写实(V1.4.0):不标 invariant 的床必须真调用引擎,否则只是一行头注在充数
  // V1.5.0:手抠 SLOT 块 + buildDetectorTools 喂 tools 的床同样是真跑判据(x-mirror / x-icon 三张床实证)
  // V1.6.0:在剥掉注释的床文本上按调用形态认 —— 注释里的一句 check.js、无关文件名 access-check.js 不再算数;checkStandardFormat(引擎真 STD 执行器)入表
  const ENGINE_CALLS = ['extractNsRuleDetector', 'buildDetectorTools', 'checkStandardFormat'];
  const callsEngine = (raw) => {
    const code = tools.tsContext.stripComments(raw);
    return ENGINE_CALLS.some((t) => new RegExp('(^|[^A-Za-z0-9_$])' + t + '\\s*\\(').test(code))
      || /(^|[\\/'"`])check\.js(?=$|[\s'"`)])/m.test(code)
      || code.indexOf('X-RULE-BEGIN-SLOT') >= 0;
  };
  for (const c of cites) {
    for (const e of c.errors) push(c.line, '测试床点名:' + e);
    for (const g of c.guards) {
      const set = gatesOf(g.spec);
      if (set === null) push(c.line, '测试床点名引用了不存在的规范 ' + g.spec);
      else if (!set.has(g.id)) push(c.line, '测试床点名引用了不存在的闸 ' + g.spec + ':' + g.id);
      if (g.spec === name) covered.add(g.id);
    }
    for (const k of c.clauses) {
      const set = clausesOf(k.spec);
      if (set === null) push(c.line, '测试床点名引用了条款 ' + k.spec + ':' + k.id + ',但规范 ' + k.spec + ' 没有条款书');
      else if (!set.has(k.id)) push(c.line, '测试床点名引用了不存在的条款 ' + k.spec + ':' + k.id);
    }
    if (c.bed === '待补床') continue;
    if (!/^x-test\/[A-Za-z0-9._\/-]+\.test\.js$/.test(c.bed || '') || /(^|\/)\.\.(\/|$)/.test(c.bed || '')) {
      push(c.line, '测试床路径须是仓根相对、位于 x-test 下的 .test.js(实为「' + c.bed + '」);没有床写 data-bed="待补床"');
      continue;
    }
    const abs = path.join(workspaceRoot, c.bed);
    if (!fs.existsSync(abs)) { push(c.line, '点名的测试床不存在:' + c.bed); continue; }
    if (!bedText.has(abs)) bedText.set(abs, fs.readFileSync(abs, 'utf-8'));
    const text = bedText.get(abs);
    for (const g of c.guards) {
      if (text.indexOf(g.spec + ':' + g.id) < 0) push(c.line, '测试床 ' + c.bed + ' 的内文没有写「' + g.spec + ':' + g.id + '」—— 点名守护某颗闸的床,必须在床里写明它守的是哪颗闸');
    }
    for (const k of c.clauses) {
      if (text.indexOf(k.spec + ':' + k.id) < 0) push(c.line, '测试床 ' + c.bed + ' 的内文没有写「' + k.spec + ':' + k.id + '」—— 点名承接某条条款的床,必须在床里写明它承接的是哪条');
    }
    if (c.kind !== 'invariant' && !callsEngine(text)) {
      push(c.line, '测试床 ' + c.bed + ' 没有调用本规范的判据(剥掉注释后既无 extractNsRuleDetector( / buildDetectorTools( / checkStandardFormat( 调用,也无作为路径段的 check.js,也无 X-RULE-BEGIN-SLOT):要么让床真跑判据,要么在点名上写 data-bed-kind="invariant" 表明它只守不变量、不跑判据');
    }
  }
  const own = gatesOf(name);
  if (own) {
    for (const id of own) {
      if (!covered.has(id)) push(1, '闸 ' + name + ':' + id + ' 没有出现在测试床点名里(有床点名床,没有床写 data-bed="待补床")');
    }
  }
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0010-架构页原件完整与测试床验真-P1-X

NS-COMMON-0011:架构页遵循系统架构标准(x-arch-standard.json.claude/arch/system/*/ARCH.html 文件)

适用文件

规则:系统架构页必须按《系统架构标准》的可机判面长成同一个样子,判定依据是标准名册 x-arch-standard.json(散文版住 .claude/arch/standards/system-architecture-standard.html,两者的关系与条款书↔判据相同)。四臂,全部是形态判定、零语义:臂①(节名册与顺序) 页面里的 <section id="s-…"> 必须全部取自名册、出现顺序与名册一致,名册里标 required 的节必须在场(s-census / s-ops 是条件节,不适用的域不建);臂②(节编号属性) 每个节带 data-indexdata-kicker —— 编号与英文提示由属性给、由样式渲染,不许写进标题(标题文字是页内导航的来源,塞编号进去会把导航撑坏);臂③(表达层在场) <body> 带名册声明的页面类、<head> 引名册声明的共用样式表 —— 所有系统架构页共用一套设计语言,不许每页自创;臂④(名册自洽,锚在名册文件) pending 按名称排序、不重复、只登记真实存在的系统架构目录,ceiling 等于登记数。pending 里登记的域整条静默 —— 名册只减不增,改造完一份划掉一份并把 ceiling 降 1,否则立闸当轮全部架构页同时爆红、无法分批推进。诚实边界:只证形态 —— 节在场不证明那一节写对了内容,挂了样式表不证明视觉真的对;标准里「不许发明第二套设计语言」「整页只有一种底色」这类要求判据够不着,由写页时读标准与人审兜底。HTML 注释里的 <section id= 已被抹掉不计。

检测逻辑代码

// X-RULE-BEGIN-SLOT-x-arch-NS-COMMON-0011-架构页遵循系统架构标准-P1-X
(function(filePath, content, workspaceRoot, tools) {
  const violations = [];
  const fs = require('fs');
  const path = require('path');
  const rel = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
  const push = (line, msg) => violations.push({ level: 'error', line: line || 1, lineViolations: [], message: '❌ [NS-COMMON-0011] ' + msg });
  const onRegistry = /x-arch-standard\.json$/.test(rel);
  const treeRel = onRegistry ? rel.split('/skills/')[0] : rel.split('/arch/system/')[0];
  const regPath = path.join(workspaceRoot, treeRel, 'skills', 'x-spec-checker', 'references', 'x-arch-standard.json');
  if (!fs.existsSync(regPath)) { push(1, '标准名册不存在:' + treeRel + '/skills/x-spec-checker/references/x-arch-standard.json'); return violations; }
  let reg = null;
  try { reg = JSON.parse(fs.readFileSync(regPath, 'utf-8')); } catch (e) { push(1, '标准名册不是合法 JSON:' + e.message); return violations; }
  const sections = Array.isArray(reg.sections) ? reg.sections : [];
  const pending = Array.isArray(reg.pending) ? reg.pending : [];
  // 臂④ 名册自洽(锚在名册文件,全局核对一次)
  if (onRegistry) {
    if (!sections.length) push(1, '名册缺 sections:节骨架是本闸的判定依据,不许为空');
    const ids = sections.map((s) => (s && s.id) || '');
    for (const id of ids) if (!/^s-[a-z][a-z-]*$/.test(id)) push(1, 'sections 里有非法节 id「' + id + '」(须形如 s-xxx)');
    if (new Set(ids).size !== ids.length) push(1, 'sections 里有重复的节 id');
    const sysDir = path.join(workspaceRoot, treeRel, 'arch', 'system');
    const exists = fs.existsSync(sysDir) ? new Set(fs.readdirSync(sysDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)) : new Set();
    const sorted = pending.slice().sort();
    if (pending.join('|') !== sorted.join('|')) push(1, 'pending 名册未按名称排序');
    if (new Set(pending).size !== pending.length) push(1, 'pending 名册有重复登记');
    for (const n of pending) if (!exists.has(n)) push(1, 'pending 登记了不存在的系统架构目录:' + n);
    if (reg.ceiling !== pending.length) push(1, 'ceiling(' + reg.ceiling + ')不等于 pending 登记数(' + pending.length + ')—— 名册只减不增,改造完一份同批把 ceiling 降到登记数');
    return violations;
  }
  const name = rel.split('/arch/system/')[1].split('/')[0];
  if (pending.indexOf(name) >= 0) return violations;
  const src = content.replace(/\r\n/g, '\n').replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, ' '));
  const lineAt = (idx) => src.slice(0, idx).split('\n').length;
  const order = sections.map((s) => s.id);
  const required = sections.filter((s) => s && s.required).map((s) => s.id);
  // 臂① 节名册与顺序
  const seen = [];
  const secRe = /<section\s+id="([^"]+)"/g;
  let m = null;
  while ((m = secRe.exec(src)) !== null) {
    seen.push({ id: m[1], line: lineAt(m.index) });
    if (order.indexOf(m[1]) < 0) push(lineAt(m.index), '自造节「' + m[1] + '」:节 id 只许取自标准名册(' + order.join(' → ') + ')');
  }
  const known = seen.filter((s) => order.indexOf(s.id) >= 0);
  for (let i = 1; i < known.length; i++) {
    if (order.indexOf(known[i].id) <= order.indexOf(known[i - 1].id)) {
      push(known[i].line, '节顺序与标准名册不符:「' + known[i].id + '」排在「' + known[i - 1].id + '」之后,名册顺序是 ' + order.join(' → '));
      break;
    }
  }
  for (const id of required) if (!known.some((s) => s.id === id)) push(1, '缺必填节「' + id + '」(标准十节里的必填项;只有条件节 s-census / s-ops 允许缺席,且不建时须在诚实边界留一句显式裁定)');
  // 臂② 每节带编号属性
  const tagRe = /<section\s+id="([^"]+)"([^>]*)>/g;
  while ((m = tagRe.exec(src)) !== null) {
    if (order.indexOf(m[1]) < 0) continue;
    const attrs = m[2] || '';
    if (!/\bdata-index="/.test(attrs)) push(lineAt(m.index), '节「' + m[1] + '」缺 data-index:节编号由属性给、由样式渲染,不许写进标题文字');
    if (!/\bdata-kicker="/.test(attrs)) push(lineAt(m.index), '节「' + m[1] + '」缺 data-kicker:节的英文提示由属性给');
  }
  // 臂③ 表达层在场
  const pageClass = typeof reg.pageClass === 'string' ? reg.pageClass : '';
  const styleHref = typeof reg.styleHref === 'string' ? reg.styleHref : '';
  const bodyM = /<body([^>]*)>/.exec(src);
  if (!bodyM) push(1, '架构页没有 body 开标签');
  else if (pageClass && !new RegExp('class="[^"]*\\b' + pageClass + '\\b').test(bodyM[1] || '')) push(lineAt(bodyM.index), 'body 缺页面类「' + pageClass + '」:表达层靠它生效,不挂等于没套用这套设计系统');
  if (styleHref && src.indexOf(styleHref) < 0) push(1, '架构页没有引共用表达层样式表(须引到 ' + styleHref + '):所有系统架构页共用一套设计语言,不许每页自创');
  return violations;
})
// X-RULE-END-SLOT-x-arch-NS-COMMON-0011-架构页遵循系统架构标准-P1-X

// X-RULE-MULTILINE-END-x-standard-STD-COMMON-0003-NS规则定义-X
// X-RULE-END-STANDARD-x-standard-STD-COMMON-0003-STD和NS章节结构-P1-X


🔧 检测工具

检测命令node .claude/skills/x-spec-checker/scripts/check-spec/check.js
退出码0 = 通过,1 = 失败

可用参数