agentic-flow-kit 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,93 +1,93 @@
1
- # agentic-flow-kit
2
-
3
- AI-Native 闭环工作流 + wiki 知识层脚手架。`flow-kit init` 一条命令为新项目装好整套项目级 AI 工作流引擎:8 个阶段命令、3 个子智能体角色契约、git 门禁钩子(闭环配对 / 敏感信息扫描 / 条件构建 / 规则面预算)、kb 跨语料检索、workflow 实时看板、intents→specs→plans→incidents 文档闭环、wiki 知识库三件套。
4
-
5
- **装完即自包含**:引擎文件拷贝进项目(零 npm 运行时依赖,Node ≥ 18 即可跑),clone 后只差 `git config core.hooksPath .githooks`——init 会自动挂好。
6
-
7
- ## English
8
-
9
- agentic-flow-kit installs a self-contained AI workflow into a repository: stage commands, role contracts, git hooks, and an intent → spec → plan → incident trail. After `init`, the project does not need this package at runtime. Node ≥ 18 is enough.
10
-
11
- The product is the hook, not another prompt pack. Five rules always block a bad push: paired docs, checked acceptance, a confirmation ledger, secret scanning, and the managed/owned ledger. Other hygiene checks are warnings, and a fresh install sets `audit: false` so only those five stay in the way. Omit `audit` on an older ledger and the full check set stays on.
12
-
13
- Hosts: `zcode`, `opencode`, `trae`, `omp`, `claude`, `cursor`, `codex`. Claude Code loads `.claude/commands` and `.claude/agents`. Cursor loads `.cursor/commands`. Codex reads the repo `AGENTS.md` and `.codex/skills/flow-kit`; the `.codex/commands` files are the same thin forwards for explicit reference.
14
-
15
- ```bash
16
- npx agentic-flow-kit init --stack node --hosts claude,cursor
17
- npx agentic-flow-kit doctor
18
- ```
19
-
20
- ## 60 秒路径
21
-
22
- ```bash
23
- npx agentic-flow-kit init --stack node --hosts claude
24
- # 按 .agents/commands/plan.md 写一份 L1 intent 与同名 plan(状态先保持 draft)
25
- node .agents/scripts/confirm-doc.mjs workflow/intents/<文件>.md # 终端里键入「可以」
26
- # 改代码,git add,然后提交。配对没齐、验收没勾、确认指纹对不上,钩子会拒绝这次提交。
27
- ```
28
-
29
- ## 五条硬规则
30
-
31
- 新装的 `kit.json` 写 `audit: false` 与 `policyVersion: 2`:check-loop 只阻断配对、验收勾验、确认留痕,外加发版提交树上仍未收口的文档。敏感信息在 pre-commit,双源和台账在 doctor / sync。`audit` 缺省(升级来的旧台账)保持全量卫生警告。本仓库显式 `audit: true`。豁免日期只在 `.agents/scripts/policy.mjs` 的 `policyVersion` 表里改。
32
-
33
- 可选红线用 `add-gate`:`dotnet-ca`、`node-layer`、`py-import`、`generated-readonly`。约定见 `modules/gates/README.md`。
34
-
35
- ## 快速开始
36
-
37
- ```bash
38
- cd <你的项目根>
39
- npx agentic-flow-kit init # 交互模式:宿主 → 技术栈 → 端口 → 显式确认安装
40
- # 或直接给参数
41
- npx agentic-flow-kit init --stack node --hosts zcode,opencode
42
-
43
- # 体检(布局 / git 钩子 / managed 台账 / 索引漂移 / check-loop)
44
- npx agentic-flow-kit doctor
45
-
46
- # 包出新版后升级(未改动→覆盖;本地已改→跳过并报告,--force 才覆盖;owned 永不触碰)
47
- npx agentic-flow-kit sync
48
-
49
- # 后补宿主 / 装门禁(dotnet-ca:Clean Architecture 红线守卫,装后归项目)
50
- npx agentic-flow-kit add-host opencode
51
- npx agentic-flow-kit add-gate dotnet-ca
52
- ```
53
-
54
- | 选项 | 说明 |
55
- |------|------|
56
- | `--stack` | `dotnet` / `node` / `python` / `go` / `none`(默认 none)——**门禁配置三处**:commit-check 条件编译检查(暂存对应扩展名时 commit 前自动构建)+ **质量检测**(`checks`:lint / 类型检查 / vet 等秒级确定性检查,存在对应配置文件(tsconfig / eslint / ruff / go.mod 等)才启用,没有则自动跳过;测试不放提交门——关单在 test.md 阶段门)、settings.json 自检验命令权限、AGENTS.md「项目适配区」命令预填 |
57
- | `--hosts` | `zcode` / `opencode` / `trae` / `omp` / `claude` / `cursor` / `codex` 逗号多选(默认 zcode)。zcode/omp 为本地配置,自动进 .gitignore。Claude Code 加载 `.claude/commands` 与 `.claude/agents`;Cursor 加载 `.cursor/commands`;Codex 读仓库根 `AGENTS.md` 与 `.codex/skills/`,`.codex/commands` 是同形薄转发 |
58
- | `--board-port` | workflow 看板端口(默认 8933) |
59
- | `--dir` | 目标项目根(默认当前目录) |
60
- | `--force` | 覆盖已存在的同名文件(默认保守跳过) |
61
-
62
- init 不问项目名 / 描述:`AGENTS.md` 只生成「AI工作流 + Wiki」两章骨架 + 「项目适配区」;技术栈经 `--stack` 传入只用于门禁配置(编译检查与自检验命令初值),密钥白名单等其余细节在 `.agents/hooks/commit-check.config.json` 里按项目填。
63
-
64
- ## 装了什么
65
-
66
- ```
67
- .agents/ 阶段命令×8 / 角色契约×3 / 门禁钩子 / 引擎脚本(检索·索引·看板·预算·聚合)/ 技能×2 / settings.json / kit.json 台账
68
- .githooks/ pre-commit(通用门禁编排+项目门禁挂载点)/ pre-push(闭环断档)/ commit-msg / post-commit / pre-merge-commit
69
- workflow/ intents/specs/plans/incidents 模板×4 + README 协议 + 台账三件(delegations/papercuts/regression-checklist)
70
- wiki/ INDEX 骨架 + 知识沉淀总览 + drafts-archive
71
- AGENTS.md 项目规范骨架(项目段占位待填)
72
- modules/gates/ 可选门禁模块(dotnet-ca:Clean Architecture 参考实现,装进项目后归项目)
73
- ```
74
-
75
- **两态文件模型**:`.agents/kit.json` 记录 managed(引擎件,随包升级)与 owned(项目内容,永不覆盖)清单及 sha256;`doctor` 会校验漂移。已存在的文件 init 保守跳过(`--force` 覆盖)。
76
-
77
- **升级语义(sync)**:managed 文件三方比对(台账 sha / 磁盘 sha / 新版渲染 sha)——未改动 → 直接覆盖新版;本地已改 → 跳过并报告(`--force` 才覆盖,git diff 自查差异;跳过件的台账保持包侧基线,后续每次 sync 持续报告,不会在下次升级被静默覆盖——消除报告的办法:`--force` 覆盖,或把本地内容改回与新版一致);改动恰好等于新版 → 视为已最新。包内新增文件自动安装;包内已删文件仅报告不删盘;缺失的 managed 文件自动恢复。INDEX / wiki 看板等生成器目标不比对 sha,收尾重跑生成器走锚点重写。门禁模块(add-gate 装入)归项目所有,sync 永不覆盖。add-host 对已存在的宿主文件保守跳过且不入台账——后续 sync 不升级(仅报告「已存在未入台账」),要纳入包管理用 `--force` 覆盖。owned 件归项目所有、sync 永不覆盖;其台账哈希仅记账不约束,sync 每次按盘面自愈刷新(手改后无须手工对账),add-gate 接线 local-pre-commit 后同步刷新其记账。
78
-
79
- ## 设计原则
80
-
81
- - **权威单源**:角色行为只在 `.agents/roles/`,流程只在 `.agents/commands/`,宿主适配层是纯转发薄层——换宿主不换流程。
82
- - **门禁即 git 钩子**:不依赖任何 agent 客户端自觉;项目专属门禁挂 `.agents/hooks/local-pre-commit`。
83
- - **中文优先**:文档协议、命令、检查输出全中文;frontmatter 受限子集供机器断言。
84
- - 抽取自真实项目长期运转的引擎(某真实项目),dogfooding 是后续路线(sync 升级 / add-host / add-gate / npm 发布)的一部分。
85
- - **威胁模型(2026-09-27 init-p1-batch)**:勿在不可信仓库运行 `init` / `doctor` / `sync`——三命令在执行目标侧 `.agents/scripts/` 脚本(生成器 / 校验器)前有供应链防线:脚本内容与包源渲染值(LF 归一 sha)一致才执行,失配即显式跳过提示,不执行不下结论。**防线不覆盖 git 钩子面**:预植的 `.githooks/`、`.agents/hooks/` 仍会被保留挂载,并在你自己的首次 git 操作时执行——不可信仓库请先审查/清除预置件再 init。
86
-
87
- ## 当前能力(已发布 0.8.0)
88
-
89
- 版本沿革写在 `CHANGELOG.md`。0.8.0 已包含:init / doctor / sync / add-host / add-gate、四套技术栈门禁初值、宿主薄适配、确认门、check-loop 16 项、看板端口上探、opencode 命令 `wf-` 前缀、pre-commit managed 台账快检。
90
-
91
- 仓库里还没打进版本号的增量:`audit` 档(新装默认 false)、`policyVersion`、检查 17(发版树上的未收口文档)、检查 18(委派台账对账)、检查 19(逐阶段审计)、逐阶段前置门(起草门 / 确认门 / done 前置门)、claude / cursor / codex 薄适配、`node-layer` / `py-import` / `generated-readonly` 三个可选门禁、fresh init 冒烟。效果数字等有外部仓库跑过再记,不在这次。
92
-
93
- 活跃层里 2026-09-23 至 09-28 的 approved / open 文档没有在这次改成 done 或 closed。那些文件的确认指纹绑着当时的正文,关单要逐份 `confirm-doc`。
1
+ # agentic-flow-kit
2
+
3
+ AI-Native 闭环工作流 + wiki 知识层脚手架。`flow-kit init` 一条命令为新项目装好整套项目级 AI 工作流引擎:8 个阶段命令、3 个子智能体角色契约、git 门禁钩子(闭环配对 / 敏感信息扫描 / 条件构建 / 规则面预算)、kb 跨语料检索、workflow 实时看板、intents→specs→plans→incidents 文档闭环、wiki 知识库三件套。
4
+
5
+ **装完即自包含**:引擎文件拷贝进项目(零 npm 运行时依赖,Node ≥ 18 即可跑),clone 后只差 `git config core.hooksPath .githooks`——init 会自动挂好。
6
+
7
+ ## English
8
+
9
+ agentic-flow-kit installs a self-contained AI workflow into a repository: stage commands, role contracts, git hooks, and an intent → spec → plan → incident trail. After `init`, the project does not need this package at runtime. Node ≥ 18 is enough.
10
+
11
+ The product is the hook, not another prompt pack. Five rules always block a bad push: paired docs, checked acceptance, a confirmation ledger, secret scanning, and the managed/owned ledger. Other hygiene checks are warnings, and a fresh install sets `audit: false` so only those five stay in the way. Omit `audit` on an older ledger and the full check set stays on.
12
+
13
+ Hosts: `zcode`, `opencode`, `trae`, `omp`, `claude`, `cursor`, `codex`. Claude Code loads `.claude/commands` and `.claude/agents`. Cursor loads `.cursor/commands`. Codex reads the repo `AGENTS.md` and `.codex/skills/flow-kit`; the `.codex/commands` files are the same thin forwards for explicit reference.
14
+
15
+ ```bash
16
+ npx agentic-flow-kit init --stack node --hosts claude,cursor
17
+ npx agentic-flow-kit doctor
18
+ ```
19
+
20
+ ## 60 秒路径
21
+
22
+ ```bash
23
+ npx agentic-flow-kit init --stack node --hosts claude
24
+ # 按 .agents/commands/plan.md 写一份 L1 intent 与同名 plan(状态先保持 draft)
25
+ node .agents/scripts/confirm-doc.mjs workflow/intents/<文件>.md # 终端里键入「可以」
26
+ # 改代码,git add,然后提交。配对没齐、验收没勾、确认指纹对不上,钩子会拒绝这次提交。
27
+ ```
28
+
29
+ ## 五条硬规则
30
+
31
+ 新装的 `kit.json` 写 `audit: false` 与 `policyVersion: 2`:check-loop 只阻断配对、验收勾验、确认留痕,外加发版提交树上仍未收口的文档。敏感信息在 pre-commit,双源和台账在 doctor / sync。`audit` 缺省(升级来的旧台账)保持全量卫生警告。本仓库显式 `audit: true`。豁免日期只在 `.agents/scripts/policy.mjs` 的 `policyVersion` 表里改。
32
+
33
+ 可选红线用 `add-gate`:`dotnet-ca`、`node-layer`、`py-import`、`generated-readonly`。约定见 `modules/gates/README.md`。
34
+
35
+ ## 快速开始
36
+
37
+ ```bash
38
+ cd <你的项目根>
39
+ npx agentic-flow-kit init # 交互模式:宿主 → 技术栈 → 端口 → 显式确认安装
40
+ # 或直接给参数
41
+ npx agentic-flow-kit init --stack node --hosts zcode,opencode
42
+
43
+ # 体检(布局 / git 钩子 / managed 台账 / 索引漂移 / check-loop)
44
+ npx agentic-flow-kit doctor
45
+
46
+ # 包出新版后升级(未改动→覆盖;本地已改→跳过并报告,--force 才覆盖;owned 永不触碰)
47
+ npx agentic-flow-kit sync
48
+
49
+ # 后补宿主 / 装门禁(dotnet-ca:Clean Architecture 红线守卫,装后归项目)
50
+ npx agentic-flow-kit add-host opencode
51
+ npx agentic-flow-kit add-gate dotnet-ca
52
+ ```
53
+
54
+ | 选项 | 说明 |
55
+ |------|------|
56
+ | `--stack` | `dotnet` / `node` / `python` / `go` / `none`(默认 none)——**门禁配置三处**:commit-check 条件编译检查(暂存对应扩展名时 commit 前自动构建)+ **质量检测**(`checks`:lint / 类型检查 / vet 等秒级确定性检查,存在对应配置文件(tsconfig / eslint / ruff / go.mod 等)才启用,没有则自动跳过;测试不放提交门——关单在 test.md 阶段门)、settings.json 自检验命令权限、AGENTS.md「项目适配区」命令预填 |
57
+ | `--hosts` | `zcode` / `opencode` / `trae` / `omp` / `claude` / `cursor` / `codex` 逗号多选(默认 zcode)。zcode/omp 为本地配置,自动进 .gitignore。Claude Code 加载 `.claude/commands` 与 `.claude/agents`;Cursor 加载 `.cursor/commands`;Codex 读仓库根 `AGENTS.md` 与 `.codex/skills/`,`.codex/commands` 是同形薄转发 |
58
+ | `--board-port` | workflow 看板端口(默认 8933) |
59
+ | `--dir` | 目标项目根(默认当前目录) |
60
+ | `--force` | 覆盖已存在的同名文件(默认保守跳过) |
61
+
62
+ init 不问项目名 / 描述:`AGENTS.md` 只生成「AI工作流 + Wiki」两章骨架 + 「项目适配区」;技术栈经 `--stack` 传入只用于门禁配置(编译检查与自检验命令初值),密钥白名单等其余细节在 `.agents/hooks/commit-check.config.json` 里按项目填。
63
+
64
+ ## 装了什么
65
+
66
+ ```
67
+ .agents/ 阶段命令×8 / 角色契约×3 / 门禁钩子 / 引擎脚本(检索·索引·看板·预算·聚合)/ 技能×2 / settings.json / kit.json 台账
68
+ .githooks/ pre-commit(通用门禁编排+项目门禁挂载点)/ pre-push(闭环断档)/ commit-msg / post-commit / pre-merge-commit
69
+ workflow/ intents/specs/plans/incidents 模板×4 + README 协议 + 台账三件(delegations/papercuts/regression-checklist)
70
+ wiki/ INDEX 骨架 + 知识沉淀总览 + drafts-archive
71
+ AGENTS.md 项目规范骨架(项目段占位待填)
72
+ modules/gates/ 可选门禁模块(dotnet-ca:Clean Architecture 参考实现,装进项目后归项目)
73
+ ```
74
+
75
+ **两态文件模型**:`.agents/kit.json` 记录 managed(引擎件,随包升级)与 owned(项目内容,永不覆盖)清单及 sha256;`doctor` 会校验漂移。已存在的文件 init 保守跳过(`--force` 覆盖)。
76
+
77
+ **升级语义(sync)**:managed 文件三方比对(台账 sha / 磁盘 sha / 新版渲染 sha)——未改动 → 直接覆盖新版;本地已改 → 跳过并报告(`--force` 才覆盖,git diff 自查差异;跳过件的台账保持包侧基线,后续每次 sync 持续报告,不会在下次升级被静默覆盖——消除报告的办法:`--force` 覆盖,或把本地内容改回与新版一致);改动恰好等于新版 → 视为已最新。包内新增文件自动安装;包内已删文件仅报告不删盘;缺失的 managed 文件自动恢复。INDEX / wiki 看板等生成器目标不比对 sha,收尾重跑生成器走锚点重写。门禁模块(add-gate 装入)归项目所有,sync 永不覆盖。add-host 对已存在的宿主文件保守跳过且不入台账——后续 sync 不升级(仅报告「已存在未入台账」),要纳入包管理用 `--force` 覆盖。owned 件归项目所有、sync 永不覆盖;其台账哈希仅记账不约束,sync 每次按盘面自愈刷新(手改后无须手工对账),add-gate 接线 local-pre-commit 后同步刷新其记账。
78
+
79
+ ## 设计原则
80
+
81
+ - **权威单源**:角色行为只在 `.agents/roles/`,流程只在 `.agents/commands/`,宿主适配层是纯转发薄层——换宿主不换流程。
82
+ - **门禁即 git 钩子**:不依赖任何 agent 客户端自觉;项目专属门禁挂 `.agents/hooks/local-pre-commit`。
83
+ - **中文优先**:文档协议、命令、检查输出全中文;frontmatter 受限子集供机器断言。
84
+ - 抽取自真实项目长期运转的引擎(某真实项目),dogfooding 是后续路线(sync 升级 / add-host / add-gate / npm 发布)的一部分。
85
+ - **威胁模型(2026-09-27 init-p1-batch)**:勿在不可信仓库运行 `init` / `doctor` / `sync`——三命令在执行目标侧 `.agents/scripts/` 脚本(生成器 / 校验器)前有供应链防线:脚本内容与包源渲染值(LF 归一 sha)一致才执行,失配即显式跳过提示,不执行不下结论。**防线不覆盖 git 钩子面**:预植的 `.githooks/`、`.agents/hooks/` 仍会被保留挂载,并在你自己的首次 git 操作时执行——不可信仓库请先审查/清除预置件再 init。
86
+
87
+ ## 当前能力(已发布 0.8.0)
88
+
89
+ 版本沿革写在 `CHANGELOG.md`。0.8.0 已包含:init / doctor / sync / add-host / add-gate、四套技术栈门禁初值、宿主薄适配、确认门、check-loop 16 项、看板端口上探、opencode 命令 `wf-` 前缀、pre-commit managed 台账快检。
90
+
91
+ 仓库里还没打进版本号的增量:`audit` 档(新装默认 false)、`policyVersion`、检查 17(发版树上的未收口文档)、检查 18(委派台账对账)、检查 19(逐阶段审计)、逐阶段前置门(起草门 / 确认门 / done 前置门)、claude / cursor / codex 薄适配、`node-layer` / `py-import` / `generated-readonly` 三个可选门禁、fresh init 冒烟。效果数字等有外部仓库跑过再记,不在这次。
92
+
93
+ 活跃层里 2026-09-23 至 09-28 的 approved / open 文档没有在这次改成 done 或 closed。那些文件的确认指纹绑着当时的正文,关单要逐份 `confirm-doc`。
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "name": "agentic-flow-kit",
3
- "version": "0.9.0",
4
- "description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
5
- "type": "module",
6
- "scripts": {
7
- "test": "node src/run-tests.mjs",
8
- "prepack": "node -e \"require('fs').rmSync('templates/_agents/cache',{recursive:true,force:true})\" && node scripts/check-pack-eol.mjs"
9
- },
10
- "bin": {
11
- "flow-kit": "bin/flow-kit.mjs"
12
- },
13
- "files": [
14
- "bin",
15
- "src",
16
- "templates",
17
- "modules",
18
- "README.md",
19
- "!templates/_agents/cache",
20
- "!templates/_agents/cache/**"
21
- ],
22
- "engines": {
23
- "node": ">=18.0.0"
24
- },
25
- "keywords": [
26
- "ai-native",
27
- "workflow",
28
- "agent",
29
- "scaffold",
30
- "cli",
31
- "wiki",
32
- "check-loop",
33
- "closed-loop"
34
- ],
35
- "license": "MIT",
36
- "private": false,
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/zerosloney/agentic-flow-kit.git"
40
- },
41
- "bugs": {
42
- "url": "https://github.com/zerosloney/agentic-flow-kit/issues"
43
- },
44
- "homepage": "https://github.com/zerosloney/agentic-flow-kit#readme"
45
- }
1
+ {
2
+ "name": "agentic-flow-kit",
3
+ "version": "0.9.1",
4
+ "description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node src/run-tests.mjs",
8
+ "prepack": "node -e \"require('fs').rmSync('templates/_agents/cache',{recursive:true,force:true})\" && node scripts/check-pack-eol.mjs"
9
+ },
10
+ "bin": {
11
+ "flow-kit": "bin/flow-kit.mjs"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "src",
16
+ "templates",
17
+ "modules",
18
+ "README.md",
19
+ "!templates/_agents/cache",
20
+ "!templates/_agents/cache/**"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "keywords": [
26
+ "ai-native",
27
+ "workflow",
28
+ "agent",
29
+ "scaffold",
30
+ "cli",
31
+ "wiki",
32
+ "check-loop",
33
+ "closed-loop"
34
+ ],
35
+ "license": "MIT",
36
+ "private": false,
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/zerosloney/agentic-flow-kit.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/zerosloney/agentic-flow-kit/issues"
43
+ },
44
+ "homepage": "https://github.com/zerosloney/agentic-flow-kit#readme"
45
+ }
@@ -53,7 +53,7 @@ next: 按阶段路由
53
53
  | 级别 | 改什么 | 流程 |
