agentic-flow-kit 1.2.1 → 1.3.0

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.
Files changed (31) hide show
  1. package/modules/hosts/claude/commands/wf-sync-hosts.md +7 -3
  2. package/modules/hosts/claude/commands/wf-test.md +1 -1
  3. package/modules/hosts/codex/commands/wf-sync-hosts.md +7 -3
  4. package/modules/hosts/codex/commands/wf-test.md +1 -1
  5. package/modules/hosts/cursor/commands/wf-sync-hosts.md +7 -3
  6. package/modules/hosts/cursor/commands/wf-test.md +1 -1
  7. package/modules/hosts/opencode/commands/wf-sync-hosts.md +7 -3
  8. package/modules/hosts/opencode/commands/wf-test.md +1 -1
  9. package/modules/hosts/trae/commands/wf-sync-hosts.md +7 -3
  10. package/modules/hosts/trae/commands/wf-test.md +1 -1
  11. package/package.json +1 -1
  12. package/src/cli.mjs +3 -1
  13. package/src/fresh-init.test.mjs +10 -0
  14. package/src/init.mjs +1 -1
  15. package/src/profiles.mjs +12 -6
  16. package/src/sync-hosts.mjs +42 -19
  17. package/src/sync-hosts.test.mjs +55 -7
  18. package/src/sync.mjs +30 -26
  19. package/src/sync.test.mjs +82 -0
  20. package/templates/AGENTS.md +1 -0
  21. package/templates/_agents/commands/sync-hosts.md +7 -3
  22. package/templates/_agents/rule-budgets.txt +3 -1
  23. package/templates/_agents/scripts/agg-delegations.cjs +15 -5
  24. package/templates/_agents/scripts/check-ledger.mjs +60 -16
  25. package/templates/_agents/scripts/check-ledger.test.mjs +70 -0
  26. package/templates/_agents/scripts/check-loop-unit.test.mjs +95 -0
  27. package/templates/_agents/scripts/check-loop.mjs +1134 -1084
  28. package/templates/_agents/scripts/check-loop.test.mjs +86 -1
  29. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +182 -0
  30. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +126 -0
  31. package/templates/_github/workflows/kit-ci.yml +28 -0
@@ -7,7 +7,10 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
7
7
  # Sync-Hosts · 跨宿主适配层正文段同步
8
8
 
9
9
  > 改权威源(`templates/_agents/{commands,roles}/*.md`)后薄适配(`modules/hosts/<h>/{agents,commands}/*.md`)要同步——薄适配正文 = 权威源正文,frontmatter 各自保留宿主特化(commands 层文件名 = 宿主命令名,opencode / trae 同用 `wf-` 前缀,单源见 `src/profiles.mjs#HOSTS.commandPrefix`;trae 另有 `name: wf-X`,zcode/omp 各自原描述)。
10
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
10
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
11
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
12
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
13
+ > 目标根取 `--dir <目录>` > 当前目录。
11
14
 
12
15
  ## 何时跑
13
16
 
@@ -21,8 +24,9 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
21
24
  ### 默认 `--diff`(仅报告)
22
25
 
23
26
  ```
24
- node bin/flow-kit.mjs sync-hosts --diff
25
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
27
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
28
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
29
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
26
30
  ```
27
31
 
28
32
  输出三类:
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -7,7 +7,10 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
7
7
  # Sync-Hosts · 跨宿主适配层正文段同步
8
8
 
9
9
  > 改权威源(`templates/_agents/{commands,roles}/*.md`)后薄适配(`modules/hosts/<h>/{agents,commands}/*.md`)要同步——薄适配正文 = 权威源正文,frontmatter 各自保留宿主特化(commands 层文件名 = 宿主命令名,opencode / trae 同用 `wf-` 前缀,单源见 `src/profiles.mjs#HOSTS.commandPrefix`;trae 另有 `name: wf-X`,zcode/omp 各自原描述)。
10
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
10
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
11
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
12
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
13
+ > 目标根取 `--dir <目录>` > 当前目录。
11
14
 
12
15
  ## 何时跑
13
16
 
@@ -21,8 +24,9 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
21
24
  ### 默认 `--diff`(仅报告)
22
25
 
23
26
  ```
24
- node bin/flow-kit.mjs sync-hosts --diff
25
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
27
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
28
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
29
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
26
30
  ```
27
31
 
28
32
  输出三类:
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -7,7 +7,10 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
7
7
  # Sync-Hosts · 跨宿主适配层正文段同步
8
8
 
9
9
  > 改权威源(`templates/_agents/{commands,roles}/*.md`)后薄适配(`modules/hosts/<h>/{agents,commands}/*.md`)要同步——薄适配正文 = 权威源正文,frontmatter 各自保留宿主特化(commands 层文件名 = 宿主命令名,opencode / trae 同用 `wf-` 前缀,单源见 `src/profiles.mjs#HOSTS.commandPrefix`;trae 另有 `name: wf-X`,zcode/omp 各自原描述)。
10
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
10
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
11
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
12
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
13
+ > 目标根取 `--dir <目录>` > 当前目录。
11
14
 
12
15
  ## 何时跑
