@dommaker/harness 1.8.1 → 1.10.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 (299) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -13
  3. package/dist/cli/commands/check.d.ts +3 -3
  4. package/dist/cli/commands/check.d.ts.map +1 -1
  5. package/dist/cli/commands/check.js +41 -78
  6. package/dist/cli/commands/check.js.map +1 -1
  7. package/dist/cli/commands/constraints-report.d.ts +2 -10
  8. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  9. package/dist/cli/commands/constraints-report.js +4 -57
  10. package/dist/cli/commands/constraints-report.js.map +1 -1
  11. package/dist/cli/commands/constraints-retire.d.ts +9 -23
  12. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  13. package/dist/cli/commands/constraints-retire.js +39 -121
  14. package/dist/cli/commands/constraints-retire.js.map +1 -1
  15. package/dist/cli/commands/constraints.d.ts +3 -8
  16. package/dist/cli/commands/constraints.d.ts.map +1 -1
  17. package/dist/cli/commands/constraints.js +8 -23
  18. package/dist/cli/commands/constraints.js.map +1 -1
  19. package/dist/cli/commands/definitions.js +2 -2
  20. package/dist/cli/commands/init.d.ts +0 -32
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/init.js +7 -149
  23. package/dist/cli/commands/init.js.map +1 -1
  24. package/dist/cli/commands/report.d.ts.map +1 -1
  25. package/dist/cli/commands/report.js +14 -17
  26. package/dist/cli/commands/report.js.map +1 -1
  27. package/dist/cli/commands/scaffold-templates.d.ts +0 -2
  28. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
  29. package/dist/cli/commands/scaffold-templates.js +2 -37
  30. package/dist/cli/commands/scaffold-templates.js.map +1 -1
  31. package/dist/cli/commands/scaffold.d.ts +0 -2
  32. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  33. package/dist/cli/commands/scaffold.js +0 -10
  34. package/dist/cli/commands/scaffold.js.map +1 -1
  35. package/dist/cli/commands/status.js +10 -10
  36. package/dist/cli/commands/status.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
  38. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
  40. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  41. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +22 -0
  42. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  43. package/dist/cli/commands/sync-docs/capabilities-syncer.js +113 -8
  44. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  45. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  46. package/dist/cli/commands/sync-docs/index.js +36 -4
  47. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  48. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  49. package/dist/core/constraints/capabilities-parser.js +16 -4
  50. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  51. package/dist/core/constraints/checker.d.ts +15 -24
  52. package/dist/core/constraints/checker.d.ts.map +1 -1
  53. package/dist/core/constraints/checker.js +33 -72
  54. package/dist/core/constraints/checker.js.map +1 -1
  55. package/dist/core/constraints/checkers/docs-freshness.js +1 -1
  56. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  57. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
  58. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  59. package/dist/core/constraints/checkers/governance-presence.js +4 -3
  60. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  61. package/dist/core/constraints/checkers/index.d.ts +1 -1
  62. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  63. package/dist/core/constraints/checkers/index.js +4 -8
  64. package/dist/core/constraints/checkers/index.js.map +1 -1
  65. package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
  66. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  67. package/dist/core/constraints/checkers/iron-flags.js +4 -5
  68. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  69. package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
  70. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  71. package/dist/core/constraints/checkers/types.d.ts +1 -1
  72. package/dist/core/constraints/checkers/types.js +1 -1
  73. package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
  74. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  75. package/dist/core/constraints/definitions/guidelines.js +9 -25
  76. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  77. package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
  78. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  79. package/dist/core/constraints/definitions/iron-laws.js +10 -81
  80. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  81. package/dist/core/constraints/definitions.d.ts +7 -9
  82. package/dist/core/constraints/definitions.d.ts.map +1 -1
  83. package/dist/core/constraints/definitions.js +13 -16
  84. package/dist/core/constraints/definitions.js.map +1 -1
  85. package/dist/core/constraints/index.d.ts +2 -5
  86. package/dist/core/constraints/index.d.ts.map +1 -1
  87. package/dist/core/constraints/index.js +3 -13
  88. package/dist/core/constraints/index.js.map +1 -1
  89. package/dist/core/constraints/injection-writer.d.ts +10 -38
  90. package/dist/core/constraints/injection-writer.d.ts.map +1 -1
  91. package/dist/core/constraints/injection-writer.js +17 -54
  92. package/dist/core/constraints/injection-writer.js.map +1 -1
  93. package/dist/core/constraints/run-env.d.ts +1 -10
  94. package/dist/core/constraints/run-env.d.ts.map +1 -1
  95. package/dist/core/constraints/run-env.js +1 -22
  96. package/dist/core/constraints/run-env.js.map +1 -1
  97. package/dist/core/constraints/usage-report.d.ts +5 -7
  98. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  99. package/dist/core/constraints/usage-report.js +5 -10
  100. package/dist/core/constraints/usage-report.js.map +1 -1
  101. package/dist/core/effective-constraints.d.ts +12 -22
  102. package/dist/core/effective-constraints.d.ts.map +1 -1
  103. package/dist/core/effective-constraints.js +13 -30
  104. package/dist/core/effective-constraints.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -2
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +3 -9
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/core/project-config-loader.d.ts +4 -42
  110. package/dist/core/project-config-loader.d.ts.map +1 -1
  111. package/dist/core/project-config-loader.js +16 -125
  112. package/dist/core/project-config-loader.js.map +1 -1
  113. package/dist/core/validators/passes-gate.d.ts +2 -2
  114. package/dist/core/validators/passes-gate.js +6 -6
  115. package/dist/core/validators/passes-gate.js.map +1 -1
  116. package/dist/hooks/bootstrap.d.ts +9 -12
  117. package/dist/hooks/bootstrap.d.ts.map +1 -1
  118. package/dist/hooks/bootstrap.js +9 -31
  119. package/dist/hooks/bootstrap.js.map +1 -1
  120. package/dist/hooks/index.d.ts +5 -7
  121. package/dist/hooks/index.d.ts.map +1 -1
  122. package/dist/hooks/index.js +4 -10
  123. package/dist/hooks/index.js.map +1 -1
  124. package/dist/index.d.ts +9 -12
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +10 -23
  127. package/dist/index.js.map +1 -1
  128. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  129. package/dist/monitoring/trace-analyzer.js +11 -12
  130. package/dist/monitoring/trace-analyzer.js.map +1 -1
  131. package/dist/monitoring/traces.d.ts +4 -4
  132. package/dist/monitoring/traces.d.ts.map +1 -1
  133. package/dist/monitoring/traces.js +8 -8
  134. package/dist/monitoring/traces.js.map +1 -1
  135. package/dist/presets/standard.d.ts +8 -10
  136. package/dist/presets/standard.d.ts.map +1 -1
  137. package/dist/presets/standard.js +10 -16
  138. package/dist/presets/standard.js.map +1 -1
  139. package/dist/types/constraint.d.ts +24 -35
  140. package/dist/types/constraint.d.ts.map +1 -1
  141. package/dist/types/constraint.js +8 -7
  142. package/dist/types/constraint.js.map +1 -1
  143. package/dist/types/passes-gate.d.ts +5 -5
  144. package/dist/types/passes-gate.d.ts.map +1 -1
  145. package/dist/types/project-config.d.ts +8 -66
  146. package/dist/types/project-config.d.ts.map +1 -1
  147. package/dist/types/project-config.js +3 -2
  148. package/dist/types/project-config.js.map +1 -1
  149. package/dist/types/trace.d.ts +8 -10
  150. package/dist/types/trace.d.ts.map +1 -1
  151. package/package.json +2 -2
  152. package/src/CONTEXT.md +3 -3
  153. package/src/__tests__/checker-extra.test.ts +37 -61
  154. package/src/__tests__/checker.test.ts +10 -115
  155. package/src/__tests__/effective-constraints.test.ts +18 -83
  156. package/src/__tests__/iron-laws.test.ts +71 -90
  157. package/src/__tests__/passes-gate-check.test.ts +2 -2
  158. package/src/__tests__/project-config-loader-extra.test.ts +22 -123
  159. package/src/__tests__/project-config-loader.test.ts +5 -33
  160. package/src/__tests__/public-exports.test.ts +31 -11
  161. package/src/__tests__/public-type-surface.test.ts +2 -13
  162. package/src/__tests__/public-value-surface.test.ts +1 -7
  163. package/src/__tests__/skip-semantics.test.ts +21 -37
  164. package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
  165. package/src/__tests__/trace-analyzer.test.ts +21 -21
  166. package/src/__tests__/trace-collector.test.ts +22 -22
  167. package/src/__tests__/traces.test.ts +27 -28
  168. package/src/cli/commands/CONTEXT.md +12 -9
  169. package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
  170. package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
  171. package/src/cli/commands/__tests__/check.test.ts +14 -31
  172. package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
  173. package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
  174. package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
  175. package/src/cli/commands/__tests__/init-ondisk.test.ts +12 -20
  176. package/src/cli/commands/__tests__/init.test.ts +0 -11
  177. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
  178. package/src/cli/commands/__tests__/registry.test.ts +39 -3
  179. package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
  180. package/src/cli/commands/__tests__/report.test.ts +1 -1
  181. package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
  182. package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
  183. package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
  184. package/src/cli/commands/__tests__/status.test.ts +12 -12
  185. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
  186. package/src/cli/commands/__tests__/sync-docs-table-layout.test.ts +259 -0
  187. package/src/cli/commands/check.ts +43 -82
  188. package/src/cli/commands/constraints-report.ts +4 -59
  189. package/src/cli/commands/constraints-retire.ts +49 -154
  190. package/src/cli/commands/constraints.ts +11 -26
  191. package/src/cli/commands/definitions.ts +2 -2
  192. package/src/cli/commands/init.ts +8 -179
  193. package/src/cli/commands/report.ts +19 -23
  194. package/src/cli/commands/scaffold-templates.ts +1 -37
  195. package/src/cli/commands/scaffold.ts +0 -11
  196. package/src/cli/commands/status.ts +10 -10
  197. package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
  198. package/src/cli/commands/sync-docs/capabilities-syncer.ts +139 -9
  199. package/src/cli/commands/sync-docs/index.ts +41 -4
  200. package/src/core/CONTEXT.md +9 -9
  201. package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
  202. package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
  203. package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
  204. package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
  205. package/src/core/constraints/__tests__/run-env.test.ts +5 -34
  206. package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
  207. package/src/core/constraints/capabilities-parser.ts +17 -5
  208. package/src/core/constraints/checker.ts +39 -88
  209. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
  210. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
  211. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
  212. package/src/core/constraints/checkers/docs-freshness.ts +1 -1
  213. package/src/core/constraints/checkers/governance-presence.ts +4 -3
  214. package/src/core/constraints/checkers/index.ts +4 -10
  215. package/src/core/constraints/checkers/iron-flags.ts +3 -12
  216. package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
  217. package/src/core/constraints/checkers/types.ts +1 -1
  218. package/src/core/constraints/definitions/guidelines.ts +8 -25
  219. package/src/core/constraints/definitions/iron-laws.ts +9 -82
  220. package/src/core/constraints/definitions.ts +14 -15
  221. package/src/core/constraints/index.ts +4 -19
  222. package/src/core/constraints/injection-writer.ts +16 -61
  223. package/src/core/constraints/run-env.ts +1 -33
  224. package/src/core/constraints/usage-report.ts +8 -15
  225. package/src/core/effective-constraints.ts +14 -45
  226. package/src/core/index.ts +3 -10
  227. package/src/core/project-config-loader.ts +20 -145
  228. package/src/core/validators/passes-gate.ts +6 -6
  229. package/src/failure/CONTEXT.md +1 -1
  230. package/src/hooks/CONTEXT.md +12 -22
  231. package/src/hooks/__tests__/bootstrap.test.ts +24 -75
  232. package/src/hooks/bootstrap.ts +9 -48
  233. package/src/hooks/index.ts +5 -20
  234. package/src/index.ts +9 -31
  235. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
  236. package/src/monitoring/__tests__/traces.test.ts +22 -22
  237. package/src/monitoring/trace-analyzer.ts +12 -13
  238. package/src/monitoring/traces.ts +8 -8
  239. package/src/presets/__tests__/standard.test.ts +12 -16
  240. package/src/presets/standard.ts +14 -23
  241. package/src/types/__tests__/types.test.ts +3 -3
  242. package/src/types/constraint.ts +26 -40
  243. package/src/types/passes-gate.ts +5 -5
  244. package/src/types/project-config.ts +8 -77
  245. package/src/types/trace.ts +8 -11
  246. package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
  247. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
  248. package/dist/core/constraints/agent-prompt-renderer.js +0 -55
  249. package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
  250. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
  251. package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
  252. package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
  253. package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
  254. package/dist/core/constraints/definitions/prompts.d.ts +0 -12
  255. package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
  256. package/dist/core/constraints/definitions/prompts.js +0 -400
  257. package/dist/core/constraints/definitions/prompts.js.map +0 -1
  258. package/dist/core/constraints/injection-drift.d.ts +0 -61
  259. package/dist/core/constraints/injection-drift.d.ts.map +0 -1
  260. package/dist/core/constraints/injection-drift.js +0 -131
  261. package/dist/core/constraints/injection-drift.js.map +0 -1
  262. package/dist/core/constraints/injection-renderer.d.ts +0 -23
  263. package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
  264. package/dist/core/constraints/injection-renderer.js +0 -44
  265. package/dist/core/constraints/injection-renderer.js.map +0 -1
  266. package/dist/hooks/config.d.ts +0 -30
  267. package/dist/hooks/config.d.ts.map +0 -1
  268. package/dist/hooks/config.js +0 -34
  269. package/dist/hooks/config.js.map +0 -1
  270. package/dist/hooks/pipeline.d.ts +0 -54
  271. package/dist/hooks/pipeline.d.ts.map +0 -1
  272. package/dist/hooks/pipeline.js +0 -173
  273. package/dist/hooks/pipeline.js.map +0 -1
  274. package/dist/hooks/registry.d.ts +0 -70
  275. package/dist/hooks/registry.d.ts.map +0 -1
  276. package/dist/hooks/registry.js +0 -141
  277. package/dist/hooks/registry.js.map +0 -1
  278. package/dist/hooks/types.d.ts +0 -113
  279. package/dist/hooks/types.d.ts.map +0 -1
  280. package/dist/hooks/types.js +0 -9
  281. package/dist/hooks/types.js.map +0 -1
  282. package/src/__tests__/hooks-pipeline.test.ts +0 -201
  283. package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
  284. package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
  285. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
  286. package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
  287. package/src/core/constraints/agent-prompt-renderer.ts +0 -67
  288. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
  289. package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
  290. package/src/core/constraints/definitions/prompts.ts +0 -414
  291. package/src/core/constraints/injection-drift.ts +0 -142
  292. package/src/core/constraints/injection-renderer.ts +0 -44
  293. package/src/hooks/__tests__/config.test.ts +0 -19
  294. package/src/hooks/__tests__/pipeline.test.ts +0 -195
  295. package/src/hooks/__tests__/registry.test.ts +0 -115
  296. package/src/hooks/config.ts +0 -33
  297. package/src/hooks/pipeline.ts +0 -202
  298. package/src/hooks/registry.ts +0 -166
  299. package/src/hooks/types.ts +0 -120