54
54
  |------|--------|------|
55
55
  | L0 | 文档/样式微调(无行为影响) | 直接 commit,豁免 intent |
56
- | L1 | **实现级改动**(未命中 L2/L3) | 快车道:液态草稿(.zcode/drafts) $\rightarrow$ 实现 $\rightarrow$ 一键固化(`solidify-task.mjs`) |
56
+ | L1 | **实现级改动**(未命中 L2/L3) | 快车道:液态草稿(.zcode/drafts) $\rightarrow$ 实现 $\rightarrow$ 固化(`solidify-task.mjs`)——用户确认后带 `--delegated "<原话>"` 执行;无确认来源只迁移不落账 |
57
57
  | L2 | **规则 / 契约**(一处改、多处依赖) | 入口文档(intent / incident) + spec + plan 三件套 |
58
58
  | L3 | **数据与运行时结构**(schema / 迁移 SQL / DI 链 / 认证与中间件管线) | 同 L2 + spec 新会话独立复核 |
59
59
 
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // pre-push.test.mjs — 加固门目标分支判定 fixture 套件(2026-10-01 v09-review-defects)
3
+ // 真仓端到端:临时仓库挂载真实 pre-push,.agents/scripts/check-loop.sh 以桩脚本记录 argv。
4
+ // 断言三态 refspec:
5
+ // main→main → argv 含 --hardening(加固门)
6
+ // experiment/x→main → argv 含 --hardening(修复回归:直推漏口)
7
+ // experiment/y→experiment/y → argv 不含 --hardening(advisory)
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { spawnSync } from 'node:child_process';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
15
+ const PRE_PUSH = path.join(SCRIPT_DIR, '..', '..', '_githooks', 'pre-push');
16
+
17
+ let pass = 0;
18
+ let fail = 0;
19
+ const check = (desc, cond, detail = '') => {
20
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
21
+ else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
22
+ };
23
+
24
+ const git = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
25
+ const run = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
26
+
27
+ const setup = () => {
28
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'prepush-test-'));
29
+ const remote = path.join(root, 'remote.git');
30
+ fs.mkdirSync(path.join(root, '.githooks'), { recursive: true });
31
+ fs.mkdirSync(path.join(root, '.agents', 'scripts'), { recursive: true });
32
+ // 挂载真实 pre-push(装户同款),stub 替换 check-loop 记录 argv
33
+ fs.copyFileSync(PRE_PUSH, path.join(root, '.githooks', 'pre-push'));
34
+ fs.writeFileSync(path.join(root, '.agents', 'scripts', 'check-loop.sh'), '#!/bin/sh\necho "$@" >> .stub-args\nexit 0\n');
35
+ fs.writeFileSync(path.join(root, 'a.txt'), 'a\n');
36
+ git(root, ['init', '-q']);
37
+ git(root, ['config', 'user.email', 't@t']);
38
+ git(root, ['config', 'user.name', 't']);
39
+ git(root, ['config', 'core.hooksPath', '.githooks']);
40
+ git(root, ['add', 'a.txt']);
41
+ git(root, ['commit', '-q', '-m', 'init']);
42
+ git(root, ['init', '-q', '--bare', remote]);
43
+ git(root, ['remote', 'add', 'origin', remote]);
44
+ return root;
45
+ };
46
+ const stubArgs = (root) => {
47
+ const p = path.join(root, '.stub-args');
48
+ return fs.existsSync(p) ? fs.readFileSync(p, 'utf8').split(/\n/).filter(Boolean) : [];
49
+ };
50
+
51
+ const root = setup();
52
+ const w = (f, c) => fs.writeFileSync(path.join(root, f), c);
53
+
54
+ // 1) main → main:应含 --hardening
55
+ git(root, ['checkout', '-q', '-b', 'main']);
56
+ let r = run(root, ['push', 'origin', 'HEAD:main']);
57
+ check('main→main 推送成功', r.status === 0, `${r.stdout}${r.stderr}`);
58
+ check('main→main argv 含 --hardening', (stubArgs(root)[0] || '').includes('--hardening'), stubArgs(root).join('|'));
59
+
60
+ // 2) experiment/x → main(回归漏口):本地分支在 experiment,目标 main → 必须含 --hardening
61
+ git(root, ['checkout', '-q', '-b', 'experiment/x']);
62
+ w('b.txt', 'b\n');
63
+ git(root, ['add', 'b.txt']);
64
+ git(root, ['commit', '-q', '-m', 'x']);
65
+ r = run(root, ['push', 'origin', 'HEAD:main']);
66
+ check('experiment/x→main 推送成功', r.status === 0, `${r.stdout}${r.stderr}`);
67
+ check('experiment/x→main argv 含 --hardening(直推漏口修复)', (stubArgs(root)[1] || '').includes('--hardening'), stubArgs(root).join('|'));
68
+
69
+ // 3) experiment/y → experiment/y:目标在探索泳道 → 不含 --hardening
70
+ git(root, ['fetch', '-q', 'origin']);
71
+ git(root, ['branch', 'experiment/y', 'origin/main']);
72
+ git(root, ['checkout', '-q', 'experiment/y']);
73
+ w('c.txt', 'c\n');
74
+ git(root, ['add', 'c.txt']);
75
+ git(root, ['commit', '-q', '-m', 'y']);
76
+ r = run(root, ['push', 'origin', 'HEAD:experiment/y']);
77
+ check('experiment→experiment 推送成功', r.status === 0, `${r.stdout}${r.stderr}`);
78
+ const line3 = stubArgs(root)[2] || '';
79
+ check('experiment→experiment argv 不含 --hardening', !line3.includes('--hardening'), stubArgs(root).join('|'));
80
+
81
+ console.log(`\n${fail ? `❌ ${fail} failed` : '✅ all passed'}(${pass} passed)`);
82
+ process.exit(fail ? 1 : 0);
@@ -1,101 +1,107 @@
1
1
  #!/usr/bin/env node