13
16
 
@@ -21,8 +24,9 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
21
24
  ### 默认 `--diff`(仅报告)
22
25
 
23
26
  ```
24
- node bin/flow-kit.mjs sync-hosts --diff
25
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
27
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
28
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
29
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
26
30
  ```
27
31
 
28
32
  输出三类:
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -7,7 +7,10 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
7
7
  # Sync-Hosts · 跨宿主适配层正文段同步
8
8
 
9
9
  > 改权威源(`templates/_agents/{commands,roles}/*.md`)后薄适配(`modules/hosts/<h>/{agents,commands}/*.md`)要同步——薄适配正文 = 权威源正文,frontmatter 各自保留宿主特化(commands 层文件名 = 宿主命令名,opencode / trae 同用 `wf-` 前缀,单源见 `src/profiles.mjs#HOSTS.commandPrefix`;trae 另有 `name: wf-X`,zcode/omp 各自原描述)。
10
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
10
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
11
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
12
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
13
+ > 目标根取 `--dir <目录>` > 当前目录。
11
14
 
12
15
  ## 何时跑
13
16
 
@@ -21,8 +24,9 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
21
24
  ### 默认 `--diff`(仅报告)
22
25
 
23
26
  ```
24
- node bin/flow-kit.mjs sync-hosts --diff
25
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
27
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
28
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
29
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
26
30
  ```
27
31
 
28
32
  输出三类:
@@ -56,7 +56,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
56
56
  - 输出测试报告(通过项 / 失败项)
57
57
  - 失败项已修复并复测通过
58
58
  - **关单**(日常闭环点,不依赖是否上 prod):
59
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
59
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
60
60
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
61
61
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
62
62
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
@@ -8,7 +8,10 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
8
8
  # Sync-Hosts · 跨宿主适配层正文段同步
9
9
 
10
10
  > 改权威源(`templates/_agents/{commands,roles}/*.md`)后薄适配(`modules/hosts/<h>/{agents,commands}/*.md`)要同步——薄适配正文 = 权威源正文,frontmatter 各自保留宿主特化(commands 层文件名 = 宿主命令名,opencode / trae 同用 `wf-` 前缀,单源见 `src/profiles.mjs#HOSTS.commandPrefix`;trae 另有 `name: wf-X`,zcode/omp 各自原描述)。
11
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
11
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
12
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
13
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
14
+ > 目标根取 `--dir <目录>` > 当前目录。
12
15
 
13
16
  ## 何时跑
14
17
 
