@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,195 +0,0 @@
1
- /**
2
- * HookPipeline errorStrategy 语义测试(G7 映射语义确认)
3
- *
4
- * block/warn 行为是 studio safeCallHook 语义在管线侧的无损承载面:
5
- * - 'block':hook 失败 → passed=false、blockedBy 记名、停止执行后续 hook
6
- * - 'warn':hook 失败 → 记录警告、继续执行后续 hook、passed 不受影响
7
- *
8
- * #159 起 `enabled` / `errorStrategy` 的唯一声明点是 HookConfig:
9
- * HookDefinition 不再携带这两个字段,有效值在注册环节由配置表填充。
10
- */
11
-
12
- import { HookPipeline } from '../pipeline';
13
- import { HookRegistry } from '../registry';
14
- import type { HookConfig, HookDefinition } from '../types';
15
-
16
- function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
17
- return { name, enabled: true, errorStrategy: 'warn', ...overrides };
18
- }
19
-
20
- function makeRegistry(
21
- entries: Array<{ hook: HookDefinition; config?: Partial<HookConfig> }>
22
- ): { registry: HookRegistry; pipeline: HookPipeline } {
23
- const registry = new HookRegistry();
24
- for (const { hook, config } of entries) {
25
- registry.register(hook, makeConfig(hook.name, config));
26
- }
27
- return { registry, pipeline: new HookPipeline(registry) };
28
- }
29
-
30
- describe('HookPipeline errorStrategy 语义', () => {
31
- it('block:失败阻断管线(passed=false、blockedBy 记名、停止后续 hook)', async () => {
32
- const second = jest.fn().mockResolvedValue({ passed: true });
33
- const { pipeline } = makeRegistry([
34
- { hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'boom' }) }, config: { errorStrategy: 'block' } },
35
- { hook: { name: 'second', phase: 'before', execute: second } },
36
- ]);
37
-
38
- const result = await pipeline.run('before', {});
39
-
40
- expect(result.passed).toBe(false);
41
- expect(result.blockedBy).toEqual(['first']);
42
- expect(second).not.toHaveBeenCalled();
43
- });
44
-
45
- it('warn:失败记录警告并继续(passed 不受影响、后续 hook 照常执行)', async () => {
46
- const second = jest.fn().mockResolvedValue({ passed: true });
47
- const { pipeline } = makeRegistry([
48
- { hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'soft' }) }, config: { errorStrategy: 'warn' } },
49
- { hook: { name: 'second', phase: 'before', execute: second } },
50
- ]);
51
-
52
- const result = await pipeline.run('before', {});
53
-
54
- expect(result.passed).toBe(true);
55
- expect(result.warnings).toEqual(['first']);
56
- expect(result.blockedBy).toEqual([]);
57
- expect(second).toHaveBeenCalled();
58
- });
59
-
60
- it('block 失败停在当前 phase,不改变其它 phase 的 hook', async () => {
61
- const afterHook = jest.fn().mockResolvedValue({ passed: true });
62
- const { pipeline } = makeRegistry([
63
- { hook: { name: 'blocker', phase: 'before', execute: async () => ({ passed: false }) }, config: { errorStrategy: 'block' } },
64
- { hook: { name: 'afterHook', phase: 'after', execute: afterHook } },
65
- ]);
66
-
67
- const before = await pipeline.run('before', {});
68
- expect(before.passed).toBe(false);
69
-
70
- const after = await pipeline.run('after', {});
71
- expect(after.passed).toBe(true);
72
- expect(afterHook).toHaveBeenCalled();
73
- });
74
-
75
- it('配置 enabled:false 的 hook 不进入管线执行(配置表是唯一声明点)', async () => {
76
- const disabledHook = jest.fn().mockResolvedValue({ passed: true });
77
- const enabledHook = jest.fn().mockResolvedValue({ passed: true });
78
- const { pipeline } = makeRegistry([
79
- { hook: { name: 'disabled', phase: 'before', execute: disabledHook }, config: { enabled: false } },
80
- { hook: { name: 'enabled', phase: 'before', execute: enabledHook } },
81
- ]);
82
-
83
- const result = await pipeline.run('before', {});
84
-
85
- expect(result.passed).toBe(true);
86
- expect(disabledHook).not.toHaveBeenCalled();
87
- expect(enabledHook).toHaveBeenCalled();
88
- expect(result.records.map(r => r.hookName)).toEqual(['enabled']);
89
- });
90
- });
91
-
92
- describe('HookPipeline.runOne 按名执行单个 hook(#167)', () => {
93
- it('按名执行并返回观测记录,不连带其它 hook', async () => {
94
- const other = jest.fn().mockResolvedValue({ passed: true });
95
- const target = jest.fn().mockResolvedValue({ passed: true });
96
- const { pipeline } = makeRegistry([
97
- { hook: { name: 'target', phase: 'before', execute: target } },
98
- { hook: { name: 'other', phase: 'before', execute: other } },
99
- ]);
100
-
101
- const record = await pipeline.runOne('target', {});
102
-
103
- expect(record.hookName).toBe('target');
104
- expect(record.phase).toBe('before');
105
- expect(record.passed).toBe(true);
106
- expect(record.skipped).toBeUndefined();
107
- expect(typeof record.durationMs).toBe('number');
108
- expect(target).toHaveBeenCalledTimes(1);
109
- expect(other).not.toHaveBeenCalled();
110
- });
111
-
112
- it('enabled:false → 实现体零调用,返回 skipped:true / passed:true 记录', async () => {
113
- const disabled = jest.fn().mockResolvedValue({ passed: true });
114
- const { pipeline } = makeRegistry([
115
- { hook: { name: 'disabled', phase: 'before', execute: disabled }, config: { enabled: false } },
116
- ]);
117
-
118
- const record = await pipeline.runOne('disabled', {});
119
-
120
- expect(disabled).not.toHaveBeenCalled();
121
- expect(record.skipped).toBe(true);
122
- expect(record.passed).toBe(true);
123
- expect(record.hookName).toBe('disabled');
124
- });
125
-
126
- it('block + 实现体抛错 → 抛错,message 含 hook 名与原文', async () => {
127
- const { pipeline } = makeRegistry([
128
- {
129
- hook: { name: 'guard', phase: 'before', execute: async () => { throw new Error('iron-law violated'); } },
130
- config: { errorStrategy: 'block' },
131
- },
132
- ]);
133
-
134
- await expect(pipeline.runOne('guard', {})).rejects.toThrow('iron-law violated');
135
- await expect(pipeline.runOne('guard', {})).rejects.toThrow('[harness] hook "guard" blocked:');
136
- });
137
-
138
- it('block + 实现体返回 passed:false → 抛错,message 含 result.error 原文', async () => {
139
- const { pipeline } = makeRegistry([
140
- { hook: { name: 'guard', phase: 'before', execute: async () => ({ passed: false, error: 'boom' }) }, config: { errorStrategy: 'block' } },
141
- ]);
142
-
143
- await expect(pipeline.runOne('guard', {})).rejects.toThrow('[harness] hook "guard" blocked: boom');
144
- });
145
-
146
- it('warn + 失败 → resolve,返回 passed:false 带 error 的记录', async () => {
147
- const { pipeline } = makeRegistry([
148
- {
149
- hook: { name: 'soft', phase: 'before', execute: async () => { throw new Error('soft-fail'); } },
150
- config: { errorStrategy: 'warn' },
151
- },
152
- ]);
153
-
154
- const record = await pipeline.runOne('soft', {});
155
-
156
- expect(record.passed).toBe(false);
157
- expect(record.error).toBe('soft-fail');
158
- expect(record.skipped).toBeUndefined();
159
- });
160
-
161
- it('未知 hook 名 → 抛错(与注册表闭环口径一致,不静默跳过)', async () => {
162
- const { pipeline } = makeRegistry([
163
- { hook: { name: 'known', phase: 'before', execute: async () => ({ passed: true }) } },
164
- ]);
165
-
166
- await expect(pipeline.runOne('no_such_hook', {})).rejects.toThrow('no_such_hook');
167
- await expect(pipeline.runOne('no_such_hook', {})).rejects.toThrow('未注册');
168
- });
169
-
170
- it('sampleRate < 1 时采样语义与 run 路径一致(未抽中 → 零调用 + sampled 记录)', async () => {
171
- const execute = jest.fn().mockResolvedValue({ passed: true });
172
- const { pipeline } = makeRegistry([
173
- { hook: { name: 'sampled', phase: 'before', sampleRate: 0.5, execute } },
174
- ]);
175
-
176
- const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.9);
177
- try {
178
- const record = await pipeline.runOne('sampled', {});
179
- expect(execute).not.toHaveBeenCalled();
180
- expect(record.sampled).toBe(true);
181
- expect(record.passed).toBe(true);
182
- } finally {
183
- randomSpy.mockRestore();
184
- }
185
-
186
- const hitSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
187
- try {
188
- const record = await pipeline.runOne('sampled', {});
189
- expect(execute).toHaveBeenCalledTimes(1);
190
- expect(record.sampled).toBeUndefined();
191
- } finally {
192
- hitSpy.mockRestore();
193
- }
194
- });
195
- });
@@ -1,115 +0,0 @@
1
- /**
2
- * Hook 注册表闭环双向校验测试(H5,复制 checker 闭环模式)
3
- *
4
- * 两个失败方向 + 重复声明/注册都必须抛错;闭环通过时不抛错。
5
- *
6
- * #159 起注册即配对:`register(hook, config)` 以 HookConfig 填充有效
7
- * `enabled` / `errorStrategy`——配置表是这两个语义的唯一声明点,
8
- * HookDefinition 类型上不再存在同名字段(矛盾在构造上不可能出现)。
9
- */
10
-
11
- import { assertHookRegistryClosed, HookRegistry } from '../registry';
12
- import type { HookConfig, HookDefinition } from '../types';
13
-
14
- function makeHook(name: string): HookDefinition {
15
- return {
16
- name,
17
- phase: 'before',
18
- execute: async () => ({ passed: true }),
19
- };
20
- }
21
-
22
- function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
23
- return { name, enabled: true, errorStrategy: 'block', ...overrides };
24
- }
25
-
26
- describe('assertHookRegistryClosed', () => {
27
- it('声明与注册一一对应时通过', () => {
28
- const configs = [makeConfig('beforeAgentExecute'), makeConfig('afterReview')];
29
- const hooks = [makeHook('beforeAgentExecute'), makeHook('afterReview')];
30
- expect(() => assertHookRegistryClosed(configs, hooks)).not.toThrow();
31
- });
32
-
33
- it('空声明 + 空注册通过(无 hook 的 consumer 合法)', () => {
34
- expect(() => assertHookRegistryClosed([], [])).not.toThrow();
35
- });
36
-
37
- it('引用未注册 → 抛错:声明了配置但没有注册实现', () => {
38
- const configs = [makeConfig('beforeAgentExecute'), makeConfig('ghostHook')];
39
- const hooks = [makeHook('beforeAgentExecute')];
40
- expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(
41
- /hook 配置 "ghostHook" 引用的实现未注册/
42
- );
43
- });
44
-
45
- it('注册无定义 → 抛错:注册了实现但没有配置声明', () => {
46
- const configs = [makeConfig('beforeAgentExecute')];
47
- const hooks = [makeHook('beforeAgentExecute'), makeHook('orphanHook')];
48
- expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(
49
- /hook "orphanHook" 注册了实现但没有对应配置声明/
50
- );
51
- });
52
-
53
- it('重复声明 → 抛错', () => {
54
- const configs = [makeConfig('dup'), makeConfig('dup')];
55
- const hooks = [makeHook('dup')];
56
- expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(/重复声明/);
57
- });
58
-
59
- it('重复注册 → 抛错', () => {
60
- const configs = [makeConfig('dup')];
61
- const hooks = [makeHook('dup'), makeHook('dup')];
62
- expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(/重复注册/);
63
- });
64
-
65
- it('与 HookRegistry.listAll 组合可用(consumer 注册点用法)', () => {
66
- const registry = new HookRegistry();
67
- registry.registerAll([makeHook('beforeAgentExecute')], [makeConfig('beforeAgentExecute')]);
68
- expect(() => assertHookRegistryClosed([makeConfig('beforeAgentExecute')], registry.listAll()))
69
- .not.toThrow();
70
- });
71
- });
72
-
73
- describe('HookRegistry 注册即配对(#159 配置表唯一化)', () => {
74
- it('register 以 HookConfig 填充有效 enabled / errorStrategy', () => {
75
- const registry = new HookRegistry();
76
- registry.register(makeHook('a'), makeConfig('a', { enabled: false, errorStrategy: 'block' }));
77
-
78
- const effective = registry.get('a');
79
- expect(effective?.enabled).toBe(false);
80
- expect(effective?.errorStrategy).toBe('block');
81
- // 配置 enabled:false → 不进 enabled 列表
82
- expect(registry.getEnabled('before')).toEqual([]);
83
- });
84
-
85
- it('register 定义与配置名称不一致 → 抛错', () => {
86
- const registry = new HookRegistry();
87
- expect(() => registry.register(makeHook('a'), makeConfig('b'))).toThrow(/名称不一致/);
88
- });
89
-
90
- it('registerAll 有实现缺配置声明 → 抛错', () => {
91
- const registry = new HookRegistry();
92
- expect(() =>
93
- registry.registerAll([makeHook('a'), makeHook('orphan')], [makeConfig('a')])
94
- ).toThrow(/hook "orphan" 没有对应的 HookConfig 声明/);
95
- });
96
-
97
- it('HookDefinition 类型上不再存在 enabled / errorStrategy(编译期钉)', () => {
98
- const withStrategy: HookDefinition = {
99
- name: 'x',
100
- phase: 'before',
101
- // @ts-expect-error HookDefinition 不再存在 errorStrategy 字段(#159)
102
- errorStrategy: 'block',
103
- execute: async () => ({ passed: true }),
104
- };
105
- const withEnabled: HookDefinition = {
106
- name: 'y',
107
- phase: 'before',
108
- // @ts-expect-error HookDefinition 不再存在 enabled 字段(#159)
109
- enabled: false,
110
- execute: async () => ({ passed: true }),
111
- };
112
- expect(withStrategy.name).toBe('x');
113
- expect(withEnabled.name).toBe('y');
114
- });
115
- });
@@ -1,33 +0,0 @@
1
- /**
2
- * Hook 配置归一(G7)
3
- *
4
- * studio 侧 per-hook 运行时配置(`blocking: boolean`)与 harness 侧
5
- * `HookConfig.errorStrategy` 双轨归一:以 errorStrategy 为唯一真相,
6
- * `blocking` → errorStrategy 为无损映射。
7
- *
8
- * 映射语义(确认于 H5):
9
- * - blocking=true → 'block':hook 失败阻断管线(停止执行后续 hook,
10
- * 管线结果 passed=false、blockedBy 记名)——对应 studio safeCallHook 抛错
11
- * - blocking=false → 'warn':hook 失败记录警告并继续执行后续 hook
12
- * (管线结果 warnings 记名,passed 不受影响)——对应 studio safeCallHook
13
- * console.warn 继续
14
- *
15
- * #159 起 `HookConfig` 是 `enabled` / `errorStrategy` 的唯一声明点:
16
- * HookDefinition 不再携带这两个字段,注册环节以配置表填充有效值,
17
- * 有效策略集合收敛为 'block' | 'warn'(原 'ignore' 退出有效面)。
18
- *
19
- * studio 侧随动(studio #150 A4):hooks/config.ts 删 DEFAULTS/safeCallHook,
20
- * blocking→errorStrategy 无损映射进 toHookDef;HARNESS_HOOK_DISABLE 保留 studio。
21
- */
22
-
23
- import type { HookErrorStrategy } from './types';
24
-
25
- /**
26
- * blocking 布尔 → errorStrategy 无损映射(block/warn)
27
- *
28
- * @param blocking studio 侧「失败是否阻断」语义
29
- * @returns 'block'(阻断)| 'warn'(警告继续)
30
- */
31
- export function toErrorStrategy(blocking: boolean): HookErrorStrategy {
32
- return blocking ? 'block' : 'warn';
33
- }
@@ -1,202 +0,0 @@
1
- /**
2
- * HookPipeline — hook 有序执行、错误隔离、采样
3
- *
4
- * 执行顺序:before hooks → consumer operation → after hooks
5
- * 每个 hook 独立 try/catch,单个 hook 失败不影���其他 hook。
6
- */
7
-
8
- import type {
9
- EffectiveHook,
10
- HookPhase,
11
- HookExecutionRecord,
12
- PipelineResult,
13
- } from './types';
14
- import type { HookRegistry } from './registry';
15
-
16
- export class HookPipeline<C = unknown> {
17
- private registry: HookRegistry<C, unknown>;
18
-
19
- constructor(registry: HookRegistry<C, unknown>) {
20
- this.registry = registry;
21
- }
22
-
23
- /**
24
- * 执行指定 phase 的所有 enabled hook
25
- *
26
- * @param phase hook 时机
27
- * @param context 传递给每个 hook 的上下文
28
- * @returns 管线执行结果
29
- */
30
- async run(
31
- phase: HookPhase,
32
- context: C
33
- ): Promise<PipelineResult> {
34
- const hooks = this.registry.getEnabled(phase);
35
- const records: HookExecutionRecord[] = [];
36
- const blockedBy: string[] = [];
37
- const warnings: string[] = [];
38
- let passed = true;
39
-
40
- for (const hook of hooks) {
41
- const record = await this.executeOne(hook, context);
42
- records.push(record);
43
-
44
- if (!record.passed) {
45
- if (hook.errorStrategy === 'block') {
46
- passed = false;
47
- blockedBy.push(hook.name);
48
- break; // blocking hook 失败,停止执行后续 hook
49
- }
50
- // 'warn':记录警告继续(有效集合仅 block/warn,策略由 HookConfig 必填声明)
51
- warnings.push(hook.name);
52
- }
53
- }
54
-
55
- return { passed, records, blockedBy, warnings };
56
- }
57
-
58
- /**
59
- * 按名执行单个 hook(#167)
60
- *
61
- * 与 run() 共用 executeOne 的采样与错误隔离路径(判定与错误策略的实现体
62
- * 只有一份);enabled / errorStrategy 的值来源仍是注册环节填充的
63
- * EffectiveHook(唯一声明点 HookConfig,#159)。
64
- *
65
- * - enabled:false → 实现体零调用,返回 skipped:true / passed:true 记录
66
- * (镜像 sampled 先例:未执行 = 记录上一面旗,不是异常)
67
- * - errorStrategy 'block' 且失败 → 抛 Error(message 含 hook 名与原文);
68
- * 'warn' 且失败 → resolve,返回 passed:false 带 error 的记录
69
- * - 未知名 → 抛错(与 assertHookRegistryClosed 闭环口径一致,拒绝静默缺失)
70
- *
71
- * @param name hook 名称(须已注册)
72
- * @param context 传递给 hook 的上下文
73
- * @returns 管线观测记录
74
- */
75
- async runOne(name: string, context: C): Promise<HookExecutionRecord> {
76
- const hook = this.registry.get(name);
77
- if (!hook) {
78
- throw new Error(
79
- `[harness] hook 执行失败:hook "${name}" 未注册。请注册同名 HookDefinition 与 HookConfig 声明。`
80
- );
81
- }
82
-
83
- if (!hook.enabled) {
84
- const now = Date.now();
85
- return {
86
- hookName: hook.name,
87
- phase: hook.phase,
88
- startedAt: now,
89
- completedAt: now,
90
- durationMs: 0,
91
- passed: true,
92
- skipped: true,
93
- };
94
- }
95
-
96
- const record = await this.executeOne(hook, context);
97
- if (!record.passed && hook.errorStrategy === 'block') {
98
- throw new Error(
99
- `[harness] hook "${hook.name}" blocked: ${record.error ?? '未知错误'}`
100
- );
101
- }
102
- return record;
103
- }
104
-
105
- /**
106
- * 执行 before + after 全套管线
107
- *
108
- * @param context 上下文
109
- * @param operation 业务操作(在 before 和 after 之间执行)
110
- * @returns 管线结果 + 操作结果
111
- */
112
- async runFull<R>(
113
- context: C,
114
- operation: () => Promise<R>
115
- ): Promise<{ pipelineResult: PipelineResult; operationResult?: R }> {
116
- // 1. Before hooks
117
- const beforeResult = await this.run('before', context);
118
- if (!beforeResult.passed) {
119
- return { pipelineResult: beforeResult };
120
- }
121
-
122
- // 2. Execute operation
123
- let operationResult: R;
124
- try {
125
- operationResult = await operation();
126
- } catch (err) {
127
- // 操作失败:仍执行 after hooks,但传递错误信息
128
- const afterResult = await this.run('after', {
129
- ...context,
130
- _operationError: (err as Error).message,
131
- } as unknown as C);
132
- return {
133
- pipelineResult: {
134
- passed: false,
135
- records: [...beforeResult.records, ...afterResult.records],
136
- blockedBy: [...beforeResult.blockedBy, '_operation'],
137
- warnings: [...beforeResult.warnings, ...afterResult.warnings],
138
- },
139
- };
140
- }
141
-
142
- // 3. After hooks
143
- const afterResult = await this.run('after', context);
144
- return {
145
- pipelineResult: {
146
- passed: beforeResult.passed && afterResult.passed,
147
- records: [...beforeResult.records, ...afterResult.records],
148
- blockedBy: [...beforeResult.blockedBy, ...afterResult.blockedBy],
149
- warnings: [...beforeResult.warnings, ...afterResult.warnings],
150
- },
151
- operationResult,
152
- };
153
- }
154
-
155
- /**
156
- * 执行单个 hook(带采样和错误隔离)
157
- */
158
- private async executeOne(
159
- hook: EffectiveHook<C, unknown>,
160
- context: C
161
- ): Promise<HookExecutionRecord> {
162
- const startedAt = Date.now();
163
-
164
- // 采样检查
165
- if (hook.sampleRate !== undefined && hook.sampleRate < 1) {
166
- if (Math.random() > hook.sampleRate) {
167
- return {
168
- hookName: hook.name,
169
- phase: hook.phase,
170
- startedAt,
171
- completedAt: Date.now(),
172
- durationMs: 0,
173
- passed: true,
174
- sampled: true,
175
- };
176
- }
177
- }
178
-
179
- try {
180
- const result = await hook.execute(context);
181
- return {
182
- hookName: hook.name,
183
- phase: hook.phase,
184
- startedAt,
185
- completedAt: Date.now(),
186
- durationMs: Date.now() - startedAt,
187
- passed: result.passed !== false,
188
- error: result.error,
189
- };
190
- } catch (err) {
191
- return {
192
- hookName: hook.name,
193
- phase: hook.phase,
194
- startedAt,
195
- completedAt: Date.now(),
196
- durationMs: Date.now() - startedAt,
197
- passed: false,
198
- error: (err as Error).message,
199
- };
200
- }
201
- }
202
- }