2
- // solidify-task: 将 L1 液态草稿一次性固化为正式 workflow 文档(迁移 + 批量确认 + 索引更新)
3
- // 用法:node .agents/scripts/solidify-task.mjs --topic "主题" [--root <仓库根>]
2
+ // solidify-task: 将 L1 液态草稿固化为正式 workflow 文档(迁移 + 可选确认 + 索引更新)
3
+ // 用法:
4
+ // node .agents/scripts/solidify-task.mjs --topic <主题> [--root <仓库根>]
5
+ // # 只迁移 + 更新索引,绝不写确认台账;末尾打印确认指引
6
+ // node .agents/scripts/solidify-task.mjs --topic <主题> --delegated "<用户原话>"
7
+ // # 迁移后,以调用方显式传入的用户原话逐份 confirm-doc --delegated(quote 原样转发)
8
+ // node .agents/scripts/solidify-task.mjs --topic <主题> --auto
9
+ // # 迁移后,逐份 confirm-doc --auto(trust-mode=Trusted 才放行;Strict 下被拒并计数失败)
10
+ // 确认门契约(incidents/2026-10-01-v09-review-defects):--delegated 的 quote 必须来自用户在对话内的
11
+ // 明确原话,由调用方显式传入——本脚本永不编造用户话术。无确认来源时只迁移不落账,末尾打印指引。
4
12
  import fs from 'node:fs';