@@ -22,8 +25,9 @@ description: 跨宿主适配层同步(正文段 sha 比对 + 用户拍板 +
22
25
  ### 默认 `--diff`(仅报告)
23
26
 
24
27
  ```
25
- node bin/flow-kit.mjs sync-hosts --diff
26
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
28
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
29
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
30
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
27
31
  ```
28
32
 
29
33
  输出三类:
@@ -57,7 +57,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
57
57
  - 输出测试报告(通过项 / 失败项)
58
58
  - 失败项已修复并复测通过
59
59
  - **关单**(日常闭环点,不依赖是否上 prod):
60
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
60
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
61
61
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
62
62
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
63
63
  **确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-flow-kit",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/cli.mjs CHANGED
@@ -52,7 +52,9 @@ init 选项(全部可选,均有默认值):
52
52
  --force 覆盖已存在的同名文件(默认保守跳过)
53
53
 
54
54
  sync / add-host 选项:--dir <目录>、--force(覆盖本地已改 / 已装内容);add-gate 选项:--dir <目录>、--force。
55
- sync-hosts 选项:--diff(默认,仅报告,不修改)/ --apply(单向同步到薄适配)/ --json(机器可读输出)。
55
+ sync-hosts 选项:--diff(默认,仅报告,不修改)/ --apply(单向同步到薄适配)/ --json(机器可读输出)/ --dir <目录>(目标项目根,默认当前目录)。
56
+ sync-hosts 双布局(2026-10-07):包源仓(含 templates/_agents + modules/hosts)比对 templates/_agents ↔ modules/hosts;
57
+ 装户仓(含 .agents)比对 .agents ↔ 各宿主目录(.opencode/.trae/.omp/.zcode…)——同一命令两处都可用。
56
58
 
57
59
  示例:
58
60
  npx agentic-flow-kit init # 交互模式:宿主 → 技术栈 → 端口 → 确认安装
@@ -42,6 +42,16 @@ check('新装 audit 为 false 且 policyVersion 为 2', kit.audit === false && k
42
42
  check('新装无包源模板对应条目无锚(生成类配置不参与感知)', cfg !== undefined && cfg.srcSha256 === undefined);
43
43
  const rb = tplAnchor('.agents/rule-budgets.txt');
44
44
  check('新装 _agents 前缀翻译参与感知(rule-budgets 带锚)', rb !== undefined && typeof rb.srcSha256 === 'string');
45
+ // CI 远端门模板(2026-10-07 adopter-ci-github):.github → _github 通用点前缀翻译 + owned 落盘带锚
46
+ const ci = tplAnchor('.github/workflows/kit-ci.yml');
47
+ const ciSrc = fs.readFileSync(path.join(ROOT, 'templates', '_github', 'workflows', 'kit-ci.yml'), 'utf8').replace(/\r\n/g, '\n');
48
+ check('新装 CI 远端门模板落盘且 kit.json owned 带锚(srcSha256 == 包源 LF 归一 sha)',
49
+ fs.existsSync(path.join(dir, '.github', 'workflows', 'kit-ci.yml'))
50
+ && ci !== undefined && ci.srcSha256 === shaOf(Buffer.from(ciSrc, 'utf8')),
51
+ JSON.stringify(ci || null));
52
+ const ciText = fs.readFileSync(path.join(dir, '.github', 'workflows', 'kit-ci.yml'), 'utf8');
53
+ check('新装 CI 模板引用统一入口 verify.mjs、含分支策略人工边界提示且无 Tab 缩进',
54
+ ciText.includes('node .agents/scripts/verify.mjs') && ciText.includes('branch protection') && !ciText.includes('\t'));
45
55
  }
46
56
  check('claude 命令与角色薄适配落盘',
47
57
  fs.existsSync(path.join(dir, '.claude', 'commands', 'wf-plan.md'))
package/src/init.mjs CHANGED
@@ -97,7 +97,7 @@ async function interactive() {
97
97
  const menu = (names) => ' ' + names.map((n, i) => dim(`${menuMark(i)} ${stackLabel(n)}`)).join(dim(' '));
98
98
 
99
99
  console.log(bold(cyan('▶ flow-kit init')) + dim(' · AI 闭环工作流 + wiki 知识层脚手架'));
100
- console.log(dim(' 安装到当前目录:.agents/ · .githooks/ · workflow/ · wiki/ · AGENTS.md(已存在的文件保守跳过;AGENTS.md 无工作流骨架时文末追加补齐)'));
100
+ console.log(dim(' 安装到当前目录:.agents/ · .githooks/ · workflow/ · wiki/ · AGENTS.md · .github/workflows/kit-ci.yml(CI 远端门,非 GitHub 装户可删)(已存在的文件保守跳过;AGENTS.md 无工作流骨架时文末追加补齐)'));
101
101
  try {
102
102
  let hosts;
103
103
  for (;;) {
package/src/profiles.mjs CHANGED
@@ -106,13 +106,17 @@ export function pickStackVars(stackKey) {
106
106
  // Node 18.20 / 20.18 / 22.11 全部 ERR_REQUIRE_ESM(只有 ≥20.19/≥22.12 可用),而本仓 engines
107
107
  // 写 >=18.0.0、CI 跑 18/22 → 装户在 CI 目标版本上根本用不了。改 .cjs 后 createRequire 同步载入
108
108
  // 全线可用。旧 .mjs 路径同样登记为 owned(免装户残留旧文件时落 managed 重演缺陷)。
109
+ // .github/ 是装户 CI 远端门(2026-10-07 adopter-ci-github):kit 只下发 workflows/kit-ci.yml 薄模板,
110
+ // 触发分支 / node 版本 / 测试命令均项目特定——归 owned,sync 永不覆盖;感知锚经 srcTemplatePath
111
+ // 通用点前缀翻译命中 templates/_github/。
109
112
  export function isOwned(rel) {
110
113
  return rel === 'AGENTS.md' || rel === '.gitattributes' || rel.startsWith('workflow/') || rel.startsWith('wiki/')
111
114
  || rel.startsWith('.agents/notes/')
112
115
  || rel === '.agents/workflow-modules.txt' || rel === '.agents/rule-budgets.txt'
113
116
  || rel === '.agents/metric-claims.txt' || rel === '.agents/metric-derivers.cjs'
114
117
  || rel === '.agents/metric-derivers.mjs'
115
- || rel === '.agents/hooks/local-pre-commit';
118
+ || rel === '.agents/hooks/local-pre-commit'
119
+ || rel.startsWith('.github/');
116
120
  }
117
121
 
118
122
  // ---- 模板下发感知(2026-10-06 template-downstream)----
@@ -124,12 +128,14 @@ export function isOwned(rel) {
124
128
  // 装户不期望跟随包源模板——模板感知排除出比对面(防「拉取了也被生成器重写」的无谓出账)。新增生成器目标须同步本清单。
125
129
  export const TEMPLATE_DRIFT_EXCLUDE = new Set(['workflow/INDEX.md', 'wiki/INDEX.md', 'wiki/知识沉淀总览.html']);
126
130
 
127
- // 映射规则单源:renderTree 的「srcRoot/templates ↔ targetRoot/项目根」关系 + 点目录前缀翻译
128
- // (render.mjs:`_agents` → `.agents`)——装户 rel 以 .agents/ 开头时探 templates/_agents/<余径>。
129
- // 复核 P2-1(2026-10-06 template-downstream 独立复核):漏翻译会让 rule-budgets.txt /
130
- // metric-claims.txt / local-pre-commit 等 5 条有包源起步模板的 owned 件永不参与感知。
131
+ // 映射规则单源:renderTree 的「srcRoot/templates ↔ targetRoot/项目根」关系 + 点目录前缀翻译。
132
+ // 通用点前缀翻译(2026-10-07 adopter-ci-github,由 .agents/ 特例泛化):装户 rel 首段以 `.` 开头时探
133
+ // templates/_<去点首段>/<余径>(.agents/notes/x → _agents/notes/x、.github/workflows/kit-ci.yml →
134
+ // _github/…)——与 render.mjs relOf 的顶层 `_`→`.` 同一规则的逆向;无点前缀 rel(AGENTS.md / workflow/…)
135
+ // 原样探 templates/<rel>。无对应模板返回 null(不参与感知)。
131
136
  export function srcTemplatePath(pkgRoot, rel) {
132
- const tplRel = rel.startsWith('.agents/') ? `_agents/${rel.slice('.agents/'.length)}` : rel;
137
+ const first = rel.split('/')[0];
138
+ const tplRel = first.startsWith('.') ? `_${first.slice(1)}${rel.slice(first.length)}` : rel;
133
139
  const p = path.join(pkgRoot, 'templates', tplRel);
134
140
  return fs.existsSync(p) ? p : null;
135
141
  }
@@ -45,17 +45,19 @@ function listMd(dir) {
45
45
  // pairsFor:权威源文件 → 薄适配 rel 列表(映射单源见 profiles.mjs#HOSTS.commandPrefix)
46
46
  // commands 权威源 → 带 commandPrefix 的宿主各一份(<dir>/commands/<prefix><name>.md)
47
47
  // roles 权威源 → 全部宿主 agents 各一份
48
- function pairsFor(authRel) {
48
+ // hostDirOf:宿主 → 适配目录名。包源布局 = 宿主键(modules/hosts/opencode/…);装户布局 = HOSTS[h].dir
49
+ // (项目根下 .opencode/…,带点)——2026-10-07 sync-hosts-target-fix 加装户支持时引入。
50
+ function pairsFor(authRel, hostDirOf = (h) => h) {
49
51
  const out = [];
50
52
  const norm = authRel.split(path.sep).join('/');
51
53
  if (norm.startsWith('commands/') && norm.endsWith('.md')) {
52
54
  const name = norm.slice('commands/'.length, -'.md'.length);
53
55
  for (const [h, v] of Object.entries(HOSTS)) {
54
- if (v.commandPrefix) out.push({ adapterRel: `${h}/commands/${v.commandPrefix}${name}.md` });
56
+ if (v.commandPrefix) out.push({ adapterRel: `${hostDirOf(h)}/commands/${v.commandPrefix}${name}.md` });
55
57
  }
56
58
  } else if (norm.startsWith('roles/') && norm.endsWith('.md')) {
57
59
  const name = norm.slice('roles/'.length, -'.md'.length);
58
- for (const h of Object.keys(HOSTS)) out.push({ adapterRel: `${h}/agents/${name}.md` });
60
+ for (const h of Object.keys(HOSTS)) out.push({ adapterRel: `${hostDirOf(h)}/agents/${name}.md` });
59
61
  }
60
62
  return out;
61
63
  }
@@ -63,12 +65,13 @@ function pairsFor(authRel) {
63
65
  // diffHosts:权威源正文段 vs 薄适配正文段的漂移分析(B-b 语义;pure function)。
64
66
  // authorityRoot:权威源根(含 commands/ + roles/)
65
67
  // adaptersRoot:薄适配根(含 <h>/{agents,commands}/)
68
+ // hostDirOf:宿主 → 适配目录名(默认宿主键 = 包源布局;装户布局传 (h) => HOSTS[h].dir)
66
69
  // 返回:{ drift, inSync, authorityMissing, adapterOrphans, note }
67
- // drift = 正文段 sha 不一致(权威源改 / 薄适配手改 都计入;不区分方向)
68
- // inSync = 正文段 sha 对齐的文件对数
69
- // authorityMissing = 权威源声明但薄适配文件不存在(apply 不自动创建——薄适配须经 add-host 接管)
70
- // adapterOrphans = 薄适配存在但权威源无对应文件(apply 不删——可能是宿主特化或历史残留)
71
- export function diffHosts({ authorityRoot, adaptersRoot }) {
70
+ // drift = 正文段 sha 不一致(权威源改 / 薄适配手改 都计入;不区分方向)
71
+ // inSync = 正文段 sha 对齐的文件对数
72
+ // authorityMissing = 权威源声明但薄适配文件不存在(apply 不自动创建——薄适配须经 add-host 接管)
73
+ // adapterOrphans = 薄适配存在但权威源无对应文件(apply 不删——可能是宿主特化或历史残留)
74
+ export function diffHosts({ authorityRoot, adaptersRoot, hostDirOf = (h) => h }) {
72
75
  if (!fs.existsSync(authorityRoot)) return { drift: [], inSync: 0, authorityMissing: [], adapterOrphans: [], note: `权威源根不存在:${authorityRoot}` };
73
76
 
74
77
  const drift = [];
@@ -83,14 +86,16 @@ export function diffHosts({ authorityRoot, adaptersRoot }) {
83
86
  const authRel = `${sub}/${f}`;
84
87
  const authAbs = path.join(authorityRoot, authRel);
85
88
  const authSha = bodySha(authAbs);
86
- const pairs = pairsFor(authRel);
89
+ const pairs = pairsFor(authRel, hostDirOf);
87
90
  if (pairs.length === 0) continue;
88
91
  for (const { adapterRel } of pairs) {
89
92
  adapterSeen.add(adapterRel);
90
93
  const adapterAbs = path.join(adaptersRoot, adapterRel);
91
94
  const adapterSha = bodySha(adapterAbs);
92
95
  if (adapterSha === null) {
93
- authorityMissing.push({ authRel, adapterRel });
96
+ // 未安装的宿主不算「缺失」——那是没装,不是漂移(2026-10-07:装户只装部分宿主时,
97
+ // 旧行为会把未装宿主的全部适配文件列成 authorityMissing,装户 --apply 恒 exit 1 且刷屏)
98
+ if (fs.existsSync(path.dirname(adapterAbs))) authorityMissing.push({ authRel, adapterRel });
94
99
  continue;
95
100
  }
96
101
  if (authSha === adapterSha) { inSync++; continue; }
@@ -103,9 +108,9 @@ export function diffHosts({ authorityRoot, adaptersRoot }) {
103
108
  const adapterOrphans = [];
104
109
  for (const h of Object.keys(HOSTS)) {
105
110
  for (const sub of ['agents', 'commands']) {
106
- const subAbs = path.join(adaptersRoot, h, sub);
111
+ const subAbs = path.join(adaptersRoot, hostDirOf(h), sub);
107
112
  for (const f of listMd(subAbs)) {
108
- const adapterRel = `${h}/${sub}/${f}`;
113
+ const adapterRel = `${hostDirOf(h)}/${sub}/${f}`;
109
114
  if (adapterSeen.has(adapterRel)) continue;
110
115
  let authGuess;
111
116
  const prefix = HOSTS[h] && HOSTS[h].commandPrefix;
@@ -168,24 +173,42 @@ function applyForward({ authorityRoot, adaptersRoot }, drift) {
168
173
  export function syncHosts(args, pkgRoot) {
169
174
  let apply = false;
170
175
  let json = false;
171
- for (const a of args) {
176
+ let dir = null;
177
+ for (let i = 0; i < args.length; i++) {
178
+ const a = args[i];
172
179
  if (a === '--diff') apply = false;
173
180
  else if (a === '--apply') apply = true;
174
181
  else if (a === '--json') json = true;
182
+ else if (a === '--dir') { dir = args[++i]; if (!dir) fail('--dir 缺少参数(目标项目根)'); }
175
183
  else fail(`未知选项:${a}(flow-kit sync-hosts --help)`);
176
184
  }
177
185
  if (!pkgRoot) fail('sync-hosts 缺少 pkgRoot(开发模式入口)');
178
186
 
179
- const authorityRoot = path.join(pkgRoot, 'templates', '_agents');
180
- const adaptersRoot = path.join(pkgRoot, 'modules', 'hosts');
181
- const result = diffHosts({ authorityRoot, adaptersRoot });
187
+ // 目标根:--dir > cwd > pkgRoot(兜底)。
188
+ // 2026-10-07 sync-hosts-target-fix:此前恒用 pkgRoot——装户里跑 `flow-kit sync-hosts` 时 pkgRoot
189
+ // 解析为**包安装目录**,于是 doctor §7.x 报漂移、照提示跑 `--apply` 会去改包源仓(装户自己的适配层纹丝不动)。
190
+ const target = path.resolve(dir || process.cwd() || pkgRoot);
191
+ // 布局判定(与 doctor.checkAdapterDrift 同口径):
192
+ // 包源(templates/_agents 与 modules/hosts 同时存在)→ 权威源 = <target>/templates/_agents,薄适配根 = <target>/modules/hosts
193
+ // 装户(其余)→ 权威源 = <target>/.agents,薄适配根 = <target>(宿主目录 .opencode/.trae/… 直接位于项目根)
194
+ const isSourceRepo = fs.existsSync(path.join(target, 'templates', '_agents'))
195
+ && fs.existsSync(path.join(target, 'modules', 'hosts'));
196
+ const authorityRoot = isSourceRepo ? path.join(target, 'templates', '_agents') : path.join(target, '.agents');
197
+ const adaptersRoot = isSourceRepo ? path.join(target, 'modules', 'hosts') : target;
198
+ if (!fs.existsSync(authorityRoot)) {
199
+ fail(`未找到权威源目录:${authorityRoot}——目标根 ${target} 既非包源(无 templates/_agents)也非装户(无 .agents);用 --dir <项目根> 指定`);
200
+ }
201
+ const layout = isSourceRepo ? '包源' : '装户';
202
+ // 适配目录名:包源 = 宿主键(modules/hosts/opencode/…);装户 = HOSTS[h].dir(项目根下 .opencode/…)
203
+ const hostDirOf = isSourceRepo ? (h) => h : (h) => (HOSTS[h] && HOSTS[h].dir) || h;
204
+ const result = diffHosts({ authorityRoot, adaptersRoot, hostDirOf });
182
205
 
183
206
  if (apply && result.drift.length > 0) {
184
207
  const { synced } = applyForward({ authorityRoot, adaptersRoot }, result.drift);
185
- console.log(`▶ flow-kit sync-hosts --apply → ${pkgRoot}`);
208
+ console.log(`▶ flow-kit sync-hosts --apply → ${target}(${layout}布局)`);
186
209
  console.log(` 按权威源正文覆盖薄适配正文段 ${synced.length} 份(薄适配 frontmatter 不动):`);
187
210
  for (const rel of synced) console.log(` - ${rel}`);
188
- const after = diffHosts({ authorityRoot, adaptersRoot });
211
+ const after = diffHosts({ authorityRoot, adaptersRoot, hostDirOf });
189
212
  console.log(` 同步后正文对齐:${after.inSync} 对;权威源缺失:${after.authorityMissing.length};正文漂移:${after.drift.length}`);
190
213
  return after.drift.length === 0 && after.authorityMissing.length === 0 ? 0 : 1;
191
214
  }
@@ -193,7 +216,7 @@ export function syncHosts(args, pkgRoot) {
193
216
  if (json) {
194
217
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
195
218
  } else {
196
- console.log(`▶ flow-kit sync-hosts --diff → ${pkgRoot}`);
219
+ console.log(`▶ flow-kit sync-hosts --diff → ${target}(${layout}布局)`);
197
220
  printDiff(result);
198
221
  }
199
222
  return 0;
@@ -1,9 +1,10 @@
1
1
  // sync-hosts.test.mjs — flow-kit sync-hosts B-b 语义四场景(2026-09-25)
2
2
  // 方法:迷你 fixture 包根(权威源 templates/_agents + 薄适配 modules/hosts/<h>/{agents,commands}/
3
3
  // 与权威源正文段一致,frontmatter 各异)+ 子进程 driver 调真实 sync-hosts(隔离 process.exit)
4
- // 判据:①全对齐 → inSync=28, drift 空 ②权威源改 → drift 列 ③apply → 正文一致,frontmatter 各自保留
5
- // ④薄适配 frontmatter 手改 → 不进 drift ⑤薄适配正文手改 → drift 列,apply 覆盖
6
- // ⑥薄适配缺失 → authorityMissing 列;apply 不自动创建
4
+ // 判据:①全对齐 → inSync=28, drift 空 ②权威源改 → drift 列 ③apply → 正文一致,frontmatter 各自保留
5
+ // ④薄适配 frontmatter 手改 → 不进 drift ⑤薄适配正文手改 → drift 列,apply 覆盖
6
+ // ⑥薄适配缺失 → authorityMissing 列;apply 不自动创建
7
+ // ⑦--json ⑧孤儿薄适配 ⑨装户布局(--dir / cwd 指向装户根 → 按 .agents ↔ 宿主目录比对)
7
8
  // 用法:node src/sync-hosts.test.mjs
8
9
  import fs from 'node:fs';
9
10
  import path from 'node:path';
@@ -66,13 +67,25 @@ function mkFixture() {
66
67
  }
67
68
 
68
69
  // ---- 子进程 driver:调真实 sync-hosts ----
69
- function runSyncHosts(fixtureRoot, extra = []) {
70
+ // 2026-10-07 起 sync-hosts 目标根 = --dir > cwd > pkgRoot,故测试必须用 --dir 锁定 fixture,
71
+ // 否则会落到「跑测试时所在的仓库」(实仓 = 包源,会把断言全带偏)。
72
+ function runSyncHosts(fixtureRoot, extra = [], opts = {}) {
70
73
  const driver = path.join(fixtureRoot, 'driver.mjs');
71
74
  W(driver, `import { syncHosts } from ${JSON.stringify(pathToFileURL(path.join(SRC_ROOT, 'sync-hosts.mjs')).href)};
72
75
  const root = ${JSON.stringify(fixtureRoot)};
73
76
  syncHosts(process.argv.slice(2), root);
74
77
  `);
75
- return spawnSync(process.execPath, [driver, ...extra], { encoding: 'utf8' });
78
+ return spawnSync(process.execPath, [driver, '--dir', fixtureRoot, ...extra], { encoding: 'utf8', ...opts });
79
+ }
80
+
81
+ // 不传 --dir 形态:把 cwd 设为 fixture(验「默认当前目录」解析;装户布局靠 cwd 判定)
82
+ function runSyncHostsByCwd(fixtureRoot, extra = []) {
83
+ const driver = path.join(fixtureRoot, 'driver.mjs');
84
+ W(driver, `import { syncHosts } from ${JSON.stringify(pathToFileURL(path.join(SRC_ROOT, 'sync-hosts.mjs')).href)};
85
+ const root = ${JSON.stringify(fixtureRoot)};
86
+ syncHosts(process.argv.slice(2), root);
87
+ `);
88
+ return spawnSync(process.execPath, [driver, ...extra], { encoding: 'utf8', cwd: fixtureRoot });
76
89
  }
77
90
 
78
91
  // ============ 场景 1:全对齐(正文段与权威源一致,frontmatter 各异)→ drift 空 ============
@@ -179,9 +192,44 @@ syncHosts(process.argv.slice(2), root);
179
192
  check('S8 apply 不删孤儿薄适配', fs.existsSync(path.join(fx, 'modules/hosts/opencode/commands/orphan.md')) && fs.existsSync(path.join(fx, 'modules/hosts/opencode/commands/wf-legacy.md')));
180
193
  }
181
194
 
195
+ // ============ 场景 9:装户布局(.agents 为权威源 + 宿主目录为薄适配,无 templates/modules)============
196
+ // 2026-10-07 sync-hosts-target-fix:此前目标根恒为 pkgRoot(= 包安装目录),装户跑 sync-hosts
197
+ // 会去改包源仓。本场景钉住「--dir / cwd 指向装户根 → 按装户布局比对」。
198
+ {
199
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-synchosts-inst-'));
200
+ W(path.join(root, '.agents/commands/test.md'),
201
+ `---\ndescription: Test 阶段\n---\n\n# Test · v2\n\n权威源正文 test v2\n`);
202
+ W(path.join(root, '.opencode/commands/wf-test.md'),
203
+ `---\ndescription: Test · opencode\n---\n\n# Test · v1\n\n旧正文 test v1\n`);
204
+ W(path.join(root, '.trae/commands/wf-test.md'),
205
+ `---\nname: wf-test\ndescription: Test · trae\n---\n\n# Test · v1\n\n旧正文 test v1\n`);
206
+
207
+ // 9a:--dir 指向装户根 → 布局判定「装户」,列出 2 处漂移
208
+ const r1 = runSyncHosts(root, ['--diff']);
209
+ const out1 = r1.stdout + r1.stderr;
210
+ check('S9 装户布局:--dir 指向装户根 → 头部标「装户布局」', /装户布局/.test(out1), out1);
211
+ check('S9 装户布局:列出 2 处正文漂移', /正文段漂移(2/.test(out1), out1);
212
+ // 未装宿主(claude/cursor/codex)目录不存在 → 不算「缺失」(否则装户刷屏 + apply 恒 exit 1)
213
+ check('S9 装户布局:未装宿主不进「权威源声明但薄适配缺失」', !/权威源声明但薄适配缺失/.test(out1), out1);
214
+
215
+ // 9b:不传 --dir(cwd = 装户根)→ 同样命中装户布局(旧行为会误指包源)
216
+ const r2 = runSyncHostsByCwd(root, ['--diff']);
217
+ const out2 = r2.stdout + r2.stderr;
218
+ check('S9 cwd 默认:装户布局解析正确(不再误指包源)', /装户布局/.test(out2) && !/包源布局/.test(out2), out2);
219
+
220
+ // 9c:apply → 正文段对齐、薄适配 frontmatter 保留
221
+ const r3 = runSyncHosts(root, ['--apply']);
222
+ const out3 = r3.stdout + r3.stderr;
223
+ const bodyOf = (p) => { const t = R(p); const m = t.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/); return t.slice(m ? m[0].length : 0); };
224
+ check('S9 装户布局 apply:opencode 正文段对齐', bodyOf(path.join(root, '.opencode/commands/wf-test.md')) === bodyOf(path.join(root, '.agents/commands/test.md')), out3);
225
+ check('S9 装户布局 apply:trae frontmatter 保留(name: wf-test)', /^---\nname: wf-test/m.test(R(path.join(root, '.trae/commands/wf-test.md'))), out3);
226
+ // 未装宿主不计缺失 → apply 摘要「权威源缺失:0」且 exit 0
227
+ check('S9 装户布局 apply:未装宿主不计缺失 → 权威源缺失 0 且 exit 0', /权威源缺失:0/.test(out3) && r3.status === 0, `status=${r3.status}\n${out3}`);
228
+ }
229
+
182
230
  {
183
- const pkg = path.resolve(SRC_ROOT, '..');
184
- const r = diffHosts({
231
+ const pkg = path.resolve(SRC_ROOT, '..');
232
+ const r = diffHosts({
185
233
  authorityRoot: path.join(pkg, 'templates', '_agents'),
186
234
  adaptersRoot: path.join(pkg, 'modules', 'hosts'),
187
235
  });
package/src/sync.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  // flow-kit sync:按 .agents/kit.json 台账升级 managed 文件。
2
2
  // 三态:未改动(磁盘 sha==台账)→ 覆盖新版;本地已改 → 跳过并报告(--force 覆盖;台账保持包侧基线,
3
3
  // 每次持续报告直至 --force 或本地对齐新版——防跳过一次后下次升级被静默覆盖,2026-09-24 语义修正);
4
- // 生成器目标(INDEX.md / wiki 看板)→ 不比对,收尾重跑生成器走锚点重写。
4
+ // 生成器目标(INDEX.md / wiki 看板)→ 不比对,生成器先于台账记账执行(2026-10-08 修 1:一次 sync
5
+ // 自洽——生成器重写盘面后按终态记账),锚点内重写。
5
6
  // 附带:包内新增 managed 文件 → 安装;包内已删 → 仅报告不删盘、出台账;managed 缺失 → 恢复。
6
7
  // 台账外文件收养(2026-09-26 managed-ledger-adopt):盘上存在但台账无该 rel 的 managed 类文件,
7
8
  // 若内容恰好等于新版渲染 sha(diskSha === fresh.sha)则收养登记——判据与台账内「改动恰好等于新版」
@@ -149,9 +150,35 @@ export function sync(args, pkgRoot) {
149
150
  console.log(` 无变化 ${unchanged} 份`);
150
151
  }
151
152
 
153
+ // 生成器目标走锚点重写(不参与 sha 比对)——执行前过供应链防线(init-p1-batch P1-3):
154
+ // 目标侧脚本与包源渲染值 sha 一致才执行(预置/被改动的脚本不可信,跳过并显式提示)。
155
+ // 先于台账记账执行(2026-10-08 papercuts-cleanup-batch 修 1):生成器会重写 owned 生成目标
156
+ // (INDEX/看板),记账段按生成后盘面收口——一次 sync 自洽,不再出现「wiki 变更后需二次 sync 自愈」。
157
+ const genGuard = (rel) => {
158
+ const g = scriptTrusted({ pkgRoot, target, rel, vars });
159
+ if (!g.ok) {
160
+ console.log(` ⚠️ 跳过执行 ${rel}——${g.note}(供应链防线:只执行与包源渲染值一致的目标侧脚本)`);
161
+ return false;
162
+ }
163
+ return true;
164
+ };
165
+ if (genGuard('.agents/scripts/gen-workflow-index.mjs')) {
166
+ const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
167
+ if (genIndex.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-workflow-index.mjs'))) {
168
+ console.log(genIndex.ok ? ' 已重生成 workflow/INDEX.md(锚点内重写)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
169
+ }
170
+ }
171
+ if (genGuard('.agents/scripts/gen-wiki-board.mjs')) {
172
+ const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
173
+ if (genBoard.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-wiki-board.mjs'))) {
174
+ console.log(genBoard.ok ? ' 已重生成 wiki 速览与看板 DATA(锚点内重写)' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
175
+ }
176
+ }
177
+
152
178
  // owned 台账按盘面自愈:owned 归项目所有,哈希只是记账不是约束——手改后无须手工刷 kit.json
153
179
  // (Shipyard 回流策略,2026-09-24:曾在消费仓被迫手工刷新 owned 哈希,根因收敛到此处)。
154
- // sha 按 LF 归一记(与 doctor checkOwnedDrift 同口径,2026-09-27 gate-coverage:跨 checkout 字节稳定)
180
+ // sha 按 LF 归一记(与 doctor checkOwnedDrift 同口径,2026-09-27 gate-coverage:跨 checkout 字节稳定)。
181
+ // 位于生成器之后(2026-10-08 papercuts-cleanup-batch 修 1):生成器重写盘面后按终态记账。
155
182
  const ownedSha = (p) => sha256(Buffer.from(fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n'), 'utf8'));
156
183
  let ownedRefreshed = 0;
157
184
  if (Array.isArray(kit.owned)) {
@@ -188,33 +215,10 @@ export function sync(args, pkgRoot) {
188
215
  console.log(' owned 归项目所有,不自动覆盖;如需跟随:从包源 templates/ 拷贝对应文件;有意定制可忽略(下次 sync 起静默)');
189
216
  }
190
217
 
191
- // 台账重写:版本对齐当前包;removed 出册;skipped 保持包侧基线持续报告
218
+ // 台账重写(生成器之后):版本对齐当前包;removed 出册;skipped 保持包侧基线持续报告
192
219
  kit.managed = managedNew.sort((a, b) => a.rel.localeCompare(b.rel));
193
220
  kit.version = pkg.version;
194
221
  fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
195
-
196
- // 生成器目标走锚点重写(不参与 sha 比对)——执行前过供应链防线(init-p1-batch P1-3):
197
- // 目标侧脚本与包源渲染值 sha 一致才执行(预置/被改动的脚本不可信,跳过并显式提示)
198
- const genGuard = (rel) => {
199
- const g = scriptTrusted({ pkgRoot, target, rel, vars });
200
- if (!g.ok) {
201
- console.log(` ⚠️ 跳过执行 ${rel}——${g.note}(供应链防线:只执行与包源渲染值一致的目标侧脚本)`);
202
- return false;
203
- }
204
- return true;
205
- };
206
- if (genGuard('.agents/scripts/gen-workflow-index.mjs')) {
207
- const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
208
- if (genIndex.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-workflow-index.mjs'))) {
209
- console.log(genIndex.ok ? ' 已重生成 workflow/INDEX.md(锚点内重写)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
210
- }
211
- }
212
- if (genGuard('.agents/scripts/gen-wiki-board.mjs')) {
213
- const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
214
- if (genBoard.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-wiki-board.mjs'))) {
215
- console.log(genBoard.ok ? ' 已重生成 wiki 速览与看板 DATA(锚点内重写)' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
216
- }
217
- }
218
222
  } finally {
219
223
  fs.rmSync(tmp, { recursive: true, force: true });
220
224
  }