agentic-flow-kit 1.2.0 → 1.2.2

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.
@@ -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.0",
3
+ "version": "1.2.2",
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 # 交互模式:宿主 → 技术栈 → 端口 → 确认安装
@@ -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
  });
@@ -20,7 +20,10 @@ next: .agents/commands/build.md(漂移属 build 改权威源未同步导致的
20
20
  # Sync-Hosts · 跨宿主适配层正文段同步
21
21
 
22
22
  > 改权威源(`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 各自原描述)。
23
- > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift)。
23
+ > 装户视角:`flow-kit doctor` §7.x 跑同名检查(仅装户,包源环境跳过——包源下永远是 drift);**修漂移用同一条命令**
24
+ > `flow-kit sync-hosts --apply`——2026-10-07 起按**目标根**自动判布局(含 `templates/_agents` + `modules/hosts` → 包源:
25
+ > 比 `templates/_agents` ↔ `modules/hosts/<宿主键>/`;含 `.agents` → 装户:比 `.agents` ↔ 项目根下 `<宿主点目录>/`),
26
+ > 目标根取 `--dir <目录>` > 当前目录。
24
27
 
25
28
  ## 何时跑
26
29
 
@@ -34,8 +37,9 @@ next: .agents/commands/build.md(漂移属 build 改权威源未同步导致的
34
37
  ### 默认 `--diff`(仅报告)
35
38
 
36
39
  ```
37
- node bin/flow-kit.mjs sync-hosts --diff
38
- node bin/flow-kit.mjs sync-hosts --json # 机器可读
40
+ node bin/flow-kit.mjs sync-hosts --diff # 包源仓(在包源根跑)
41
+ node bin/flow-kit.mjs sync-hosts --json # 机器可读
42
+ flow-kit sync-hosts --diff # 装户仓(在装户根跑;或用 --dir <项目根> 指定)
39
43
  ```
40
44
 
41
45
  输出三类:
@@ -75,7 +75,7 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
75
75
  - 输出测试报告(通过项 / 失败项)
76
76
  - 失败项已修复并复测通过
77
77
  - **关单**(日常闭环点,不依赖是否上 prod):
78
- - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
78
+ - 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单);**装户仓**(根目录无 `package.json`,工程在子目录)默认 `npm test` 不可跑 → 脚本**显式跳过步骤 1 且不落测试绿凭证**(防假绿)并提示;此时按项目口径手工跑测试,或带 `--test-cmd "<项目测试命令>"` 指定(2026-10-07 papercut)
79
79
  - 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
80
80
  - **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
81
81
  **确认落态唯一入口**(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。
@@ -10,8 +10,11 @@
10
10
  // (窗口判定/门禁在消费方 confirm-doc 与 check-loop 检查 8);消费语义 = 「按门跑了 verify 且全绿」,
11
11
  // 裸跑 npm test 不落账(对齐引导)。npm test 步骤输出从 inherit 改 pipe 捕获后回放(延迟显示,
12
12
  // 关单场景可接受)——仅为 best-effort 抓「合计: PASS n / FAIL n」计数行,抓不到则省略计数键。
13
+ // 装户仓(根目录无 package.json,工程在子目录)下默认 npm test 恒 ENOENT(exit 4058)→ **显式跳过步骤 1**
14
+ // 且**不落测试绿凭证**(凭证语义 = 测试真跑绿,跳过即无凭证,防假绿),并提示用
15
+ // --test-cmd "<项目测试命令>" 指定;显式 --test-cmd 不受此判定影响(2026-10-07 papercut)。
13
16
  // 测试:node templates/_agents/scripts/verify.test.mjs(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具,不触真实 npm test 与 workflow)
14
- // 用法:node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"](--test-cmd 仅供测试注入)
17
+ // 用法:node .agents/scripts/verify.mjs [--test-cmd "<shell 命令>"](--test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令)
15
18
  import fs from 'node:fs';
16
19
  import { spawnSync } from 'node:child_process';
17
20
  import path from 'node:path';
@@ -19,19 +22,31 @@ import { fileURLToPath } from 'node:url';
19
22
 
20
23
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
21
24
  const args = process.argv.slice(2);
22
- const testCmd = args.includes('--test-cmd') ? args[args.indexOf('--test-cmd') + 1] : 'npm test';
25
+ const explicitTestCmd = args.includes('--test-cmd') ? args[args.indexOf('--test-cmd') + 1] : null;
26
+ const testCmd = explicitTestCmd ?? 'npm test';
23
27
  const checkLoop = path.join(SCRIPT_DIR, 'check-loop.mjs');
24
28
  const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
25
29
 
26
- console.log(`[verify] ▶ 1/2 ${testCmd}`);
27
- const t = spawnSync(testCmd, { shell: true, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
28
- if (t.stdout) process.stdout.write(t.stdout);
29
- if (t.stderr) process.stderr.write(t.stderr);
30
- if (t.status !== 0) {
31
- console.error(`[verify] ❌ 1/2 ${testCmd} 未通过(exit ${t.status ?? t.error?.code})——修复后重跑,非绿不关单`);
32
- process.exit(1);
30
+ // 装户仓判定:默认命令只在**当前目录**有 package.json 时才跑(npm test 亦在该目录执行)。
31
+ // 跳过时后续不落测试绿凭证(见文末落账段)。
32
+ const hasRootPackageJson = fs.existsSync(path.join(process.cwd(), 'package.json'));
33
+ const skipTest = explicitTestCmd === null && !hasRootPackageJson;
34
+ let testStdout = '';
35
+
36
+ if (skipTest) {
37
+ console.log('[verify] ⏭ 1/2 跳过 npm test:当前目录无 package.json(装户仓)——请按项目口径手工跑测试,或用 --test-cmd "<项目测试命令>" 指定后重跑');
38
+ } else {
39
+ console.log(`[verify] ▶ 1/2 ${testCmd}`);
40
+ const t = spawnSync(testCmd, { shell: true, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
41
+ if (t.stdout) process.stdout.write(t.stdout);
42
+ if (t.stderr) process.stderr.write(t.stderr);
43
+ if (t.status !== 0) {
44
+ console.error(`[verify] ❌ 1/2 ${testCmd} 未通过(exit ${t.status ?? t.error?.code})——修复后重跑,非绿不关单`);
45
+ process.exit(1);
46
+ }
47
+ testStdout = String(t.stdout || '');
48
+ console.log('[verify] ✅ 1/2 测试通过');
33
49
  }
34
- console.log('[verify] ✅ 1/2 测试通过');
35
50
 
36
51
  console.log('[verify] ▶ 2/2 check-loop');
37
52
  const c = spawnSync(process.execPath, [checkLoop], { stdio: 'inherit' });
@@ -41,15 +56,20 @@ if (c.status !== 0) {
41
56
  }
42
57
  console.log('[verify] ✅ 2/2 闭环校验通过');
43
58
 
44
- // 凭证落账:两步全绿才落(check-loop 红不产生凭证——凭证语义 = verify 全绿);IO 失败出账不阻断
59
+ // 凭证落账:两步全绿才落(check-loop 红不产生凭证——凭证语义 = verify 全绿);**步骤 1 被跳过同样不落**
60
+ // (测试未跑即无「测试绿」事实,落账即假绿);IO 失败出账不阻断
45
61
  try {
46
- const m = [...String(t.stdout || '').matchAll(/合计: PASS (\d+) \/ FAIL (\d+)/g)].pop();
47
- const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
48
- if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
49
- if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
50
- fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
51
- fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
52
- console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(PASS ${entry.passed} / FAIL ${entry.failed})` : ''}`);
62
+ if (skipTest) {
63
+ console.log('[verify] ⚠️ 步骤 1 已跳过——本次不落测试绿凭证(凭证语义 = 测试真跑绿)');
64
+ } else {
65
+ const m = [...testStdout.matchAll(/合计: PASS (\d+) \/ FAIL (\d+)/g)].pop();
66
+ const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
67
+ if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
68
+ if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
69
+ fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
70
+ fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
71
+ console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(PASS ${entry.passed} / FAIL ${entry.failed})` : ''}`);
72
+ }
53
73
  } catch (e) {
54
74
  console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
55
75
  }
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // verify.mjs 的 fixture 测试(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具根,不触真实 npm test 与 workflow)
3
3
  // 断言:①绿路径(假过 + 空夹具闭环)exit 0;②步骤 1 假败 exit 1 且不出全绿;
4
- // ③步骤 2 配对断裂夹具 exit 1(verify 自身标记断言——孙进程 stdio inherit 输出不进本测试管道)。
4
+ // ③步骤 2 配对断裂夹具 exit 1(verify 自身标记断言——孙进程 stdio inherit 输出不进本测试管道);
5
+ // ④⑤⑥凭证落账三态(绿落 / check-loop 红不落 / 无汇总行省略计数键);
6
+ // ⑦装户仓(cwd 无 package.json)未给 --test-cmd → 跳过步骤 1、不落凭证、仍跑步骤 2、exit 0。
5
7
  // 用法:node templates/_agents/scripts/verify.test.mjs
6
8
  import fs from 'node:fs';
7
9
  import os from 'node:os';
@@ -116,6 +118,20 @@ const run = (argv, opts = {}) => spawnSync(process.execPath, [VERIFY, ...argv],
116
118
  fs.rmSync(root, { recursive: true, force: true });
117
119
  }
118
120
 
121
+ // ---- 场景 7:装户仓(cwd 无 package.json)且未给 --test-cmd → 跳过步骤 1、不落凭证、仍跑步骤 2 → exit 0 ----
122
+ {
123
+ const root = mkfix();
124
+ const bare = fs.mkdtempSync(path.join(os.tmpdir(), 'verify-bare-'));
125
+ const r = run([], { cwd: bare, env: { ...process.env, CHECK_LOOP_ROOT: root } });
126
+ check('场景 7:exit 0', r.status === 0, `exit=${r.status}\n${r.stdout}\n${r.stderr}`);
127
+ check('场景 7:步骤 1 显式跳过(⏭ 1/2)、步骤 2 通过且全绿', r.stdout.includes('⏭ 1/2') && r.stdout.includes('✅ 2/2') && r.stdout.includes('全绿'), r.stdout);
128
+ check('场景 7:跳过时不落测试绿凭证(防假绿)',
129
+ !fs.existsSync(path.join(root, '.agents', 'verifications.jsonl')) && r.stdout.includes('不落测试绿凭证'),
130
+ r.stdout);
131
+ fs.rmSync(root, { recursive: true, force: true });
132
+ fs.rmSync(bare, { recursive: true, force: true });
133
+ }
134
+
119
135
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
120
136
  if (fail) {
121
137
  console.log('\n用法:node templates/_agents/scripts/verify.test.mjs');
@@ -3,22 +3,59 @@ name: verify-ui
3
3
  description: 启动本地服务并用浏览器实测 UI 改动(只读验证、不留脏数据);项目无测试基建时靠实测兜底。当用户要求"实测 / UI 验证 / 浏览器验证"时使用。
4
4
  ---
5
5
 
6
- # verify-ui — UI 实测引导(骨架,项目按实际填「启动命令 / 实测入口」)
6
+ # verify-ui — UI 实测引导
7
+
8
+ 无测试基建时,UI 改动必须浏览器实测。步骤:
7
9
 
8
10
  ## 1. 起服务
9
11
 
10
- - API:<待填:启动命令(见 .agents/notes/runtime-env.md §2)>
11
- - 前端:<待填:启动命令>
12
+ 启动命令**不写死在本文件**(本文件是 managed 件,随包升级覆盖——项目特定值写这里会丢):以 `.agents/notes/runtime-env.md`「本地端口与进程」节的记录为准;该节无记录时,先起服务并把命令补记进该文件再继续。
12
13
 
13
14
  进程清单记进 `.agents/notes/runtime-env.md`,实测结束必须关闭。
14
15
 
15
- ## 2. 实测步骤
16
+ ## 2. 浏览器验证
17
+
18
+ - 优先 `browser` 工具;不可用时用 `.agents/scripts/e2e/harness.mjs`(如项目已装;封装 `launch`/`seedSession`/`goto`/`hardGoto`/`clickText`/`pressKey`/`ensureClean`/`screenshot` + 网络/控制台/toast 采集器);自写脚本优先复用其常量与函数。
19
+ - 浏览器首选**较新的 Chromium 内核 headless**(如 Edge `--headless=new`)——旧版 Chrome 会把 antd 图标字体渲染成方框、表格 `waitFor` 超时(见下「浏览器选择」)。
20
+ - 登录态:先调 API 登录拿 token,注入前端约定的 localStorage 键,**并整页重载**让前端状态库重读 token(仅改 hash 的导航不重建文档,会被路由守卫弹回登录页)。
21
+ - 路由为 hash 模式时:`goto` 用 hash;深链接(query 变化)用 `hardGoto`(about:blank 中转强制新文档)。
22
+
23
+ ## 3. 实测步骤
16
24
 
17
25
  1. 打开目标页面,按改动点逐项操作:正常路径一条 + 失败路径一条(校验/空态/权限)。
18
26
  2. 证据落盘:截图或关键控制台/网络报文,路径写在任务文档「验收标准」勾验项后。
19
- 3. 不留脏数据:验证产生的临时数据当场删除或还原;不确定可回滚的操作先停下问用户。
27
+ 3. **不留脏数据**:验证产生的临时数据当场删除或还原;清理**只按本次创建的 ID 精确删除**(ID 取自网络捕获的 POST 响应),**禁止按文案/字段模糊匹配**——模糊匹配会误删用户手建数据;清理脚本一律先备份再删;不确定可回滚的操作先停下问用户。
20
28
 
21
- ## 3. 收尾
29
+ ## 4. 收尾
22
30
 
23
31
  - 关闭本次起动的全部进程(按 §1 清单逐个停)。
24
32
  - 结论按「页面 / 步骤 / 期望 vs 实际 / 证据」回报;发现缺陷只记录定性,不修代码(修复走 build/maintain 流程)。
33
+
34
+ ## 关键坑
35
+
36
+ - API 用 **HTTP** 避开自签证书问题(HTTPS 自签证书会让浏览器/脚本报错或需额外信任配置)。
37
+ - MCP 报「browser is already running」是残留实例占 MCP 专用 profile 锁(`~/.cache/chrome-devtools-mcp/`),清掉对应 chrome 进程重试即可,勿误判为 MCP 不可用。
38
+ - 布局类修复用 `getBoundingClientRect` 程序化检测,比截图肉眼更可靠。
39
+ - 后台任务生命周期不可依赖(实测可长可短):长命服务一律独立进程起(如 PowerShell `Start-Process`)。
40
+ - 用 shell 工具起长命服务时**勿在命令末尾接管道**(`| tail` 等)——子进程继承 stdout 句柄会让命令永不返回,直到被工具超时杀掉(并可能连带杀掉服务)。
41
+
42
+ ### 浏览器选择
43
+
44
+ - 旧版 Chrome(实测 83)内核太旧 → antd 图标字体全渲染成方框、表格 `waitFor` 超时。**优先较新的 Chromium 内核**(如 Edge `--headless=new`);harness 的 `CHROME` 常量按本机实际安装路径改。
45
+
46
+ ### antd 交互(JS `.click()` 不够)
47
+
48
+ - 按钮文本内含空格(`确 定` / `保 存` / `取 消`)→ 匹配须去空白,勿用 `innerText === '确定'`。
49
+ - Select / Picker / Dropdown 的 `.click()` 打不开弹层 → 须 `Input.dispatchMouseEvent`(mousedown+mouseup 序列);普通按钮用 `clickText` 够用,弹层控件须补 `realClick`。
50
+ - `new KeyboardEvent('Escape')` 关不掉弹层 → 须 `Input.dispatchKeyEvent`(rawKeyDown+keyUp)。
51
+ - **关弹层慎用 Esc**:antd Drawer 同样响应 Esc,会连带关闭抽屉并 reset 表单(表现为「保存后列表无新行」,易误判为保存链路故障)——改点遮罩/关闭按钮。
52
+ - 表格行常无 `tr[data-row-key]` 属性 → 用 `.ant-table-tbody > tr.ant-table-row` + textContent 过滤业务键(如单号)定位行。
53
+ - 宽表 `:scroll="{x:N}"` 截图前把 `.ant-table-content` scrollLeft 置底,露出右侧列。
54
+ - 虚拟滚动下拉(a-select)DOM 只渲染约 10 个节点 → **勿按 DOM 节点数断言选项条数**,按接口响应条数断言。
55
+
56
+ ### CDP 采集(SPA hash 导航不重建文档)
57
+
58
+ - Log/Console 采集不随导航重置 → 每次导航前重置采集器,防上一页错误泄漏成后续页面的「控制台错误」。
59
+ - toast 观察器须**导航前**安装;导航后装会漏首屏 toast。
60
+ - 首屏无 token 被路由守卫落登录页:写 localStorage 后仅 hash 变化的 `Page.navigate` 不整页重载、前端状态库仍持空 token → 须 `about:blank` 中转强制新文档,或写后 `Page.reload`。
61
+ - 同路由 hash query 变化不重挂载(`#/x?k=A` → `#/x?k=B`)→ 深链接用例走 `about:blank` 中转。