5
13
  import path from 'node:path';
6
14
  import { spawnSync } from 'node:child_process';
7
15
  import { fileURLToPath } from 'node:url';
8
16
 
9
17
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
10
- const ROOT = process.argv.includes('--root') ?
11
- process.argv[process.argv.indexOf('--root') + 1] :
12
- process.cwd();
13
- const DRAFTS_DIR = path.join(ROOT, '.zcode', 'drafts');
14
- const WF_ROOT = path.join(ROOT, 'workflow');
15
18
 
16
- function fail(msg) { console.error('❌ ' + msg); process.exit(1); }
19
+ function fail(msg) {
20
+ console.error(`❌ ${msg}`);
21
+ process.exit(1);
22
+ }
17
23
 
18
24
  function parseArgs() {
19
25
  const topicIdx = process.argv.indexOf('--topic');
20
- if (topicIdx === -1 || !process.argv[topicIdx + 1]) {
21
- console.error('用法: node .agents/scripts/solidify-task.mjs --topic "主题"');
22
- process.exit(1);
23
- }
24
- return process.argv[topicIdx + 1];
26
+ const topic = topicIdx !== -1 ? process.argv[topicIdx + 1] : '';
27
+ if (!topic) fail('用法: node .agents/scripts/solidify-task.mjs --topic "主题" [--root <仓库根>] [--delegated "<用户原话>" | --auto]');
28
+ let root = process.cwd();
29
+ const rootIdx = process.argv.indexOf('--root');
30
+ if (rootIdx !== -1 && process.argv[rootIdx + 1]) root = process.argv[rootIdx + 1];
31
+ const delegatedIdx = process.argv.indexOf('--delegated');
32
+ const delegated = delegatedIdx !== -1 ? String(process.argv[delegatedIdx + 1] ?? '') : '';
33
+ const auto = process.argv.includes('--auto');
34
+ if (delegated && auto) fail('--delegated 与 --auto 互斥,只能选其一');
35
+ return { topic, root, delegated, auto };
25
36
  }
