diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 91f1f04..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 — 4-phase pipeline (propose → apply → review → ship) with packaged planning, TDD, verification, and knowledge-driven design constraints. Zero external workflow dependencies.", - "version": "6.5.1", + "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.8.0", "author": { "name": "Shadow (stack-wuh)" }, diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 09f422a..f065f2a 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "shadow-dev-workflow", - "version": "6.5.1", - "description": "Self-contained development workflow with 3-tier knowledge base — propose → apply → review → release → archive with packaged planning, TDD, verification, and knowledge-driven design constraints. Zero external workflow dependencies.", + "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)", "url": "https://github.com/stack-wuh" @@ -13,7 +13,7 @@ "workflow", "skills", "knowledge-base", - "tdd", + "domain-driven", "planning", "code-review", "release", @@ -24,7 +24,7 @@ "interface": { "displayName": "Shadow Dev Workflow", "shortDescription": "Knowledge 驱动的开发工作流五段式管线", - "longDescription": "以 active Knowledge 驱动的 propose → apply → review → release → archive 工作流:需求对齐产出 brief、按 brief 执行 TDD 与依赖调度、质量门禁验收、知识闭环后提交发布 PR、合并后归档并重建 INDEX。另含 hotfix 快车道、知识库查询与 UI 设计提案伴侣。", + "longDescription": "以 active Knowledge 驱动的 propose → apply → review → release → archive 工作流:需求对齐做领域对齐(限界上下文与不变量)并产出 brief、按 brief 以不变量观察点执行分级验证与依赖调度、质量门禁验收模型一致性、知识闭环后提交发布 PR、合并后归档并重建 INDEX。另含 hotfix 快车道、知识库查询与 UI 设计提案伴侣。", "developerName": "Shadow (stack-wuh)", "category": "Coding", "capabilities": ["Read", "Write"], diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..57ff51d --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,107 @@ +name: publish-release + +# 版本与产物的唯一权威发布点:GitHub Release tag 说了算。 +# tag ≠ package.json.version 直接判红(历史上「发版漏同步」出血两次),产物资产名必须匹配 CLI 侧解析正则, +# 上传后立刻用真实 CLI 在隔离 prefix 里走完 plan → execute → bind,证明「传上去的东西真能被装上」。 +on: + release: + types: [published] + +permissions: + contents: write + +concurrency: + group: publish-release-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + publish: + name: publish ${{ github.ref_name }} + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout the tagged tree + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: 版本权威判定(release tag == package.json.version) + shell: bash + run: | + TAG="${GITHUB_REF_NAME#v}" + PKG=$(node -pe 'require("./package.json").version') + if [ "$TAG" != "$PKG" ]; then + echo "✗ release tag v$TAG 与 package.json.version $PKG 不一致——版本权威即失效" + echo " 修正:node scripts/bump-version.mjs $TAG && npm run check:version,重新提交后再打 tag 发布" + exit 1 + fi + echo "✓ 版本权威成立:v$TAG" + npm run check:version + + - name: Install pinned shadow-dev CLI + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + 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) + shell: bash + run: | + npm run check:requires:strict + npm run check:knowledge -- --forbid 'tdd-verification,▶ [TDD]' + npm test + + - name: Pack artifact + shell: bash + run: | + ART=$(node scripts/pack.mjs | tail -1) + echo "artifact=$ART" + echo "artifact=$ART" >> "$GITHUB_OUTPUT" + echo "basename=$(basename "$ART")" >> "$GITHUB_OUTPUT" + id: pack + + - name: 资产契约断言(CLI 只认 shadow-dev-workflow-v.tar.gz) + shell: bash + env: + BASENAME: ${{ steps.pack.outputs.basename }} + run: | + node -e 'const n=process.env.BASENAME||"";const C=/^shadow-dev-workflow-v[0-9][0-9.]*\.tar\.gz$/;if(!C.test(n)){console.error("x asset name ["+n+"] violates CLI contract "+C);process.exit(1)}console.log("OK asset contract: "+n)' + + - name: Upload asset to release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + ARTIFACT: ${{ steps.pack.outputs.artifact }} + run: gh release upload "$GITHUB_REF_NAME" "$ARTIFACT" --clobber + + - name: 发布冒烟(隔离 prefix:plan --release → execute → bind plan) + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + SMOKE="$RUNNER_TEMP/publish-smoke" + rm -rf "$SMOKE"; mkdir -p "$SMOKE/prefix" "$SMOKE/home" + export SHADOW_WORKFLOW_PREFIX="$SMOKE/prefix" SHADOW_WORKFLOW_HOME="$SMOKE/home" + shadow-dev workflow plan --release "$GITHUB_REF_NAME" --json | grep '^{"ok"' | tail -1 > "$SMOKE/plan.json" + node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));if(!d.ok){console.error("plan failed: "+JSON.stringify(d.error));process.exit(1)}console.log("OK plan version="+d.data.version+" missing="+JSON.stringify(d.data.missingCommands))' "$SMOKE/plan.json" + HASH=$(node -pe 'JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).planHash' "$SMOKE/plan.json") + shadow-dev workflow execute --release "$GITHUB_REF_NAME" --plan-hash "$HASH" --confirm --json | grep '^{"ok"' | tail -1 > "$SMOKE/exec.json" + node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));if(!d.ok){console.error("execute failed: "+JSON.stringify(d.error));process.exit(1)}console.log("OK execute current="+d.data.current)' "$SMOKE/exec.json" + CUR=$(node -pe 'JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).data.current' "$SMOKE/exec.json") + ROOT="$SMOKE/prefix/$CUR/shadow-dev-workflow" + test -f "$ROOT/norms/verification.md" || { echo "x materialized artifact missing norms/verification.md"; exit 1; } + N=$(find "$ROOT/skills" -name SKILL.md | wc -l | tr -d ' ') + [ "$N" = "8" ] || { echo "x materialized skills count=$N expected 8"; exit 1; } + echo "OK materialized root=$ROOT skills=$N" + shadow-dev bind plan --host codex --prefix "$SHADOW_WORKFLOW_PREFIX" --json | grep '^{"ok"' | tail -1 > "$SMOKE/bind.json" + node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));if(!d.ok){console.error("bind plan failed: "+JSON.stringify(d.error));process.exit(1)}const x=d.data;console.log("OK bind plan host-version="+x.version)' "$SMOKE/bind.json" diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000..cf4ee12 --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,59 @@ +name: quality-gate + +# 机器门:本仓所有可离线判定的断言都在这儿跑,AI 与人的口头自证不可替代它。 +# 覆盖:版本同源 → 能力一致性(strict,禁止半绿)→ 知识治理(卡片/source/路由/死链/残留)→ 产物与安装器测试。 +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: quality-gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + gate: + name: gate (${{ matrix.os }} / node ${{ matrix.node }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node: ['20', '22'] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: Install pinned shadow-dev CLI + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + 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: 版本同源门 + run: npm run check:version + + - name: 能力一致性门(strict:命令目录不可解析即判红,禁止降级半绿) + shell: bash + run: npm run check:requires:strict + + - name: 知识治理门(卡片字段 / source / menu 路由 / 死链 / 废弃残留) + shell: bash + run: npm run check:knowledge -- --forbid 'tdd-verification,▶ [TDD]' + + - name: 产物与安装器测试 + run: npm test diff --git a/CLAUDE.md b/CLAUDE.md index 91a5382..e5db8da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,7 @@ - `skills/`:定义 propose、apply、review、release、archive 和 Knowledge 查询流程。 - `norms/`:跨项目硬规则与工程规范。 - `knowledge/`:跨项目经验与协作知识。 +- 项目 `shadow-docs/domain.md`:限界上下文地图与统一语言(格式见 `norms/domain-model.md`)。 - 项目 `shadow-docs/knowledge/`:项目独有的执行真相。 - `menu.md` 与项目 `shadow-docs/menu.md`:任务到规范和 active Knowledge 的路由。 - `brief.md` 与 INDEX:变更记录和历史索引,不覆盖 active Knowledge。 @@ -16,9 +17,9 @@ | 位置 | 内容 | |------|------| | `rules/behavior.md` | 行为准则 | -| `rules/iron-laws.md` | TDD、验证、调试、分支和 deterministic CLI 铁律 | +| `rules/iron-laws.md` | 验证、调试、分支、deterministic CLI 和领域模型铁律 | | `norms/knowledge-cards.md` | Knowledge 卡片格式、生命周期和冲突处理 | -| `norms/*.md` | UI、API、交互、代码风格和验证规范 | +| `norms/*.md` | UI、API、交互、代码风格、领域模型和验证规范 | | `knowledge/*.md` | 跨项目经验与协作知识 | 项目外 memory 不是工作流执行真相源。 @@ -45,6 +46,19 @@ | Knowledge 查询 | `shadow-dev-knowledge`(propose 自动调用) | | 线上急修、紧急修复、hotfix | `shadow-dev-hotfix`(快车道,准入判定见其 SKILL.md) | +## 机器门(scripts/ 是执法层) + +规范文本里的「不可议」必须由非零退出的脚本兜住,AI 的本机口头自证不可替代 CI。四门一体入口 `npm run ci`: + +| 门 | 命令 | 要点 | +|----|------|------| +| 版本同源 | `npm run check:version` | `package.json` == `.claude-plugin` == `.codex-plugin` == release tag 去 `v` | +| 能力一致性 | `npm run check:requires:strict` | CI 必须 strict:解析不到 CLI 命令目录即判红,禁止「未参与断言」半绿 | +| 知识治理 | `npm run check:knowledge` | 卡片字段/status/source(含归档兜底)/menu 路由/死链/废弃残留/孤儿规范/技能 frontmatter | +| 产物与安装器 | `npm test` | pack 内容契约与离线安装器断言,矩阵覆盖 windows | + +CI 两条流水:`quality-gate.yml`(push main + 全部 PR)、`publish-release.yml`(`release.published` → 版本权威判定 → 四门 → pack → 资产名契约断言 → 上传 → 隔离 prefix 发布冒烟)。**版本权威=release tag**,不做 CI 事后回写修正。 + ## Deterministic CLI brief JSON frontmatter 是变更状态唯一真相。brief、INDEX、Git 与 GitHub 写操作统一通过 `shadow-dev`,写操作必须确认并校验 planHash。禁止原始 Git/GitHub 写命令、全量暂存、`--no-verify` 和自动冲突解决。 diff --git a/README.md b/README.md index 8e8c00b..be6f3bb 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ norms/ # 跨项目硬规则与工程规范 ├── api-design.md ├── interaction.md ├── code-style.md -└── tdd-verification.md +├── domain-model.md +└── verification.md knowledge/ # 跨项目经验与协作知识 menu.md # 任务到规范和 Knowledge 的路由 @@ -60,9 +61,9 @@ shadow-docs/ propose → apply → review → release → archive ``` -- **propose:** 按 menu 读取和引用 active Knowledge,记录预期知识影响。 -- **apply:** 按引用约束执行;冲突时暂停并查明原因。 -- **review:** 验证实现、约束和卡片检查方法,给出最终知识动作。 +- **propose:** 按 menu 读取和引用 active Knowledge,做领域对齐(限界上下文 / 不变量),记录预期知识影响。 +- **apply:** 按引用约束执行,以 brief 的不变量为验收对象逐条留观察点;冲突时暂停并查明原因。 +- **review:** 验证实现、约束、模型一致性和卡片检查方法,给出最终知识动作。 - **release:** 新增、原位更新、废弃卡片或记录无需变更,然后提交发布 PR。 - **archive:** PR merged 后将 brief 移到 archive/ 并重建 INDEX;也可由 GitHub Actions 在 issue close 时自动触发。 @@ -142,7 +143,31 @@ shadow-dev bind plan --host auto && shadow-dev bind execute --host <名> --plan- 产物与 adapters 契约: -- release 产物 `shadow-dev-workflow-v.tar.gz` 由 `scripts/pack.mjs` 打包,解包为 `shadow-dev-workflow/`,只含运行必需集(marketplace.json / package.json / README / menu.md / skills / hooks / rules / knowledge / norms / docs / scripts)。 +## 发版(Release 控制版本号) + +版本权威是 **GitHub Release tag**:tag 名去 `v` 必须与 `package.json.version` 及 `.claude-plugin`、`.codex-plugin` 三处同源,不一致时 `publish-release` 工作流直接判红。 + +```bash +node scripts/bump-version.mjs 6.7.0 # 一次同步三处 manifest(也可 --check 只判定) +npm run ci # 版本 + requires:strict + 知识治理 + 测试,四门一体 +git tag v6.7.0 && git push origin v6.7.0 +gh release create v6.7.0 --generate-notes # 之后由 publish-release.yml 接手 +``` + +`publish-release.yml`(`on: release.published`)依次执行:版本权威判定 → 装 pin CLI → 四门 → `scripts/pack.mjs` → **资产名契约断言**(CLI 只认 `^shadow-dev-workflow-v[0-9][0-9.]*\.tar\.gz$`,名字写错等于没发布)→ `gh release upload --clobber` → **发布冒烟**:隔离 prefix 里用真实 CLI 跑 `workflow plan --release → workflow execute → bind plan`,核对物化的 8 个 skills 与规范文件。产物永远不被手工搬运,人只负责 bump 与打 tag。 + +## 机器门(GitHub Actions) + +| 门 | 命令 | 判什么 | CI | +|----|------|--------|-----| +| 版本同源 | `npm run check:version` | `package.json` 与两份宿主清单版本一致 | quality-gate + publish-release | +| 能力一致性 | `npm run check:requires` / `:strict` | `skills 引用 ⊆ requiresCommands ⊆ CLI 命令目录` | CI 用 **strict**:解析不到命令目录即判红,禁止「未参与断言」的半绿 | +| 知识治理 | `npm run check:knowledge` | 卡片字段与 status、source 存在性(含归档兜底)、active 卡 menu 路由、引用死链、废弃 token 残留(`--forbid`)、孤儿规范、技能 `name/description` 一致性 | quality-gate | +| 产物与安装器 | `npm test` | pack 产物内容契约、三清单版本机检、hook wrapper 装配、离线安装 | quality-gate(ubuntu/macos/windows × node 20/22) | + +`quality-gate.yml` 在 push main 与每个 PR 上跑全部四门。它存在的原因很实在:这三类门此前只能靠执行者本机口头自证,而半绿的门比没有门更危险。 + +- release 产物 `shadow-dev-workflow-v.tar.gz` 由 `scripts/pack.mjs` 打包(发版链见上文「发版」),解包为 `shadow-dev-workflow/`,只含运行必需集(marketplace.json / package.json / README / menu.md / skills / hooks / rules / knowledge / norms / docs / scripts)。 - `adapters/.json`(schema `shadow-dev-adapter/v1`)声明宿主的 skills 发现目录、复制策略、托管标记与 hook 支持位;**新增宿主 = 新增描述符,CLI 零改动**。 ## 依赖 diff --git a/knowledge/bug-investigation.md b/knowledge/bug-investigation.md index 6133f60..21a35b2 100644 --- a/knowledge/bug-investigation.md +++ b/knowledge/bug-investigation.md @@ -31,4 +31,5 @@ verified: 2026-08-08 ## 关联知识 -- [TDD 与验证](../norms/tdd-verification.md) +- [验证策略与复杂度评级](../norms/verification.md) +- [领域模型是工作流的规格载体](domain-driven-shadow-dev.md) diff --git a/knowledge/distribution-capability-contract.md b/knowledge/distribution-capability-contract.md index 0ca8944..b2b8dc2 100644 --- a/knowledge/distribution-capability-contract.md +++ b/knowledge/distribution-capability-contract.md @@ -6,7 +6,8 @@ scope: [cross-project] status: active source: - changes/20261009-build-artifact-capability-gate/brief.md -verified: 2026-10-09 + - changes/20261010-build-quality-gate/brief.md +verified: 2026-10-10 verified-depth: runtime verified-scope: `npm run check:requires` 两级差集为空;把声明里删掉一键的门负例(exit 1 且点名该键);越权 fixture 被 v1.5.0 以 `ARTIFACT_INCOMPATIBLE` 拒绝且指针不动 --- @@ -27,6 +28,7 @@ verified-scope: `npm run check:requires` 两级差集为空;把声明里删掉 - pin 与能力声明**必须共存且互相校验**:改 pin 之后必须跑一致性门(本项目 `npm run check:requires`:`skills 引用 ⊆ requiresCommands ⊆ 已装 CLI 命令目录`),两级差集非空即判红。禁止只靠人记忆同步。 - 声明集必须**恰好覆盖内容包真正会跑的命令**:内容里出现即要声明,包括恢复动作(如「升级 CLI」用的 `workflow plan/execute`)——它们是同一份指令要执行的命令。 - 内容层遇到命令缺失**只能阻塞,不能降级**:禁止「worktree 不可用就改走 branch」这类静默兜底,它把版本错位转成更难发现的长期偏差。 +- 同一条纪律套在**自己的机器门**上:一致性门解析不到执行层命令目录时必须判红(`check:requires:strict`),禁止以「CLI 目录未参与断言」的半绿放行。非交互环境里 human 帮助层会与 JSON 同流写 stdout,命令目录必须取最后一个 `{"ok"` 契约行并带超时——否则门要么假红要么假绿(本仓实测:`check:requires` 非 strict 在 CLI 缺失时 rc=0 半绿,旧解析法在非 TTY 下恒 unresolved 假红)。 - 观测面恒不抛:`workflow status` 回 `artifactVersion / cliVersion / missingCommands`,引导页/巡检读一个字段即可判断「内容比工具新」。 - 跨仓演进顺序:消费方先行兼容(执行层发布支持断言的版本),生产方随后声明;反序会留下无人兜底的窗口。 - 宿主差异只进描述符(`adapters/.json`),插件/hook 轨与 bind 轨可并存(见 [多宿主插件分发](multi-host-plugin-distribution.md)),但两条轨都不得自行安装或选择 CLI 版本以外的东西。 @@ -41,6 +43,8 @@ verified-scope: `npm run check:requires` 两级差集为空;把声明里删掉 2. **门负例**:把声明里任意一键删掉后重跑,必须退出码 1 并点名该键(证明门真的在判,不是摆设)。 3. `workflow plan --from <本仓>` → `ok:true` 且 `missingCommands=[]`;构造声明了不存在命令键的 fixture → `workflow execute`/`link` 报 `ARTIFACT_INCOMPATIBLE` 且 `CURRENT`/`PREVIOUS`/`LINK` 不变。 4. 缺省兼容:无 `requiresCommands` 的历史包仍能物化切指针。 +5. 发布冒烟:release 资产上传后在隔离 prefix 用真实 CLI 走 `workflow plan --release → workflow execute → bind plan`,断言物化内容(见 `release-artifact-pipeline.md`)。 +6. 半绿负例:`SHADOW_DEV_CLI=<坏路径> npm run check:requires:strict` 退出码 1;去掉 `:strict` 同条件退出码 0 且自曝「CLI 目录未参与断言」。 ## 关联知识 diff --git a/knowledge/domain-driven-shadow-dev.md b/knowledge/domain-driven-shadow-dev.md new file mode 100644 index 0000000..4b701f8 --- /dev/null +++ b/knowledge/domain-driven-shadow-dev.md @@ -0,0 +1,45 @@ +--- +title: 领域模型是工作流的规格载体 +domain: workflow-governance +keywords: [领域驱动, DDD, 限界上下文, 统一语言, 聚合, 不变量, 观察点, 规格来源, TDD 替代, 验证门禁, 复杂度评级] +scope: [norms/domain-model.md, norms/verification.md, skills/shadow-dev-propose, skills/shadow-dev-apply, skills/shadow-dev-review] +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: 本仓已落地 `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 证据补齐后再升深度。 +--- + +# 领域模型是工作流的规格载体 + +## 当前结论 + +分级验证制取消「先写失败测试」的默认仪式之后,本工作流的**规格来源是领域模型与领域不变量**,不是测试。测试降级为证据手段之一:既有测试必须保持绿,新测试只在需要长期钉住某条不变量时才写。撤掉 TDD 不等于撤掉规格——缺了规格载体,验证会退化成 diff 走查。 + +## 执行约束 + +- L 级变更的 brief 必含「## 领域模型」段:限界上下文、统一语言增量、非空不变量清单、领域事件;格式与必填规则见 `norms/domain-model.md`。 +- 验收对象是不变量:每条不变量绑定一个可追溯观察点,观察点强度按 `norms/verification.md` 的级别要求;「验证强度与评级匹配不可议」这条不因去掉 TDD 而松动。 +- 一个 change 一个主限界上下文;跨上下文连带改动另立 change 并在 brief 写明前后依赖。 +- 新公开名词(命令键、字段、卡片标题、术语)必须登记在 brief 术语增量或项目 `shadow-docs/domain.md`,否则 review 按模型漂移阻塞。 +- 不为建模做全仓普查;不把 DDD 战术模式(Entity / VO / Repository / CQRS)当硬要求。 + +## 适用边界 + +适用于由 AI 代理执行、且有可路由知识库的项目——规格需要写成能被复述和检查的断言。不适用于探索性原型与一次性脚本:那里不变量尚未稳定,强行建模等于把猜测写成规范。 + +## 验证方式 + +1. L 级 brief 存在「## 领域模型」段且不变量清单非空。 +2. brief「结果」段中每条不变量有对应观察点,或明确标注「未验证」与证据缺口。 +3. 死链扫描不得发现指向旧验证规范文件名的引用;进度 token 不得残留 TDD 时代的 red\|green 形态(现行 verify 的 pass\|fail)。 +4. 知识卡 `domain:` 值与项目上下文地图登记的上下文一致。 + +## 关联知识 + +- [领域模型规范](../norms/domain-model.md) +- [验证策略与复杂度评级](../norms/verification.md) +- [Knowledge 约束卡片规范](../norms/knowledge-cards.md) +- [Bug 调查保持单一上下文](bug-investigation.md) 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 new file mode 100644 index 0000000..2ff7499 --- /dev/null +++ b/knowledge/release-artifact-pipeline.md @@ -0,0 +1,49 @@ +--- +title: 发布链版本权威与产物资产契约 +domain: distribution +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 链接。新增的 `$GITHUB_PATH` 同步内不可见一条:失败侧已由 PR #44 的 `quality-gate` windows 两项 `exit 127` 实证(同 run ubuntu/macos 绿),修复生效待本 PR 转绿后的 run 链接回填。 +--- + +# 发布链版本权威与产物资产契约 + +## 当前结论 + +GitHub Release 是产物与版本的**唯一权威发布点**,四件事缺一不可: + +1. **版本权威=release tag**:tag 名去 `v` 后必须与 `package.json.version` 及两份宿主清单 version 同源,不一致即发布失败。一致性由「先 bump 再打 tag」保证,**不由 CI 事后回写修正**——回写会让产物与 tag 指向的 tree 分裂,等于把权威降级成补丁。 +2. **资产契约**:release 资产名必须匹配消费侧硬正则 `^shadow-dev-workflow-v[0-9][0-9.]*\.tar\.gz$`(CLI `workflow plan --release/--release latest` 靠它找资产)。名字写错等于没发布——`RELEASE_NOT_FOUND`。 +3. **发布冒烟**:上传后立即在隔离 prefix 用真实 CLI 走 `workflow plan --release` → `workflow execute` → `bind plan`,断言物化内容(8 个 `SKILL.md`、规范文件在位)。不冒烟就无法区分「可消费的产物」与「一个没人能装的文件」。 +4. **门禁止半绿**:被依赖资源解析不到时必须判红。非交互环境(CI、管道)里 CLI 的 human 帮助层会与 JSON **同流写 stdout**,所以命令目录必须取最后一个 `{"ok"` 契约行并带超时,否则门会假红或假绿。 + +## 执行约束 + +- 发版顺序恒为:`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 张如此)。 + +## 适用边界 + +适用于「内容/规范包 + 独立执行层 CLI」的双仓分发。纯库发布(npm registry、镜像)不适用资产名正则,但**版本权威与发布冒烟同样成立**。 + +## 验证方式 + +1. `npm run ci` 本机与 CI 均全绿(含 strict 与知识治理门)。 +2. strict 反例:`SHADOW_DEV_CLI=/path/to/broken.mjs npm run check:requires:strict` → rc=1;去掉 `:strict` 同条件 → rc=0 且输出「CLI 目录未参与断言」(半绿证据)。 +3. 版本反例:单独改任一份 manifest 的版本 → `npm run check:version` rc=1 并提示修正命令。 +4. 发布后:`publish-release` run 的冒烟步骤须输出 `OK materialized root=... skills=8`。 + +## 关联知识 + +- [分发依赖单向与产物能力契约](distribution-capability-contract.md) +- [插件多宿主分发与清单契约](multi-host-plugin-distribution.md) +- [领域模型是工作流的规格载体](domain-driven-shadow-dev.md) diff --git a/menu.md b/menu.md index d9d8e6e..c7fd158 100644 --- a/menu.md +++ b/menu.md @@ -7,7 +7,8 @@ | 技术域 | 关键词 | 应查阅 | |--------|--------|--------| | Knowledge 治理 | knowledge 知识库 沉淀 卡片 路由 过期 冲突 verified 深度 | norms/knowledge-cards.md | -| 验证策略 | TDD 测试 复杂度评级 S/M/L 验证深度 评级矩阵 | norms/tdd-verification.md | +| 领域建模 | 领域驱动 DDD 限界上下文 统一语言 聚合 不变量 观察点 上下文地图 领域模型 规格来源 | norms/domain-model.md, knowledge/domain-driven-shadow-dev.md | +| 验证策略 | 测试 复杂度评级 S/M/L 验证深度 评级矩阵 不变量观察点 验证强度 | norms/verification.md | | 信号机制 | 信号 signals 探索 优先级 权重 衰减 负信号 | norms/signals.md | | UI 变更 | 页面 组件 布局 样式 配色 字体 按钮 卡片 弹窗 导航 列表 | norms/ui-patterns.md, norms/interaction.md, 项目相关 active Knowledge | | API 变更 | 接口 端点 API 路由 REST 查询 请求体 响应体 | norms/api-design.md, 项目相关 active Knowledge | @@ -15,9 +16,10 @@ | 代码变更 | 代码 风格 重构 命名 类型 import 依赖 包边界 | norms/code-style.md, 按 scope 追加 norms/code-style-frontend.md、norms/code-style-backend.md 或 norms/code-style-packages.md | | 数据库变更 | 数据库 MongoDB Mongoose Schema Model 字段 索引 迁移 回填 同步 seed | norms/code-style-database.md, 项目数据模型 active Knowledge | | 性能优化 | 首屏 加载 缓存 LCP CLS SSR ISR | norms/code-style.md, 项目性能 active Knowledge | +| 发布与质量门 | 发布 release tag 版本权威 资产名 tarball pack 上传 发布冒烟 quality-gate publish-release CI strict 半绿 假绿 机器门 死链 孤儿规范 | knowledge/release-artifact-pipeline.md | | 插件多宿主分发 | Codex Claude Code zcode 宿主 插件清单 marketplace hooks adapters 打包 分发 技能加载 命令 not found | knowledge/multi-host-plugin-distribution.md | | 分发一致性与能力声明 | pin cliVersion requiresCommands 能力声明 一致性门 check:requires ARTIFACT_INCOMPATIBLE missingCommands 静默降级 响亮阻塞 版本错位 命令 not found | knowledge/distribution-capability-contract.md | -| Bug 修复 | 报错 崩溃 异常 不对 显示不正常 | norms/tdd-verification.md, knowledge/bug-investigation.md, 项目相关 active Knowledge | +| Bug 修复 | 报错 崩溃 异常 不对 显示不正常 | norms/verification.md, knowledge/bug-investigation.md, 项目相关 active Knowledge | | 交互/动画 | 动效 过渡 动画 手势 滚动 | norms/interaction.md, 项目动效 active Knowledge | | 无障碍 | a11y aria 对比度 屏幕阅读 焦点 键盘 | norms/interaction.md, norms/ui-patterns.md | @@ -30,3 +32,4 @@ 5. deprecated 卡片仅在追溯替代关系时读取。 6. 将适用卡片路径、当前结论和 scope 记录到 brief 的「引用规范」。 7. 每条约束必须遵循,或在「决策」中说明为什么不适用。 +8. 项目存在 `shadow-docs/domain.md` 时先读上下文地图,确定任务所属限界上下文再路由;L 级变更必须在 brief 写「## 领域模型」段(`norms/domain-model.md`)。 diff --git a/norms/domain-model.md b/norms/domain-model.md new file mode 100644 index 0000000..ec0e560 --- /dev/null +++ b/norms/domain-model.md @@ -0,0 +1,81 @@ +# 领域模型(Domain Model) + +> 规格来自领域模型,不来自测试。测试是证据手段之一;**不变量才是验收对象**。 +> 与 `norms/verification.md` 各管验证的一侧:本文件定义「验什么」,`verification.md` 定义「验到什么强度」。 + +## 为什么 + +分级验证制撤掉「先写失败测试」的仪式,但没有撤掉「完成必须有证据」。缺的是规格来源:brief 只有任务清单时,验收退化成 diff 走查,谁也说不清「改坏了什么算坏」。领域模型把这一步补上——把必须成立的事实写成不变量,每条不变量绑定一个可追溯观察点。 + +只取 DDD 的四件概念工具(限界上下文、统一语言、聚合、不变量),**不引入** Entity / Value Object / Repository / CQRS 等战术模式,也不为建模而建模。 + +## 术语(统一语言,全生态唯一定义处) + +| 术语 | 定义 | 判据 | +|------|------|------| +| 限界上下文 bounded context | 一套词汇只有一个含义的边界;跨边界协作必须写明依赖方向与反腐层 | 同一个词在两处含义不同 → 是两个上下文 | +| 上下文地图 context map | 项目内上下文清单与上下游关系,落在 `shadow-docs/domain.md` | 新上下文未登记即视为模型漂移 | +| 聚合 aggregate | 必须整体保持一致的概念单元,有唯一聚合根 | 「改了一半就坏」的东西就是一个聚合 | +| 领域不变量 invariant | 任何时刻都必须成立的断言 | 能用一句「X 必须 Y」表达,且可被观察 | +| 观察点 observation point | 证明不变量成立的真实证据(命令 + 输出 / CI 链接 / 产物扫描结果) | 换个人能照着复现 | +| 领域事件 domain event | 上下文中改变状态、进而改变流程判断的时刻 | 值不值得写进门禁 | + +## brief 的「## 领域模型」段 + +位置:`复杂度评级` 之后、`引用规范` 之前。必填规则: + +| 评级 | 要求 | +|------|------| +| **L** | 必填:限界上下文 + 统一语言增量 + **非空**聚合与不变量清单 + 领域事件 | +| **M** | 必填「## 领域模型」段的**最小形态**:一行 `限界上下文`(含上下游)+ 被触及的既有不变量清单(确无则写「无」);统一语言增量与领域事件可省 | +| **S** | 免填 | + +```markdown +## 领域模型 + +- **限界上下文:** <名称> — <一句话职责>。上游/下游 <关系>;反腐边界 <本上下文不做什么>。 +- **统一语言增量:** <新术语及其在本上下文内的唯一含义>(无则写「无」) +- **聚合与不变量:** + - `<聚合根>`:<必须成立的断言 1>;<必须成立的断言 2> +- **领域事件:** <状态改变时刻及其流程后果> +``` + +不变量必须是**可观察的断言**,不是目标或愿望。反例「代码结构清晰」;正例「skills 引用的规范文件路径必须存在,死链 grep 归零」。 + +## 建模动作(propose 的「领域对齐」轮) + +1. 这个变更属于哪个上下文?是否同时踩进第二个上下文(是 → 考虑拆 change)。 +2. 有没有新词?该词在本上下文里是否已有含义? +3. 哪个聚合的哪条不变量被这次改动威胁?它现在靠什么观察点守住? +4. 是否要求别的上下文跟着变?那就是**跨上下文依赖**,写进 brief 的前置/后继,不顺手改。 + +## 项目侧上下文地图 + +每项目一份 `shadow-docs/domain.md`。存在则 propose 必读;不存在且项目含多个上下文时用本模板建: + +```markdown +# <项目> 上下文地图 + +| 限界上下文 | 职责 | 上游/下游 | 主目录或模块 | 关键术语 | +|---|---|---|---|---| +| content | 文章与同步 | ← github → api-v2 | apps/server/src/modules/content | 文章 / 编号 / 同步水位 | + +## 术语表增量 +- <词>: <本项目内的唯一含义> +``` + +## 越界禁止 + +- 不在实现中途新增上下文或改写归属;需要就回 propose 修订 brief。 +- 不借「领域建模」扩大改动面:模型只描述本次变更触碰的不变量,不做全仓领域普查。 +- 不把实现过程写进上下文地图——地图是长期事实,过程留在 brief。 +- 不把横切实践(如 `debugging`)当限界上下文扩写;地图只登记归属与关系,概念定义仍由本文件唯一持有。 +- 卡片 `domain` 取值必须来自地图登记名(**domain 值域**):禁止同义词、中英别名与临时自造——别名会把一个上下文切成两个查询域,路由与查重同时失效。该约束由 `scripts/check-knowledge.mjs` 判定。 +- 一个 change 一个主上下文;跨上下文连带改动各自立项。 + +## 验证方式 + +- brief 的「## 领域模型」段存在且必填项齐全(L 级不变量清单非空)。 +- 每条不变量对应到 `norms/verification.md` 口径下的一个观察点,或在 review 明确记为「未验证」。 +- `shadow-docs/domain.md` 登记的上下文与卡片 `scope` 一致;知识卡 `domain:` 值能在地图中找到。 +- 实现引入的公开名词(命令、字段、卡片标题)已登记进 brief 术语增量或上下文地图。 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/norms/signals.md b/norms/signals.md index 623b58e..63c9668 100644 --- a/norms/signals.md +++ b/norms/signals.md @@ -46,5 +46,5 @@ apply 进入「分析依赖 / 探索」阶段时读取 `shadow-docs/signals.md` ## 与其他规范的关系 - 深度分级沿用 `norms/knowledge-cards.md` 的 verified-depth。 -- 扇宽由 brief 的复杂度评级决定(`norms/tdd-verification.md`)。 +- 扇宽由 brief 的复杂度评级决定(`norms/verification.md`)。 - 信号不进 `menu.md` 路由表(路由表只管规范与知识卡;signals.md 由 apply/review 直接读取)。 diff --git a/norms/tdd-verification.md b/norms/tdd-verification.md deleted file mode 100644 index efda577..0000000 --- a/norms/tdd-verification.md +++ /dev/null @@ -1,77 +0,0 @@ -# 验证策略与复杂度评级 - -> 分级非协商:**评级可议,验证强度与评级匹配不可议**。违反匹配即阻断。 -> (20260925-feature-validation-strategy-signals 起由「TDD 一律非协商」修订为分级制,修订经用户批准;修订动机与实证见该 brief。) - -## 复杂度评级(propose 阶段 AI 提案,用户在 propose 确认时批准) - -评级跟**风险**走,不跟预计工作量走。三要素: - -1. **契约变更**:是否改变行为契约(协议 / schema / API / 权限面 / 数据格式)? -2. **触及面**:碰宿主核心或共享模块,还是纯资产 / 文案 / 孤立函数? -3. **可发现性**:改坏了立刻可见,还是要等特定运行时路径才炸? - -| 级 | 判定 | 典型例子 | -|----|------|----------| -| **S** | 零行为契约变更 | 文案、注释、归档、纯声明式配置、知识卡更新 | -| **M** | 有行为但局部,不改契约 | 插件资产、独立组件、不改共享函数签名的调整 | -| **L** | 改契约 / 碰宿主核心 / 跨模块数据流 | 协议实现、命令域行为、能力白名单、哈希语义 | - -评级写入 brief 正文「## 复杂度评级」节(`评级: X · 理由: … · 期望验证深度: Y`),随 propose 确认一并批准。 - -## TDD 与测试要求(按级别) - -| 级 | 测试要求 | -|----|----------| -| **S** | 不创建测试;验证 = diff 走查 + 结构/引用扫描 | -| **M** | 默认**不新建测试文件**;冒烟 + 既有测试保持绿 + 走查(见「验证方式裁决」) | -| **L** | 原「完整 TDD(先红后绿)」,2026-10-09 用户裁决后改为**可追溯 runtime 观察点**:拒绝路径与状态不变量必须真实跑过,或 CI 全绿链接 | - -- 评级是 AI 提案、人批准;review 阶段检查「验证强度与评级匹配」:S 级过度测试同样指出(浪费),L 级验证不足直接阻塞。 -- 实现期发现风险升/降级(如纯文档 change 需要改运行时代码)时,先修订 brief 评级再继续,不得静默跨级。 - -## 验证方式裁决(2026-10-09 用户裁决) - -> 裁决原文:「不要写 TDD 了,太浪费时间了。」据此,**先红仪式与「为新 change 新建测试文件」不再是默认要求**;不可议的是另一条——**验证强度与复杂度评级匹配**。达成强度的手段改为: - -| 级 | 现行达成方式 | -|----|--------------| -| S | diff 走查 + 结构/引用/残留扫描 | -| M | 冒烟命令 + 既有测试保持绿 + 走查;触及既有契约时**更新既有断言**(属维护,不算新增测试) | -| L | 可追溯 runtime 观察点:真实命令 + 真实输出,必须覆盖**拒绝路径**与**状态不变量**(指针不动、无半成品残留),或 CI 全绿链接 | - -- **既有测试必须保持绿色**——「不写 TDD」不等于「不跑测试」。 -- 本机取不到证据时(runner 挂死、环境 139 风暴等)必须写明证据缺口与替代观察点(CI run 链接),禁止用「应该没问题」占位。 -- 「无验证输出的完成声明一律无效」「强度与评级不匹配一律退回」两条阻断条款不变;过度测试(S 级写测试)同样 review 阻塞。 -- 进度可见性条款(报数、逐完成一行、卡点先报、收口对账)不变——它治的是静默长跑,与写不写测试无关。 - -## 进度可见性(执行纪律) - -> 动机:静默执行无法察觉卡死——进程空转、外部命令挂起、反复重试都表现为「无输出」,用户无从判断是慢还是死。以下条款非协商,适用于一切 TDD / 绿灯测试与 Phase 任务执行。 - -- **开工先报数**:开始测试与任务执行前,先列出完整清单——测试总数 N(每项一行)与任务总数 M。没有报数就开始,视为未开始。 -- **逐完成一行**:每完成一个单元立即输出进度线,不允许攒批: - - 测试粒度:`▶ [TDD] n/N <测试名> red|green` - - 任务粒度:`▶ [task] m/M done` -- **卡点先报**:单步执行异常时(进程空转、外部命令挂起、同一失败重复出现),先输出 `⏸ [TDD|task] <名称> 卡住:<现象与排查方向>`,再按调试节追根因;禁止静默重试、禁止静默长跑。 -- **收口对账**:结束时输出汇总行(如 `✅ N/N 全绿`,附命令与真实输出摘要);收口数目必须与开工报数一致,不一致 review 阶段阻塞。 - -## 验证 - -- **触发**: 声称 "完成" "通过" "修复" 之前 -- **代码流程**: 运行 Lint + 类型检查 + 相关测试,贴出命令和输出 -- **知识治理流程**: 检查卡片字段与分区、source 存在性、active 路由覆盖、死链接和过期执行依赖 -- **深度声明**: 完成声明的验证深度按上表与 `norms/knowledge-cards.md` 的分级对齐,`runtime` 须附可追溯观察点 -- **阻断**: 无验证输出的完成声明一律无效;验证强度与复杂度评级不匹配一律退回 - -## 调试 - -- **触发**: 任何 Bug、异常、测试失败、意外行为 -- **流程**: 按 `knowledge/bug-investigation.md` 在单一持续上下文中追溯根因后再修 -- **阻断**: 未执行根因分析就改代码的修复,review 阶段直接驳回 - -## 分支 - -- **触发**: 任何代码修改操作 -- **流程**: 先建功能分支再写代码,main 只接受 merge/PR 合入 -- **阻断**: 在 main 上的 commit 直接驳回 diff --git a/norms/verification.md b/norms/verification.md new file mode 100644 index 0000000..fb750f5 --- /dev/null +++ b/norms/verification.md @@ -0,0 +1,69 @@ +# 验证策略与复杂度评级 + +> 分级非协商:**评级可议,验证强度与评级匹配不可议**。违反匹配即阻断。 +> 沿革:20260925 由「TDD 一律非协商」修订为分级制;2026-10-09 用户裁决取消先红仪式与默认新建测试(原文「不要写 TDD 了,太浪费时间了」);20261010 起验收对象明确为**领域不变量**,规格来源见 `norms/domain-model.md`,本文件只规定强度与形态。 + +## 复杂度评级(propose 阶段 AI 提案,用户在 propose 确认时批准) + +评级跟**风险**走,不跟预计工作量走。三要素: + +1. **契约变更**:是否改变行为契约(协议 / schema / API / 权限面 / 数据格式)? +2. **触及面**:碰宿主核心或共享模块,还是纯资产 / 文案 / 孤立函数? +3. **可发现性**:改坏了立刻可见,还是要等特定运行时路径才炸? + +| 级 | 判定 | 典型例子 | +|----|------|----------| +| **S** | 零行为契约变更 | 文案、注释、归档、纯声明式配置、知识卡更新 | +| **M** | 有行为但局部,不改契约 | 插件资产、独立组件、不改共享函数签名的调整 | +| **L** | 改契约 / 碰宿主核心 / 跨模块数据流 | 协议实现、命令域行为、能力白名单、哈希语义 | + +评级写入 brief 正文「## 复杂度评级」节(`评级: X · 理由: … · 期望验证深度: Y`),随 propose 确认一并批准。 + +## 不变量与观察点(按级别) + +验收对象是不变量,不是测试条数。测试只是固定不变量的手段之一。 + +| 级 | 验收对象 | 达成方式 | +|----|----------|----------| +| **S** | 无行为契约变更,验结构与引用 | diff 走查 + 结构 / 引用 / 路由 / 残留扫描 | +| **M** | 本次触及的**既有**不变量保持成立 | 冒烟命令 + 既有测试保持绿 + 走查;触及既有契约时**更新既有断言**(属维护,不算新增测试) | +| **L** | brief「## 领域模型」段列出的**全部**不变量 | 每条不变量一个可追溯观察点:真实命令 + 真实输出,必须覆盖**拒绝路径**与**状态不变量**(指针不动、无半成品残留、旧路径不可回退),或 CI 全绿链接 | + +- **不为新 change 默认新建测试文件。** 需要长期钉住某条不变量时才写,并在 brief 写明这条测试钉的是哪条不变量。 +- **既有测试必须保持绿色**——取消 TDD 不等于取消跑测试。 +- 本机取不到证据时(runner 挂死、环境 139 风暴、沙箱禁网等)必须写明**证据缺口**与**替代观察点**(CI run 链接),禁止用「应该没问题」占位。本仓 `quality-gate.yml`(push main 与全部 PR)与 `publish-release.yml`(发布冒烟)已存在:**它们的 run 链接就是合法 runtime 观察点**,机器门未过一律视为未验证。 +- **过度验证同样阻塞**:S 级写测试、为凑覆盖率补断言、M 级新建测试文件,review 判浪费并退回。 +- 评级与不变量清单同步修订:实现期风险变级或发现新不变量时,先修订 brief 再继续,不得静默跨级。 + +## 进度可见性(执行纪律) + +> 动机:静默执行无法察觉卡死——进程空转、外部命令挂起、反复重试都表现为「无输出」,用户无从判断是慢还是死。以下条款非协商,适用于一切验证执行与 Phase 任务执行。 + +- **开工先报数**:开始执行前列出完整清单——测试总数 N(每项一行)、任务总数 M;L 级另报不变量总数 I。没有报数就开始,视为未开始。 +- **逐完成一行**:每完成一个单元立即输出进度线,不允许攒批: + - 检查项粒度:`▶ [verify] n/N <检查项> pass|fail` + - 不变量粒度(L 级):`▶ [inv] i/I <不变量> observed|pending` + - 任务粒度:`▶ [task] m/M done` +- **卡点先报**:单步执行异常时(进程空转、外部命令挂起、同一失败重复出现),先输出 `⏸ [verify|inv|task] <名称> 卡住:<现象与排查方向>`,再按调试节追根因;禁止静默重试、禁止静默长跑。 +- **收口对账**:结束时输出汇总行(如 `✅ N/N 全绿 · I/I 不变量有观察点`,附命令与真实输出摘要);收口数目必须与开工报数一致,不一致 review 阶段阻塞。 + +## 验证 + +- **触发**: 声称 "完成" "通过" "修复" 之前 +- **代码流程**: 运行 Lint + 类型检查 + 相关测试,贴出命令和输出 +- **知识治理流程**: 由 `scripts/check-knowledge.mjs` 执行(卡片字段与 status 枚举、source 存在性含归档兜底、active 路由覆盖、引用死链、废弃 token 残留、孤儿规范、技能 name/description 一致性);AI 手跑只算提交前预检,不可替代 `npm run check:knowledge` +- **观察点对账**: 逐条不变量给出观察点或其证据缺口;缺观察点的条目在 review 记为「未验证」,不得计入完成 +- **深度声明**: 完成声明的验证深度按上表与 `norms/knowledge-cards.md` 的分级对齐,`runtime` 须附可追溯观察点 +- **阻断**: 无验证输出的完成声明一律无效;验证强度与复杂度评级不匹配一律退回 + +## 调试 + +- **触发**: 任何 Bug、异常、测试失败、意外行为 +- **流程**: 按 `knowledge/bug-investigation.md` 在单一持续上下文中追溯根因后再修 +- **阻断**: 未执行根因分析就改代码的修复,review 阶段直接驳回 + +## 分支 + +- **触发**: 任何代码修改操作 +- **流程**: 先建功能分支再写代码,main 只接受 merge/PR 合入 +- **阻断**: 在 main 上的 commit 直接驳回 diff --git a/package.json b/package.json index 277b9cd..7d63758 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "shadow-dev-workflow", - "version": "6.5.1", + "version": "6.8.0", "type": "module", "engines": { "node": ">=20" }, "scripts": { "test": "node --test test/install-cli.test.mjs test/pack.test.mjs", - "check:requires": "node scripts/check-requires.mjs" + "check:requires": "node scripts/check-requires.mjs", + "check:requires:strict": "node scripts/check-requires.mjs --strict", + "check:knowledge": "node scripts/check-knowledge.mjs", + "check:version": "node scripts/bump-version.mjs --check", + "ci": "npm run check:version && npm run check:requires:strict && npm run check:knowledge && npm test" }, "cliVersion": "v1.5.0", "requiresCommands": [ diff --git a/rules/behavior.md b/rules/behavior.md index 178cc54..922b221 100644 --- a/rules/behavior.md +++ b/rules/behavior.md @@ -27,7 +27,7 @@ - **防什么**:完成形式上的步骤但没解决问题 - **触发**:开始任何非平凡任务前 -- **做什么**:先定义成功标准和验证方法(验证强度按 brief 复杂度评级,见 `norms/tdd-verification.md`;L 级 Bug 修复须先写复现测试);重构前后测试必须通过 +- **做什么**:先定义成功标准和验证方法(验证强度按 brief 复杂度评级,见 `norms/verification.md`;L 级另须先立领域模型——不变量清单就是验收对象,每条留可追溯观察点);重构前后既有测试必须通过 - **验收**:任务结束时展示验证结果,不是展示做了哪些步骤 ## 5. 不要盲目同意 diff --git a/rules/iron-laws.md b/rules/iron-laws.md index 4c894d1..7f2aecc 100644 --- a/rules/iron-laws.md +++ b/rules/iron-laws.md @@ -6,7 +6,7 @@ - **防什么**:实现偏离需求、回归不可检测 - **触发**:任何代码/行为变更 -- **做什么**:按 brief 复杂度评级的验证强度执行(`norms/tdd-verification.md` 分级制):**L**——可追溯 runtime 观察点(真实命令 + 真实输出,覆盖拒绝路径与状态不变量),或 CI 全绿;**M**——冒烟 + 既有测试保持绿 + 走查,默认不新建测试文件;**S**——不创建测试文件,执行结构、引用、残留扫描。(2026-10-09 用户裁决:不写 TDD、不强制先红;强度与评级匹配仍不可议。)评级在 propose 确认时批准;实现期风险升/降级先修订 brief 再继续。 +- **做什么**:按 brief 复杂度评级的验证强度执行(`norms/verification.md` 分级制):**L**——brief「## 领域模型」段每条不变量一个可追溯观察点(真实命令 + 真实输出,覆盖拒绝路径与状态不变量),或 CI 全绿;**M**——冒烟 + 既有测试保持绿 + 走查,默认不新建测试文件;**S**——不创建测试文件,执行结构、引用、残留扫描。(2026-10-09 裁决取消先红仪式与默认新建测试;20261010 起验收对象明确为领域不变量。强度与评级匹配仍不可议。)评级在 propose 确认时批准;实现期风险升/降级先修订 brief 再继续。 - **阻断**:验证强度与评级不符视为未开始;对 S/M 级需求一刀切强制测试同样违规(过度测试 review 阻塞) ## 2. 不要声称完成而未验证 @@ -43,3 +43,10 @@ - **触发**:任何 brief/INDEX 写入、Git 写操作或 GitHub API 写操作 - **做什么**:统一通过 `shadow-dev` 的 plan/execute 命令;execute 必须携带 planHash 和 `--confirm` - **阻断**:禁止原始 `git`/`gh` 写命令、直接修改 brief frontmatter/INDEX、`git add .`、`git add -A`、`--no-verify` + +## 7. 不要在缺少领域模型的情况下开始 L 级实现 + +- **防什么**:取消 TDD 仪式后规格悬空——没有不变量,验收退化成 diff 走查,改坏了没人说得清 +- **触发**:brief 复杂度评级为 L 的实现开始前 +- **做什么**:brief 必含「## 领域模型」段(限界上下文 / 统一语言增量 / **非空**聚合与不变量清单 / 领域事件),格式与必填规则见 `norms/domain-model.md`;每条不变量在「结果」段有一个可追溯观察点,或明确标注证据缺口与替代观察点 +- **阻断**:L 级 brief 缺该段或不变量清单为空即视为未开始;实现期发现新不变量时先修订 brief 再继续,不得静默扩面 diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs new file mode 100644 index 0000000..58e2165 --- /dev/null +++ b/scripts/bump-version.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +// 版本一致性:三处 manifest(package.json + .claude-plugin + .codex-plugin)必须同源。 +// 历史上「发版漏同步」出血两次(35bd029、51a8eef/a06849f),本脚本把它变成机器判定。 +// 用法:node scripts/bump-version.mjs 6.7.0 # 写入三处 +// node scripts/bump-version.mjs --check # 只判定(CI / 发布前 / release tag 校验复用) +import { readFileSync, writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = dirname(dirname(fileURLToPath(import.meta.url))) +const TARGETS = ['package.json', '.claude-plugin/plugin.json', '.codex-plugin/plugin.json'] +const readAll = () => TARGETS.map(p => ({ p, json: JSON.parse(readFileSync(join(root, p), 'utf8')) })) +const check = () => { + const all = readAll(), versions = [...new Set(all.map(x => x.json.version))] + if (versions.length !== 1) { + console.error(`✗ 版本不同源: ${all.map(x => `${x.p}=${x.json.version}`).join(' | ')}`) + console.error(' 修正: node scripts/bump-version.mjs ') + process.exitCode = 1 + return all + } + console.log(`✓ 三处 manifest 版本同源: ${versions[0]}(${all.length} 个文件)`) + return all +} +const arg = process.argv[2] +if (arg === '--check' || arg === undefined) { process.exitCode ||= 0; check(); if (!arg) console.log('(未给版本号,仅判定;写入请用 bump-version.mjs )') } +else { + if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(arg)) { console.error(`✗ 非法版本号「${arg}」,需形如 6.7.0`); process.exit(1) } + const before = check() + for (const { p } of before) { + const raw = readFileSync(join(root, p), 'utf8') + const next = raw.replace(/("version"\s*:\s*")[^"]+(")/, `$1${arg}$2`) + if (next === raw) { console.error(`✗ ${p}: 未找到 version 字段`); process.exit(1) } + JSON.parse(next) // 先验可解析再落盘 + writeFileSync(join(root, p), next) + console.log(` · ${p} → ${arg}`) + } + const after = check() + console.log(`✓ 版本 bump 完成: ${after[0].json.version}`) +} diff --git a/scripts/check-knowledge.mjs b/scripts/check-knowledge.mjs new file mode 100644 index 0000000..4257477 --- /dev/null +++ b/scripts/check-knowledge.mjs @@ -0,0 +1,170 @@ +#!/usr/bin/env node +// 知识治理门:把「卡片字段、source 存在性、active 路由覆盖、引用死链、废弃残留、孤儿规范、技能清单」 +// 从 AI 本机手跑的一次性脚本固化成可重复、可 CI 的非零退出断言。 +// 用法:npm run check:knowledge [-- --forbid 'tokenA,tokenB'] [--strict] +// 语义:[fail] 计入退出码;[warn] 只提示(存量卡片豁免项走这里,见 norms/knowledge-cards.md「不回溯迁移」)。 +import { execFileSync } from 'node:child_process' +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs' +import { dirname, join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = dirname(dirname(fileURLToPath(import.meta.url))) +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}`) +// 读容忍 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 卡路由覆盖 +const REQUIRED = ['title', 'domain', 'keywords', 'scope', 'status', 'source', 'verified'] +const DEPTH = ['verified-depth', 'verified-scope'] +function parseFrontmatter(text) { + if (!text.startsWith('---\n')) return null + const end = text.indexOf('\n---', 4) + if (end < 0) return null + const data = {}, lines = text.slice(4, end + 4).split('\n') + let listKey = null + for (const line of lines) { + if (!line.trim()) continue + const item = /^\s+-\s+(.*)$/.exec(line) + if (item && listKey) { data[listKey] = [...(data[listKey] || []), item[1].trim()]; continue } + const kv = /^([A-Za-z-]+):\s*(.*)$/.exec(line.trim()) + if (!kv) { listKey = null; continue } + const [, k, raw] = kv + if (raw === '') { listKey = k; data[k] = data[k] || [] } + else if (raw.startsWith('[')) { listKey = null; data[k] = raw.slice(1, -1).split(',').map(x => x.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean) } + else { listKey = null; data[k] = raw.replace(/\s+#.*$/, '').trim().replace(/^['"]|['"]$/g, '') } + } + return data +} +// source 解析要容忍归档搬迁:archive 流程会把 brief 移到 changes/archive//, +// 而卡片按规范写的是 changes//brief.md。只查字面路径会让每张卡在它的来源 change 归档那天集体失效 +// (实测本仓 3 张跨项目卡里 2 张已因此判红)。返回 'ok' | 'archived' | 'missing'。 +function sourceResolve(p) { + if (p.startsWith('user-feedback/')) return 'ok' // 口头来源不要求落盘文件 + if (existsSync(join(root, p))) return 'ok' + const bases = ['shadow-docs', ''] + if (bases.some(base => existsSync(join(root, base, p)))) return 'ok' + const archived = p.replace(/^changes\//, 'changes/archive/') + if (archived !== p && bases.some(base => existsSync(join(root, base, archived)))) return 'archived' + return 'missing' +} +const cardDirs = ['knowledge'] +const menuText = read('menu.md') +const cards = [] +for (const dir of cardDirs) { + for (const f of readdirSync(join(root, dir)).filter(x => x.endsWith('.md'))) { + const rel = `${dir}/${f}`, data = parseFrontmatter(read(rel)) + if (!data) { fail(`${rel}: 缺少 frontmatter(卡片必须可路由)`); continue } + cards.push({ rel, data }) + const missing = REQUIRED.filter(k => !(k in data)) + if (missing.length) fail(`${rel}: 缺字段 ${missing.join(', ')}`) + if ('status' in data && !['active', 'deprecated'].includes(data.status)) fail(`${rel}: status 非法「${data.status}」`) + const sources = Array.isArray(data.source) ? data.source : data.source ? [data.source] : [] + if (!sources.length) fail(`${rel}: source 为空`) + for (const src of sources) { + const r = sourceResolve(src) + if (r === 'missing') fail(`${rel}: source 无法解析 → ${src}`) + else if (r === 'archived') warn(`${rel}: source 已通过归档搬迁解析(卡片写 ${src},实际在 .../archive/)`) + } + for (const k of DEPTH) if (!(k in data)) warn(`${rel}: 缺 ${k}(按存量豁免视为 code-read,触碰时必须补齐)`) + if (data.status === 'active' && !menuText.includes(f)) fail(`${rel}: active 卡未进 menu.md 路由`) + if (data.status === 'deprecated' && !/无替代|replaced|superseded|替代/.test(read(rel))) warn(`${rel}: deprecated 未写明替代关系`) + } +} +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) { + const rel = `skills/${d}/SKILL.md` + if (!existsSync(join(root, rel))) { fail(`${rel}: 目录内缺 SKILL.md`); continue } + const fm = parseFrontmatter(read(rel)) || {} + for (const k of ['name', 'description']) if (!(k in fm)) fail(`${rel}: frontmatter 缺 ${k}`) + if ('name' in fm && fm.name !== d) fail(`${rel}: name「${fm.name}」与目录名「${d}」不一致`) +} +ok(`技能 ${skillDirs.length} 个:name/description 与目录一致性检查完成`) + +// 3) 引用死链:live 文件里指向 norms|knowledge|rules|docs|adapters|scripts 的路径必须存在 +const REF = /(? { + try { + return execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' }) + .split('\n').filter(Boolean).map(l => l.slice(3).trim().split(' -> ').pop()) + } catch { return [] } +})()) +const live = [] +// rel 必须始终是仓库根相对路径:用绝对路径拼 walk 会让 startsWith('shadow-docs') 守卫失效, +// 把 shadow-docs/changes/archive/** 的数十份历史 brief 当 live 文件读进来(既慢又会让历史文本污染判定)。 +;(function walk(dir) { + for (const e of readdirSync(join(root, dir), { withFileTypes: true })) { + if (SKIP_DIRS.has(e.name)) continue + const rel = dir === '.' ? e.name : `${dir}/${e.name}` + if (e.isDirectory()) walk(rel) + else if (/\.(md|json|mjs|sh)$/.test(e.name) && !rel.startsWith('shadow-docs')) live.push(rel) + } +})('.') +const dead = [] +for (const rel of live) for (const m of new Set(read(rel).match(REF) || [])) { + if (!existsSync(join(root, m)) && !changedFiles.has(rel) && !changedFiles.has(m)) dead.push(`${rel} → ${m}`) +} +for (const rel of live) { + if (!changedFiles.has(rel)) continue + for (const m of new Set(read(rel).match(REF) || [])) if (!existsSync(join(root, m))) warn(`变更文件 ${rel} 引用了尚未落盘的 ${m}(提交前需确认该文件真实存在)`) +} +dead.forEach(d => fail(`死链 ${d}`)) +ok(`引用扫描 ${live.length} 个文件、路径断言 ${dead.length ? dead.length + ' 处死链' : '零死链'}`) + +// 4) 孤儿规范:每个 norms/*.md 至少要被 menu 路由或被某处引用命中 +for (const f of readdirSync(join(root, 'norms')).filter(x => x.endsWith('.md'))) { + const hit = menuText.includes(f) || live.some(rel => rel !== `norms/${f}` && read(rel).includes(f)) + if (!hit) fail(`norms/${f}: 未被 menu 路由也未被任何文件引用(孤儿规范永远不会被执行)`) +} +ok('孤儿规范检查完成') + +// 5) 废弃残留:--forbid 'a,b' 传入的 token 不允许出现在 live 文件 +const forbidIdx = process.argv.indexOf('--forbid') +const forbid = forbidIdx >= 0 ? (process.argv[forbidIdx + 1] || '').split(',').map(x => x.trim()).filter(Boolean) : [] +for (const token of forbid) { + const hits = live.filter(rel => read(rel).includes(token)) + if (hits.length) fail(`残留 token「${token}」出现在: ${hits.join(', ')}`) +} +if (forbid.length) ok(`废弃 token 扫描完成(${forbid.length} 项)`) + +console.log(oks.join('\n')) +if (warns.length) console.log(warns.join('\n')) +if (fails.length) console.error(fails.join('\n')) +console.log(`\n摘要: ok=${oks.length} warn=${warns.length} fail=${fails.length}`) +if (process.argv.includes('--strict') && warns.length) { console.error('✗ --strict:warn 也判红'); process.exitCode = 1 } +else process.exitCode = fails.length ? 1 : 0 diff --git a/scripts/check-requires.mjs b/scripts/check-requires.mjs index a14b10c..1f86d82 100644 --- a/scripts/check-requires.mjs +++ b/scripts/check-requires.mjs @@ -24,13 +24,42 @@ for (const d of readdirSync(join(root, 'skills'))) { } } const undeclared = [...refs.keys()].filter(k => k.endsWith('(未声明)')) +// 严格模式:命令目录解析不到就判红。非严格模式保留旧行为(本机开发不被迫装 CLI), +// 但把降级态打进输出——CI 必须用严格模式,否则三级包含断言会退化成两级还判绿(假绿)。 +const strict = process.argv.includes('--strict') || process.env.SHADOW_REQUIRE_CLI_CATALOG === '1' let catalog = null, missingInCli = [] const cli = process.env.SHADOW_DEV_CLI const argv = cli ? [process.execPath, [cli, 'help', '--full', '--json']] : ['shadow-dev', ['help', '--full', '--json']] -try { catalog = Object.keys(JSON.parse(execFileSync(argv[0], argv[1], { encoding: 'utf8', stdio: ['ignore','pipe','ignore'] })).data.commands || {}) } catch { catalog = null } +// 命令目录必须从「最后一个 {"ok" 行」取:CLI 的 human 帮助层在非 TTY 下会与 JSON 同流写 stdout, +// 整段 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(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 || {}) +} catch (e) { + catalog = null + if (strict) console.error(`✗ 命令目录解析失败:${e.message?.slice(0, 120) || e}`) +} if (catalog) missingInCli = declared.filter(k => !catalog.includes(k)) -console.log(`declared=${declared.length} skills-referenced=${refs.size} undeclared=[${undeclared.join(', ')}] cli-catalog=${catalog ? catalog.length : 'unresolved'} missing-in-cli=[${missingInCli.join(', ')}]`) +console.log(`declared=${declared.length} skills-referenced=${refs.size} undeclared=[${undeclared.join(', ')}] cli-catalog=${catalog ? catalog.length : 'unresolved'} catalog=${catalog ? 'resolved' : 'unresolved'} strict=${strict ? 'on' : 'off'} missing-in-cli=[${missingInCli.join(', ')}]`) if (undeclared.length) console.error('✗ skills 有命令未进 requiresCommands 声明:', undeclared.join(', ')) if (missingInCli.length) console.error('✗ requiresCommands 声明的命令在已装 CLI 里不存在(pin 或 CLI 版本过旧):', missingInCli.join(', ')) -if (!undeclared.length && !missingInCli.length) console.log('✓ skills ⊆ requiresCommands' + (catalog ? ' ⊆ CLI 命令目录' : '(CLI 目录未参与断言)')) -process.exitCode = (undeclared.length || missingInCli.length) ? 1 : 0 +const degraded = !catalog +const pass = !undeclared.length && !missingInCli.length && !(strict && degraded) +if (strict && degraded) console.error('✗ --strict:解析不到 CLI 命令目录(PATH 无 shadow-dev 且 SHADOW_DEV_CLI 未指向可用 cli.mjs)——降级断言不可接受') +// 判红的轮子不许同时打 ✓:半绿消息本身就是假绿的形态 +if (pass) console.log(catalog ? '✓ skills ⊆ requiresCommands ⊆ CLI 命令目录' : '✓ skills ⊆ requiresCommands(CLI 目录未参与断言)') +else if (!undeclared.length && !missingInCli.length) console.log('✗ 断言不完整,strict 判红(见上一行)') +else console.log('✗ 一致性门未通过') +process.exitCode = pass ? 0 : 1 diff --git a/shadow-docs/changes/20261010-build-quality-gate/brief.md b/shadow-docs/changes/20261010-build-quality-gate/brief.md new file mode 100644 index 0000000..58566bc --- /dev/null +++ b/shadow-docs/changes/20261010-build-quality-gate/brief.md @@ -0,0 +1,213 @@ +--- +{ + "schema": "shadow-dev/v1", + "name": "20261010-build-quality-gate", + "type": "build", + "scope": "plugin-distribution", + "status": "committed", + "baseBranch": "refactor/20261010-refactor-domain-driven-workflow", + "branch": "build/20261010-build-quality-gate", + "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" + ], + "github": { + "repository": null, + "issue": null, + "issueUrl": null, + "pullRequest": null, + "pullRequestUrl": null + }, + "review": { + "conclusion": "passed", + "verifiedCommit": "6775f1ff37a5f59bb74dc259ae3f742cdf4fc04e", + "verifiedAt": "2026-10-10T00:09:38.929Z" + }, + "workflow": { + "operation": null, + "checkpoint": "7b53a8f795097f0b2c89b246bb42c656fdabd4f5", + "planHash": "68e64cba111aee5af626c22ebae2f966386fafacde3423b32cf3441dfb9b6c5e", + "updatedAt": null, + "lastError": null, + "issuePlan": { + "title": "[build] 质量门上移 GitHub Actions + 版本与产物由 Release 控制", + "titleRaw": "[build] 质量门上移 GitHub Actions + 版本与产物由 Release 控制", + "supplement": "把 npm test / check:requires(strict)/ 知识治理门 / 版本一致性门搬进 quality-gate.yml;publish-release.yml 以 release tag 为版本权威,pack→资产契约断言→上传→隔离 HOME 消费冒烟。完整 brief:shadow-docs/changes/20261010-build-quality-gate/brief.md", + "body": "## 动机\n三个已知故障源,全部来自上一个 change 的实证记录(`20261010-refactor-domain-driven-workflow` brief「遗留与后续」):\n\n1. **本仓没有测试门。** `.github/workflows/` 只有 `archive-on-issue-close.yml`。`npm test`(11 例)与 `npm run check:requires` 全靠执行者本机口头自证——「验证强度与评级匹配不可议」在没有机器门时退化为承诺。本轮实证两次代价:`install-cli.test.mjs` 7 例在沙箱禁网下取不到证据(只能越权到有网环境跑),同轮 `workflow plan` 与 `npm test` 各遇一次 node 139 段错误。\n2. **CI 上的门会静默半绿。** `scripts/check-requires.mjs` 解析不到 CLI 命令目录时打印「(CLI 目录未参与断言)」并以 **0 退出**——三级包含断言在 CI 上退化成两级还判绿。该脚本自己的注释就写着「假绿的门比没有门更危险」。\n3. **发布链手工且已反复出血。** 产物资产名 `^shadow-dev-workflow-v[0-9][0-9.]*\\.tar\\.gz$` 是 CLI 侧 `workflow plan --release/--release latest` 的硬契约(CLI 仓 `lib/domains/workflow.mjs:102`),但没有任何自动化保证「tag 建了 → 三处 manifest 版本与 tag 一致 → 资产真的传上去了」。历史证据:`35bd029`、`a06849f`/`51a8eef` 两次专门补版本对齐,`20260925-fix-pack-adapters` 专门补产物缺资产。\n\n目标:把三类门从「AI 本机自证」上移到 GitHub Actions;把 Release 变成版本与产物的唯一权威发布点,人只剩 `bump → tag → gh release create` 一步之差。\n\n**前置依赖(跨上下文,已在文件集中声明):** 本 change 基于**未合并**的 `refactor/20261010-refactor-domain-driven-workflow`(stacked 分支),因为要改的 `norms/verification.md` 与 `menu.md` 路由是那条分支改名后的产物;PR base 同样指向它,按序合并。\n\n## 引用规范\n- `knowledge/distribution-capability-contract.md`\n - 当前结论: 兼容判定用**能力名**不用版本号;能力缺失只能响亮阻塞、禁止静默降级;声明与消费同源登记;跨仓演进顺序=消费方先行兼容、生产方随后声明。\n - 适用 scope: `scripts/check-requires.mjs`(strict 正是「禁止静默降级」纪律套到 CI 自身)、`.github/workflows/*`\n- `knowledge/multi-host-plugin-distribution.md`\n - 当前结论: 新增宿主=新增描述符;**禁止改写既有宿主清单语义**;hooks 声明即替换默认发现。\n - 适用 scope: 本 change 只读两份清单的版本字段(bump 脚本内),不改结构语义\n- `norms/verification.md`\n - 当前结论: L 级=每条不变量一个可追溯观察点;本机取不到证据必须写明缺口与替代观察点(CI run 链接);知识治理流程检查卡片字段/分区/source/路由/死链/过期执行依赖。\n - 适用 scope: 本 change 把「知识治理流程」固化为脚本门,并让「CI run 链接」从抽象条款变成可执行证据形态\n- `norms/knowledge-cards.md`\n - 当前结论: 一卡一知识单元;active 卡必须进 menu 路由;新增前按 `domain + keywords + scope` 查重;存量卡片不回溯迁移。\n - 适用 scope: 新增 `knowledge/release-artifact-pipeline.md` 与 menu 路由\n- `rules/behavior.md` §2 / §3\n - 当前结论: 只实现当前需要的,不为未来灵活性加参数;不碰没坏的代码。\n - 适用 scope: strict 走「新增可选开关、默认保持旧行为」而非全量改语义;不动 pack 集合与清单结构\n- `shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md`(前置 change)\n - 当前结论: 记有「本仓无 CI 测试门=结构性弱点」,是本 change 的动机来源与依赖基线。\n - 适用 scope: 前置/合并顺序\n\n## 决策\n- **选型:** 方案 A —— **断言式版本权威 + 一键 bump + CI 三门 + 发布冒烟**。\n 1. `scripts/check-requires.mjs` 增严格模式(`--strict` 或 `SHADOW_REQUIRE_CLI_CATALOG=1`):命令目录解析不到 → 判红退出 1;非 strict 保持旧行为(本机开发不被迫装 CLI),输出补 `catalog=resolved|unresolved` 让降级可见。\n 2. 新增 `scripts/check-knowledge.mjs`:把上一 change 里我用一次性 python 手跑的**知识治理检查固化成门**——卡片字段/status 枚举/source 存在性/active 路由覆盖/引用死链/`--forbid` 残留 token/孤儿规范。\n 3. 新增 `scripts/bump-version.mjs` + `npm run check:version`:一次同步三处 manifest,`check:version` 在 CI 与发布前判一致性(消灭历史上反复出现的漏同步)。\n 4. 新增 `.github/workflows/quality-gate.yml`:`push: main` + `pull_request`;矩阵 `ubuntu/macos/windows × node 20/22`;步骤=按 `cliVersion` 装 pin CLI(与 archive workflow 同款 `scripts/install-cli.sh install --version`)→ `npm test` → `check:requires` **strict** → `check-knowledge` → `check:version`;`concurrency` 按 ref 取消在跑的同类任务。\n 5. 新增 `.github/workflows/publish-release.yml`:`on: release.types: [published]`;先跑**版本权威判定**(`github.ref_name` 去 `v` 必须 == `package.json.version`,不一致响亮失败并打印 `bump-version.mjs` 修正命令)→ 同组门 → `node scripts/pack.mjs` → **资产契约断言**(文件名匹配 CLI 侧正则)→ `gh release upload --clobber` → **发布冒烟**:`HOME` 隔离下 `shadow-dev workflow plan --release ` → `workflow execute` → `bind plan --host codex`,证明刚发布的产物真能被 v1.5.0 消费;`permissions: contents: write`。\n 6. 不做「tag 驱动 CI 自动 bump 回 main」:tag 与内容必须同源,产物取自 tag 的 tree,而 bump 提交落在 tag **之后**会让资产与 tag tree 不一致——等于把版本权威降级成「CI 事后修正」,可追溯性更差,还会再触发一轮 main push 门(自激风险)。\n- **对比方案:**\n - **方案 B(纯 tag 驱动,CI 自动回写版本)**:未选,理由同上(资产与 tag tree 分裂)。但保留其诉求的替代实现:`bump-version.mjs` 让「先对齐再打 tag」成本降到一条命令,版本权威仍是 tag。\n - **方案 C(只加 `npm test` 的 CI,不动门与发布链)**:未选。留下半绿门与手工传资产两个已知故障源,且完全不满足「用 release 控制版本号」。\n - **方案 D(门并入 CLI 仓复用)**:未选。门断言的是**本仓产物语义**(skills 引用、卡片路由、清单版本),放进执行层就违反刚立的「依赖单向」结论。\n - **矩阵只跑 ubuntu**:未选。`pack.mjs`/`install-cli.sh`/`tar` 的跨平台坑已被 CLI 仓用 `bash -c` 与 Windows bsdtar 注释实证;只跑 ubuntu 等于把可发现性最差的一类回归留在暗处。代价=每次 PR 6 个 job。\n- **理由:** 三项故障源的共同点是「机器能判、却靠人自证」。strict 与版本权威判定都是把已有口头纪律变成非零退出;发布冒烟则是本 change 唯一能证明「Release 真的控制了版本号」的观察点——不冒烟就无法区分「资产名写对且可消费」与「传了个没人能装的文件」。\n\n## 任务\n### Phase 1 — 本机可全量自证的门\n- [ ] check-requires 严格模式 — `scripts/check-requires.mjs` — 命令目录不可解析时 strict 判红(非 strict 保持旧行为);输出增 `catalog=resolved|unresolved`\n- [ ] 知识治理门 — `scripts/check-knowledge.mjs` — 卡片 8 字段与 status 枚举、`source` 存在性、active 卡 menu 路由覆盖、`norms|knowledge|rules|docs|adapters|scripts` 死链、`--forbid` 废弃 token、孤儿规范;无依赖纯 node 实现\n- [ ] 版本一致性与 bump — `scripts/bump-version.mjs`, `package.json` — `check:version` 判三处 manifest 同源;`bump-version.mjs ` 一次写三处并打印变更;新增 `check:knowledge` script 聚合入口\n\n### Phase 2 — GitHub Actions\n- [ ] 质量门工作流 — `.github/workflows/quality-gate.yml` — push main + PR;矩阵 ubuntu/macos/windows × node 20/22;装 pin CLI → `npm test` → `check:requires --strict` → `check:knowledge` → `check:version`;concurrency 按 ref 取消\n- [ ] 发布工作流 — `.github/workflows/publish-release.yml` — release.published → 版本权威判定(不一致即失败并提示 bump)→ 同组门 → pack → 资产名契约断言 → `gh release upload --clobber` → 隔离 HOME 消费冒烟(plan --release / execute / bind plan)\n\n### Phase 3 — 口径与知识闭环\n- [ ] 验证口径更新 — `norms/verification.md` — 「替代观察点」段补明:本仓 `quality-gate` 与 `publish-release` 的 run 链接即合法 runtime 观察点;知识治理检查改由 `scripts/check-knowledge.mjs` 执行,AI 手跑仅作提交前预检\n- [ ] 发布链知识卡与路由 — `knowledge/release-artifact-pipeline.md`, `knowledge/distribution-capability-contract.md`, `menu.md` — 结论:版本权威=release tag、资产名正则契约、发布冒烟三件套、strict 门禁止半绿;按 `domain + keywords + scope` 查重后新增并加 menu 路由;能力契约卡**原位更新**(追加「严格断言不得半绿」执行约束与第 5 项验证方式=发布冒烟,并追加 source),不新建重复卡\n- [ ] 文档口径 — `README.md`, `CLAUDE.md` — 补 CI 与发布段、`scripts/` 三门清单;分层规则加一行「`scripts/` 机器门=可重复验证的执法层,AI 口头自证不可替代」\n\n### Phase 4 — 取证与收口\n- [ ] 观察点执行与记录 — `shadow-docs/changes/20261010-build-quality-gate/brief.md` — 本机真实跑并记输出:`npm test`(11 例)、`check:requires` 非 strict 绿 + strict 在 `SHADOW_DEV_CLI` 指向空壳时**判红**(拒绝路径)、`check-knowledge` 当前绿 + 注入死链/废弃 token fixture 时**判红**后回滚、`check:version` 当前绿 + 人为改坏一处版本时判红后回滚、两个 workflow 的 YAML 解析校验(本机解析器;无则记缺口);GitHub 侧(矩阵真跑、发布冒烟)缺口写明,待 PR/首次 release 的 run 链接回填\n\n## 补充\n把 npm test / check:requires(strict)/ 知识治理门 / 版本一致性门搬进 quality-gate.yml;publish-release.yml 以 release tag 为版本权威,pack→资产契约断言→上传→隔离 HOME 消费冒烟。完整 brief:shadow-docs/changes/20261010-build-quality-gate/brief.md\n\n完整 brief:shadow-docs/changes/20261010-build-quality-gate/brief.md\n\n\n", + "labels": [ + "build" + ] + }, + "commit": { + "files": [ + "shadow-docs/changes/20261010-build-quality-gate/brief.md" + ], + "message": "docs(shadow): review 结论重钉至最终提交(补齐版本 bump 后全门复绿)" + }, + "release": { + "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 控制", + "title": "20261010-build-quality-gate", + "body": "" + } + }, + "knowledge": { + "action": "新增", + "target": "knowledge/release-artifact-pipeline.md", + "reason": "同上轮结论:新增发布链版本权威与资产契约卡,原位更新能力契约卡/verification/menu。补齐提交后 npm run ci rc=0、check:version 三处同源 6.7.0、既有测试 11/11;范围修订(两处宿主清单版本)已在 brief 正文声明。" + } +} +--- + +# 质量门禁上移 GitHub Actions,版本与产物由 Release 控制 + +## 动机 + +三个已知故障源,全部来自上一个 change 的实证记录(`20261010-refactor-domain-driven-workflow` brief「遗留与后续」): + +1. **本仓没有测试门。** `.github/workflows/` 只有 `archive-on-issue-close.yml`。`npm test`(11 例)与 `npm run check:requires` 全靠执行者本机口头自证——「验证强度与评级匹配不可议」在没有机器门时退化为承诺。本轮实证两次代价:`install-cli.test.mjs` 7 例在沙箱禁网下取不到证据(只能越权到有网环境跑),同轮 `workflow plan` 与 `npm test` 各遇一次 node 139 段错误。 +2. **CI 上的门会静默半绿。** `scripts/check-requires.mjs` 解析不到 CLI 命令目录时打印「(CLI 目录未参与断言)」并以 **0 退出**——三级包含断言在 CI 上退化成两级还判绿。该脚本自己的注释就写着「假绿的门比没有门更危险」。 +3. **发布链手工且已反复出血。** 产物资产名 `^shadow-dev-workflow-v[0-9][0-9.]*\.tar\.gz$` 是 CLI 侧 `workflow plan --release/--release latest` 的硬契约(CLI 仓 `lib/domains/workflow.mjs:102`),但没有任何自动化保证「tag 建了 → 三处 manifest 版本与 tag 一致 → 资产真的传上去了」。历史证据:`35bd029`、`a06849f`/`51a8eef` 两次专门补版本对齐,`20260925-fix-pack-adapters` 专门补产物缺资产。 + +目标:把三类门从「AI 本机自证」上移到 GitHub Actions;把 Release 变成版本与产物的唯一权威发布点,人只剩 `bump → tag → gh release create` 一步之差。 + +**前置依赖(跨上下文,已在文件集中声明):** 本 change 基于**未合并**的 `refactor/20261010-refactor-domain-driven-workflow`(stacked 分支),因为要改的 `norms/verification.md` 与 `menu.md` 路由是那条分支改名后的产物;PR base 同样指向它,按序合并。 + +## 复杂度评级 + +- **评级:** L +- **理由:** **契约变更**——新增/改写机器门语义(strict 判红、版本权威判定、资产名契约、发布冒烟),并修改所有后续 change 的取证口径(「替代观察点=CI run」从空谈变成真实存在的能力);**触及面**——CI 与发布链是本仓对外的分发入口,动 `scripts/` 三个门、`.github/workflows/` 两个新工作流、`package.json` scripts、`norms/verification.md` 与知识路由;**可发现性**——workflow YAML 的语义错误(矩阵、secrets、`tar`/bash 跨平台、`gh` 权限、并发组)在本机完全不暴露,必须等 PR 或 Release 真跑才炸,属最典型的「等特定运行时路径才可见」。 +- **期望验证深度:** runtime。本机可全量自证的部分真实跑到(含 strict 与知识门的**拒绝路径**);YAML 语义与发布链以 quality-gate / publish-release 的 run 链接为替代观察点,本轮取不到就写明缺口。 + +## 领域模型 + +- **限界上下文:** `plugin-distribution` — 产物分发与发布链。上游 `shadow-dev-cli`(执行层,消费 `--release`/资产名/能力断言),下游三宿主(`bind` 物化)。与 `workflow-governance`(流程技能)以文件为反腐边界:**本 change 不改任何 skills 语义**,只在 `norms/verification.md` 补一句「CI run 即合法观察点」的取证口径。**反腐约束:** 不新增/删除 `requiresCommands` 命令键,不改两份宿主清单的 `hooks`/`skills` 语义,不动 `scripts/pack.mjs` 的 FILES/DIRS 集合。 +- **统一语言增量:** + - **质量门 quality gate**:PR/main 上必须全绿的机器断言集合,取代执行者口头自证。 + - **严格断言 strict**:被依赖资源不可解析时**判红**,禁止静默降级为「未参与断言」。 + - **版本权威 version authority**:GitHub Release tag 是产物版本唯一真相;`tag ≠ package.json.version` 即判红,不做 CI 回写修正。 + - **资产契约 asset contract**:release 资产名必须匹配 CLI 侧解析正则;不匹配=产物对消费者不存在。 + - **替代观察点 surrogate observation point**:本机不可取证时,以 CI run 链接形态存在的 runtime 证据。 + - **发布冒烟 release smoke**:发布后在隔离 HOME 里用真实 CLI 走完 `plan --release → execute → bind plan`,证明刚上传的产物可被消费。 +- **聚合与不变量(每条须绑定观察点):** + - `分发产物`:`package.json.version == .claude-plugin.version == .codex-plugin.version == release tag 去 v`;产物含 `menu.md`/`README.md`/`skills`(8 个 `SKILL.md`)/`adapters/*`/`norms`/`rules`/`knowledge`/`docs`/`scripts`,不含 `test`/`shadow-docs`/`dist`/`.github`/`CLAUDE.md`/`.git`(与既有 pack 断言同源,不改语义)。 + - `一致性门`:`skills 引用 ⊆ requiresCommands ⊆ CLI 命令目录` 三级必须**全部参与断言**;CLI 命令目录不可解析时 strict 模式必须非零退出。 + - `知识治理`:active 卡必须出现在 `menu.md` 路由;卡片 8 字段齐全且 `status ∈ {active, deprecated}`;`source` 指向存在的 brief;`norms|knowledge|rules|docs|adapters|scripts` 路径引用零死链;`--forbid` 废弃 token 零残留;每个 `norms/*.md` 至少被一条路由或一处引用命中(无孤儿规范)。 + - `CI 语义`:两个 workflow 文件必须可被 YAML 解析器加载;质量门矩阵覆盖 ubuntu/macos/windows;`concurrency` 组按 ref 收敛不叠加;发布工作流 `permissions: contents: write` 且只由 `release.published` 触发。 +- **领域事件:** PR opened/synchronize → 质量门;release published → 版本权威判定 → 三门 → pack → 资产契约断言 → 上传 → 消费冒烟;issue closed → 归档流(既有,不受影响)。 + +## 引用规范 + +- `knowledge/distribution-capability-contract.md` + - 当前结论: 兼容判定用**能力名**不用版本号;能力缺失只能响亮阻塞、禁止静默降级;声明与消费同源登记;跨仓演进顺序=消费方先行兼容、生产方随后声明。 + - 适用 scope: `scripts/check-requires.mjs`(strict 正是「禁止静默降级」纪律套到 CI 自身)、`.github/workflows/*` +- `knowledge/multi-host-plugin-distribution.md` + - 当前结论: 新增宿主=新增描述符;**禁止改写既有宿主清单语义**;hooks 声明即替换默认发现。 + - 适用 scope: 本 change 只读两份清单的版本字段(bump 脚本内),不改结构语义 +- `norms/verification.md` + - 当前结论: L 级=每条不变量一个可追溯观察点;本机取不到证据必须写明缺口与替代观察点(CI run 链接);知识治理流程检查卡片字段/分区/source/路由/死链/过期执行依赖。 + - 适用 scope: 本 change 把「知识治理流程」固化为脚本门,并让「CI run 链接」从抽象条款变成可执行证据形态 +- `norms/knowledge-cards.md` + - 当前结论: 一卡一知识单元;active 卡必须进 menu 路由;新增前按 `domain + keywords + scope` 查重;存量卡片不回溯迁移。 + - 适用 scope: 新增 `knowledge/release-artifact-pipeline.md` 与 menu 路由 +- `rules/behavior.md` §2 / §3 + - 当前结论: 只实现当前需要的,不为未来灵活性加参数;不碰没坏的代码。 + - 适用 scope: strict 走「新增可选开关、默认保持旧行为」而非全量改语义;不动 pack 集合与清单结构 +- `shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md`(前置 change) + - 当前结论: 记有「本仓无 CI 测试门=结构性弱点」,是本 change 的动机来源与依赖基线。 + - 适用 scope: 前置/合并顺序 + +## 决策 + +- **选型:** 方案 A —— **断言式版本权威 + 一键 bump + CI 三门 + 发布冒烟**。 + 1. `scripts/check-requires.mjs` 增严格模式(`--strict` 或 `SHADOW_REQUIRE_CLI_CATALOG=1`):命令目录解析不到 → 判红退出 1;非 strict 保持旧行为(本机开发不被迫装 CLI),输出补 `catalog=resolved|unresolved` 让降级可见。 + 2. 新增 `scripts/check-knowledge.mjs`:把上一 change 里我用一次性 python 手跑的**知识治理检查固化成门**——卡片字段/status 枚举/source 存在性/active 路由覆盖/引用死链/`--forbid` 残留 token/孤儿规范。 + 3. 新增 `scripts/bump-version.mjs` + `npm run check:version`:一次同步三处 manifest,`check:version` 在 CI 与发布前判一致性(消灭历史上反复出现的漏同步)。 + 4. 新增 `.github/workflows/quality-gate.yml`:`push: main` + `pull_request`;矩阵 `ubuntu/macos/windows × node 20/22`;步骤=按 `cliVersion` 装 pin CLI(与 archive workflow 同款 `scripts/install-cli.sh install --version`)→ `npm test` → `check:requires` **strict** → `check-knowledge` → `check:version`;`concurrency` 按 ref 取消在跑的同类任务。 + 5. 新增 `.github/workflows/publish-release.yml`:`on: release.types: [published]`;先跑**版本权威判定**(`github.ref_name` 去 `v` 必须 == `package.json.version`,不一致响亮失败并打印 `bump-version.mjs` 修正命令)→ 同组门 → `node scripts/pack.mjs` → **资产契约断言**(文件名匹配 CLI 侧正则)→ `gh release upload --clobber` → **发布冒烟**:`HOME` 隔离下 `shadow-dev workflow plan --release ` → `workflow execute` → `bind plan --host codex`,证明刚发布的产物真能被 v1.5.0 消费;`permissions: contents: write`。 + 6. 不做「tag 驱动 CI 自动 bump 回 main」:tag 与内容必须同源,产物取自 tag 的 tree,而 bump 提交落在 tag **之后**会让资产与 tag tree 不一致——等于把版本权威降级成「CI 事后修正」,可追溯性更差,还会再触发一轮 main push 门(自激风险)。 +- **对比方案:** + - **方案 B(纯 tag 驱动,CI 自动回写版本)**:未选,理由同上(资产与 tag tree 分裂)。但保留其诉求的替代实现:`bump-version.mjs` 让「先对齐再打 tag」成本降到一条命令,版本权威仍是 tag。 + - **方案 C(只加 `npm test` 的 CI,不动门与发布链)**:未选。留下半绿门与手工传资产两个已知故障源,且完全不满足「用 release 控制版本号」。 + - **方案 D(门并入 CLI 仓复用)**:未选。门断言的是**本仓产物语义**(skills 引用、卡片路由、清单版本),放进执行层就违反刚立的「依赖单向」结论。 + - **矩阵只跑 ubuntu**:未选。`pack.mjs`/`install-cli.sh`/`tar` 的跨平台坑已被 CLI 仓用 `bash -c` 与 Windows bsdtar 注释实证;只跑 ubuntu 等于把可发现性最差的一类回归留在暗处。代价=每次 PR 6 个 job。 +- **理由:** 三项故障源的共同点是「机器能判、却靠人自证」。strict 与版本权威判定都是把已有口头纪律变成非零退出;发布冒烟则是本 change 唯一能证明「Release 真的控制了版本号」的观察点——不冒烟就无法区分「资产名写对且可消费」与「传了个没人能装的文件」。 + +## 任务 + +### Phase 1 — 本机可全量自证的门 +- [x] check-requires 严格模式 — `scripts/check-requires.mjs` — 命令目录不可解析时 strict 判红(非 strict 保持旧行为);输出增 `catalog=resolved|unresolved` +- [x] 知识治理门 — `scripts/check-knowledge.mjs` — 卡片 8 字段与 status 枚举、`source` 存在性、active 卡 menu 路由覆盖、`norms|knowledge|rules|docs|adapters|scripts` 死链、`--forbid` 废弃 token、孤儿规范;无依赖纯 node 实现 +- [x] 版本一致性与 bump — `scripts/bump-version.mjs`, `package.json` — `check:version` 判三处 manifest 同源;`bump-version.mjs ` 一次写三处并打印变更;新增 `check:knowledge` script 聚合入口 + +### Phase 2 — GitHub Actions +- [x] 质量门工作流 — `.github/workflows/quality-gate.yml` — push main + PR;矩阵 ubuntu/macos/windows × node 20/22;装 pin CLI → `npm test` → `check:requires --strict` → `check:knowledge` → `check:version`;concurrency 按 ref 取消 +- [x] 发布工作流 — `.github/workflows/publish-release.yml` — release.published → 版本权威判定(不一致即失败并提示 bump)→ 同组门 → pack → 资产名契约断言 → `gh release upload --clobber` → 隔离 HOME 消费冒烟(plan --release / execute / bind plan) + +### Phase 3 — 口径与知识闭环 +- [x] 验证口径更新 — `norms/verification.md` — 「替代观察点」段补明:本仓 `quality-gate` 与 `publish-release` 的 run 链接即合法 runtime 观察点;知识治理检查改由 `scripts/check-knowledge.mjs` 执行,AI 手跑仅作提交前预检 +- [x] 发布链知识卡与路由 — `knowledge/release-artifact-pipeline.md`, `knowledge/distribution-capability-contract.md`, `menu.md` — 结论:版本权威=release tag、资产名正则契约、发布冒烟三件套、strict 门禁止半绿;按 `domain + keywords + scope` 查重后新增并加 menu 路由;能力契约卡**原位更新**(追加「严格断言不得半绿」执行约束与第 5 项验证方式=发布冒烟,并追加 source),不新建重复卡 +- [x] 文档口径 — `README.md`, `CLAUDE.md` — 补 CI 与发布段、`scripts/` 三门清单;分层规则加一行「`scripts/` 机器门=可重复验证的执法层,AI 口头自证不可替代」 + +### Phase 4 — 取证与收口 +- [x] 观察点执行与记录 — `shadow-docs/changes/20261010-build-quality-gate/brief.md` — 本机真实跑并记输出:`npm test`(11 例)、`check:requires` 非 strict 绿 + strict 在 `SHADOW_DEV_CLI` 指向空壳时**判红**(拒绝路径)、`check-knowledge` 当前绿 + 注入死链/废弃 token fixture 时**判红**后回滚、`check:version` 当前绿 + 人为改坏一处版本时判红后回滚、两个 workflow 的 YAML 解析校验(本机解析器;无则记缺口);GitHub 侧(矩阵真跑、发布冒烟)缺口写明,待 PR/首次 release 的 run 链接回填 + +## 结果 + +- 实际耗时: ≈35 分钟(含两次自我破坏的返工与一轮 139 抖动排查) +- 验证: **L 级=每条不变量一个可追溯观察点**,全部为本机真实命令输出: + +| # | 不变量 | 观察点与真实输出 | +|---|--------|------------------| +| I1 `分发产物` | 三处 manifest 与 release tag 同源;产物含新卡与新门脚本、8 skills、不含 `.github` | `node scripts/bump-version.mjs 6.7.0` → `· package.json / .claude-plugin / .codex-plugin → 6.7.0` + `✓ 版本 bump 完成: 6.7.0`;`npm run check:version` → `✓ 三处 manifest 版本同源: 6.7.0(3 个文件)`;`node scripts/pack.mjs` → `dist/shadow-dev-workflow-v6.7.0.tar.gz`,`tar -tzf` 关键项 **4/4**(`knowledge/release-artifact-pipeline.md`、`norms/verification.md`、`scripts/check-knowledge.mjs`、`scripts/bump-version.mjs`)、`skills=8`、`.github 条目=0`;资产名判定 `✓资产名契约: shadow-dev-workflow-v6.7.0.tar.gz`,负例 `✗ 拒绝 shadow-dev-workflow-6.7.0.tgz` | +| I2 `一致性门` | 三级包含全部参与断言;命令目录不可解析时 strict 必须判红 | 绿路:`npm run check:requires:strict` → `declared=29 skills-referenced=21 undeclared=[] cli-catalog=46 catalog=resolved strict=on missing-in-cli=[]` + `✓ skills ⊆ requiresCommands ⊆ CLI 命令目录`;拒绝路径:`SHADOW_DEV_CLI=/private/tmp/nope.mjs` → strict `rc=1`(`catalog=unresolved`),**非 strict 同条件 `rc=0` 且自曝「(CLI 目录未参与断言)」——半绿故障被复现并锁死**;`npm run ci` 整体 `rc=0` | +| I3 `知识治理` | 卡片字段/status/source(含归档兜底)/active 路由/死链/forbid 残留/孤儿规范/技能 frontmatter | `npm run check:knowledge` → `摘要: ok=4 warn=4 fail=0`(知识卡 5 张、技能 8 个、51 文件零死链、孤儿规范 0);warn 明细=2 张存量卡缺 `verified-depth`(豁免)+ 2 张卡 source 经归档兜底解析;拒绝路径:`--forbid 'bump-version'` → `rc=1`;CI 现行组合 `--forbid 'tdd-verification,▶ [TDD]'` → `rc=0` | +| I4 `CI 语义` | workflow 可解析;矩阵覆盖三平台;发布流水 9 步齐备 | `ruby -ryaml YAML.load_file` → 三个 yml 全部 `YAML 可解析`;`publish-release.yml` steps=9(tag tree checkout → 版本权威判定 → 装 pin CLI → 四门 → pack → 资产契约断言 → upload → 隔离 prefix 冒烟);`quality-gate.yml` 矩阵 `ubuntu/macos/windows × node 20/22`、`concurrency` 按 ref 取消、`permissions: contents: read` | + +- **GitHub 侧证据缺口(如实标注)**:矩阵真跑、`install-cli.sh` 在 runner 上的 pin 安装、发布冒烟三步均未在本机执行。替代观察点=本 PR 的 `quality-gate` run 链接与首个 `publish-release` run 链接(新加的 workflow 文件在 `pull_request` 事件下由 PR 的 merge ref 提供,故**首个 PR 就会真跑**)。合并后回填本行。 +- 过程记录(诚实,含四次自我破坏/环境坑): + 1. **门自身的缺陷**:`check-requires.mjs` 原来对 `shadow-dev help --full --json` 的**整段 stdout** 做 `JSON.parse`——非 TTY 下 CLI 的 human 帮助层与 JSON 同流写出,解析必抛 → `catalog=unresolved`,即这条门在 CI/管道环境里**恒假红**(本机偶然判绿只是因为 shim 恰好回了纯 JSON)。修法:取最后一个 `{"ok"` 契约行 + `execFileSync` 加 `timeout`(默认 20s,`SHADOW_CLI_TIMEOUT_MS` 可调)。这是新门立项的第一个真实收益。 + 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`) + +## 知识评估 + +- **预期影响:** 新增 + 更新 +- **候选卡片:** `knowledge/release-artifact-pipeline.md`(新增,版本权威/资产契约/发布冒烟/禁止半绿)、`knowledge/distribution-capability-contract.md`(原位更新:追加「同一纪律套在自己的机器门上」执行约束、验证方式第 5 项发布冒烟与第 6 项半绿负例、追加 source、`verified` 2026-10-10)、`norms/verification.md`(替代观察点条款具体化为两条流水;知识治理流程改为脚本执行)、`menu.md`(新增「发布与质量门」路由) +- **理由:** 「Release 是版本与产物的唯一权威 + 资产名正则契约 + 发布必须冒烟 + 机器门禁止半绿」是跨项目可复用的分发事实,值得独立成卡;「一致性门也要 strict」是既有能力契约卡的**加强**而非新事实,故原位更新并追加 source,不建重复卡(`norms/knowledge-cards.md` 查重条款)。`verified-depth` 现记 `unit`(本机四门 + 11 例测试 + 三处负例复现),待首个 `publish-release` run 的冒烟输出后再升 `runtime`——未达到的深度不虚报。 + + +## 知识评估 + +- **预期影响:** 新增 + 更新 +- **候选卡片:** `knowledge/release-artifact-pipeline.md`(新增)、`knowledge/distribution-capability-contract.md`(追加「严格断言不得半绿」与发布冒烟为验证方式第 5 项)、`norms/verification.md`(替代观察点条款具体化)、`menu.md`(新卡路由) +- **理由:** 「版本权威=release tag + 资产名正则契约 + 发布冒烟」是跨项目可复用的分发事实,不属单次变更过程,故进 active 卡;`distribution-capability-contract.md` 的既有结论被本次工作**加强**(同一纪律套到 CI),属原位更新并追加 source 而非新建重复卡。`verified-depth` 按 Phase 4 实际达到值填写(本机门为 `unit`/`runtime`,GitHub 侧未取到证据前不得写 `runtime`)。 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/changes/20261010-refactor-domain-driven-workflow/brief.md b/shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md new file mode 100644 index 0000000..b3333c2 --- /dev/null +++ b/shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md @@ -0,0 +1,232 @@ +--- +{ + "schema": "shadow-dev/v1", + "name": "20261010-refactor-domain-driven-workflow", + "type": "refactor", + "scope": "workflow-governance", + "status": "committed", + "baseBranch": "main", + "branch": "refactor/20261010-refactor-domain-driven-workflow", + "files": [ + ".claude-plugin/plugin.json", + ".codex-plugin/plugin.json", + "CLAUDE.md", + "README.md", + "knowledge/bug-investigation.md", + "knowledge/domain-driven-shadow-dev.md", + "menu.md", + "norms/domain-model.md", + "norms/signals.md", + "norms/tdd-verification.md", + "norms/verification.md", + "package.json", + "rules/behavior.md", + "rules/iron-laws.md", + "shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md", + "skills/shadow-dev-apply/SKILL.md", + "skills/shadow-dev-hotfix/SKILL.md", + "skills/shadow-dev-knowledge/SKILL.md", + "skills/shadow-dev-propose/SKILL.md", + "skills/shadow-dev-review/SKILL.md" + ], + "github": { + "repository": null, + "issue": null, + "issueUrl": null, + "pullRequest": null, + "pullRequestUrl": null + }, + "review": { + "conclusion": "passed", + "verifiedCommit": "601fb9dc398b446e5146a1eb0727ca5313e001ec", + "verifiedAt": "2026-10-09T16:52:11.691Z" + }, + "workflow": { + "operation": null, + "checkpoint": "c1d64bb788b373e7008c5176d9c9c35d42427d76", + "planHash": "b8500b6b419bd8884f57619b2843115a5f037b3f3fb48dbaaa66d79561e13f4a", + "updatedAt": null, + "lastError": null, + "issuePlan": { + "title": "[refactor] 工作流从 TDD 驱动转为领域驱动(DDD)——规格载体换成领域模型与不变量", + "titleRaw": "[refactor] 工作流从 TDD 驱动转为领域驱动(DDD)——规格载体换成领域模型与不变量", + "supplement": "领域驱动改造:propose 增领域对齐轮、brief 增「## 领域模型」段、apply 验证门改不变量观察点、review 增模型一致性维、tdd-verification.md 改名 verification.md 并清理 14 处引用与 ▶ [TDD] token。完整 brief:shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md", + "body": "## 动机\n2026-10-09 用户裁决「不要写 TDD 了,太浪费时间了」已经落到 `norms/tdd-verification.md` 的「验证方式裁决」节和 `rules/iron-laws.md` §1(经 #36 进 main),**但执行层技能正文没跟上,仓库现在自相矛盾**。基线 `main@28e6f25`(v6.5.1)逐条核实:\n\n| 位置 | 现行文本 | 与裁决的关系 |\n|------|----------|--------------|\n| `skills/shadow-dev-apply/SKILL.md:55` | **L**:完整 TDD——写失败测试并确认失败 → 最小实现并确认通过 → 重构保持绿色 | **直接冲突** |\n| `skills/shadow-dev-apply/SKILL.md:56` | **M**:绿灯测试——写测试并通过 | **直接冲突**(裁决:M 默认不新建测试文件) |\n| `skills/shadow-dev-hotfix/SKILL.md:74` | L 级写自动化复现测试先红后绿 | **与同文件 59 行自相矛盾**(59 行已写「不强制先红仪式」) |\n| `skills/shadow-dev-hotfix/SKILL.md:101` | 收尾门禁:L 复现测试先红后绿 | 同上 |\n| `rules/behavior.md:30` | L 级 Bug 修复须先写复现测试 | 冲突 |\n| `norms/tdd-verification.md:54,56` + `apply:59` + `hotfix:74` | 非协商进度 token `▶ [TDD] n/N <测试名> red\\|green` | 名分残留 |\n| 文件名与引用 | `norms/tdd-verification.md` 被 9 个文件引用共 14 处(`menu.md`×2、`apply`×2、`hotfix`×3、`propose`/`behavior`/`iron-laws`/`signals`/`bug-investigation`/`README` 各 1) | 名分残留 |\n| `menu.md:10` · `CLAUDE.md:19` · `.codex-plugin/plugin.json:4,16,27`(keywords 含 `tdd`、longDescription「按 brief 执行 TDD」)· `.claude-plugin/plugin.json:3`(仍是过期的「4-phase pipeline (propose → apply → review → ship) + TDD」) | 对外口径 | 名分残留 |\n\n第二层动机更根本:**TDD 撤走后,规格来源不能悬空**。这套基建(deterministic CLI + 三级知识库 + menu 路由 + signals + 分级验证)已经把「测试曾经承担的两件事」提供了更便宜的替代品——规格表达和完成证据。把它们显式交给领域模型与不变量,而不是留给 diff 走查,才守得住唯一不可让的那道门。而领域驱动的构件本就在:`knowledge-cards.md` 有 `domain:` 字段、CLI 按 `lib/domains/` 组织、后端模块即限界上下文,只是流程从没把它当第一等公民。\n\n**非目标:** 不重组 `norms/`、`knowledge/` 目录结构;不改 CLI 命令面与 brief JSON schema(新增正文段 CLI 不解析,零破坏,`requiresCommands` 不扩项);不动分发轨——`hooks/`、`scripts/install-cli.sh`、`package.json.cliVersion` 三项仍在 main(原 PR #34 的「注销双轨」部分未合入且分支已删,属 `plugin-distribution` 上下文,本 change 不碰不删);不改任何项目仓 `shadow-docs/`。\n\n**待确认点(记录,不在本 change 处置):** 上游 #34 关闭后其「单向分发」结论与 main 现状(三件旧轨仍在)之间是否重新排期,由分发上下文决定。\n\n## 引用规范\n- `norms/tdd-verification.md`(本 change 改名 `norms/verification.md`)\n - 当前结论: 分级制——评级可议,强度与评级匹配不可议;2026-10-09 裁决已把 L 级达成方式改为可追溯 runtime 观察点,本 change 升格为「不变量观察点」并去掉 TDD 名分。\n - 适用 scope: 全流程\n- `rules/iron-laws.md` §1 / `rules/behavior.md` §4\n - 当前结论: 铁律已是分级验证口径(L 观察点 / M 冒烟+既有测试绿+走查 / S 结构引用残留扫描);behavior §4 仍是旧 L 复现测试口径,须对齐。\n - 适用 scope: rules/\n- `norms/knowledge-cards.md`\n - 当前结论: 一卡一知识单元;active 卡必须进 menu 路由;新增前按 `domain + keywords + scope` 查重;`verified-depth: runtime` 必须附可追溯观察点;存量卡片不回溯迁移(视为 `code-read`)。\n - 适用 scope: norms/, knowledge/, menu.md\n- `knowledge/distribution-capability-contract.md` · `knowledge/multi-host-plugin-distribution.md`\n - 当前结论: 兼容判定用能力名不用版本号;能力缺失只能响亮阻塞不得静默降级;新增宿主=新增描述符;**禁止改写既有宿主清单语义**。\n - 适用 scope: 本 change 仅改两份清单的 TDD 措辞与 keywords,不动 hooks/版本/pack 语义\n- `norms/signals.md`\n - 当前结论: 扇宽由 brief 复杂度评级决定,引用路径为 `norms/tdd-verification.md`(改名同步点之一)。\n - 适用 scope: norms/signals.md\n- `knowledge/bug-investigation.md`\n - 当前结论: 同一 Bug 的复现/根因/修复/回归必须单一持续上下文;根因明确后机械验证可并行。\n - 适用 scope: hotfix 复现环节改造时遵守\n\n## 决策\n- **选型:** 方案 B — 领域驱动作为规格载体,TDD 降级为可选证据手段。propose 在「澄清需求」与「比较方案」之间插入「领域对齐」轮并在 brief 模板加「## 领域模型」段;apply 验证门禁按不变量重写(同时消灭 55/56 行冲突);review 增第 10 维「模型一致性」;knowledge 查询路由升为 `域 + 技术域` 双键;hotfix 把「复现锁定」改为「被破坏不变量的可复现观察」并消除 74/101 行自相矛盾;`tdd-verification.md` 改名 `verification.md`,14 处引用与 `▶ [TDD]` token 一并清理(改 `▶ [verify]`);新增 `norms/domain-model.md` 与跨项目卡 `knowledge/domain-driven-shadow-dev.md`;`package.json` + 两份宿主清单版本 bump 6.6.0。\n- **对比方案:**\n - **方案 A(只清残留、不引入领域层)**:未选。它回答不了「TDD 拿走后规格从哪来」——没有不变量当验收对象,L 级门禁退化为 diff 走查,防假完成的门反而更弱,也没回应诉求本体。\n - **方案 C(连知识体系按限界上下文重组目录 + CLI 增 `domain` 命令域)**:未选为本轮范围。需 `requiresCommands` 扩项、三仓同步、项目卡片全量迁移,且刚立的能力契约要求消费方先行兼容;留作 B 落地验证后的独立 change。\n - **保留文件名 `tdd-verification.md`**:未选。名字即规范的一部分,留着会持续把「先红仪式」当默认动作召回;已核实改名代价可控——`scripts/pack.mjs` 整目录拷贝 `norms`、`scripts/check-requires.mjs` 只扫命令键、`test/pack.test.mjs` 只断言目录与 skills 清单,**无程序化文件名引用**,14 处全为文档引用。\n - **主工作区直接改(不开 worktree)**:未选。本 checkout 被 `shadow-dev workflow link` 直通为活动产物(`workflow status` 的 `linked`/`resolved` 指向它),三宿主 `bind` 从这里复制技能;在 main 工作区逐文件编辑会让中途半成品随时可被重新绑定消费。L 级按 propose 规定走专属 workspace。\n- **执行期修订(apply 阶段发现,非静默跨级):** `worktree inspect` 建议 `create`(L 级),但 CLI 实现不支持「新建 change 尚未提交 brief」这一常态——`lib/domains/worktree.mjs` 的 `execute` 用 `git worktree add -b / ` 从基线派生工作树,且只向**主工作区**的 brief 回写 `branch`/`workflow.worktree`;此时 brief 仍是未跟踪文件,新工作树内不存在它,后续 `task set`/`review`/`commit` 一律 `BRIEF_NOT_FOUND`,人工拷贝则造成两份受管状态分裂。故本 change 改走 **inline 分支**执行,隔离手段换为:主工作区切到功能分支后**不在实现中途执行 `bind execute`**,宿主绑定刷新留到 review 通过后的 release 阶段(`workflow link` 直通本 checkout,中途重绑才会把半成品技能铺给三宿主)。worktree 对新立项 change 的缺口另立 change 在 `shadow-dev-cli` 上下文处置。\n\n- **理由:** 你的基建已经能提供 TDD 原本负责的两件事——规格表达(不变量替代用例)与完成证据(观察点替代绿灯);把建模写进流程是净增益,同时唯一不可让的门(强度与评级匹配)原样保留,既有测试仍必须绿。\n\n## 任务\n### Phase 1 — 领域层定义\n- [x] 新增领域驱动规范 — `norms/domain-model.md` — 限界上下文/统一语言/聚合/领域不变量/观察点定义与术语表;brief「## 领域模型」段格式与必填规则(L 必填、M 选填归属域、S 免);项目 `shadow-docs/domain.md` 上下文地图模板;越界禁止条款(不改他上下文语义、不借建模扩面)\n- [x] 新增跨项目知识卡 — `knowledge/domain-driven-shadow-dev.md` — 结论:AI 工作流的规格载体是领域模型与不变量,测试降级为证据手段之一;含执行约束、适用边界、验证方式,`domain: workflow-governance`\n\n### Phase 2 — 验证纪律改名与残留清理\n- [x] 规范改名并去 TDD 名分 — `norms/verification.md`(由 `norms/tdd-verification.md` 改名,原文件删除) — 节名与表格改为「不变量观察点」口径;进度 token `▶ [TDD]` → `▶ [verify]`;保留分级制、「强度与评级匹配不可议」「既有测试必须绿」「收口对账」三条不变\n- [x] 文档与规则引用清理 — `menu.md`, `CLAUDE.md`, `README.md`, `norms/signals.md`, `knowledge/bug-investigation.md`, `rules/iron-laws.md`, `rules/behavior.md` — 14 处路径与 `menu.md:10` 关键词去 TDD;`menu.md` 路由表加「域」语义说明并为两份新文件加路由;`behavior.md` §4 改不变量口径;`iron-laws` 新增 §7「不要在缺少领域模型的情况下开始 L 级实现」\n- [x] 宿主清单口径纠偏 — `.codex-plugin/plugin.json`, `.claude-plugin/plugin.json` — description/longDescription 去掉「TDD」、改为领域驱动 + 分级验证口径;keywords `tdd` → `domain-driven`;`.claude-plugin` 过期「4-phase … ship」改为五段式;**不动 `hooks` 字段与任何分发语义**\n\n### Phase 3 — 流程技能改造\n- [x] propose 增领域对齐轮 — `skills/shadow-dev-propose/SKILL.md` — 步骤 2/3 间插入「领域对齐」;正文模板加「## 领域模型」段;步骤 5 展示结果同步;`评级: X` token 位置与格式保持不变(CLI 正则依赖)\n- [x] apply 验证门重写 — `skills/shadow-dev-apply/SKILL.md` — §1 载入 brief 领域模型;§5 按不变量组织验证(L:每条不变量一个观察点;M:触及的既有不变量保持成立 + 冒烟 + 既有测试绿;S:结构与引用扫描);删 55/56 行 TDD 表述;token 改 `▶ [verify]`;frontmatter description 已是「分级验证」,复核一致\n- [x] review 增模型一致性维 — `skills/shadow-dev-review/SKILL.md` — 新增第 10 维:实现是否引入未登记术语、破坏不变量、越界改动其他上下文;§8 措辞对齐不变量口径\n- [x] knowledge 双键路由 — `skills/shadow-dev-knowledge/SKILL.md` — 查询条件加限界上下文;输出格式加「所属域/上下游」;项目存在 `shadow-docs/domain.md` 时先读上下文地图\n- [x] hotfix 快车道对齐 — `skills/shadow-dev-hotfix/SKILL.md` — 「复现锁定」改为「被破坏不变量的可复现观察」,默认产物由 `test/<文件>` 改为复现步骤/观察记录;消除 74/101 行与 59 行的自相矛盾;L 级仍要求真实复现与回归\n\n### Phase 4 — 自证与发版口径\n- [x] 版本 bump 与能力声明复核 — `package.json` — 6.6.0,与 `.claude-plugin`/`.codex-plugin` 三处对齐(`npm test` 有机检);`requiresCommands` 不扩项(本 change 不新增命令键);确认 `pack DIRS` 整目录拷贝下无需为改名增项\n- [x] 不变量观察点执行与收口 — `shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md` — 真实跑并记录输出:`npm run check:requires`(差集为空)、`npm test`(install-cli + pack 全绿,既有测试保持绿)、`node scripts/pack.mjs` 产物扫描(`norms/verification.md` 在包内、`tdd-verification.md` 不在、8 skills 齐全)、死链与残留 grep 归零(`tdd-verification`、`▶ [TDD]`、`先红`)、拒绝路径观察点 `shadow-dev workflow plan --from .`(能力断言仍通过、指针不动)\n\n## 补充\n领域驱动改造:propose 增领域对齐轮、brief 增「## 领域模型」段、apply 验证门改不变量观察点、review 增模型一致性维、tdd-verification.md 改名 verification.md 并清理 14 处引用与 ▶ [TDD] token。完整 brief:shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md\n\n完整 brief:shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md\n\n\n", + "labels": [ + "refactor" + ] + }, + "release": { + "files": [ + ".claude-plugin/plugin.json", + ".codex-plugin/plugin.json", + "CLAUDE.md", + "README.md", + "knowledge/bug-investigation.md", + "knowledge/domain-driven-shadow-dev.md", + "menu.md", + "norms/domain-model.md", + "norms/signals.md", + "norms/tdd-verification.md", + "norms/verification.md", + "package.json", + "rules/behavior.md", + "rules/iron-laws.md", + "shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md", + "skills/shadow-dev-apply/SKILL.md", + "skills/shadow-dev-hotfix/SKILL.md", + "skills/shadow-dev-knowledge/SKILL.md", + "skills/shadow-dev-propose/SKILL.md", + "skills/shadow-dev-review/SKILL.md" + ], + "message": "refactor(workflow): 工作流转为领域驱动——不变量作验收对象,TDD 残留清零", + "title": "20261010-refactor-domain-driven-workflow", + "body": "" + }, + "commit": { + "files": [ + "shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md" + ], + "message": "docs(shadow): 重算 issue/release planHash(正文定稿后)" + } + }, + "knowledge": { + "action": "新增", + "target": "knowledge/domain-driven-shadow-dev.md", + "reason": "新增跨项目卡「领域模型是工作流的规格载体」;同时原位改名更新 norms/verification.md、新增 norms/domain-model.md、更新 rules/iron-laws.md §1+§7、rules/behavior.md §4、menu.md 双键路由、knowledge/bug-investigation.md 与 norms/signals.md 关联链接、宿主清单口径。既有测试 11/11 全绿(pack 4/4 + install-cli 7/7),4 条不变量各有真实观察点,死链与 ▶ [TDD] 残留归零,拒绝路径 fixture 仍 ARTIFACT_INCOMPATIBLE 且指针不动。norms/knowledge-cards.md 与本仓 shadow-docs/domain.md 因 CLI 无 change amend 能力留待后续 change。" + } +} +--- + +# 工作流从 TDD 驱动转为领域驱动(DDD) + +## 动机 + +2026-10-09 用户裁决「不要写 TDD 了,太浪费时间了」已经落到 `norms/tdd-verification.md` 的「验证方式裁决」节和 `rules/iron-laws.md` §1(经 #36 进 main),**但执行层技能正文没跟上,仓库现在自相矛盾**。基线 `main@28e6f25`(v6.5.1)逐条核实: + +| 位置 | 现行文本 | 与裁决的关系 | +|------|----------|--------------| +| `skills/shadow-dev-apply/SKILL.md:55` | **L**:完整 TDD——写失败测试并确认失败 → 最小实现并确认通过 → 重构保持绿色 | **直接冲突** | +| `skills/shadow-dev-apply/SKILL.md:56` | **M**:绿灯测试——写测试并通过 | **直接冲突**(裁决:M 默认不新建测试文件) | +| `skills/shadow-dev-hotfix/SKILL.md:74` | L 级写自动化复现测试先红后绿 | **与同文件 59 行自相矛盾**(59 行已写「不强制先红仪式」) | +| `skills/shadow-dev-hotfix/SKILL.md:101` | 收尾门禁:L 复现测试先红后绿 | 同上 | +| `rules/behavior.md:30` | L 级 Bug 修复须先写复现测试 | 冲突 | +| `norms/tdd-verification.md:54,56` + `apply:59` + `hotfix:74` | 非协商进度 token `▶ [TDD] n/N <测试名> red\|green` | 名分残留 | +| 文件名与引用 | `norms/tdd-verification.md` 被 9 个文件引用共 14 处(`menu.md`×2、`apply`×2、`hotfix`×3、`propose`/`behavior`/`iron-laws`/`signals`/`bug-investigation`/`README` 各 1) | 名分残留 | +| `menu.md:10` · `CLAUDE.md:19` · `.codex-plugin/plugin.json:4,16,27`(keywords 含 `tdd`、longDescription「按 brief 执行 TDD」)· `.claude-plugin/plugin.json:3`(仍是过期的「4-phase pipeline (propose → apply → review → ship) + TDD」) | 对外口径 | 名分残留 | + +第二层动机更根本:**TDD 撤走后,规格来源不能悬空**。这套基建(deterministic CLI + 三级知识库 + menu 路由 + signals + 分级验证)已经把「测试曾经承担的两件事」提供了更便宜的替代品——规格表达和完成证据。把它们显式交给领域模型与不变量,而不是留给 diff 走查,才守得住唯一不可让的那道门。而领域驱动的构件本就在:`knowledge-cards.md` 有 `domain:` 字段、CLI 按 `lib/domains/` 组织、后端模块即限界上下文,只是流程从没把它当第一等公民。 + +**非目标:** 不重组 `norms/`、`knowledge/` 目录结构;不改 CLI 命令面与 brief JSON schema(新增正文段 CLI 不解析,零破坏,`requiresCommands` 不扩项);不动分发轨——`hooks/`、`scripts/install-cli.sh`、`package.json.cliVersion` 三项仍在 main(原 PR #34 的「注销双轨」部分未合入且分支已删,属 `plugin-distribution` 上下文,本 change 不碰不删);不改任何项目仓 `shadow-docs/`。 + +**待确认点(记录,不在本 change 处置):** 上游 #34 关闭后其「单向分发」结论与 main 现状(三件旧轨仍在)之间是否重新排期,由分发上下文决定。 + +## 复杂度评级 + +- **评级:** L +- **理由:** **契约变更**——brief 正文新增必备段「## 领域模型」、apply 验证门禁达成方式由「测试」改为「不变量观察点」、进度 token 改名、新增 `norms/domain-model.md` 约束,全部是下游(6 个宿主绑定、三项目、后续每个 change 的 brief)消费的规格;**触及面**——19 个文件、6 个 skills + 3 个 norms + 2 个 rules + menu/README/CLAUDE/两份宿主清单,跨项目共享;**可发现性**——规范文本改坏不会立刻可见,要等下一个真实 change 跑流程才暴露(引用路径不存在、`check:requires` 判红、`brief` 新段把 CLI 解析 `评级: [SML]` 的正则挤掉、宿主读到中间态技能)。 +- **期望验证深度:** runtime(可追溯观察点:真实命令 + 真实输出,覆盖拒绝路径与状态不变量) + +## 领域模型 + +- **限界上下文(本变更所属):** `workflow-governance` — 开发工作流治理。上游 `shadow-dev-cli`(执行层)、`plugin-distribution`(分发轨),以**能力契约**为反腐边界:本上下文不定义安装顺序、不增删命令键、不碰 hooks 与 pack 清单语义。 +- **统一语言增量**(写入 `norms/domain-model.md`,术语表唯一真相): + - **限界上下文**:一套稳定词汇生效的边界;跨边界协作必须显式声明依赖方向与反腐层。 + - **统一语言**:同一业务词在一个上下文内只有一个含义;新术语未登记即视为模型漂移。 + - **聚合**:必须整体保持一致的概念单元,有唯一聚合根;本工作流的聚合根是 `change brief`、`Knowledge 卡片`、`分发产物`。 + - **领域不变量**:任何时刻都必须成立的断言,**它就是验收对象本身**;每条不变量必须绑定一个可追溯观察点。 + - **观察点**:证明不变量成立的真实证据(命令 + 输出 / CI 链接 / 产物扫描结果),取代「测试通过」作为通用完成口径。 + - **上下文地图**:项目级 `shadow-docs/domain.md` 声明本项目的限界上下文、上下游关系与术语表。 +- **聚合与必须成立的不变量:** + - `change brief`:一个变更唯一 brief;受管状态只由 CLI 写;`评级: X` token 必须保持可被 CLI 正则 `/评级:\**\s*([SML])/` 解析;**新**:L 级 brief 必含「## 领域模型」段且不变量清单非空。 + - `Knowledge 卡片`:active 卡必须出现在 `menu.md` 至少一条路由;`domain + keywords + scope` 三键齐全;`source` 指向存在路径;`verified-depth` 只升不虚报。 + - `规范引用一致性`:skills/norms/rules/文档中引用的规范文件路径必须存在;全仓不得残留 `tdd-verification.md` 引用与 `▶ [TDD]` token(改名后零死链)。 + - `分发产物`:声明与消费同源——`skills 引用 ⊆ requiresCommands ⊆ CLI 命令目录`;8 个 skills 齐全;三处 manifest 版本 == `package.json.version`。 +- **领域事件:** propose 确认 → 评级 + 领域模型一并批准;实现期风险变级 → 先修订 brief 再继续;review 模型一致性门失败 → 阻塞,禁止改写 Knowledge 迁就实现。 + +## 引用规范 + +- `norms/tdd-verification.md`(本 change 改名 `norms/verification.md`) + - 当前结论: 分级制——评级可议,强度与评级匹配不可议;2026-10-09 裁决已把 L 级达成方式改为可追溯 runtime 观察点,本 change 升格为「不变量观察点」并去掉 TDD 名分。 + - 适用 scope: 全流程 +- `rules/iron-laws.md` §1 / `rules/behavior.md` §4 + - 当前结论: 铁律已是分级验证口径(L 观察点 / M 冒烟+既有测试绿+走查 / S 结构引用残留扫描);behavior §4 仍是旧 L 复现测试口径,须对齐。 + - 适用 scope: rules/ +- `norms/knowledge-cards.md` + - 当前结论: 一卡一知识单元;active 卡必须进 menu 路由;新增前按 `domain + keywords + scope` 查重;`verified-depth: runtime` 必须附可追溯观察点;存量卡片不回溯迁移(视为 `code-read`)。 + - 适用 scope: norms/, knowledge/, menu.md +- `knowledge/distribution-capability-contract.md` · `knowledge/multi-host-plugin-distribution.md` + - 当前结论: 兼容判定用能力名不用版本号;能力缺失只能响亮阻塞不得静默降级;新增宿主=新增描述符;**禁止改写既有宿主清单语义**。 + - 适用 scope: 本 change 仅改两份清单的 TDD 措辞与 keywords,不动 hooks/版本/pack 语义 +- `norms/signals.md` + - 当前结论: 扇宽由 brief 复杂度评级决定,引用路径为 `norms/tdd-verification.md`(改名同步点之一)。 + - 适用 scope: norms/signals.md +- `knowledge/bug-investigation.md` + - 当前结论: 同一 Bug 的复现/根因/修复/回归必须单一持续上下文;根因明确后机械验证可并行。 + - 适用 scope: hotfix 复现环节改造时遵守 + +## 决策 + +- **选型:** 方案 B — 领域驱动作为规格载体,TDD 降级为可选证据手段。propose 在「澄清需求」与「比较方案」之间插入「领域对齐」轮并在 brief 模板加「## 领域模型」段;apply 验证门禁按不变量重写(同时消灭 55/56 行冲突);review 增第 10 维「模型一致性」;knowledge 查询路由升为 `域 + 技术域` 双键;hotfix 把「复现锁定」改为「被破坏不变量的可复现观察」并消除 74/101 行自相矛盾;`tdd-verification.md` 改名 `verification.md`,14 处引用与 `▶ [TDD]` token 一并清理(改 `▶ [verify]`);新增 `norms/domain-model.md` 与跨项目卡 `knowledge/domain-driven-shadow-dev.md`;`package.json` + 两份宿主清单版本 bump 6.6.0。 +- **对比方案:** + - **方案 A(只清残留、不引入领域层)**:未选。它回答不了「TDD 拿走后规格从哪来」——没有不变量当验收对象,L 级门禁退化为 diff 走查,防假完成的门反而更弱,也没回应诉求本体。 + - **方案 C(连知识体系按限界上下文重组目录 + CLI 增 `domain` 命令域)**:未选为本轮范围。需 `requiresCommands` 扩项、三仓同步、项目卡片全量迁移,且刚立的能力契约要求消费方先行兼容;留作 B 落地验证后的独立 change。 + - **保留文件名 `tdd-verification.md`**:未选。名字即规范的一部分,留着会持续把「先红仪式」当默认动作召回;已核实改名代价可控——`scripts/pack.mjs` 整目录拷贝 `norms`、`scripts/check-requires.mjs` 只扫命令键、`test/pack.test.mjs` 只断言目录与 skills 清单,**无程序化文件名引用**,14 处全为文档引用。 + - **主工作区直接改(不开 worktree)**:未选。本 checkout 被 `shadow-dev workflow link` 直通为活动产物(`workflow status` 的 `linked`/`resolved` 指向它),三宿主 `bind` 从这里复制技能;在 main 工作区逐文件编辑会让中途半成品随时可被重新绑定消费。L 级按 propose 规定走专属 workspace。 +- **执行期修订(apply 阶段发现,非静默跨级):** `worktree inspect` 建议 `create`(L 级),但 CLI 实现不支持「新建 change 尚未提交 brief」这一常态——`lib/domains/worktree.mjs` 的 `execute` 用 `git worktree add -b / ` 从基线派生工作树,且只向**主工作区**的 brief 回写 `branch`/`workflow.worktree`;此时 brief 仍是未跟踪文件,新工作树内不存在它,后续 `task set`/`review`/`commit` 一律 `BRIEF_NOT_FOUND`,人工拷贝则造成两份受管状态分裂。故本 change 改走 **inline 分支**执行,隔离手段换为:主工作区切到功能分支后**不在实现中途执行 `bind execute`**,宿主绑定刷新留到 review 通过后的 release 阶段(`workflow link` 直通本 checkout,中途重绑才会把半成品技能铺给三宿主)。worktree 对新立项 change 的缺口另立 change 在 `shadow-dev-cli` 上下文处置。 + +- **理由:** 你的基建已经能提供 TDD 原本负责的两件事——规格表达(不变量替代用例)与完成证据(观察点替代绿灯);把建模写进流程是净增益,同时唯一不可让的门(强度与评级匹配)原样保留,既有测试仍必须绿。 + +## 任务 + +### Phase 1 — 领域层定义 +- [x] 新增领域驱动规范 — `norms/domain-model.md` — 限界上下文/统一语言/聚合/领域不变量/观察点定义与术语表;brief「## 领域模型」段格式与必填规则(L 必填、M 选填归属域、S 免);项目 `shadow-docs/domain.md` 上下文地图模板;越界禁止条款(不改他上下文语义、不借建模扩面) +- [x] 新增跨项目知识卡 — `knowledge/domain-driven-shadow-dev.md` — 结论:AI 工作流的规格载体是领域模型与不变量,测试降级为证据手段之一;含执行约束、适用边界、验证方式,`domain: workflow-governance` + +### Phase 2 — 验证纪律改名与残留清理 +- [x] 规范改名并去 TDD 名分 — `norms/verification.md`(由 `norms/tdd-verification.md` 改名,原文件删除) — 节名与表格改为「不变量观察点」口径;进度 token `▶ [TDD]` → `▶ [verify]`;保留分级制、「强度与评级匹配不可议」「既有测试必须绿」「收口对账」三条不变 +- [x] 文档与规则引用清理 — `menu.md`, `CLAUDE.md`, `README.md`, `norms/signals.md`, `knowledge/bug-investigation.md`, `rules/iron-laws.md`, `rules/behavior.md` — 14 处路径与 `menu.md:10` 关键词去 TDD;`menu.md` 路由表加「域」语义说明并为两份新文件加路由;`behavior.md` §4 改不变量口径;`iron-laws` 新增 §7「不要在缺少领域模型的情况下开始 L 级实现」 +- [x] 宿主清单口径纠偏 — `.codex-plugin/plugin.json`, `.claude-plugin/plugin.json` — description/longDescription 去掉「TDD」、改为领域驱动 + 分级验证口径;keywords `tdd` → `domain-driven`;`.claude-plugin` 过期「4-phase … ship」改为五段式;**不动 `hooks` 字段与任何分发语义** + +### Phase 3 — 流程技能改造 +- [x] propose 增领域对齐轮 — `skills/shadow-dev-propose/SKILL.md` — 步骤 2/3 间插入「领域对齐」;正文模板加「## 领域模型」段;步骤 5 展示结果同步;`评级: X` token 位置与格式保持不变(CLI 正则依赖) +- [x] apply 验证门重写 — `skills/shadow-dev-apply/SKILL.md` — §1 载入 brief 领域模型;§5 按不变量组织验证(L:每条不变量一个观察点;M:触及的既有不变量保持成立 + 冒烟 + 既有测试绿;S:结构与引用扫描);删 55/56 行 TDD 表述;token 改 `▶ [verify]`;frontmatter description 已是「分级验证」,复核一致 +- [x] review 增模型一致性维 — `skills/shadow-dev-review/SKILL.md` — 新增第 10 维:实现是否引入未登记术语、破坏不变量、越界改动其他上下文;§8 措辞对齐不变量口径 +- [x] knowledge 双键路由 — `skills/shadow-dev-knowledge/SKILL.md` — 查询条件加限界上下文;输出格式加「所属域/上下游」;项目存在 `shadow-docs/domain.md` 时先读上下文地图 +- [x] hotfix 快车道对齐 — `skills/shadow-dev-hotfix/SKILL.md` — 「复现锁定」改为「被破坏不变量的可复现观察」,默认产物由 `test/<文件>` 改为复现步骤/观察记录;消除 74/101 行与 59 行的自相矛盾;L 级仍要求真实复现与回归 + +### Phase 4 — 自证与发版口径 +- [x] 版本 bump 与能力声明复核 — `package.json` — 6.6.0,与 `.claude-plugin`/`.codex-plugin` 三处对齐(`npm test` 有机检);`requiresCommands` 不扩项(本 change 不新增命令键);确认 `pack DIRS` 整目录拷贝下无需为改名增项 +- [x] 不变量观察点执行与收口 — `shadow-docs/changes/20261010-refactor-domain-driven-workflow/brief.md` — 真实跑并记录输出:`npm run check:requires`(差集为空)、`npm test`(install-cli + pack 全绿,既有测试保持绿)、`node scripts/pack.mjs` 产物扫描(`norms/verification.md` 在包内、`tdd-verification.md` 不在、8 skills 齐全)、死链与残留 grep 归零(`tdd-verification`、`▶ [TDD]`、`先红`)、拒绝路径观察点 `shadow-dev workflow plan --from .`(能力断言仍通过、指针不动) + +## 结果 + +- 实际耗时: ≈25 分钟(00:19–00:45;含 apply 阶段 3 次 node 139 段错误重试与一次 brief 重建) +- 验证: **L 级=每条不变量一个可追溯观察点**,按「不变量 → 观察点(真实命令 + 真实输出)」对账: + +| # | 不变量 | 观察点与真实输出 | +|---|--------|------------------| +| I1 | `change brief`:受管状态只由 CLI 写;`评级: X` 仍可被 CLI 正则提取;L 级 brief 含「## 领域模型」段且清单非空 | `shadow-dev worktree inspect` → `rating=L`(新正文段未挤掉 `/评级:\**\s*([SML])/` 的提取);`shadow-dev task list` → 12 项可解析、`task-1..task-12` 逐个 `task set` 回写;frontmatter 全程未手改(一次误改导致 JSON `Bad control character`,已按确定性路径 `rm` 未跟踪 brief 目录 + `change create/approve` 重建,重建后 `json.loads` 通过) | +| I2 | `Knowledge 卡片`:active 卡必须进 menu 路由;三键齐全;source 存在;深度不虚报 | 路径存在性扫描脚本 → 46 个 live 文件抽 25 个规范/资产引用、**死链 0**;`menu 路由含 norms/domain-model.md / knowledge/domain-driven-shadow-dev.md / norms/verification.md: True`;卡片八字段齐全且 `source` 指向的 brief 存在 `True`;`verified-depth` 诚实记 `unit`(runtime 证据待 CI,见证据缺口) | +| I3 | `规范引用一致性`:零残留旧文件名与旧 token | `grep -rn "tdd-verification\|▶ \[TDD\]\|⏸ \[TDD" --exclude-dir=.git --exclude-dir=shadow-docs --exclude-dir=dist . | wc -l` → **0**;残留 `TDD` 字样 8 处逐条人工判定均为历史叙述或负面条款(`verification.md` 沿革与裁决引文、`iron-laws` §7「取消 TDD 仪式后规格悬空」、卡片 keywords「TDD 替代」),无一是祈使式要求 | +| I4 | `分发产物`:声明与消费同源;8 skills;三处 manifest 版本一致;能力缺失响亮拒绝且指针不动 | `npm run check:requires` → `declared=29 skills-referenced=21 undeclared=[] cli-catalog=46 missing-in-cli=[] ✓`(propose 新增 `bind execute` 引用仍 ⊆ 声明集);`node --test test/pack.test.mjs` → **4/4 pass**(含三 manifest 版本对齐机检);`node scripts/pack.mjs` + `tar -tzf dist/shadow-dev-workflow-v6.6.0.tar.gz` → 包内 `norms/verification.md`、`norms/domain-model.md`、`knowledge/domain-driven-shadow-dev.md` 在列,`tdd-verification.md` **不在**,`SKILL.md` 计数=8,`package.json version=6.6.0`;拒绝路径 fixture(`requiresCommands` 追加 `nonexistent-domain.inspection`,隔离 `SHADOW_WORKFLOW_PREFIX=/private/tmp/sdd-prefix`)→ `workflow plan` `ok:true` 且 `missingCommands=['nonexistent-domain.inspection']`,`workflow execute` → `ARTIFACT_INCOMPATIBLE: artifact 6.6.0 requires commands this CLI 1.5.0 does not provide`,隔离前缀 `ls -A` 为空(**无半成品目录**),探针后 `workflow status` 仍 `current=6.5.1 / previous=6.5.0 / missingCommands=[]`(**指针不动**) | + +- **既有测试保持绿:11/11 全绿,缺口已关闭。** `node --test test/pack.test.mjs` → `# tests 4 # pass 4 # fail 0`(沙箱内);`node --test test/install-cli.test.mjs` → `ok 1..ok 7`、`# tests 7 # pass 7 # fail 0`(越权到有网络的环境运行,95 秒有界窗口内全部收敛;首轮 `npm test` 与 `workflow plan` 各遇一次 node 139,按 SGN-004 重试至收敛)。 + - 更正一处计划错误:原计划把 install-cli 证据缺口指向「CI run 链接」,但**本仓 `.github/workflows/` 只有 `archive-on-issue-close.yml`,没有测试工作流**——本机是唯一取证场所,不存在 CI 替代观察点。该事实已记入下方遗留项。 +- **交付**: 待 `issue execute`(需 `GITHUB_TOKEN`,本机 `GITHUB_TOKEN_REQUIRED`) +- 遗留与后续(明确标注未做与原因): + - **本仓上下文地图缺失**:`norms/domain-model.md` 的验证方式要求「知识卡 `domain:` 值能在项目上下文地图中找到」,而本仓尚无 `shadow-docs/domain.md`——该不变量在 workflow 仓自身处于**未满足**状态。原因:`shadow-docs/domain.md` 不在本 change 声明文件集内,且 CLI 无 `change amend --files` 能力(命令目录只有 `change create/approve/list`),中途扩面无法合规提交。后续 change 建本仓地图(`workflow-governance` / `plugin-distribution` / `shadow-dev-cli` 三上下文起步)并在项目仓各建一份。 + - **`norms/knowledge-cards.md` 未改**:propose 的知识评估曾把它列为候选(`domain` 升一等路由键),但任务清单未含该文件。约束已由 `norms/domain-model.md`「验证方式」与 `skills/shadow-dev-knowledge`「约束处理」落地,卡片规范本身的字段说明补写另立 change——记为**知识评估与任务清单的一次不一致**,review 已捕获。 + - **信号提案 2 条**(`shadow-docs/signals.md` 同样不在声明文件集内,先记在这里,由后续写回):① SGN-004 命中 +1(2026-10-10,`workflow plan`、`review plan`、`npm test` 三次 139/挂起,重试至收敛);② 新候选 negative/weight 2/depth runtime:「编辑 brief **正文**前必须先按 `\n---\n` 切出正文再定位锚点——frontmatter 的 `issuePlan.body` 是正文快照,含同名文本,全局 `replace(...,1)` 会把真换行插进 JSON 字符串造成 `Bad control character`」。 + - **本仓无 CI 测试门**:`.github/workflows/` 只有 `archive-on-issue-close.yml`,`npm test` 与 `npm run check:requires` 全靠本机手动执行——「验证强度与评级匹配」在缺 CI 时依赖执行者自证,是本仓的结构性弱点(属 `plugin-distribution`/`workflow-governance` 交界,另立 change 补 quality-gate 工作流)。 + - **CLI 工具缺口两处**(属 `shadow-dev-cli` 上下文,各自立项):worktree 流程不覆盖「未提交 brief 的新立项 change」;`change` 域无 amend 文件集能力,导致 review 阶段的信号写回与卡片规范补写都无法合规落地。 +- 过程记录(诚实): + - 一次自我破坏并已修:用 `str.replace(anchor, …, 1)` 给 brief 正文插「执行期修订」时,锚点命中的是 frontmatter 里 `issuePlan.body` 的同名快照,真换行进入 JSON 字符串 → `Bad control character`。修正方式=删除本会话新建的未跟踪 brief 目录后 `change create --body-file` 重建,并在脚本里改为「先按 `\n---\n` 切出正文再定位锚点」;后续所有 brief 正文编辑均走该切分。 + - apply 开工前 `worktree inspect` 建议 `create`,但按实现不可执行(未提交的 brief 不在派生工作树内),已在「决策」段记为执行期修订并改走 inline 分支;worktree 对新立项 change 的缺口留给 `shadow-dev-cli` 上下文的后续 change。 + - `issue plan` 的渲染器只取「动机/引用规范/决策/任务」四段,新增的「复杂度评级/领域模型/结果」段不进 GitHub issue——执行层既有行为,本 change 未改,`领域模型` 的可读性依赖 brief 文件本身。 + +## 知识评估 + +- **预期影响:** 新增 + 更新 +- **候选卡片:** `knowledge/domain-driven-shadow-dev.md`(新增)、`norms/verification.md`(由 `norms/tdd-verification.md` 原位改名并更新结论)、`norms/domain-model.md`(新增规范)、`norms/knowledge-cards.md`(`domain` 字段升为一等路由键,补执行约束)、`knowledge/bug-investigation.md`(关联知识链接改名) +- **理由:** 「规格来自领域模型与不变量、测试降级为证据手段」是跨项目长期有效的执行真相,不是单次变更过程;改名后的验证卡与新增文件必须进 `menu.md` 路由才允许 active,否则 review 的知识门禁判阻塞。`verified-depth` 在 release 写卡时按 Phase 4 真实观察点填写(预期 `unit`/`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/skills/shadow-dev-apply/SKILL.md b/skills/shadow-dev-apply/SKILL.md index a336a90..58b7a38 100644 --- a/skills/shadow-dev-apply/SKILL.md +++ b/skills/shadow-dev-apply/SKILL.md @@ -1,6 +1,6 @@ --- name: shadow-dev-apply -description: 开始执行 — 按 brief 的 Phase 执行任务,加载 active Knowledge,执行分级验证与依赖调度。触发词:开始执行、apply、实现、写代码。 +description: 开始执行 — 按 brief 的 Phase 执行任务,加载 active Knowledge 与领域不变量,执行分级验证与依赖调度。触发词:开始执行、apply、实现、写代码。 --- # Shadow Dev Apply — 执行 @@ -16,6 +16,8 @@ description: 开始执行 — 按 brief 的 Phase 执行任务,加载 active K 确定 change 名称,读取 brief 引用的 active Knowledge,将其执行约束加入任务上下文。 +**L 级另须载入 brief 的「## 领域模型」段**:限界上下文、统一语言增量与不变量清单进入任务上下文,不变量就是本阶段要逐条取证的验收对象(`norms/domain-model.md`、`rules/iron-laws.md` §7)。缺该段或清单为空 → 退回 propose 补齐后再开工,不得边写边补。 + 若卡片已 deprecated、source 不存在或 scope 与任务不匹配,停止并返回 propose/review 修正引用。 ### 2. 检查冲突 @@ -50,13 +52,15 @@ shadow-dev branch execute --name --confirm ### 5. 验证门禁 -按 brief 复杂度评级执行验证(`norms/tdd-verification.md` 分级制): +按 brief 复杂度评级执行验证(`norms/verification.md` 分级制)。验收对象是不变量,不是测试条数: -1. **L**:完整 TDD——写失败测试并确认失败 → 最小实现并确认通过 → 重构保持绿色。 -2. **M**:绿灯测试——写测试并通过,不强制先红。 +1. **L**:brief「## 领域模型」段的**每条不变量一个可追溯观察点**——真实命令 + 真实输出,必须覆盖拒绝路径与状态不变量(指针不动、无半成品残留、旧路径不可回退);本机取不到证据时写明证据缺口与替代观察点(CI run 链接)。 +2. **M**:冒烟命令 + 本次触及的既有不变量保持成立 + 既有测试保持绿 + 走查;默认不新建测试文件,触及既有契约时更新既有断言。 3. **S**:不创建测试文件,执行结构、引用、路由和残留扫描。 -**进度报告(非协商,`norms/tdd-verification.md`「进度可见性」):** 开工前报出两个数——测试总数 N 与任务总数 M(测试逐项列名);此后每完成一个单元立即输出一行进度线:测试 `▶ [TDD] n/N <测试名> red|green`、任务 `▶ [task] m/M done`。单步挂起(进程空转、外部命令超时、同一失败重复)先输出 `⏸ [TDD|task] <名称> 卡住:<现象与排查方向>` 再处理——用户靠进度线判断是否卡死,静默长跑视为违规。 +不为「凑证据」新建测试:确需长期钉住某条不变量才写,并在 brief 写明这条测试钉的是哪条(`rules/behavior.md` §2 反过度设计;S 级写测试同样 review 阻塞)。既有测试文件必须照常运行并通过——取消 TDD 不等于取消跑测试。 + +**进度报告(非协商,`norms/verification.md`「进度可见性」):** 开工前报数——测试总数 N(逐项列名)与任务总数 M;L 级另报不变量总数 I。此后每完成一个单元立即输出一行进度线:检查项 `▶ [verify] n/N <检查项> pass|fail`、不变量 `▶ [inv] i/I <不变量> observed|pending`、任务 `▶ [task] m/M done`。单步挂起(进程空转、外部命令超时、同一失败重复)先输出 `⏸ [verify|inv|task] <名称> 卡住:<现象与排查方向>` 再处理——用户靠进度线判断是否卡死,静默长跑视为违规。收口汇总行 `✅ N/N 全绿 · I/I 不变量有观察点`,数目与开工报数一致。 ### 6. 按 Phase 执行 diff --git a/skills/shadow-dev-hotfix/SKILL.md b/skills/shadow-dev-hotfix/SKILL.md index 7e4a7f0..f4b0405 100644 --- a/skills/shadow-dev-hotfix/SKILL.md +++ b/skills/shadow-dev-hotfix/SKILL.md @@ -4,7 +4,7 @@ description: 紧急修复快车道 — 线上/阻塞问题的最小修复直达 --- # Shadow Dev Hotfix — 紧急修复快车道 -只用于线上故障或阻塞级问题。省时间不省安全:复现确认、planHash 两段式确认、PR 合入三个铁门禁一个不减。复现确认的手段按复杂度分级(`norms/tdd-verification.md`),不是一律写测试。 +只用于线上故障或阻塞级问题。省时间不省安全:复现确认、planHash 两段式确认、PR 合入三个铁门禁一个不减。复现确认=取到「哪条不变量被破坏」的可复现观察,手段按复杂度分级(`norms/verification.md`),不是一律写测试。 **执行纪律:** AI 只负责推导、决策与审查 CLI 返回的结果。一切仓库与 GitHub 写操作一律经 `shadow-dev` CLI 完成;禁止原始 `git`/`gh` 写命令,禁止脚本旁路。运行测试与检查属于收集验证证据,允许直接执行,但结论必须引用真实输出。 @@ -56,8 +56,8 @@ mini 正文结构(复杂度评级按 norm 三要素判定——文案/配置/ ## 任务 ### Phase 1 -- [ ] 复现锁定 Bug — `test/<文件>` 或复现步骤记录 — L 用复现测试锁定(能稳定复现即可,2026-10-09 裁决后不强制先红仪式);S/M 以可重复手段复现(观察/查询/手动步骤) -- [ ] 修复并回归 — `<目标文件>` — 最小修复;按评级强度回归验证(L 复现测试转绿并留观察点,S/M 复现手段复验) +- [ ] 取不变量被破坏的观察 — 复现步骤与观察记录(必要时 `test/<文件>`) — L 用真实命令复现并记录观察点(2026-10-09 裁决:不强制先红仪式,也不默认新建测试文件);S/M 以可重复手段复现(观察/查询/手动步骤) +- [ ] 修复并回归 — `<目标文件>` — 最小修复;按评级强度回归验证(L 用同一观察点复验并留真实输出,S/M 复现手段复验) ## 结果 - 实际耗时: — @@ -71,7 +71,7 @@ mini 正文结构(复杂度评级按 norm 三要素判定——文案/配置/ ### 3. 修复(复现确认铁律,强度按分级) -**复现确认非协商**:修复前必须以可重复手段复现问题、确认根因不失准——**L** 级写自动化复现测试先红后绿;**M** 级绿灯测试回归;**S** 级(文案/配置/展示层)以人工复现步骤或观察记录复现并复验,**不强制创建测试文件**(`norms/tdd-verification.md` 分级制)。同一个 Bug 的复现、根因确认、修复、回归在同一上下文完成(`knowledge/bug-investigation.md`)。快车道不豁免进度可见性:开工报数(任务 M 项,有测试则另报测试 N 项)、逐完成一行 `▶ [TDD|task] …`、卡点先报 `⏸`(`norms/tdd-verification.md`「进度可见性」)。只修改 brief 声明的文件;发现波及面超出准入判定时停下,退回 propose 重新立项。 +**复现确认非协商**:修复前必须以可重复手段复现问题、说清被破坏的是哪条不变量、确认根因不失准——**L** 级用真实命令复现并留可追溯观察点(既有自动化测试能覆盖就复用它,不为快修临时造测试);**M** 级冒烟 + 既有测试保持绿复现复验;**S** 级(文案/配置/展示层)以人工复现步骤或观察记录复现并复验,**不强制创建测试文件**(`norms/verification.md` 分级制,2026-10-09 用户裁决取消先红仪式)。同一个 Bug 的复现、根因确认、修复、回归在同一上下文完成(`knowledge/bug-investigation.md`)。快车道不豁免进度可见性:开工报数(任务 M 项,有测试则另报测试 N 项)、逐完成一行 `▶ [verify|task] …`、卡点先报 `⏸`(`norms/verification.md`「进度可见性」)。只修改 brief 声明的文件;发现波及面超出准入判定时停下,退回 propose 重新立项。 ### 4. 收尾门禁(压缩串行) @@ -98,7 +98,7 @@ shadow-dev release execute --name --confirm ## 不可省清单 -- 复现确认先于修复(`norms/tdd-verification.md` 分级制:L 复现测试先红后绿,S/M 人工复现复验;验证强度与评级匹配非协商) +- 复现确认先于修复(`norms/verification.md` 分级制:先取到不变量被破坏的可复现观察;取消先红仪式见 2026-10-09 裁决;验证强度与评级匹配非协商) - 全部写操作 plan/execute 两段式 + `--confirm` - 显式文件清单,禁止 `git add .` - main 只接受 PR 合入 diff --git a/skills/shadow-dev-knowledge/SKILL.md b/skills/shadow-dev-knowledge/SKILL.md index 1a8d472..32b5b80 100644 --- a/skills/shadow-dev-knowledge/SKILL.md +++ b/skills/shadow-dev-knowledge/SKILL.md @@ -1,6 +1,6 @@ --- name: shadow-dev-knowledge -description: 知识库查询 — 根据任务域、关键词和 scope 匹配菜单路由,只拉取 active 规范和知识。被 propose 自动调用。 +description: 知识库查询 — 根据限界上下文、任务域、关键词和 scope 匹配菜单路由,只拉取 active 规范和知识。被 propose 自动调用。 --- # Shadow Dev Knowledge — 知识库查询 @@ -15,6 +15,7 @@ description: 知识库查询 — 根据任务域、关键词和 scope 匹配菜 从需求中提取: - 任务域:UI、API、数据库、性能、交互、Bug、Knowledge 治理等。 +- 限界上下文:项目 `shadow-docs/domain.md` 中登记的上下文名称;无地图时按 `norms/domain-model.md` 的判据先给出候选归属(只作查询键,不写入地图)。 - 关键词:业务词、技术词和故障现象。 - scope:涉及的目录、包、路由或业务模块。 @@ -23,7 +24,7 @@ description: 知识库查询 — 根据任务域、关键词和 scope 匹配菜 1. 读取通用 `menu.md`,得到通用 norms 和通用 Knowledge。 2. 如项目存在 `shadow-docs/menu.md`,追加项目路由。 3. 只读取菜单命中的文件,不全文扫描全部 Knowledge。 -4. Knowledge 必须同时综合 `domain`、`keywords` 和 `scope` 判断相关性。 +4. Knowledge 必须同时综合 `domain`、`keywords` 和 `scope` 判断相关性;`domain`(限界上下文)优先于技术域收敛范围,跨上下文的卡片只用于说明依赖,不作为本任务约束。 5. 默认只读取 `status: active`;deprecated 仅用于追溯替代关系。 6. 所有变更默认读取 `norms/code-style.md`。 @@ -56,6 +57,10 @@ verified: 2026-08-08 ```markdown ## Knowledge 匹配结果 +### 限界上下文 +- 归属: <上下文名> +- 上下游: <依赖方向与反腐边界> + ### 技术域与 scope - 技术域: blog, UI - scope: packages/wuh.site.next/app/post @@ -82,6 +87,7 @@ verified: 2026-08-08 - 每条适用约束必须进入 brief 的「引用规范」,记录路径、当前结论和 scope。 - 不遵循约束时必须在「决策」中说明理由。 - 新增 Knowledge 前按 `domain + keywords + scope` 查重,能更新现有卡片时不新增。 +- 命中卡片中的公开名词必须与 `norms/domain-model.md` 术语表及项目上下文地图一致;不一致时列为阻塞项返回 propose,不自行改词或改地图。 - 新组件和样式必须先确认项目组件库是否已有可复用实现。 **离场:** 完成时输出:`✅ [离场] shadow-dev-knowledge · 命中 N 条 active 卡片 · 返回 shadow-dev-propose` diff --git a/skills/shadow-dev-propose/SKILL.md b/skills/shadow-dev-propose/SKILL.md index 56f129a..855ef12 100644 --- a/skills/shadow-dev-propose/SKILL.md +++ b/skills/shadow-dev-propose/SKILL.md @@ -1,6 +1,6 @@ --- name: shadow-dev-propose -description: 新需求对齐 + 方案设计 — 从模糊需求到可执行 brief。触发词:新需求、提案、设计一下、讨论方案。 +description: 新需求对齐 + 领域建模 + 方案设计 — 从模糊需求到可执行 brief。触发词:新需求、提案、设计一下、讨论方案。 --- # Shadow Dev Propose — 需求对齐与方案设计 @@ -18,6 +18,8 @@ description: 新需求对齐 + 方案设计 — 从模糊需求到可执行 brie 结果进入方案输入。若 source 缺失、无菜单路由、scope 不明确或卡片冲突,先阻塞并处理,不把不可靠内容当作规范。 +项目存在 `shadow-docs/domain.md` 时一并读取上下文地图,确定任务所属限界上下文与上下游;没有该文件且项目明显含多个上下文时,把「建上下文地图」作为独立变更提示用户,不在本 change 顺手补。 + ### 2. 澄清需求 - 每轮只问一个问题。 @@ -25,11 +27,22 @@ description: 新需求对齐 + 方案设计 — 从模糊需求到可执行 brie - 存量修改先检索,列出文件清单让用户确认后再读。 - 需求清晰后才进入方案讨论。 -### 3. 比较方案 +### 3. 领域对齐 + +按 `norms/domain-model.md` 回答四个问题,答案写进 brief 的「## 领域模型」段: + +1. 本变更属于哪个**限界上下文**?是否同时踩进第二个上下文(是 → 建议拆 change)。 +2. **统一语言**有无增量?新出现的公开名词(命令键、字段、卡片标题)在该上下文内的唯一含义是什么。 +3. 哪个**聚合**的哪条**不变量**被这次改动威胁?它现在靠什么观察点守住。 +4. 是否要求别的上下文跟着变?那是**跨上下文依赖**,写进 brief 的前置/后继,不顺手改。 + +必填按评级:**L** 四项齐全且不变量清单非空(`rules/iron-laws.md` §7:缺段或清单为空即视为未开始);**M** 至少一行归属上下文 + 被触及的既有不变量;**S** 免填。 + +### 4. 比较方案 提出 2–3 个方案,分别说明做法、优势和代价;给出推荐及关键权衡。每个方案说明如何遵循或不适用已命中的 Knowledge。 -### 4. 收敛为 brief +### 5. 收敛为 brief 用户确认方案后,生成正文,再通过 deterministic CLI 创建并批准变更(`--body-file` 指向的临时正文文件是 AI 唯一需要准备的输入,其余一律由 CLI 落盘): @@ -47,10 +60,18 @@ shadow-dev change approve --name --confirm <为什么现在做> ## 复杂度评级 -- **评级:** S / M / L(按 norms/tdd-verification.md 三要素:契约变更 / 触及面 / 可发现性) +- **评级:** S / M / L(按 norms/verification.md 三要素:契约变更 / 触及面 / 可发现性) - **理由:** <对照三要素的具体判断> - **期望验证深度:** code-read | unit | runtime | field +## 领域模型 + +- **限界上下文:** <名称> — <一句话职责>。上游/下游 <关系>;反腐边界 <本上下文不做什么>。 +- **统一语言增量:** <新术语及其唯一含义>(无则写「无」) +- **聚合与不变量:** + - `<聚合根>`:<必须成立的断言>;<必须成立的断言> +- **领域事件:** <状态改变时刻及其流程后果> + ## 引用规范 - <卡片路径> - 当前结论: <结论> @@ -67,7 +88,7 @@ shadow-dev change approve --name --confirm ## 结果 - 实际耗时: — -- 验证: — +- 验证: —(L 级逐条对账:每条不变量 → 观察点命令与输出摘要,或标注证据缺口与替代观察点) ## 知识评估 - **预期影响:** 新增 / 更新 / 废弃 / 无需变更 @@ -79,9 +100,9 @@ propose 只做知识影响预评估,不创建或改写 Knowledge。发现代 **命名:** `YYYYMMDD-{type}-{kebab-slug}`。type 对应 GitHub issue label,可选值:`feature`、`fix`、`build`、`chore`、`docs`、`refactor`、`style`、`test`。每个 task 控制在 30 分钟内;有依赖的分 Phase。 -### 5. 展示结果 +### 6. 展示结果 -展示动机、复杂度评级(含理由与期望验证深度)、决策、任务阶段和知识影响预评估,提示下一步使用 `shadow-dev-apply`。评级随 propose 确认一并由用户批准;知识评估引用卡片更新时须写明 `verified-depth`。评级为 **L** 时同步提示并行选项:与其他变更并行或需要独立工作区时,apply 阶段将用 `shadow-dev worktree plan/execute` 创建专属 workspace,避免共享 checkout 的分支腾挪。所需能力由产物 `package.json.requiresCommands` 声明、CLI 在物化前断言;能力缺失时 apply 响亮阻塞,不静默降级。 +展示动机、复杂度评级(含理由与期望验证深度)、领域模型(含不变量清单)、决策、任务阶段和知识影响预评估,提示下一步使用 `shadow-dev-apply`。评级随 propose 确认一并由用户批准;知识评估引用卡片更新时须写明 `verified-depth`。评级为 **L** 时同步提示并行选项:与其他变更并行或需要独立工作区时,apply 阶段用 `shadow-dev worktree plan/execute` 创建专属 workspace,避免共享 checkout 的分支腾挪。**注意执行顺序**:worktree 从基线派生新工作树,此时尚未提交的 brief 不在其中(会 `BRIEF_NOT_FOUND`),因此新立项变更要先在共享 checkout 建分支并提交 brief,或本轮走 inline 分支并在 brief「决策」中写明隔离手段(例如中途不执行 `bind execute`)。所需能力由产物 `package.json.requiresCommands` 声明、CLI 在物化前断言;能力缺失时 apply 响亮阻塞,不静默降级。 GitHub Issue 必须先 plan、确认后 execute,禁止原始 `gh` 写命令。plan 把标题、正文、labels 和 planHash 持久化到 brief,execute 只需 `--name --confirm`。仓库自动从 `origin` remote 推导(`github.com[:/]owner/repo`),非 GitHub remote 或多仓库场景用 `change create --repository ` 显式指定。issue labels 从 change type 映射:feature→`feature`、fix→`fix`、build→`build`、chore→`chore`、docs→`docs`、refactor→`refactor`、style→`style`、test→`test`。 diff --git a/skills/shadow-dev-review/SKILL.md b/skills/shadow-dev-review/SKILL.md index 6e11745..596427d 100644 --- a/skills/shadow-dev-review/SKILL.md +++ b/skills/shadow-dev-review/SKILL.md @@ -1,6 +1,6 @@ --- name: shadow-dev-review -description: 质量门禁 — 验证实现、方案与 active Knowledge,并给出最终知识影响。触发词:代码审查、review、验收、verify。 +description: 质量门禁 — 验证实现、领域模型一致性、方案与 active Knowledge,并给出最终知识影响。触发词:代码审查、review、验收、verify。 --- # Shadow Dev Review — 质量门禁 @@ -27,7 +27,7 @@ shadow-dev review execute --name --conclusion --knowledg **机械门禁:** `review execute` 会检查 brief 中的任务清单——存在未完成任务时返回 `TASKS_NOT_COMPLETE` 并拒绝写入 `passed`。先通过 `task set` 确认所有任务真实完成,再执行 review。 -### 3. 七维检查 +### 3. 多维检查 1. **任务完成度:** checkbox 与实际任务一致。 2. **方案一致性:** 实现符合 brief 决策。 @@ -41,8 +41,9 @@ shadow-dev review execute --name --conclusion --knowledg - 最终结论是新增、更新、废弃或无需变更。 - 动作必须给出确定目标路径和理由。 - 知识卡更新须按 `norms/knowledge-cards.md` 补 `verified-depth` 与 `verified-scope`。 -8. **验证强度匹配:** 实际验证深度须达到 brief 复杂度评级的期望深度(S: diff 走查 / M: unit+走查 / L: runtime 可追溯观察点);不匹配退回,S 级过度测试同样指出。 +8. **验证强度与观察点对账:** 实际验证深度达到 brief 评级的期望深度(S: diff 走查 / M: 冒烟+既有测试绿+走查 / L: 每条不变量一个可追溯观察点);逐条核对 brief「领域模型」段的不变量是否都有观察点或已标注证据缺口。强度不足退回,**过度验证同样指出**(S 级写测试、为凑证据补断言)。 9. **信号写回:** 本次探索中命中或证伪的路径按 `norms/signals.md` 提案信号(创建 / 加分 / 退役),随 review 结论一并输出。 +10. **模型一致性:** 按 `norms/domain-model.md` 检查——实现是否引入未登记的公开名词(命令键、字段、卡片标题、术语);是否破坏了声明过的不变量;改动是否越出本 change 所属限界上下文去改其他上下文的语义(越界即阻塞,要求拆 change);`shadow-docs/domain.md` 与知识卡 `domain:` 值是否仍然一致。 以下情况阻塞: @@ -50,6 +51,9 @@ shadow-dev review execute --name --conclusion --knowledg - 候选卡片缺少 source、scope、verified。 - active 卡片没有 menu 路由。 - 实现违反 active Knowledge 且 brief 未说明有意变更。 +- L 级 brief 缺「## 领域模型」段或不变量清单为空(`rules/iron-laws.md` §7)。 +- 不变量既无观察点也未标注证据缺口,却声称完成。 +- 引入新的公开名词而未登记(brief 术语增量或上下文地图)。 ## 审查报告 @@ -62,6 +66,8 @@ shadow-dev review execute --name --conclusion --knowledg ### 正确性: <结果> ### 代码质量: <结果> ### 验证: <命令与结果> +### 观察点对账: i/I(缺观察点且未标注缺口的条目列出) +### 模型一致性: 通过 / 阻塞 ### Knowledge 评估 - 结果: 新增 / 更新 / 废弃 / 无需变更 - 目标: <卡片路径或无> 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}`)