@@ -1,141 +0,0 @@
1
- "use strict";
2
- /**
3
- * HookRegistry — hook 注册/注销/查询
4
- *
5
- * 纯簿记,无执行逻辑。线程不安全(Node.js 单线程)。
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.HookRegistry = void 0;
9
- exports.assertHookRegistryClosed = assertHookRegistryClosed;
10
- /**
11
- * 注册表闭环双向校验(H5,复制 checker 闭环模式)
12
- *
13
- * 声明(HookConfig,consumer 的 per-hook 配置表)↔ 实现(注册的
14
- * HookDefinition)加载期双向校验,任一不满足即抛错,拒绝静默缺失:
15
- * - 引用未注册 → 抛错(声明了配置但没有注册实现 = 死配置)
16
- * - 注册无定义 → 抛错(注册了实现但没有配置声明 = 死代码)
17
- * - 重复声明 / 重复注册 → 抛错
18
- *
19
- * 断言限构建/测试期调用(harness 无内置 hook 定义,加载期闭环由
20
- * consumer 在其注册点/测试中调用本函数),不进入运行时热路径,
21
- * 不破坏 --help/--version 懒加载。消灭「hook 定义不注册 = 死代码」
22
- * 类 prompt 规矩(studio 侧随动)。
23
- *
24
- * @throws 任一方向不满足时抛错
25
- */
26
- function assertHookRegistryClosed(configs, hooks) {
27
- const configNames = new Set();
28
- for (const config of configs) {
29
- if (configNames.has(config.name)) {
30
- throw new Error(`[harness] hook 注册表闭环校验失败:hook 配置 "${config.name}" 重复声明。`);
31
- }
32
- configNames.add(config.name);
33
- }
34
- const hookNames = new Set();
35
- for (const hook of hooks) {
36
- if (hookNames.has(hook.name)) {
37
- throw new Error(`[harness] hook 注册表闭环校验失败:hook "${hook.name}" 重复注册。`);
38
- }
39
- hookNames.add(hook.name);
40
- }
41
- for (const config of configs) {
42
- if (!hookNames.has(config.name)) {
43
- throw new Error(`[harness] hook 注册表闭环校验失败:hook 配置 "${config.name}" 引用的实现未注册。` +
44
- `请注册同名 HookDefinition,或删除该配置声明。`);
45
- }
46
- }
47
- for (const hook of hooks) {
48
- if (!configNames.has(hook.name)) {
49
- throw new Error(`[harness] hook 注册表闭环校验失败:hook "${hook.name}" 注册了实现但没有对应配置声明。` +
50
- `请补齐 HookConfig 声明,或移除该实现。`);
51
- }
52
- }
53
- }
54
- class HookRegistry {
55
- hooks = new Map();
56
- /**
57
- * 注册 hook(同名覆盖)
58
- *
59
- * 有效 `enabled` / `errorStrategy` 由 HookConfig 填充——配置表是这两个
60
- * 语义的唯一声明点(#159),定义与配置名称不一致即抛错。
61
- */
62
- register(hook, config) {
63
- if (hook.name !== config.name) {
64
- throw new Error(`[harness] hook 注册失败:定义 "${hook.name}" 与配置 "${config.name}" 名称不一致。`);
65
- }
66
- this.hooks.set(hook.name, {
67
- ...hook,
68
- enabled: config.enabled,
69
- errorStrategy: config.errorStrategy,
70
- });
71
- }
72
- /**
73
- * 批量注册(按 name 配对配置;有实现缺配置声明即抛错)
74
- */
75
- registerAll(hooks, configs) {
76
- const configByName = new Map(configs.map(c => [c.name, c]));
77
- for (const hook of hooks) {
78
- const config = configByName.get(hook.name);
79
- if (!config) {
80
- throw new Error(`[harness] hook 注册失败:hook "${hook.name}" 没有对应的 HookConfig 声明。`);
81
- }
82
- this.register(hook, config);
83
- }
84
- }
85
- /**
86
- * 注销 hook
87
- */
88
- unregister(name) {
89
- return this.hooks.delete(name);
90
- }
91
- /**
92
- * 获取单个 hook(含有效 enabled / errorStrategy)
93
- */
94
- get(name) {
95
- return this.hooks.get(name);
96
- }
97
- /**
98
- * 获取指定时机、已启用的 hook,按优先级排序
99
- */
100
- getEnabled(phase) {
101
- return Array.from(this.hooks.values())
102
- .filter(h => h.phase === phase && h.enabled)
103
- .sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
104
- }
105
- /**
106
- * 列出所有 hook 名称
107
- */
108
- listNames() {
109
- return Array.from(this.hooks.keys());
110
- }
111
- /**
112
- * 列出所有 hook(含禁用)
113
- */
114
- listAll() {
115
- return Array.from(this.hooks.values());
116
- }
117
- /**
118
- * 启用/禁用 hook
119
- */
120
- setEnabled(name, enabled) {
121
- const hook = this.hooks.get(name);
122
- if (!hook)
123
- return false;
124
- hook.enabled = enabled;
125
- return true;
126
- }
127
- /**
128
- * 清空所有 hook
129
- */
130
- clear() {
131
- this.hooks.clear();
132
- }
133
- /**
134
- * Hook 数量
135
- */
136
- get size() {
137
- return this.hooks.size;
138
- }
139
- }
140
- exports.HookRegistry = HookRegistry;
141
- //# sourceMappingURL=registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hooks/registry.ts"],"names":[],"mappings":"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"}
@@ -1,113 +0,0 @@
1
- /**
2
- * Hooks 类型定义
3
- *
4
- * 通用 hook 管线:注册、排序、错误隔离、采样。
5
- * 无业务逻辑,consumer 自行定义 hook 名称和语义。
6
- */
7
- /**
8
- * Hook 执行时机
9
- */
10
- export type HookPhase = 'before' | 'after' | 'around';
11
- /**
12
- * Hook 错误处理策略(有效集合:'block' | 'warn')
13
- *
14
- * #159 起原 'ignore' 退出有效面:策略的唯一声明点是 HookConfig(必填),
15
- * 不存在「未声明 strategy」的运行时形态。
16
- */
17
- export type HookErrorStrategy = 'block' | 'warn';
18
- /**
19
- * Hook 配置(G7 归一后形状:consumer 侧的 per-hook 声明表)
20
- *
21
- * 声明与实现构成注册表闭环(assertHookRegistryClosed 双向校验):
22
- * - 声明(HookConfig)是「定义」侧:引用未注册实现 → 抛错
23
- * - 注册(HookDefinition)是「实现」侧:注册无对应声明 → 抛错
24
- *
25
- * #159 起本表是 `enabled` / `errorStrategy` 的**唯一声明点**:
26
- * HookDefinition 不再携带这两个字段,有效值在注册环节由本表填充
27
- * (见 EffectiveHook),声明与实现两处值矛盾在构造上不可能出现。
28
- *
29
- * 对应 studio 侧 per-hook 运行时配置(hooks/config.ts 的 DEFAULTS)语义:
30
- * blocking:true ↔ errorStrategy 'block'(失败阻断管线),
31
- * blocking:false ↔ errorStrategy 'warn'(记录警告继续)——无损映射见
32
- * config.ts 的 toErrorStrategy。
33
- */
34
- export interface HookConfig {
35
- /** Hook 唯一名称(须与注册的 HookDefinition.name 一致) */
36
- name: string;
37
- /** 是否启用(false 时不进入管线) */
38
- enabled: boolean;
39
- /** 错误策略:block=失败阻断管线,warn=记录警告继续 */
40
- errorStrategy: HookErrorStrategy;
41
- }
42
- /**
43
- * Hook 定义
44
- *
45
- * Consumer 自行选择 name(如 'beforeAgentExecute'),harness 只提供管线。
46
- * `enabled` / `errorStrategy` 不在此处声明——唯一声明点是 HookConfig(#159)。
47
- */
48
- export interface HookDefinition<C = unknown, R = unknown> {
49
- /** Hook 唯一名称(consumer 定义语义) */
50
- name: string;
51
- /** 执行时机 */
52
- phase: HookPhase;
53
- /** 优先级(越小越先执行,默认 100) */
54
- priority?: number;
55
- /** 采样率 0-1(1=100% 执行,0.1=10% 采样) */
56
- sampleRate?: number;
57
- /** Hook 执行函数 */
58
- execute: (context: C) => Promise<HookResult<R>>;
59
- }
60
- /**
61
- * 有效 hook = HookDefinition + 注册环节由 HookConfig 填充的有效值
62
- *
63
- * 管线与注册表的判定(启用过滤、block/warn 分派)只读本类型,
64
- * 值来源单一(HookConfig),不存在第二声明点。
65
- */
66
- export interface EffectiveHook<C = unknown, R = unknown> extends HookDefinition<C, R> {
67
- /** 是否启用(来自 HookConfig.enabled) */
68
- enabled: boolean;
69
- /** 错误策略(来自 HookConfig.errorStrategy) */
70
- errorStrategy: HookErrorStrategy;
71
- }
72
- /**
73
- * Hook 执行结果
74
- */
75
- export interface HookResult<R = unknown> {
76
- /** 是否通过(blocking hook 必须返回 false 才阻断) */
77
- passed: boolean;
78
- /** 结果数据 */
79
- data?: R;
80
- /** 错误信息 */
81
- error?: string;
82
- /** 元数据(consumer 自行定义) */
83
- metadata?: Record<string, unknown>;
84
- }
85
- /**
86
- * Hook 执行记录
87
- */
88
- export interface HookExecutionRecord {
89
- hookName: string;
90
- phase: HookPhase;
91
- startedAt: number;
92
- completedAt: number;
93
- durationMs: number;
94
- passed: boolean;
95
- error?: string;
96
- sampled?: boolean;
97
- /** 未执行标记之二:enabled:false 被 runOne 跳过(实现体零调用,镜像 sampled 先例) */
98
- skipped?: boolean;
99
- }
100
- /**
101
- * 管线执行结果
102
- */
103
- export interface PipelineResult {
104
- /** 是否全部通过(blocking hook 任一失败 = false) */
105
- passed: boolean;
106
- /** 各 hook 执行记录 */
107
- records: HookExecutionRecord[];
108
- /** 失败的 blocking hook 名称列表 */
109
- blockedBy: string[];
110
- /** 警告 hook 名称列表 */
111
- warnings: string[];
112
- }
113
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,aAAa,EAAE,iBAAiB,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACtD,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB;IAChB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;CACjD;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,CAAE,SAAQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,aAAa,EAAE,iBAAiB,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,yCAAyC;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW;IACX,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,kBAAkB;IAClB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,6BAA6B;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB"}
@@ -1,9 +0,0 @@
1
- "use strict";
2
- /**
3
- * Hooks 类型定义
4
- *
5
- * 通用 hook 管线:注册、排序、错误隔离、采样。
6
- * 无业务逻辑,consumer 自行定义 hook 名称和语义。
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
@@ -1,201 +0,0 @@
1
- /**
2
- * HookRegistry + HookPipeline 测试(Phase 1)
3
- *
4
- * #159 起 `enabled` / `errorStrategy` 由 HookConfig 唯一声明,
5
- * 注册时以配置填充有效值;HookDefinition 不再携带这两个字段。
6
- */
7
- import { describe, it, expect, beforeEach } from '@jest/globals';
8
- import { HookRegistry } from '../hooks/registry';
9
- import { HookPipeline } from '../hooks/pipeline';
10
- import type { HookConfig, HookDefinition } from '../hooks/types';
11
-
12
- interface TestContext {
13
- value: string;
14
- calls: string[];
15
- }
16
-
17
- function makeHook(
18
- name: string,
19
- opts: Partial<HookDefinition<TestContext, unknown>> = {}
20
- ): HookDefinition<TestContext, unknown> {
21
- return {
22
- name,
23
- phase: 'before',
24
- priority: 100,
25
- execute: async (ctx) => {
26
- ctx.calls.push(name);
27
- return { passed: true };
28
- },
29
- ...opts,
30
- };
31
- }
32
-
33
- function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
34
- return { name, enabled: true, errorStrategy: 'block', ...overrides };
35
- }
36
-
37
- describe('HookRegistry', () => {
38
- let registry: HookRegistry<TestContext, unknown>;
39
-
40
- beforeEach(() => {
41
- registry = new HookRegistry();
42
- });
43
-
44
- it('应注册 hook', () => {
45
- registry.register(makeHook('test'), makeConfig('test'));
46
- expect(registry.get('test')).toBeDefined();
47
- });
48
-
49
- it('同名注册应覆盖', () => {
50
- registry.register(makeHook('test', { priority: 50 }), makeConfig('test'));
51
- registry.register(makeHook('test', { priority: 90 }), makeConfig('test'));
52
- expect(registry.get('test')?.priority).toBe(90);
53
- });
54
-
55
- it('应获取已启用的 hook 按优先级排序', () => {
56
- registry.register(makeHook('low', { priority: 200 }), makeConfig('low'));
57
- registry.register(makeHook('high', { priority: 10 }), makeConfig('high'));
58
- registry.register(makeHook('mid', { priority: 100 }), makeConfig('mid'));
59
- registry.register(makeHook('disabled'), makeConfig('disabled', { enabled: false }));
60
-
61
- const enabled = registry.getEnabled('before');
62
- expect(enabled.map(h => h.name)).toEqual(['high', 'mid', 'low']);
63
- });
64
-
65
- it('应注销 hook', () => {
66
- registry.register(makeHook('test'), makeConfig('test'));
67
- expect(registry.unregister('test')).toBe(true);
68
- expect(registry.get('test')).toBeUndefined();
69
- });
70
-
71
- it('应设置启用/禁用', () => {
72
- registry.register(makeHook('test'), makeConfig('test'));
73
- expect(registry.setEnabled('test', false)).toBe(true);
74
- expect(registry.getEnabled('before').length).toBe(0);
75
- expect(registry.setEnabled('test', true)).toBe(true);
76
- expect(registry.getEnabled('before').length).toBe(1);
77
- });
78
-
79
- it('应正确统计数量', () => {
80
- expect(registry.size).toBe(0);
81
- registry.register(makeHook('a'), makeConfig('a'));
82
- registry.register(makeHook('b'), makeConfig('b'));
83
- expect(registry.size).toBe(2);
84
- });
85
- });
86
-
87
- describe('HookPipeline', () => {
88
- let registry: HookRegistry<TestContext, unknown>;
89
- let pipeline: HookPipeline<TestContext>;
90
-
91
- beforeEach(() => {
92
- registry = new HookRegistry();
93
- pipeline = new HookPipeline(registry);
94
- });
95
-
96
- it('应执行所有 before hook', async () => {
97
- const ctx: TestContext = { value: '', calls: [] };
98
- registry.register(makeHook('h1'), makeConfig('h1'));
99
- registry.register(makeHook('h2'), makeConfig('h2'));
100
-
101
- const result = await pipeline.run('before', ctx);
102
-
103
- expect(result.passed).toBe(true);
104
- expect(ctx.calls).toEqual(['h1', 'h2']);
105
- expect(result.records.length).toBe(2);
106
- });
107
-
108
- it('blocking hook 失败应停止后续', async () => {
109
- const ctx: TestContext = { value: '', calls: [] };
110
- registry.register(makeHook('pass1'), makeConfig('pass1'));
111
- registry.register(makeHook('block', {
112
- execute: async () => ({ passed: false, error: 'blocked' }),
113
- }), makeConfig('block', { errorStrategy: 'block' }));
114
- registry.register(makeHook('never_runs'), makeConfig('never_runs'));
115
-
116
- const result = await pipeline.run('before', ctx);
117
-
118
- expect(result.passed).toBe(false);
119
- expect(result.blockedBy).toEqual(['block']);
120
- // block hook 的 execute 被覆盖,不 push calls
121
- expect(ctx.calls).toEqual(['pass1']);
122
- expect(ctx.calls).not.toContain('never_runs');
123
- });
124
-
125
- it('warn hook 失败应继续但记录警告', async () => {
126
- const ctx: TestContext = { value: '', calls: [] };
127
- registry.register(makeHook('warn_hook', {
128
- execute: async () => ({ passed: false, error: 'warning' }),
129
- }), makeConfig('warn_hook', { errorStrategy: 'warn' }));
130
- registry.register(makeHook('should_still_run'), makeConfig('should_still_run'));
131
-
132
- const result = await pipeline.run('before', ctx);
133
-
134
- expect(result.passed).toBe(true);
135
- expect(result.warnings).toEqual(['warn_hook']);
136
- expect(ctx.calls).toContain('should_still_run');
137
- });
138
-
139
- it('hook 抛异常应被捕获', async () => {
140
- const ctx: TestContext = { value: '', calls: [] };
141
- registry.register(makeHook('throws', {
142
- execute: async () => { throw new Error('crash'); },
143
- }), makeConfig('throws', { errorStrategy: 'warn' }));
144
- registry.register(makeHook('after_crash', { priority: 200 }), makeConfig('after_crash'));
145
-
146
- const result = await pipeline.run('before', ctx);
147
-
148
- expect(result.records[0].error).toContain('crash');
149
- expect(ctx.calls).toContain('after_crash');
150
- });
151
-
152
- it('runFull 应在 before/after 之间执行操作', async () => {
153
- const ctx: TestContext = { value: '', calls: [] };
154
- registry.register(makeHook('before_hook'), makeConfig('before_hook'));
155
-
156
- const afterHook = makeHook('after_hook', { phase: 'after' });
157
- registry.register(afterHook, makeConfig('after_hook'));
158
-
159
- const { pipelineResult, operationResult } = await pipeline.runFull(ctx, async () => {
160
- ctx.calls.push('operation');
161
- return 'done';
162
- });
163
-
164
- expect(pipelineResult.passed).toBe(true);
165
- expect(operationResult).toBe('done');
166
- expect(ctx.calls).toEqual(['before_hook', 'operation', 'after_hook']);
167
- });
168
-
169
- it('before hook 失败不应执行操作', async () => {
170
- const ctx: TestContext = { value: '', calls: [] };
171
- registry.register(makeHook('block', {
172
- execute: async () => ({ passed: false }),
173
- }), makeConfig('block', { errorStrategy: 'block' }));
174
-
175
- const { pipelineResult, operationResult } = await pipeline.runFull(ctx, async () => {
176
- ctx.calls.push('should_not_run');
177
- return 'nope';
178
- });
179
-
180
- expect(pipelineResult.passed).toBe(false);
181
- expect(operationResult).toBeUndefined();
182
- expect(ctx.calls).not.toContain('should_not_run');
183
- });
184
-
185
- it('空注册表应返回通过', async () => {
186
- const result = await pipeline.run('before', { value: '', calls: [] });
187
- expect(result.passed).toBe(true);
188
- expect(result.records).toEqual([]);
189
- });
190
-
191
- it('应忽略配置禁用的 hook', async () => {
192
- const ctx: TestContext = { value: '', calls: [] };
193
- registry.register(makeHook('enabled'), makeConfig('enabled'));
194
- registry.register(makeHook('disabled'), makeConfig('disabled', { enabled: false }));
195
-
196
- await pipeline.run('before', ctx);
197
-
198
- expect(ctx.calls).toEqual(['enabled']);
199
- expect(ctx.calls).not.toContain('disabled');
200
- });
201
- });
@@ -1,158 +0,0 @@
1
- /**
2
- * check 命令注入漂移警告测试(ADR-0001 决策 7,P6)
3
- *
4
- * 警告不阻断:漂移项目跑 check 仍 0 退出;版本漂移单独显眼警告;
5
- * 无漂移零输出。使用真实临时目录(漂移检测读真实 fs),
6
- * 仅 mock checker(直接判过)与 git 调用。
7
- */
8
-
9
- import * as fs from 'fs';
10
- import { captureIO, type CapturingIO } from '../../command-contract';
11
- import * as path from 'path';
12
- import { check } from '../check';
13
- import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
14
- import { getEffectiveConstraints } from '../../../core/effective-constraints';
15
- import { createProjectFixture } from '../../../test-setup/project-fixture';
16
-
17
- // 命令 per-run 构造 checker(harness#88):构造替身即控制 checkConstraints 返回值
18
- const mockChecker = { checkConstraints: jest.fn() };
19
- jest.mock('../../../core/constraints/checker', () => ({
20
- ConstraintChecker: jest.fn(function () {
21
- return mockChecker;
22
- }),
23
- }));
24
-
25
- // 命令 per-run 锚根构造收集器(harness#139):构造替身即拦截 trace 落盘
26
- jest.mock('../../../monitoring/traces', () => ({
27
- TraceCollector: jest.fn(function () {
28
- return { record: jest.fn() };
29
- }),
30
- }));
31
-
32
- jest.mock('../../../utils/exec', () => ({
33
- execAsync: jest.fn().mockResolvedValue({ stdout: '', stderr: '' }),
34
- }));
35
-
36
- jest.mock('child_process', () => ({
37
- exec: jest.fn(),
38
- execSync: jest.fn(() => Buffer.from('')),
39
- }));
40
-
41
- // eslint-disable-next-line @typescript-eslint/no-var-requires
42
- const REAL_VERSION = require('../../../../package.json').version as string;
43
-
44
- function writeClaudeMd(root: string, version: string): void {
45
- const section =
46
- '## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
47
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# Test Project\n\n${section}`, 'utf-8');
48
- }
49
-
50
- describe('check 命令注入漂移警告', () => {
51
- let io: CapturingIO;
52
-
53
- beforeEach(() => {
54
-
55
- io = captureIO();
56
- jest.clearAllMocks();
57
- mockChecker.checkConstraints.mockResolvedValue({
58
- passed: true,
59
- ironLaws: [],
60
- guidelines: [],
61
- warningCount: 0,
62
- });
63
- });
64
-
65
- const outputText = () => io.outText();
66
-
67
- it('版本漂移:黄色警告块 + ⚠️⚠️ 版本行,但不阻断(exit 未调用,仍判通过)', async () => {
68
- const root = createProjectFixture({ name: 'harness-check-drift-test' });
69
- writeClaudeMd(root, '0.0.1-old');
70
-
71
- const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
72
-
73
- const output = outputText();
74
- expect(output).toContain('约束注入漂移');
75
- expect(output).toContain('⚠️⚠️');
76
- expect(output).toContain('agent 上下文中的规则与已安装 harness 版本不一致');
77
- expect(output).toContain('0.0.1-old');
78
- expect(output).toContain('npx @dommaker/harness init');
79
- // 不阻断:检查仍通过、未调用 process.exit
80
- expect(output).toContain('约束检查通过');
81
- expect(result.kind).toBe('ok');
82
- });
83
-
84
- it('内容漂移:手改一条 → 警告块含缺失/多余计数,exit 未调用', async () => {
85
- const root = createProjectFixture({ name: 'harness-check-drift-test' });
86
- writeClaudeMd(root, REAL_VERSION);
87
- const claudeMdPath = path.join(root, 'CLAUDE.md');
88
- const content = fs.readFileSync(claudeMdPath, 'utf-8');
89
- const originalLine = content.split('\n').find(l => l.startsWith('- **'))!;
90
- fs.writeFileSync(claudeMdPath, content.replace(originalLine, originalLine.replace(/: .+$/, ': 篡改')), 'utf-8');
91
-
92
- const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
93
-
94
- const output = outputText();
95
- expect(output).toContain('内容漂移: 缺失 1 条 / 多余 1 条');
96
- expect(output).toContain('约束检查通过');
97
- expect(result.kind).toBe('ok');
98
- });
99
-
100
- it('无漂移:零警告输出(不增加噪音)', async () => {
101
- const root = createProjectFixture({ name: 'harness-check-drift-test' });
102
- writeClaudeMd(root, REAL_VERSION);
103
-
104
- const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
105
-
106
- const output = outputText();
107
- expect(output).not.toContain('注入漂移');
108
- expect(output).toContain('约束检查通过');
109
- expect(result.kind).toBe('ok');
110
- });
111
-
112
- it('未注入(无 CLAUDE.md):不警告', async () => {
113
- const root = createProjectFixture({ name: 'harness-check-drift-test' });
114
-
115
- const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
116
-
117
- expect(outputText()).not.toContain('注入漂移');
118
- expect(result.kind).toBe('ok');
119
- });
120
-
121
- // ── 生效集来源与漂移比对的同一性(ADR-0023 步骤 4.5)──────────────────
122
- // 改前 CLI 给 -p 塞了缺省值 'standard',「没传」与「传了」不可区分,
123
- // 于是「--preset 仅在无自定义配置时覆盖」这条规则恒被触发:项目 config.yml 的
124
- // preset 静默失效,而漂移侧按 config.yml 算 → 两边稳定对不上(该报的漂移不报)。
125
-
126
- it('config.yml 的 preset 不再被 CLI 缺省顶掉:不带 -p 按 relaxed 执法且零漂移', async () => {
127
- const root = createProjectFixture({
128
- name: 'harness-check-drift-relaxed',
129
- config: 'preset: relaxed\n',
130
- });
131
- writeClaudeMd(root, REAL_VERSION); // getEffectiveConstraints(root) 走 config.yml = relaxed
132
-
133
- const result = await check({ staged: false, projectPath: root }, io);
134
-
135
- // relaxed 裁掉的内置条目会出现在「已禁用约束」行——改前 CLI 塞了 standard,这行根本不出现
136
- const output = outputText();
137
- expect(output).toContain('已禁用约束:');
138
- expect(output).not.toContain('内容漂移');
139
- expect(output).toContain('约束检查通过');
140
- expect(result.kind).toBe('ok');
141
- });
142
-
143
- it('显式 -p 时,漂移比对用的就是本 run 实际执法的那份', async () => {
144
- const root = createProjectFixture({
145
- name: 'harness-check-drift-override',
146
- config: 'preset: relaxed\n',
147
- });
148
- writeClaudeMd(root, REAL_VERSION); // 段里是 relaxed 的少数条目
149
-
150
- await check({ preset: 'standard', staged: false, projectPath: root }, io);
151
-
152
- // standard 不裁条目(无「已禁用约束」行),且期望段 = 本 run 执法的 standard
153
- // → 段里条目只少不多,报「缺失 N 条 / 多余 0 条」
154
- const output = outputText();
155
- expect(output).not.toContain('已禁用约束:');
156
- expect(output).toMatch(/内容漂移: 缺失 [1-9]\d* 条 \/ 多余 0 条/);
157
- });
158
- });