26
37
 
27
- async function run() {
28
- const topic = parseArgs();
29
- console.log(`🚀 开始固化 L1 任务: ${topic}...`);
38
+ const classify = (content) => {
39
+ if (content.includes('# INTENT')) return 'intents';
40
+ if (content.includes('# PLAN')) return 'plans';
41
+ if (content.includes('# SPEC')) return 'specs';
42
+ if (content.includes('# INCIDENT')) return 'incidents';
43
+ return null;
44
+ };
30
45
 
31
- if (!fs.existsSync(DRAFTS_DIR)) fail('草稿目录 .zcode/drafts/ 不存在');
32
-
33
- // 1. 搜寻草稿
34
- const files = fs.readdirSync(DRAFTS_DIR).filter(f => f.includes(topic) && f.endsWith('.md'));
35
- if (!files.length) fail(`在 ${DRAFTS_DIR} 中未找到主题为 ${topic} 的草稿文件`);
46
+ function run() {
47
+ const { topic, root, delegated, auto } = parseArgs();
48
+ const draftsDir = path.join(root, '.zcode', 'drafts');
49
+ if (!fs.existsSync(draftsDir)) fail(`草稿目录 ${draftsDir} 不存在`);
50
+ const files = fs.readdirSync(draftsDir).filter((f) => f.includes(topic) && f.endsWith('.md'));
51
+ if (!files.length) fail(`在 ${draftsDir} 中未找到主题为 ${topic} 的草稿文件`);
36
52
 
37
53
  const today = new Date().toISOString().slice(0, 10);
38
54
  const solidified = [];
39
-
40
- // 2. 迁移并规范化命名
41
55
  for (const file of files) {
42
- const content = fs.readFileSync(path.join(DRAFTS_DIR, file), 'utf8');
43
- let targetDir = '';
44
-
45
- // 简单启发式判定类型
46
- if (content.includes('# INTENT')) targetDir = 'intents';
47
- else if (content.includes('# PLAN')) targetDir = 'plans';
48
- else if (content.includes('# SPEC')) targetDir = 'specs';
49
- else if (content.includes('# INCIDENT')) targetDir = 'incidents';
50
- else continue;
51
-
52
- const targetName = `${today}-${file.replace(/.*-/, '').replace('.md', '.md')}`;
53
- if (!targetName.startsWith('20')) {
54
- // 如果文件名没日期前缀,强制加上
55
- // 假设文件名为 <topic>.md
56
- }
57
-
58
- // 统一处理:强制 YYYY-MM-DD- 前缀
56
+ const content = fs.readFileSync(path.join(draftsDir, file), 'utf8');
57
+ const targetDir = classify(content);
58
+ if (!targetDir) continue;
59
+ // 统一强制 YYYY-MM-DD- 前缀;已有前缀的文件名原样保留
59
60
  const finalName = /^\d{4}-\d{2}-\d{2}-/.test(file) ? file : `${today}-${file}`;
60
- const targetPath = path.join(WF_ROOT, targetDir, finalName);
61
-
61
+ const targetPath = path.join(root, 'workflow', targetDir, finalName);
62
62
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
63
- fs.copyFileSync(path.join(DRAFTS_DIR, file), targetPath);
64
-
65
- solidified.push({
66
- rel: `workflow/${targetDir}/${finalName}`,
67
- abs: targetPath,
68
- type: targetDir
69
- });
70
- console.log(` 📦 迁移: ${file} -> ${targetKebab(targetDir)}/${finalName}`);
63
+ fs.copyFileSync(path.join(draftsDir, file), targetPath);
64
+ solidified.push({ rel: `workflow/${targetDir}/${finalName}`, abs: targetPath, type: targetDir });
65
+ console.log(` 📦 迁移: ${file} -> ${targetDir}/${finalName}`);
71
66
  }
67
+ if (!solidified.length) fail(`草稿中未找到可识别的 INTENT/PLAN/SPEC/INCIDENT 文档(主题 ${topic})`);
72
68
 
73
- function targetKebab(dir) { return dir; }
74
-
75
- // 3. 批量确认 (Delegated)
76
- console.log('🛡️ 执行批量确认...');
77
- for (const item of solidified) {
78
- const res = spawnSync('node', [
79
- path.join(SCRIPT_DIR, 'confirm-doc.mjs'),
80
- item.rel,
81
- '--delegated',
82
- `固化 L1 任务 ${topic}:用户确认草稿无误,执行固化归档`
83
- ], { encoding: 'utf8' });
84
-
85
- if (res.status !== 0) {
86
- console.error(` ❌ 确认失败 [${item.rel}]: ${res.stderr}`);
87
- // 此时不 exit,尝试继续其他文件的确认
88
- } else {
89
- console.log(` ✅ 已确认: ${item.rel}`);
69
+ // 确认:仅当调用方显式提供确认来源时执行;否则只迁移,绝不落账。
70
+ let failed = 0;
71
+ if (delegated || auto) {
72
+ console.log('🛡️ 执行逐份确认...');
73
+ for (const item of solidified) {
74
+ const args = [path.join(SCRIPT_DIR, 'confirm-doc.mjs'), item.rel, '--root', root];
75
+ if (delegated) args.push('--delegated', delegated);
76
+ else args.push('--auto');
77
+ const res = spawnSync('node', args, { cwd: root, encoding: 'utf8' });
78
+ if (res.status !== 0) {
79
+ failed++;
80
+ console.error(` ❌ 确认失败 [${item.rel}]: ${(res.stderr || '').trim()}`);
81
+ } else {
82
+ console.log(` ✅ 已确认: ${item.rel}`);
83
+ }
84
+ }
85
+ console.log(`确认汇总:${solidified.length - failed}/${solidified.length} 成功${failed ? `,${failed} 失败` : ''}`);
86
+ } else {
87
+ console.log('⏭️ 未提供确认来源,仅迁移不落账。');
88
+ for (const item of solidified) {
89
+ console.log(` → 确认指引: node .agents/scripts/confirm-doc.mjs ${item.rel}(TTY 键入「可以」)`);
90
+ console.log(` 或(用户对话内明确确认后): node .agents/scripts/confirm-doc.mjs ${item.rel} --delegated "<用户原话>"`);
90
91
  }
91
92
  }
92
93
 
93
- // 4. 更新索引
94
+ // 更新索引;失败同样传播退出码。
94
95
  console.log('📖 更新工作流索引...');
95
- spawnSync('node', [path.join(SCRIPT_DIR, 'gen-workflow-index.mjs')], { stdio: 'inherit' });
96
+ const idx = spawnSync('node', [path.join(SCRIPT_DIR, 'gen-workflow-index.mjs')], { cwd: root, stdio: 'inherit' });
97
+ if (idx.status !== 0) {
98
+ console.error(' ❌ 索引更新失败');
99
+ process.exit(1);
100
+ }
96
101
 
102
+ if (failed) process.exit(1);
97
103
  console.log(`\n✨ 任务 ${topic} 已成功固化!`);
98
104
  console.log(`👉 建议执行: git add workflow/ .agents/confirmations.jsonl && git commit -m "docs(workflow): solidify L1 task ${topic}"`);
99
105
  }
100
106
 
101
- run();
107
+ run();
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ // solidify-task.test.mjs — 快车道固化脚本 fixture 套件(2026-10-01 v09-review-defects)
3
+ // 验证确认门契约:无确认来源只迁移不落账;--delegated quote 原样转发;--auto Strict 下拒绝且失败退出;
4
+ // 互斥/缺草稿/非文档等边界。gen-workflow-index 无 --root 参数,spawn 时以 fixture 根为 cwd。
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { spawnSync } from 'node:child_process';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
12
+ const SCRIPT = path.join(SCRIPT_DIR, 'solidify-task.mjs');
13
+
14
+ let pass = 0;
15
+ let fail = 0;
16
+ const check = (desc, cond, detail = '') => {
17
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
18
+ else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
19
+ };
20
+
21
+ const ENUMS_FIXTURE = [
22
+ 'doc.status.all=draft approved done superseded cancelled',
23
+ 'doc.status.confirmed=approved done superseded cancelled',
24
+ 'doc.status.active=draft approved',
25
+ 'doc.status.terminal=done superseded cancelled',
26
+ 'doc.status.abandoned=superseded cancelled',
27
+ 'incident.status.all=open fixed closed',
28
+ 'incident.status.active=open',
29
+ 'level.all=L0 L1 L2 L3',
30
+ ].join('\n') + '\n';
31
+
32
+ const mkfix = () => {
33
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'solidify-test-'));
34
+ for (const s of ['intents', 'plans', 'specs', 'incidents']) {
35
+ fs.mkdirSync(path.join(d, 'workflow', s), { recursive: true });
36
+ }
37
+ fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
38
+ fs.mkdirSync(path.join(d, '.zcode', 'drafts'), { recursive: true });
39
+ fs.writeFileSync(path.join(d, '.agents', 'workflow-enums.txt'), ENUMS_FIXTURE);
40
+ fs.writeFileSync(path.join(d, '.agents', 'workflow-modules.txt'), '# modules\npipeline\nui\n');
41
+ return d;
42
+ };
43
+ const DRAFT = (name, eol = '\n') =>
44
+ `---${eol}状态: draft${eol}级别: L1${eol}日期: 2026-10-01${eol}模块: pipeline${eol}---${eol}# INTENT — ${name}${eol}`;
45
+ const run = (root, args) => {
46
+ const r = spawnSync(process.execPath, [SCRIPT, ...args], { cwd: root, encoding: 'utf8' });
47
+ return { code: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
48
+ };
49
+ const ledgerLines = (root) => {
50
+ const p = path.join(root, '.agents', 'confirmations.jsonl');
51
+ return fs.existsSync(p) ? fs.readFileSync(p, 'utf8').split(/\n/).filter(Boolean) : [];
52
+ };
53
+
54
+ // T1:默认只迁移不落账
55
+ {
56
+ const d = mkfix();
57
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
58
+ const r = run(d, ['--topic', 'demo', '--root', d]);
59
+ check('T1 默认迁移成功 exit 0', r.code === 0, r.out);
60
+ check('T1 草稿已迁移到 intents 且日期前缀正确',
61
+ fs.existsSync(path.join(d, 'workflow', 'intents', '2026-10-01-demo-task.md')), '');
62
+ check('T1 零台账新增(无确认来源绝不落账)', ledgerLines(d).length === 0, JSON.stringify(ledgerLines(d)));
63
+ check('T1 输出含确认指引', r.out.includes('确认指引') && r.out.includes('--delegated'), r.out);
64
+ }
65
+ // T2:--delegated 原话原样转发
66
+ {
67
+ const d = mkfix();
68
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
69
+ const r = run(d, ['--topic', 'demo', '--root', d, '--delegated', '固化了可以']);
70
+ check('T2 --delegated 流程 exit 0', r.code === 0, r.out);
71
+ const lines = ledgerLines(d);
72
+ check('T2 台账落 1 行', lines.length === 1, JSON.stringify(lines));
73
+ check('T2 quote 原样(脚本不编造话术)',
74
+ lines[0].includes('"quote":"固化了可以"') && lines[0].includes('chat-delegated'), lines[0]);
75
+ check('T2 目标文档状态已 approved',
76
+ fs.readFileSync(path.join(d, 'workflow', 'intents', '2026-10-01-demo-task.md'), 'utf8').includes('状态: approved'), '');
77
+ }
78
+ // T3:--auto 在 Strict(缺 trust-mode.json)下被拒 → 失败计数并 exit 1
79
+ {
80
+ const d = mkfix();
81
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
82
+ const r = run(d, ['--topic', 'demo', '--root', d, '--auto']);
83
+ check('T3 --auto 在缺省 Strict 下 exit 1(不吞失败)', r.code === 1, r.out);
84
+ check('T3 输出含失败汇总', r.out.includes('确认汇总') && r.out.includes('失败'), r.out);
85
+ check('T3 台账零行(拒绝不落账)', ledgerLines(d).length === 0, JSON.stringify(ledgerLines(d)));
86
+ }
87
+ // T4:缺草稿 → exit 1
88
+ {
89
+ const d = mkfix();
90
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'other.md'), DRAFT('other'));
91
+ const r = run(d, ['--topic', 'nope', '--root', d]);
92
+ check('T4 缺主题草稿 exit 1 且带指引', r.code === 1 && r.out.includes('未找到主题'), r.out);
93
+ }
94
+ // T5:草稿非可识别文档 → exit 1
95
+ {
96
+ const d = mkfix();
97
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-note.md'), '# 随手笔记\nnot a workflow doc\n');
98
+ const r = run(d, ['--topic', 'demo', '--root', d]);
99
+ check('T5 无可识别文档 exit 1', r.code === 1 && r.out.includes('未找到可识别'), r.out);
100
+ }
101
+ // T6:--delegated 与 --auto 互斥 → exit 1
102
+ {
103
+ const d = mkfix();
104
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
105
+ const r = run(d, ['--topic', 'demo', '--root', d, '--delegated', 'x', '--auto']);
106
+ check('T6 互斥 exit 1', r.code === 1 && r.out.includes('互斥'), r.out);
107
+ }
108
+ // T7:失败路径退出码传播(确认失败后 exit 1,不吞)
109
+ {
110
+ const d = mkfix();
111
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
112
+ // 让确认失败:用 --auto 触发 Strict 拒绝(缺 trust-mode.json 即 Strict)。
113
+ const r = run(d, ['--topic', 'demo', '--root', d, '--auto']);
114
+ check('T7 失败后 exit 1 且索引仍生成', r.code === 1 && fs.existsSync(path.join(d, 'workflow', 'INDEX.md')), r.out);
115
+ }
116
+ // T8:索引更新失败传播退出码(独立于确认失败路径——删除 workflow/plans 目录使 gen-workflow-index 失败)
117
+ {
118
+ const d = mkfix();
119
+ fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
120
+ fs.rmSync(path.join(d, 'workflow', 'plans'), { recursive: true });
121
+ const r = run(d, ['--topic', 'demo', '--root', d]);
122
+ check('T8 索引更新失败 exit 1 且输出指明', r.code === 1 && r.out.includes('索引更新失败'), r.out);
123
+ }
124
+
125
+ console.log(`\n${fail ? `❌ ${fail} failed` : '✅ all passed'}(${pass} passed)`);
126
+ process.exit(fail ? 1 : 0);
@@ -5,9 +5,10 @@
5
5
  # - 警告(advisory)= 状态未确认 / 模板占位残留 / 引用漂移 等卫生项 → 仅打印提示
