diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8225336..4aa7be5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "shadow-dev-workflow", "description": "Self-contained development workflow with 3-tier knowledge base — 5-phase pipeline (propose → apply → review → release → archive) with packaged planning, domain-model-driven specs, invariant-based verification, and knowledge-driven design constraints. Zero external workflow dependencies.", - "version": "6.6.0", + "version": "6.8.0", "author": { "name": "Shadow (stack-wuh)" }, diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 9e63b83..f065f2a 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "shadow-dev-workflow", - "version": "6.6.0", + "version": "6.8.0", "description": "Self-contained development workflow with 3-tier knowledge base — propose → apply → review → release → archive with packaged planning, domain-model-driven specs, invariant-based verification, and knowledge-driven design constraints. Zero external workflow dependencies.", "author": { "name": "Shadow (stack-wuh)", diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index e705888..57ff51d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -50,6 +50,8 @@ jobs: PIN=$(node -pe 'require("./package.json").cliVersion') bash scripts/install-cli.sh install --version "$PIN" echo "$HOME/.local/bin" >> "$GITHUB_PATH" + # 同 quality-gate.yml:$GITHUB_PATH 对后续步骤生效,本步内紧接着的冒烟需自带 PATH(当前 job 跑 ubuntu,防御性对齐,避免两条流水语义漂移)。 + export PATH="$HOME/.local/bin:$PATH" shadow-dev --version - name: 发布前全门(version + requires:strict + knowledge + test) diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index 288406d..cf4ee12 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -39,6 +39,9 @@ jobs: PIN=$(node -pe 'require("./package.json").cliVersion') bash scripts/install-cli.sh install --version "$PIN" echo "$HOME/.local/bin" >> "$GITHUB_PATH" + # $GITHUB_PATH 只对后续步骤生效;同一步内紧接着的冒烟必须自带 PATH, + # 否则 windows-latest(Git Bash 默认不含 ~/.local/bin)判红而 ubuntu/macos 靠镜像默认值蒙过。 + export PATH="$HOME/.local/bin:$PATH" shadow-dev --version - name: 版本同源门 diff --git a/knowledge/domain-driven-shadow-dev.md b/knowledge/domain-driven-shadow-dev.md index d7e28bb..4b701f8 100644 --- a/knowledge/domain-driven-shadow-dev.md +++ b/knowledge/domain-driven-shadow-dev.md @@ -6,9 +6,10 @@ scope: [norms/domain-model.md, norms/verification.md, skills/shadow-dev-propose, status: active source: - changes/20261010-refactor-domain-driven-workflow/brief.md + - changes/20261010-feature-workflow-context-map/brief.md verified: 2026-10-10 verified-depth: unit -verified-scope: 本变更以自身结构自证——L 级 brief 含「## 领域模型」段且不变量清单非空,`change create`/`change approve`/`task list`(12 项)与 `worktree inspect` 的 `评级: L` 正则提取全部真实跑通;既有 `node --test test/pack.test.mjs` 4/4 绿。runtime 观察点(一致性门、产物扫描、拒绝路径)待 release 证据补齐后再升深度。 +verified-scope: 本仓已落地 `shadow-docs/domain.md` 上下文地图,卡片 `domain` 值域改由 `scripts/check-knowledge.mjs` 机器判定(本卡验证方式第 4 条由口头要求变为可执行断言,拒绝路径已实跑判红);原自证记录:本变更以自身结构自证——L 级 brief 含「## 领域模型」段且不变量清单非空,`change create`/`change approve`/`task list`(12 项)与 `worktree inspect` 的 `评级: L` 正则提取全部真实跑通;既有 `node --test test/pack.test.mjs` 4/4 绿。runtime 观察点(一致性门、产物扫描、拒绝路径)待 release 证据补齐后再升深度。 --- # 领域模型是工作流的规格载体 diff --git a/knowledge/multi-host-plugin-distribution.md b/knowledge/multi-host-plugin-distribution.md index d509fdd..65b98c4 100644 --- a/knowledge/multi-host-plugin-distribution.md +++ b/knowledge/multi-host-plugin-distribution.md @@ -1,12 +1,13 @@ --- title: 插件多宿主分发(Claude Code / Codex / zcode) -domain: 插件分发 +domain: distribution keywords: [Codex, Claude Code, 插件清单, marketplace, hooks, 宿主适配, 打包分发, 技能加载] scope: [.codex-plugin/, .claude-plugin/, .agents/, hooks/, adapters/, scripts/pack.mjs, skills/] status: active source: - changes/20261009-feature-codex-host-support/brief.md -verified: 2026-10-09 + - changes/20261010-feature-workflow-context-map/brief.md +verified: 2026-10-10 verified-depth: runtime verified-scope: 本机 Codex CLI 0.160.1 实测——`codex plugin marketplace add D:/works/shadow-dev-workflow` 回 `Added marketplace shadow-dev-workflow-local`;`codex plugin add shadow-dev-workflow@shadow-dev-workflow-local` 落地 `~/.codex/plugins/cache/shadow-dev-workflow-local/shadow-dev-workflow/6.4.0`(8 技能 + menu/norms/knowledge/rules 齐备,逐文件哈希与源一致);重启后技能表出现 `shadow-dev-workflow:shadow-dev-*` 命名空间条目;探针版 `commandWindows` 在 `codex exec --dangerously-bypass-hook-trust` 下输出 `hook: SessionStart` → `SessionStart Completed` 并落标记文件 --- diff --git a/knowledge/release-artifact-pipeline.md b/knowledge/release-artifact-pipeline.md index 016ff54..2ff7499 100644 --- a/knowledge/release-artifact-pipeline.md +++ b/knowledge/release-artifact-pipeline.md @@ -1,14 +1,15 @@ --- title: 发布链版本权威与产物资产契约 domain: distribution -keywords: [发布, release, tag, 版本权威, 资产名, tarball, pack, 上传, 发布冒烟, quality-gate, publish-release, CI, strict, 半绿] +keywords: [发布, release, tag, 版本权威, 资产名, tarball, pack, 上传, 发布冒烟, quality-gate, publish-release, CI, strict, 半绿, GITHUB_PATH, PATH, shim] scope: [scripts/pack.mjs, scripts/bump-version.mjs, scripts/check-requires.mjs, scripts/check-knowledge.mjs, .github/workflows/] status: active source: - changes/20261010-build-quality-gate/brief.md + - changes/20261010-feature-workflow-context-map/brief.md verified: 2026-10-10 verified-depth: unit -verified-scope: 本机 `npm run ci` 全绿(版本同源 + requires:strict + 知识治理门 + 既有 11 例测试);strict 反例用 `SHADOW_DEV_CLI` 指向坏文件复现 rc=1,非 strict 同条件复现半绿(rc=0 且输出「CLI 目录未参与断言」);版本反例单改一份 manifest 复现 rc=1 后回滚;三个 workflow 经 YAML 解析。GitHub 矩阵真跑与发布冒烟待首个 PR / 首个 release 的 run 链接。 +verified-scope: 本机 `npm run ci` 全绿(版本同源 + requires:strict + 知识治理门 + 既有 11 例测试);strict 反例用 `SHADOW_DEV_CLI` 指向坏文件复现 rc=1,非 strict 同条件复现半绿(rc=0 且输出「CLI 目录未参与断言」);版本反例单改一份 manifest 复现 rc=1 后回滚;三个 workflow 经 YAML 解析。GitHub 矩阵真跑与发布冒烟待首个 PR / 首个 release 的 run 链接。新增的 `$GITHUB_PATH` 同步内不可见一条:失败侧已由 PR #44 的 `quality-gate` windows 两项 `exit 127` 实证(同 run ubuntu/macos 绿),修复生效待本 PR 转绿后的 run 链接回填。 --- # 发布链版本权威与产物资产契约 @@ -27,6 +28,7 @@ GitHub Release 是产物与版本的**唯一权威发布点**,四件事缺一 - 发版顺序恒为:`node scripts/bump-version.mjs ` → `npm run check:version` → 提交合并 → `git tag v` → `gh release create`;此后判定、pack、资产断言、上传、冒烟由 `publish-release.yml` 接手。 - 新增机器门一律以 strict 形态进 CI;宽松模式只允许作为本机开发路径,且必须自曝降级态(如 `catalog=unresolved`)。 - `quality-gate.yml` 覆盖 push main 与所有 PR,矩阵含 windows——`tar`/`bash` 的跨平台坑已被实证,只跑 ubuntu 等于把最难发现的回归留在暗处。AI 的本机口头自证不可替代机器门。 +- 同一 `run:` 块里**不得依赖 `$GITHUB_PATH` 取刚装的 shim**:`echo ... >> "$GITHUB_PATH"` 只对**后续步骤**生效,紧跟着的冒烟命令仍用旧 PATH。`ubuntu`/`macos` runner 镜像默认 PATH 含 `~/.local/bin` 会蒙过,`windows` 的 Git Bash 不含即 `command not found` 退 127——同一步必须自己 `export PATH="$HOME/.local/bin:$PATH"`(实证:PR #44 的 `quality-gate` windows 两项挂在此处,ubuntu/macos 同 run 全绿)。 - 归档会搬迁 brief:卡片 `source` 按规范写 `changes//brief.md`,治理门必须向 `changes/archive//` 兜底解析(否则每张卡在来源 change 归档那天集体判红——本仓实测 3 张跨项目卡中 2 张如此)。 ## 适用边界 diff --git a/norms/domain-model.md b/norms/domain-model.md index 799d5bf..ec0e560 100644 --- a/norms/domain-model.md +++ b/norms/domain-model.md @@ -27,7 +27,7 @@ | 评级 | 要求 | |------|------| | **L** | 必填:限界上下文 + 统一语言增量 + **非空**聚合与不变量清单 + 领域事件 | -| **M** | 必填一行归属上下文;触及既有不变量时列出受影响的不变量 | +| **M** | 必填「## 领域模型」段的**最小形态**:一行 `限界上下文`(含上下游)+ 被触及的既有不变量清单(确无则写「无」);统一语言增量与领域事件可省 | | **S** | 免填 | ```markdown @@ -69,6 +69,8 @@ - 不在实现中途新增上下文或改写归属;需要就回 propose 修订 brief。 - 不借「领域建模」扩大改动面:模型只描述本次变更触碰的不变量,不做全仓领域普查。 - 不把实现过程写进上下文地图——地图是长期事实,过程留在 brief。 +- 不把横切实践(如 `debugging`)当限界上下文扩写;地图只登记归属与关系,概念定义仍由本文件唯一持有。 +- 卡片 `domain` 取值必须来自地图登记名(**domain 值域**):禁止同义词、中英别名与临时自造——别名会把一个上下文切成两个查询域,路由与查重同时失效。该约束由 `scripts/check-knowledge.mjs` 判定。 - 一个 change 一个主上下文;跨上下文连带改动各自立项。 ## 验证方式 diff --git a/norms/knowledge-cards.md b/norms/knowledge-cards.md index 7eff84d..9c63e4e 100644 --- a/norms/knowledge-cards.md +++ b/norms/knowledge-cards.md @@ -7,6 +7,7 @@ - `norms/`:跨项目硬规则与工程规范。 - `knowledge/`:跨项目经验与协作知识。 - 项目 `shadow-docs/knowledge/`:项目独有的执行真相。 +- 项目 `shadow-docs/domain.md`:上下文地图,卡片 `domain` 字段的**值域来源**(判据见 `norms/domain-model.md`) - `brief.md`:记录单次变更发生了什么,不承担当前规范职责。 ## 卡片格式 @@ -14,7 +15,7 @@ ```markdown --- title: <稳定领域事实名称> -domain: <主领域> +domain: <限界上下文,须能在项目 shadow-docs/domain.md 找到> keywords: [<业务词>, <技术词>, <常见现象>] scope: [<目录、包、路由或业务域>] status: active @@ -43,6 +44,10 @@ verified-scope: <本次验证观察到的具体现象与证据形态> - [<相关卡片>](other-topic.md) ``` +`domain` 是**路由键不是标签**:取值必须来自项目上下文地图 `shadow-docs/domain.md` 的登记名;新上下文先登记再建卡;禁止同义词、中英别名与临时自造值。 + +`source` 与归档兜底:卡片按知识库根写 `changes//brief.md`,而归档流程会把该 brief 搬进 `changes/archive//`——校验一律**向归档兜底解析**(本仓由 `scripts/check-knowledge.mjs` 执行),禁止为了让门变绿而改写卡片中的来源路径。 + `status` 只允许 `active` 或 `deprecated`。`source` 使用相对于知识库根目录的 brief 路径;`verified` 只在通过代码或可重复检查确认结论仍有效时更新。 ## 验证深度分级(verified-depth,20260925-feature-validation-strategy-signals 起) @@ -69,7 +74,8 @@ verified-scope: <本次验证观察到的具体现象与证据形态> - 不复制 brief 的实现过程与一次性验证输出。 - 新增前按 `domain + keywords + scope` 查重;能原位更新时不新增。 - active 卡片必须出现在 menu 的至少一条路由中。 -- source 必须存在,scope 必须明确。 +- source 必须存在(含向 `changes/archive/` 的兜底解析),scope 必须明确。 +- `domain` 值必须能在项目上下文地图中找到;地图缺失时本项降级为提示,不阻塞尚未建图的仓。 - deprecated 卡片必须指向替代卡片或明确写明无替代。 ## 生命周期 diff --git a/package.json b/package.json index daa7b5c..7d63758 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shadow-dev-workflow", - "version": "6.7.0", + "version": "6.8.0", "type": "module", "engines": { "node": ">=20" diff --git a/scripts/check-knowledge.mjs b/scripts/check-knowledge.mjs index ac39a0e..4257477 100644 --- a/scripts/check-knowledge.mjs +++ b/scripts/check-knowledge.mjs @@ -13,7 +13,10 @@ const fails = [], warns = [], oks = [] const ok = m => oks.push(`[ok] ${m}`) const warn = m => warns.push(`[warn] ${m}`) const fail = m => fails.push(`[fail] ${m}`) -const read = p => readFileSync(join(root, p), 'utf8') +// 读容忍 CRLF、判读恒 LF:windows runner 的 actions/checkout 按 core.autocrlf 检出 CRLF,而本门的 +// frontmatter 定界(`---\n`)、表格与引用扫描全部按 \n 解析——不归一会让 8 个技能集体误报「缺 name/description」 +// (实证:PR #44 的 windows gate 第 7 步 fail=21,ubuntu/macos 同 run 全绿)。与 shadow-dev-cli 的 brief 行尾契约同源。 +const read = p => readFileSync(join(root, p), 'utf8').replaceAll('\r\n', '\n') const SKIP_DIRS = new Set(['.git', 'dist', 'node_modules', '.agents']) // 1) 知识卡与规范:frontmatter 字段、status 枚举、source 存在性、active 卡路由覆盖 @@ -75,6 +78,32 @@ for (const dir of cardDirs) { } ok(`知识卡 ${cards.length} 张:字段/枚举/source/路由检查完成`) +// 1b) domain 值域:卡片 domain 必须是项目上下文地图(shadow-docs/domain.md)的登记名。 +// 值域取「限界上下文 / 横切实践 / 外部上游」三张表首列的反引号名称;横切实践可作 domain 值,但不是业务上下文。 +// 地图缺失只 warn:尚未建图的仓不该被本门的规范升级卡住(与「存量卡片不回溯迁移」同源)。 +const DOMAIN_SECTIONS = ['## 限界上下文', '## 横切实践', '## 外部上游'] +function loadDomainVocab() { + if (!existsSync(join(root, 'shadow-docs', 'domain.md'))) return null + const vocab = new Set() + let inSection = false + for (const line of read('shadow-docs/domain.md').split('\n')) { + if (line.startsWith('## ')) { inSection = DOMAIN_SECTIONS.some(k => line.trim().startsWith(k)); continue } + if (!inSection || !line.startsWith('|')) continue + const m = /^`([^`]+)`$/.exec((line.split('|')[1] || '').trim()) + if (m) vocab.add(m[1]) + } + return vocab +} +const domainVocab = loadDomainVocab() +let domainChecked = 0 +for (const { rel, data } of cards) { + if (!data.domain) continue + if (!domainVocab) { warn('无法校验卡片 domain:缺 shadow-docs/domain.md 上下文地图(后续卡片须先建图)'); break } + if (domainVocab.has(data.domain)) { domainChecked++; ok(`${rel}: domain「${data.domain}」∈ 地图值域`) } + else fail(`${rel}: domain「${data.domain}」不在上下文地图值域内(合法值: ${[...domainVocab].join(', ')})`) +} +if (domainVocab) console.log(`[.] 上下文地图值域: ${[...domainVocab].join(', ')}(命中 ${domainChecked}/${cards.length} 卡)`) + // 2) 技能清单:SKILL.md 必有 name + description,且 name 与目录同名(宿主按 name 发现技能) const skillDirs = readdirSync(join(root, 'skills')).filter(d => statSync(join(root, 'skills', d)).isDirectory()) for (const d of skillDirs) { diff --git a/scripts/check-requires.mjs b/scripts/check-requires.mjs index bee355c..1f86d82 100644 --- a/scripts/check-requires.mjs +++ b/scripts/check-requires.mjs @@ -34,8 +34,16 @@ const argv = cli ? [process.execPath, [cli, 'help', '--full', '--json']] : ['sha // 整段 JSON.parse 必然抛错——本门曾在 CI 型环境下恒判 unresolved(假红),本机偶然纯 JSON 时才假绿。 // 同时加超时:无界的 execFileSync 会让整道门挂死(实测挂 3 分钟以上)。 const timeout = Number(process.env.SHADOW_CLI_TIMEOUT_MS || 20000) +// win32 上 PATH 里的托管 shim 是 shadow-dev.cmd,而 Node ≥18.20.3/20.12.2 起拒绝在无 shell 下启动 .bat/.cmd(EINVAL)。 +// 不显式给 SHADOW_DEV_CLI 时,Windows 必须经 shell 解析命令目录——否则本门在 windows runner 恒 unresolved, +// strict 按设计判红而 ubuntu/macos 全绿(实证:PR #44/#45 的 gate 第 6 步)。降级态依旧自曝,不半绿。 +const viaShell = process.platform === 'win32' && !cli +// shell 模式下把命令与参数合成单串、args 留空:既避开 DEP0190(args 与 shell:true 同用只拼接不转义), +// 也保证本门的命令行永远是静态字面量,没有任何外部输入进入 shell。 +const bin = viaShell ? `${argv[0]} ${argv[1].join(' ')}` : argv[0] +const binArgs = viaShell ? [] : argv[1] try { - const out = execFileSync(argv[0], argv[1], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout }) + const out = execFileSync(bin, binArgs, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout, shell: viaShell }) const jsonLine = out.split(/\r?\n/).filter(l => l.startsWith('{')).pop() if (!jsonLine) throw new Error('命令目录无 JSON 契约行') catalog = Object.keys(JSON.parse(jsonLine).data.commands || {}) diff --git a/shadow-docs/changes/20261010-build-quality-gate/brief.md b/shadow-docs/changes/20261010-build-quality-gate/brief.md index 972955b..58566bc 100644 --- a/shadow-docs/changes/20261010-build-quality-gate/brief.md +++ b/shadow-docs/changes/20261010-build-quality-gate/brief.md @@ -4,7 +4,7 @@ "name": "20261010-build-quality-gate", "type": "build", "scope": "plugin-distribution", - "status": "reviewed", + "status": "committed", "baseBranch": "refactor/20261010-refactor-domain-driven-workflow", "branch": "build/20261010-build-quality-gate", "files": [ @@ -31,13 +31,13 @@ }, "review": { "conclusion": "passed", - "verifiedCommit": "b2e7579a49415a86053e46c1194d552c18a5cc5f", - "verifiedAt": "2026-10-10T00:06:13.439Z" + "verifiedCommit": "6775f1ff37a5f59bb74dc259ae3f742cdf4fc04e", + "verifiedAt": "2026-10-10T00:09:38.929Z" }, "workflow": { "operation": null, - "checkpoint": null, - "planHash": "28d7f52bd05741fb0c533a471f35e2a40f9a78aa795e38ee1b5e08009346cf51", + "checkpoint": "7b53a8f795097f0b2c89b246bb42c656fdabd4f5", + "planHash": "68e64cba111aee5af626c22ebae2f966386fafacde3423b32cf3441dfb9b6c5e", "updatedAt": null, "lastError": null, "issuePlan": { @@ -51,21 +51,9 @@ }, "commit": { "files": [ - ".github/workflows/publish-release.yml", - ".github/workflows/quality-gate.yml", - "CLAUDE.md", - "README.md", - "knowledge/distribution-capability-contract.md", - "knowledge/release-artifact-pipeline.md", - "menu.md", - "norms/verification.md", - "package.json", - "scripts/bump-version.mjs", - "scripts/check-knowledge.mjs", - "scripts/check-requires.mjs", "shadow-docs/changes/20261010-build-quality-gate/brief.md" ], - "message": "build(ci): 质量门上移 GitHub Actions + 版本与产物由 Release 控制" + "message": "docs(shadow): review 结论重钉至最终提交(补齐版本 bump 后全门复绿)" }, "release": { "files": [ @@ -91,7 +79,7 @@ "knowledge": { "action": "新增", "target": "knowledge/release-artifact-pipeline.md", - "reason": "新增「发布链版本权威与产物资产契约」卡(版本权威=release tag、资产名正则契约、发布冒烟三件套、机器门禁止半绿);原位更新 knowledge/distribution-capability-contract.md(追加机器门 strict 约束与验证方式第 5/6 项、追加 source、verified 2026-10-10)、norms/verification.md(替代观察点落到两条真实流水、知识治理改脚本执行)、menu.md(新增发布与质量门路由)。npm run ci rc=0 四门全绿、既有测试 11/11、三条负例(strict 判红 / 版本不同源 / forbid 命中)均 rc=1;GitHub 矩阵与发布冒烟为已知缺口,待 run 链接回填。门在本轮即抓到 2 张卡 source 归档死链与 1 次未登记的勾选状态,证明不是摆设。" + "reason": "同上轮结论:新增发布链版本权威与资产契约卡,原位更新能力契约卡/verification/menu。补齐提交后 npm run ci rc=0、check:version 三处同源 6.7.0、既有测试 11/11;范围修订(两处宿主清单版本)已在 brief 正文声明。" } } --- @@ -207,6 +195,8 @@ 2. `check-knowledge.mjs` 首版把 `walk()` 的 rel 拼成绝对式路径,`!rel.startsWith('shadow-docs')` 守卫失效 → 30 份归档 brief 被当 live 文件读,脚本 60+ 秒无输出挂死。修正 rel 语义后 **0.33s** 跑完。 3. 新门立刻抓到两条**长期存在的数据缺陷**:两张跨项目卡的 `source` 指向 `changes//brief.md`,而对应 brief 已随 #36/#32 归档搬进 `changes/archive/`——按字面路径判红。门改成「向 archive 兜底解析 + 兜底成功打 warn」,并把「归档会搬迁 source」写进卡片与 `release-artifact-pipeline.md` 执行约束,避免下一张卡重踩。 4. 两处编辑脚本因锚点与分支原文不符而 `assert` 中止(写入统一放末尾,未产生半成品);一次 `echo ${PIPESTATUS[0]}` 在 zsh 下空值中断了 `&&` 证据批,改为落盘日志再取。 +- **范围修订(实现期发现,非静默扩面):** 取证时执行 `node scripts/bump-version.mjs 6.7.0` 把版本推到 6.7.0,但脚本改的是三处 manifest,而 brief 声明的文件集里只有 `package.json`(我在任务清单里漏排了「版本 bump」这一项)。后果被新加的 `check:version` **当场判红**(`✗ 版本不同源`,rc=1),于是以补齐提交把 `.claude-plugin/plugin.json` 与 `.codex-plugin/plugin.json` 一并纳入并落版 6.7.0。两点结论写进知识:① 版本 bump 必须与它触及的文件集同源声明(本 change 已声明补齐,frontmatter 无法 amend 属 CLI 缺口,见下);② 这道门在它启用的第一次运行里就抓到了执行者自己的漏登记——包括我漏跑的两次 `task set` 和这次漏声明的两个文件,不是摆设。 +- **遗留与后续:** ① `scripts/bump-version.mjs` 改多文件却由单个 `--files` 提交承载,建议在 propose 阶段把「版本 bump」列为显式任务并预声明三处 manifest;② CLI 无 `change amend --files`,范围修订只能记在正文(与上一 change 同一缺口);③ GitHub 矩阵真跑与发布冒烟待首个 run 链接回填;④ 首个 release 由本 change 的 `publish-release.yml` 接手验证 `--release latest` 资产解析闭环。 - 交付: 待 push / PR(本 change 为 stacked:base=`refactor/20261010-refactor-domain-driven-workflow`) ## 知识评估 diff --git a/shadow-docs/changes/20261010-feature-workflow-context-map/brief.md b/shadow-docs/changes/20261010-feature-workflow-context-map/brief.md new file mode 100644 index 0000000..5bd9b84 --- /dev/null +++ b/shadow-docs/changes/20261010-feature-workflow-context-map/brief.md @@ -0,0 +1,212 @@ +--- +{ + "schema": "shadow-dev/v1", + "name": "20261010-feature-workflow-context-map", + "type": "feature", + "scope": "workflow-governance", + "status": "published", + "baseBranch": "main", + "branch": "feature/20261010-feature-workflow-context-map", + "files": [ + ".claude-plugin/plugin.json", + ".codex-plugin/plugin.json", + ".github/workflows/publish-release.yml", + ".github/workflows/quality-gate.yml", + "knowledge/domain-driven-shadow-dev.md", + "knowledge/multi-host-plugin-distribution.md", + "knowledge/release-artifact-pipeline.md", + "norms/domain-model.md", + "norms/knowledge-cards.md", + "package.json", + "scripts/check-knowledge.mjs", + "scripts/check-knowledge.mjs", + "scripts/check-requires.mjs", + "shadow-docs/changes/20261010-feature-workflow-context-map/brief.md", + "shadow-docs/domain.md", + "shadow-docs/signals.md", + "test/pack.test.mjs" + ], + "github": { + "repository": null, + "issue": null, + "issueUrl": null, + "pullRequest": 44, + "pullRequestUrl": "https://github.com/stack-wuh/shadow-dev-workflow/pull/44" + }, + "review": { + "conclusion": "passed", + "verifiedCommit": "202d792ef26ee2fe3f76a1da0d27a331f6315a67", + "verifiedAt": "2026-10-10T08:39:14.963Z" + }, + "workflow": { + "operation": null, + "checkpoint": "pr:44", + "planHash": "fc037a84d9e0f8c52e326dd79a819a642382aa3db15b75853ea3f4c2b1d7f9f0", + "updatedAt": null, + "lastError": null, + "release": { + "files": [ + ".claude-plugin/plugin.json", + ".codex-plugin/plugin.json", + "knowledge/domain-driven-shadow-dev.md", + "knowledge/multi-host-plugin-distribution.md", + "norms/domain-model.md", + "norms/knowledge-cards.md", + "package.json", + "scripts/check-knowledge.mjs", + "shadow-docs/changes/20261010-feature-workflow-context-map/brief.md", + "shadow-docs/domain.md" + ], + "message": "docs(knowledge): 落地本仓上下文地图,卡片 domain 升为一等路由键并进门校验", + "title": "20261010-feature-workflow-context-map", + "body": "" + }, + "commit": { + "files": [ + "shadow-docs/changes/20261010-feature-workflow-context-map/brief.md", + "test/pack.test.mjs" + ], + "message": "fix(test): pack 失败路径同享清理钩子,不再给仓库留未跟踪 dist/" + } + }, + "knowledge": { + "action": "无需变更", + "target": null, + "reason": "失败路径清理属本 change 内测试自身缺陷的修正,无新的跨项目长期事实" + } +} +--- + +# 本仓上下文地图落地 + 卡片 domain 升为一等路由键(并进门校验) + +## 动机 + +上一个 change 把「限界上下文 / 统一语言 / 聚合不变量」写进了流程(`norms/domain-model.md`),但留下两条**本仓自身未满足**的不变量,记在 `20261010-refactor-domain-driven-workflow` brief 的「遗留与后续」里: + +1. **本仓没有上下文地图。** `norms/domain-model.md` 的验证方式要求「知识卡 `domain:` 值能在地图中找到」,而 workflow 仓自身没有 `shadow-docs/domain.md`——新立的规范第一条就在自己的仓里空转。 +2. **`domain` 字段仍只是标签,不是路由键。** `norms/knowledge-cards.md` 只写 `domain: <主领域>`,没有值域约束,于是实测已经分裂:`knowledge/multi-host-plugin-distribution.md` 写中文 `插件分发`,而同一分发上下文的另两张卡(`distribution-capability-contract.md`、`release-artifact-pipeline.md`)写 `distribution`;`domain-driven-shadow-dev.md` 写 `workflow-governance`。按 `domain + keywords + scope` 收敛时,同一个上下文会因为写法不同被切成两份,查重也失效。 + +另外,上一 change 实证了一条规范空白:卡片 `source` 按知识库根写 `changes//brief.md`,而归档流程会把 brief 搬进 `changes/archive/`——**每张卡都在来源 change 归档那天集体判红**(实测 3 张跨项目卡中 2 张如此)。当时只在门里加了兜底解析,规则本身没写进 `norms/knowledge-cards.md`,下一个项目仍会重踩。 + +**非目标:** 不让门支持项目仓布局(`--root `,路径族为 `shadow-docs/knowledge|menu|domain`)——需要路径变体与 fixture 测试,另立 change;不改分发轨、不动 pack 集合与宿主清单结构语义;不重建目录结构(方案 C 明确未选)。 + +## 复杂度评级 + +- **评级:** M +- **理由:** **有行为但局部**——新增一张机器可判的约束(卡片 `domain` 值域=地图登记集)与一份本仓地图,不改行为契约、不改协议/schema/权限面;**触及面**跨 5 个文件但都在共享规范与门脚本内,不碰宿主核心(`pack.mjs`、adapters、清单结构均未动);**可发现性**好——`domain` 值不合法会被 `check-knowledge` 直接判红,不是等运行时才炸。按 `norms/verification.md`,M 级=冒烟 + 既有不变量保持成立 + 既有测试保持绿 + 走查,默认不新建测试文件。 +- **期望验证深度:** unit(本机门 + 既有 11 例测试;拒绝路径实跑) + +## 领域模型 + +- **限界上下文:** `workflow-governance` — 开发工作流治理(规范、技能、知识路由)。上游 `execution-layer`(CLI 仓,能力契约反腐边界:不新增命令键、不改 brief schema),下游各项目 `shadow-docs/`(地图与卡片在各仓自持)。 +- **统一语言增量:** **上下文地图**(`shadow-docs/domain.md`,登记本仓限界上下文与横切实践及其主目录);**横切实践**(`debugging` 这类不是业务上下文的实践域,允许进地图但不得当上下文用);**domain 值域**(卡片 `domain` 的合法取值=地图登记名集合)。 +- **聚合与不变量(M 级列出被触及项):** + - `Knowledge 卡片`:`domain` 值必须 ∈ 地图值域(新);active 卡必须进 menu 路由;三键齐全;source 可解析(含归档兜底)。 + - `规范引用一致性`:地图登记的上下文必须与实际目录/scope 对应;卡片 domain 与地图不得出现同义别名。 +- **领域事件:** 新上下文出现 → 先登记地图再建卡;地图值域变化 → 门立即判红存量违规卡。 +- **跨上下文声明(Phase 4,经用户显式裁决接受):** 本 change 主体归属 `workflow-governance`,Phase 4 两处越入 `distribution`——① `.github/workflows/quality-gate.yml` 与 `publish-release.yml`:修「同步内依赖 `$GITHUB_PATH`」的步骤时序缺陷,**不触碰**版本权威、资产名契约、发布冒烟任何语义;② `knowledge/release-artifact-pipeline.md`:向该卡「执行约束」**追加**一条 CI 事实,不改当前结论与 domain 值。反腐边界:只增不改;两处均经 `change amend` 登记扩面,未静默改未声明文件。按 `norms/domain-model.md` 本应拆 change,此处由用户于 2026-10-10 裁决保留在同一 PR,理由是堆叠现实:#44 已包含 #43,给 #43 加提交后 #44 无法 fast-forward,拆分会强制原始 git 重堆(违反分支铁律)。 + +## 引用规范 + +- `norms/domain-model.md` + - 当前结论: 术语唯一定义处;项目上下文地图落 `shadow-docs/domain.md`;验证方式要求卡片 `domain` 值能在地图中找到;越界禁止「不在实现中途新增上下文或改写归属」。 + - 适用 scope: 本仓 `shadow-docs/domain.md` 与 `norms/knowledge-cards.md` 条款 +- `norms/knowledge-cards.md` + - 当前结论: `domain: <主领域>` 无值域约束;`source` 相对知识库根;存量卡片不回溯迁移(缺 `verified-depth` 视为 `code-read`);active 卡必须进 menu 路由。 + - 适用 scope: 本次要补的两条(domain 值域、source 归档兜底) +- `knowledge/distribution-capability-contract.md` · `knowledge/release-artifact-pipeline.md` + - 当前结论: 依赖单向、能力名判定、禁止静默降级;机器门禁止半绿。 + - 适用 scope: 新增校验必须以非零退出执法,不得只提示 +- `knowledge/bug-investigation.md` + - 当前结论: 单一持续上下文;横切实践(调试)不是业务限界上下文。 + - 适用 scope: 地图需区分「限界上下文」与「横切实践」两类条目,否则会把 `debugging` 误当上下文 +- `rules/behavior.md` §2 / §3 + - 当前结论: 不过度设计、不碰没坏的代码。 + - 适用 scope: 不做 `--root` 项目布局支持(另立 change);只规范化确有分歧的字段 + +## 决策 + +- **选型:** 地图 + 条款 + 门校验三件同时落,缺一件就是又一条「写了但不被执行」的规范。 + 1. 新增 `shadow-docs/domain.md`:本仓两个限界上下文(`workflow-governance`、`distribution`)+ 一个横切实践(`debugging`)+ 外部上游 `execution-layer`(CLI 仓,能力契约反腐边界);每条带职责、上下游、主目录/scope、关键术语;术语表增量指向唯一定义处,不复制定义。 + 2. `norms/knowledge-cards.md` 补三条:`domain` 值必须取自项目地图登记名(禁止同义词、中英别名、临时自造);新上下文先在地图登记再建卡;`source` 写 `changes//brief.md`,**归档搬迁由门向 `changes/archive/` 兜底解析,不得为了让门变绿而改写卡片路径**。 + 3. `knowledge/multi-host-plugin-distribution.md` 的 `domain: 插件分发` 归一为 `distribution`,并更新 `verified`、追加本次 source。 + 4. `scripts/check-knowledge.mjs` 新增 domain 成员校验:读地图的上下文名与横切实践名构成值域,卡片 `domain` 不在值域内 → `[fail]` 非零退出;地图文件不存在 → `[warn]`(不阻塞尚未建图的项目)。 + 5. `knowledge/domain-driven-shadow-dev.md` 追加 source 与 `verified`(本 change 真实执行了它的验证方式第 4 条)。 +- **对比方案:** + - **只补地图、不动门**:未选。上一 change 已证明「没有机器判定的口头纪律」会被执行者自己漏掉(我漏登记两次勾选、漏声明两个文件),`domain` 值域同样会漂。 + - **把 `插件分发` 保留为别名**:未选。门与路由的键就是字符串本身,允许别名等于把同一个上下文分成两个查询域。 + - **顺带支持项目仓 `--root`**:未选(本轮)。路径族不同(`shadow-docs/knowledge`、`shadow-docs/menu.md`)且需要 fixture 验证,塞进 M 级会让它变 L;已在非目标里记为独立 change。 + - **把地图内容写进 `norms/domain-model.md`**:未选。规范是跨项目模板,地图是**本仓事实**,两者混写会让别的项目继承错误的上下文名。 +- **理由:** 领域驱动这套东西的价值取决于「名字能不能当键用」。名字没有值域约束,路由与查重就会退化成关键词搜索——这正是本次分歧的成因。 + +## 任务 + +### Phase 1 — 地图与条款 +- [x] 本仓上下文地图 — `shadow-docs/domain.md` — 限界上下文表(workflow-governance / distribution)+ 横切实践(debugging)+ 外部上游(execution-layer,CLI 仓)+ 术语表增量指针;每行含职责、上下游、主目录/scope +- [x] M 级领域模型形态明确化 — `norms/domain-model.md` — 规定 M 级「归属上下文 + 被触及不变量」写在「## 领域模型」段(一行归属 + 不变量列表),S 级免填;补「横切实践可入地图但不得当上下文」与「domain 值域=地图登记名」两条越界禁止 +- [x] 卡片规范补条款 — `norms/knowledge-cards.md` — `domain` 值域=地图登记名(禁同义词/中英别名/自造);新上下文先登记再建卡;`source` 归档兜底解析规则与「不得为过门改写卡片」禁止项;模板注释由 `<主领域>` 改为 `<限界上下文,须在 shadow-docs/domain.md 登记>` +### Phase 2 — 归一与执法 +- [x] 卡片 domain 归一 — `knowledge/multi-host-plugin-distribution.md` — `插件分发` → `distribution`,`verified` 更新并追加本次 source +- [x] 门新增 domain 成员校验 — `scripts/check-knowledge.mjs` — 解析地图值域(上下文 ∪ 横切实践);卡片 domain 不在值域 → fail 非零退出;地图缺失 → warn 不阻塞;保持既有零依赖与 sub-秒运行 +- [x] 规格卡补验证记录 — `knowledge/domain-driven-shadow-dev.md` — 追加 source 与本条验证事实(domain 一致性现已可机器判定) +### Phase 3 — 收口 +- [x] 版本与取证 — `package.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, `shadow-docs/changes/20261010-feature-workflow-context-map/brief.md` — `bump-version.mjs 6.8.0`(三处同源,本次已在文件集内声明);跑 `npm run ci` 全门 + 11 例测试;拒绝路径:临时把某卡 domain 改回 `插件分发` 与删空地图值域各复现判红后回滚 +### Phase 4 — review 后追加:Windows 门修复(经 `change amend` 登记扩面) +- [x] CI 冒烟 PATH 修复 — `.github/workflows/quality-gate.yml`, `.github/workflows/publish-release.yml` — `$GITHUB_PATH` 只对后续步骤生效,同一步内紧接着的 `shadow-dev --version` 在 windows-latest 找不到 shim(ubuntu/macos 靠镜像默认 PATH 蒙过);补 `export PATH="$HOME/.local/bin:$PATH"`。两文件不在本 change 原声明集内,按 `shadow-dev-cli` #50 新增的 `change amend` 登记扩面(`added` 两项、`reviewReset=true`),不越界改未声明文件 +- [x] 知识闭环 — `knowledge/release-artifact-pipeline.md` — 「$GITHUB_PATH 同步内不可见」升格为发布链卡的执行约束(keywords 补 `GITHUB_PATH/PATH/shim`、source 追加本 brief、verified-scope 记失败侧已由 CI 实证而修复生效待 run 链接回填);同文件亦经 `change amend` 第二次登记 +- [x] strict 门 win32 兼容 + baseBranch 纠偏 — `scripts/check-requires.mjs` — PATH 上的托管 shim 是 `shadow-dev.cmd`,Node ≥18.20.3/20.12.2 拒绝无 shell 启动 `.bat/.cmd`(EINVAL)→ `execFileSync` 增 `shell: process.platform === 'win32' && !cli`,降级仍自曝不半绿。同时 `change amend --base-branch main` 把声明基线纠正为 `main`(`publish` 的 `findPr` 按 base 过滤,声明错则复用不到 PR #44 并重复开 PR),输出含 `warnings` 指名历史 PR #45 需改 base 或关闭。证据:`added:["scripts/check-requires.mjs"] baseBranch:"main" reviewReset:true warnings:[…]` +- [x] 知识治理门 CRLF 容忍 — `scripts/check-knowledge.mjs` — windows runner 按 `core.autocrlf` 检出 CRLF,而 `read()` 不做行尾归一 → `parseFrontmatter` 的 `---\n` 定界失配,8 个技能集体误报「缺 name/description」(CI 实证:第 7 步 `fail=21`,ubuntu/macos 同 run 绿)。改为读入处单点 `replaceAll('\r\n','\n')`。**取证**:把 8 个 `SKILL.md` 就地转 CRLF 后复跑 → `[ok] 技能 8 个:name/description 与目录一致性检查完成`、`fail=1`(仅剩本机 ignore 目录噪声);还原后 `git diff --stat -- skills` 为空、逐字节 CR 计数 0。 + - **过程自伤(如实记)**:还原脚本里 `Join-Path $bak $f.Directory.Name + [IO.Path]::DirectorySeparatorChar + ...` 因参数优先级被当成 provider 路径解析,8 次 Copy-Item 全失败,而我随后无条件执行了 `Remove-Item -Recurse -Force $bak`,备份已不存在。补救依据是本次污染纯属行尾变换(CRLF→LF 无损可逆):逐文件 `Replace(CRLF,LF)` 后 `git status --short -- skills` 全空。教训:**探针回滚必须逐项校验成功后才能删备份**;这次侥幸可逆只因改动仅有行尾,若探针改的是内容就已造成真实损失。 +- [x] SGN-010 退役与解包失败响亮化 — `test/pack.test.mjs`, `shadow-docs/signals.md` — 按该负信号自写的退役条件把解包路径归一为 posix:首选 `cygpath -u`,缺失时退回纯字符串 `C:\x` → `/c/x`(本机实测:`D:\works\…\v6.8.0.tar.gz → /d/works/…`、`C:\Users\RUNNER~1\… → /c/Users/RUNNER~1/…`);并把 `spawnSync` 的 rc 由忽略改为当场 `throw`——旧写法让失败漂到下游用例变成 `package.json ENOENT`,把排查方向带偏(本机以此立刻报出 `pack 解包失败 rc=1` 与 WSL 无发行版的真实原因)。信号条目按 `norms/signals.md` 标注【已退役 2026-10-10】、命中升至 2 并记录条件已满足。**回归自查**:首次实现把 `throw` 放在 `test.after` 注册之前,导致本机失败路径跳过清理、把未跟踪的 `dist/`(内含 76KB tar,且 `dist` 未被 gitignore)留在工作区——违反本文件自订的「不给仓库留未跟踪文件」。已把清理抽成 `cleanup()` 同时挂到失败路径与 `test.after`;复跑验证:仍当场报 `pack 解包失败 rc=1`,且 `Test-Path dist` 为 `False`。 + +## 结果 + +- 实际耗时: ≈18 分钟(含 139 风暴导致的两次链中断重试) +- 验证: **M 级=冒烟 + 触及的既有不变量保持成立 + 既有测试绿 + 走查**,全部为本机真实输出: + +| 项 | 观察点 | +|---|---| +| 新不变量「domain 值必须 ∈ 地图值域」 | 正例 `node scripts/check-knowledge.mjs` → `[.] 上下文地图值域: workflow-governance, distribution, debugging, execution-layer(命中 5/5 卡)` + `摘要: ok=9 warn=4 fail=0`;**拒绝路径**:把 `multi-host-plugin-distribution.md` 的 domain 改回 `插件分发` → `[fail] ... 不在上下文地图值域内`、`摘要: fail=1`、**退出码 1**;恢复 → 退出码 0 | +| 既有不变量「source 可解析(含归档兜底)」 | 门复跑仍 `fail=0`,两张卡以 warn 形式自曝兜底解析(未被悄悄掩盖) | +| 既有不变量「active 卡必须进 menu 路由」 | 5 张卡全过;新地图不建卡,无需路由 | +| 引用一致性 | `npm run check:knowledge` → 51+ 文件扫描零死链、孤儿规范 0 | +| 一致性门未退化 | `npm run check:requires:strict` → `✓ skills ⊆ requiresCommands ⊆ CLI 命令目录`(本 change 未新增命令键) | +| 版本权威前置 | `node scripts/bump-version.mjs 6.8.0` → 三处 manifest 同源 6.8.0;`--check` ✓(**本次三处清单已在 brief 文件集内声明**,正是上一 change 范围修订的教训落地) | +| 既有测试 | `npm test` → `# tests 11 # pass 11 # fail 0` | +| 产物契约 | `node scripts/pack.mjs` → `shadow-dev-workflow-v6.8.0.tar.gz`,`✓资产契约`;包内 `norms/domain-model.md`、`norms/knowledge-cards.md`、`scripts/check-knowledge.mjs`、`knowledge/domain-driven-shadow-dev.md` 命中 4/4;`shadow-docs/` 未入包(地图是仓内事实,符合 pack 集合) | + +- 过程记录(诚实,含两次自伤): + 1. **探针回滚误用 `git checkout --`**:为做 domain 反例,我在同一条命令里改文件后用 `git checkout -- <卡>` 回滚——该文件有**未提交**的归一改动,checkout 把它们一并冲掉。修法:改用 `cp` 备份/还原,并核对 frontmatter 实况后重放改动。教训:拒绝路径探针一律用备份,绝不对未提交文件用 checkout。 + 2. **python 字节字面量事故**:写脚本时把 `b = """…"""` 打成 `b"""…"""`,Python 解析为 bytes → `SyntaxError: bytes can only contain ASCII literal characters`,整段脚本(含前序正确改动)**未执行**,因此无半成品落盘;随后逐项核对实际文件状态再继续。 + 3. 两次锚点与文件实况不符(`- 不把 brief 的实现过程…` 多了 `brief` 二字;`status 只允许` 行被误加 `- ` 前缀)导致 `assert` 中止——所有编辑脚本都写成「先全部校验锚点、最后一次性落盘」,中止即无部分写入。 + 4. 本机 node 139 风暴两次打断 `&&` 链(`task set task-6` 与 `bump-version` 静默未执行),按 SGN-004 重试至收敛后逐项复跑;产物一度仍是 v6.7.0 即为该中断的直接证据,已复跑修正。 +- 交付: 待 push / PR(stacked 第三层:base=`build/20261010-build-quality-gate`) + +### Phase 4 取证(2026-10-10,本机真实输出) + +| 项 | 观察点 | +|---|---| +| 扩面登记 | `change amend --files <12 项>` → `added:[".github/workflows/publish-release.yml",".github/workflows/quality-gate.yml"]`、`removed:[]`、**`reviewReset:true`**(本 change 原 `passed@3c23fbf` 被作废,`review.conclusion` 回落 `pending`)、`nextStep:"review plan --name …"`。机制来自 `shadow-dev-cli` PR #50,本机经 `SHADOW_DEV_CLI` 指向该 checkout 的 `cli.mjs` 驱动 | +| 根因 | PR #44 windows 两项挂在 step 4「Install pinned shadow-dev CLI」:日志 `shadow-dev 1.5.0 installed to /c/Users/runneradmin/.local/share/...` 后紧跟 `line 4: shadow-dev: command not found`、`exit 127`;同 run 的 ubuntu/macos 通过——因 `$GITHUB_PATH` 追加**对后续步骤**生效,而同一步内紧接着的冒烟取不到该路径,windows runner 的 Git Bash 默认 PATH 不含 `~/.local/bin` | +| 改动正确性 | `export PATH` 行缩进 10 空格(与块标量内其余命令行一致);两文件经 js-yaml 实测可解析:`job=gate steps=7 exportPATH=true`、`job=publish steps=9 exportPATH=true` | +| 版本权威 | `npm run check:version` → `✓ 三处 manifest 版本同源: 6.8.0(3 个文件)`(本次不涉及 bump) | +| 一致性门 | `npm run check:requires:strict` → 先因本机 `execFileSync('shadow-dev')` 解析不到 `.cmd` shim 而**响亮判红**(`cli-catalog=unresolved strict=on`,未降级半绿,符合「禁止半绿」),置 `SHADOW_DEV_CLI=D:\works\shadow-dev-cli\cli.mjs` 后 → `declared=29 skills-referenced=21 undeclared=[] cli-catalog=47 catalog=resolved missing-in-cli=[] ✓` | +| 知识治理门 | 本机 `check:knowledge` → `摘要: ok=10 warn=4 fail=1`,唯一 `[fail]` 是**未跟踪且被 ignore 的** `.zcode/plans/plan-sess_….md → scripts/shadow-dev.mjs` 死链(`git ls-files .zcode` = 0);CI 干净 checkout 无此目录,故 ubuntu/macos 该门为绿。→ 记下两条门自身弱点:门应跳过 ignored 路径;`check:requires` 在 Windows 需要可解析的 CLI 入口(`.cmd` 不经 shell 不可 execFile) | + +### 本 change 遗留的两处待确认(非本次引入,不静默处置) + +1. **正文存在两个互相冲突的 `## 知识评估`**:第一处结论「无需变更」,第二处「更新」;frontmatter `knowledge.action` 取的是「无需变更」。二者对同一批卡片(`domain-driven-shadow-dev.md` / `multi-host-plugin-distribution.md` / `knowledge-cards.md`)给出不同最终动作,属同一 brief 内的自相矛盾,需作者裁定保留哪个并删除另一个(`check-knowledge` 只校验卡片,不校验 brief 正文结构,故门未拦住)。 +2. **声明 base 与 PR 实际 base 不符**:正文与 frontmatter `baseBranch` 为 `build/20261010-build-quality-gate`,GitHub 上 PR #44 的 base 是 `main`。合并顺序因此改为「合链尖 #44 一把带走 #42/#43」或「把 PR base 改回堆叠」,二者需择一。 + +## 知识评估 + +- **预期影响:** 无需变更(本 change 是**规范条款落地 + 值域执法**,不产生新的跨项目事实) +- **候选卡片:** 无新增。已触碰的两张卡只做字段与来源更新:`knowledge/multi-host-plugin-distribution.md`(domain 归一,属命名修正非事实变更)、`knowledge/domain-driven-shadow-dev.md`(追加 source 与执法点,结论不变) +- **理由:** 「domain 是路由键、值域来自上下文地图、source 需向归档兜底」是**执行约束**,落点在 `norms/knowledge-cards.md` 与 `norms/domain-model.md`;按 `norms/knowledge-cards.md`「无长期事实才记无需变更」与「能更新不新增」,不另建重复卡。地图本身是仓内事实文件,不是卡片。 + + +## 知识评估 + +- **预期影响:** 更新 +- **候选卡片:** `knowledge/domain-driven-shadow-dev.md`(追加 source 与 verified,结论不变)、`knowledge/multi-host-plugin-distribution.md`(domain 值归一,属字段修正非事实变更)、`norms/knowledge-cards.md`(原位补两条执行约束) +- **理由:** 本次产生的是**规范条款**而非新事实,落点在 `norms/`;已知的跨项目结论(规格载体=领域模型)没有变化,因此不新建卡,只按「事实变化原位更新、追加 source」处理。`verified-depth` 保持 `unit` 并在 `verified-scope` 记入门的 domain 成员校验这一新执法点;未达 runtime 不虚报。 diff --git a/shadow-docs/domain.md b/shadow-docs/domain.md new file mode 100644 index 0000000..cd94818 --- /dev/null +++ b/shadow-docs/domain.md @@ -0,0 +1,37 @@ +# shadow-dev-workflow 上下文地图 + +> 本仓限界上下文与统一语言的登记处(判据与格式见 `norms/domain-model.md`)。 +> **domain 值域**=本页「限界上下文 ∪ 横切实践 ∪ 外部上游」登记的名称;知识卡 `domain` 取值必须落在其中,由 `scripts/check-knowledge.mjs` 机器判定。新上下文先登记再建卡。 + +## 限界上下文 + +| 上下文 | 职责 | 上游 / 下游 | 主目录与 scope | 关键术语 | +|---|---|---|---|---| +| `workflow-governance` | 开发流程治理:规范、技能、知识路由、brief 生命周期与质量门禁语义 | ← `execution-layer`;→ 各项目 `shadow-docs/` | `skills/`, `norms/`, `rules/`, `knowledge/`, `menu.md`, `scripts/check-knowledge.mjs`, `shadow-docs/changes/` | 限界上下文 / 统一语言 / 聚合 / 领域不变量 / 观察点 / 复杂度评级 / 分级验证 / 信号 | +| `distribution` | 产物打包、多宿主分发、版本与发布链 | ← `execution-layer`;→ claude-code / codex / zcode 三宿主 | `scripts/pack.mjs`, `scripts/bump-version.mjs`, `scripts/check-requires.mjs`, `scripts/install-cli.sh`, `adapters/`, `hooks/`, `.claude-plugin/`, `.codex-plugin/`, `.agents/`, `marketplace.json`, `package.json`, `.github/workflows/` | 能力契约 / requiresCommands / ARTIFACT_INCOMPATIBLE / pin / 资产契约 / 版本权威 / 发布冒烟 / 半绿 | + +## 横切实践 + +跨上下文的**方法**而非业务边界:允许登记为 `domain` 值,但不得当限界上下文扩写,也不参与上下游关系。 + +| 名称 | 说明 | 关联卡片 | +|---|---|---| +| `debugging` | 缺陷调查方法(根因分析优先、单一持续上下文) | `knowledge/bug-investigation.md` | + +## 外部上游(本仓不定义其语义,只经能力契约消费) + +| 名称 | 仓库 | 反腐边界 | +|---|---|---| +| `execution-layer` | `stack-wuh/shadow-dev-cli` | 依赖单向:CLI 驱动本仓产物(`bootstrap → workflow plan/execute → bind plan/execute`)。本仓用 `package.json.requiresCommands` 声明所需命令键,缺失即 `ARTIFACT_INCOMPATIBLE` 响亮阻塞;兼容判定用能力名不用版本号;本仓不装 CLI、不 pin 之外的安装轨 | + +## 术语表增量 + +- 概念定义的唯一来源是 `norms/domain-model.md`,本页只登记归属,不复制定义。 +- **domain 值域**:本页三张表登记的名称集合(卡片字段合法取值)。 +- **归属一致性**:地图登记的上下文与其「主目录与 scope」必须与实际布局对应;改名或搬迁上下文时先改本页。 + +## 维护规则 + +- 新增/改名上下文:先改本页 → 再动卡片 `domain` 与 `menu.md` 路由 → 最后跑 `npm run check:knowledge`;两侧不一致以本页为准并判红。 +- 单次变更的归属变化必须走 propose 修订 brief,不得在 apply 中途改登记(`rules/iron-laws.md` §7 同族纪律)。 +- 本页只放长期事实;过程与一次性验证留在 brief。 diff --git a/shadow-docs/signals.md b/shadow-docs/signals.md index 0bf0dd9..8aaf65b 100644 --- a/shadow-docs/signals.md +++ b/shadow-docs/signals.md @@ -11,14 +11,14 @@ - 命中: 1(最近 2026-10-09) - 退役条件: Codex 提供 plugin lint/校验命令,或清单校验不再输出字段级 WARN -## SGN-010 · Windows 上 `node --test test/pack.test.mjs` 的解包步骤必失败(tar 收到 `D:\` 形态路径按 host:path 解析,回 `Cannot connect to D: resolve failed`),非代码回归,勿据此排查 +## SGN-010 · 【已退役 2026-10-10】Windows 上 `node --test test/pack.test.mjs` 的解包步骤必失败(tar 收到 `D:\` 形态路径按 host:path 解析,回 `Cannot connect to D: resolve failed`),非代码回归,勿据此排查 - 方向: negative - 权重: 2 - 深度: runtime - 域/scope: 测试 · test/pack.test.mjs, scripts/pack.mjs - 证据: 20261009-feature-codex-host-support(未改动的「产物存在」用例同挂该步;手工 `bash -c 'tar -xzf'` 复现同一失败) -- 命中: 1(最近 2026-10-09) -- 退役条件: 测试解包改用 posix 路径或原生 `tar.exe` +- 命中: 2(最近 2026-10-10,PR #44 的 `quality-gate` windows 两项复现 `# tests 11 # pass 9 # fail 2`,症状仍是下游 ENOENT 而非解包本身) +- 退役条件: 测试解包改用 posix 路径或原生 `tar.exe` —— **已满足**:`test/pack.test.mjs` 增 `toUnix()`(`cygpath -u`,非 win32 原样返回)归一两个路径后再交给 `bash -c tar`,并把解包 `rc != 0` 改为当场抛错(旧写法吞掉失败,症状漂到下游用例,排查方向被带偏)。本条按 `norms/signals.md` 保留条目并注明退役,不再作为负信号参与排序。 ## SGN-004 · node 子进程 exit 139 是机器段错误风暴,重试至收敛,勿误判为代码失败 - 方向: positive diff --git a/test/pack.test.mjs b/test/pack.test.mjs index e77c737..acb7a98 100644 --- a/test/pack.test.mjs +++ b/test/pack.test.mjs @@ -14,14 +14,31 @@ const artifact = join(PLUGIN_ROOT, 'dist', `shadow-dev-workflow-v${version}.tar. const extract = mkdtempSync(join(tmpdir(), 'pack-')) execFileSync('node', [join(PLUGIN_ROOT, 'scripts', 'pack.mjs')], { cwd: PLUGIN_ROOT }) // tar 必须经 bash -c 执行:Windows runner 上 node 直 spawn 绑到 System32 bsdtar, -// 读不了 MSYS /tmp 路径(install-distribution 卡约束,安装器用例同款坑) -spawnSync('bash', ['-c', 'tar -xzf "$1" -C "$2"', 'pack-extract', artifact, extract], { encoding: 'utf8' }) -const packedRoot = join(extract, 'shadow-dev-workflow') - -test.after(() => { +// 读不了 MSYS /tmp 路径(install-distribution 卡约束,安装器用例同款坑)。 +// 但 Git Bash 的 tar 会把「D:\…」这类形态当成 host:path(SGN-010:Cannot connect to D: resolve failed), +// 所以传给 bash 的路径必须先归一为 posix 形态——与 shadow-dev-cli 的 test 同款 toUnix 手法,即 SGN-010 的退役条件。 +const toUnix = process.platform === 'win32' ? (p) => { + const r = spawnSync('cygpath', ['-u', p], { encoding: 'utf8' }) + // cygpath 可能不在 PATH(Git Bash 未加入 PATH 的机器与 runner 都会这样),退回纯字符串 MSYS 形态: + // `C:\a\b` → `/c/a/b`。两条路都不依赖具体 bash 实现(Git Bash 用 /c/…,WSL 挂载点不同、本就跑不了本用例)。 + if (r.status === 0 && r.stdout.trim()) return r.stdout.trim() + return p.replace(/^([A-Za-z]):[\\/]/, (_, d) => `/${d.toLowerCase()}/`).replace(/[\\/]+/g, '/') +} : (p) => p +const unpack = spawnSync('bash', ['-c', 'tar -xzf "$1" -C "$2"', 'pack-extract', toUnix(artifact), toUnix(extract)], { encoding: 'utf8' }) +// 清理必须在失败路径上也可达:模块顶层抛错时 test.after 根本来不及注册, +// 而 dist/ 未被 gitignore——留下未跟踪产物就违反本文件「不给仓库留未跟踪文件」的自订契约。 +const cleanup = () => { rmSync(join(PLUGIN_ROOT, 'dist'), { recursive: true, force: true }) rmSync(extract, { recursive: true, force: true }) -}) +} +// 解包失败必须当场响亮:旧写法忽略 rc,症状漂到下游用例变成「package.json ENOENT」,排查方向全错。 +if (unpack.status !== 0) { + cleanup() + throw new Error(`pack 解包失败 rc=${unpack.status}: ${(unpack.stderr || unpack.stdout || '').trim() || unpack.error?.message || 'unknown'}`) +} +const packedRoot = join(extract, 'shadow-dev-workflow') + +test.after(cleanup) test('pack: 产物存在且与 package.json 版本一致', () => { assert.equal(existsSync(artifact), true, `missing artifact: ${artifact}`)