6
6
  # stdin 有行时,每个非删除的本地 sha 用 --rev 扫那棵树。没有行时(手跑钩子)仍无参数扫工作区。
7
7
  #
8
- # experiment/* 泳道分支(2026-09-30 hybrid-governance-explore-hardening):check-loop 降级 advisory——
9
- # 快速 PoC 推送不因闭环断档被拦(断档报告照出);转正(合入 main)由下方加固门兜底。
10
- # 加固门(同日):推送目标为 refs/heads/main 时以 --hardening 运行——exploring 任务须先收口
8
+ # experiment/* 泳道(2026-09-30 hybrid-governance-explore-hardening):**目标分支**为 experiment/* 时,
9
+ # 闭环扫描降级 advisory(报告不阻断)——快速 PoC 推送不因闭环断档被拦;转正(目标为 main)由加固门兜底。
10
+ # 加固门(同日;2026-10-01 v09-review-defects 修正判定键):推送**目标**为 refs/heads/main 时以
11
+ # --hardening 运行(无论来源分支——experiment/* 直推 main 同样覆盖),exploring 任务须先收口
11
12
  # (intent approved/done + 同名 plan 在场 + L2/L3 另须同名 spec;判据见 check-loop.mjs 加固门段),
12
13
  # hard 失败即阻断 push,防「未文档化的探索件」进 main。
13
14
  #
@@ -34,33 +35,36 @@ run_loop() {
34
35
  saw=0
35
36
  rc=0
36
37
  while read -r local_ref local_sha remote_ref _remote_sha; do
37
- saw=1
38
- case "$local_sha" in
39
- *[!0]*) ;;
40
- *) continue ;;
41
- esac
42
- # experiment/* 泳道:advisory(报告不阻断; detached / 其他分支不受影响)
43
- case "$local_ref" in
44
- refs/heads/experiment/*|experiment/*)
45
- echo "pre-push: experiment/* 泳道分支($local_ref)——闭环扫描降级 advisory,不阻断" >&2
46
- if command -v sh >/dev/null 2>&1; then
47
- sh "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正合入 main 须过加固门" >&2
48
- else
49
- bash "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正合入 main 须过加固门" >&2
50
- fi
51
- continue
52
- ;;
53
- esac
54
- # 加固门:推 main 时追加 --hardening(其余目标维持既有全量扫描)
55
- hard_args=""
56
- case "$remote_ref" in
57
- refs/heads/main|main) hard_args="--hardening" ;;
58
- esac
59
- run_loop --rev "$local_sha" $hard_args
60
- one=$?
61
- if [ "$one" -ne 0 ] && [ "$rc" -eq 0 ]; then
62
- rc=$one
63
- fi
38
+ saw=1
39
+ case "$local_sha" in
40
+ *[!0]*) ;;
41
+ *) continue ;;
42
+ esac
43
+ # 按目标分支三态分派(2026-10-01 v09-review-defects:判定键从 local_ref 改为 remote_ref——
44
+ # 修复 experiment/*→main 直推被 advisory continue 吞掉加固门的漏口):
45
+ # main → 加固门(必带 --hardening)
46
+ # experiment/* → advisory(报告不阻断)
47
+ # 其他 → 全量扫描
48
+ case "$remote_ref" in
49
+ refs/heads/main|main)
50
+ run_loop --rev "$local_sha" --hardening
51
+ ;;
52
+ refs/heads/experiment/*|experiment/*)
53
+ echo "pre-push: experiment/* 目标分支($remote_ref)——闭环扫描降级 advisory,不阻断" >&2
54
+ if command -v sh >/dev/null 2>&1; then
55
+ sh "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正(推 main)须过加固门" >&2
56
+ else
57
+ bash "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正(推 main)须过加固门" >&2
58
+ fi
59
+ ;;
60
+ *)
61
+ run_loop --rev "$local_sha"
62
+ ;;
63
+ esac
64
+ one=$?
65
+ if [ "$one" -ne 0 ] && [ "$rc" -eq 0 ]; then
66
+ rc=$one
67
+ fi
64
68
  done
65
69
 
66
70
  if [ "$saw" -eq 0 ]; then
@@ -50,7 +50,7 @@
50
50
 
51
51
  1. **同名配对**:L1 有 plan;L2/L3 有 spec 与 plan。incident 的回路断档同样阻断。
52
52
  2. **验收勾验**:新建 intent 关到 done 时,验收标准未勾则阻断。
53
- 3. **确认留痕**:approved / done / fixed / closed 须经 `confirm-doc.mjs`,指纹与台账一致。
53
+ 3. **确认留痕**:approved / done / fixed / closed 须经 `confirm-doc.mjs`,指纹与台账一致。`--delegated` 原话仅限用户对话内明确确认的当次措辞——官方脚本/门禁不得自动代录或编造 quote(2026-10-01 v09-review-defects:solidify-task 曾自动伪造原话,已修复并纳入回归)。
54
54
  4. **敏感信息**:pre-commit 的 commit-check 扫描密钥。
55
55
  5. **双源与台账**:doctor 核对 managed / owned 的 sha;引擎改动改包源 `templates/` 再 `sync`。
56
56