@dommaker/harness 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (563) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/harness.js +17 -1
  3. package/dist/agents/index.d.ts +6 -2
  4. package/dist/agents/index.d.ts.map +1 -1
  5. package/dist/agents/index.js +6 -16
  6. package/dist/agents/index.js.map +1 -1
  7. package/dist/cli/commands/__tests__/gate-decision.d.ts +18 -0
  8. package/dist/cli/commands/__tests__/gate-decision.d.ts.map +1 -0
  9. package/dist/cli/commands/__tests__/gate-decision.js +26 -0
  10. package/dist/cli/commands/__tests__/gate-decision.js.map +1 -0
  11. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  12. package/dist/cli/commands/acceptance.js +17 -28
  13. package/dist/cli/commands/acceptance.js.map +1 -1
  14. package/dist/cli/commands/check.d.ts +19 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +36 -22
  17. package/dist/cli/commands/check.js.map +1 -1
  18. package/dist/cli/commands/command.d.ts +0 -1
  19. package/dist/cli/commands/command.d.ts.map +1 -1
  20. package/dist/cli/commands/command.js +8 -6
  21. package/dist/cli/commands/command.js.map +1 -1
  22. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  23. package/dist/cli/commands/constraints-retire.js +36 -57
  24. package/dist/cli/commands/constraints-retire.js.map +1 -1
  25. package/dist/cli/commands/contract.d.ts.map +1 -1
  26. package/dist/cli/commands/contract.js +36 -42
  27. package/dist/cli/commands/contract.js.map +1 -1
  28. package/dist/cli/commands/definitions.d.ts +2 -0
  29. package/dist/cli/commands/definitions.d.ts.map +1 -1
  30. package/dist/cli/commands/definitions.js +11 -6
  31. package/dist/cli/commands/definitions.js.map +1 -1
  32. package/dist/cli/commands/failure.d.ts.map +1 -1
  33. package/dist/cli/commands/failure.js +16 -2
  34. package/dist/cli/commands/failure.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +14 -8
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +172 -364
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/knowledge-view.d.ts +90 -0
  40. package/dist/cli/commands/knowledge-view.d.ts.map +1 -0
  41. package/dist/cli/commands/knowledge-view.js +198 -0
  42. package/dist/cli/commands/knowledge-view.js.map +1 -0
  43. package/dist/cli/commands/knowledge.d.ts +137 -52
  44. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  45. package/dist/cli/commands/knowledge.js +534 -400
  46. package/dist/cli/commands/knowledge.js.map +1 -1
  47. package/dist/cli/commands/passes-gate.d.ts +4 -1
  48. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  49. package/dist/cli/commands/passes-gate.js +11 -2
  50. package/dist/cli/commands/passes-gate.js.map +1 -1
  51. package/dist/cli/commands/performance.d.ts.map +1 -1
  52. package/dist/cli/commands/performance.js +19 -31
  53. package/dist/cli/commands/performance.js.map +1 -1
  54. package/dist/cli/commands/report.d.ts.map +1 -1
  55. package/dist/cli/commands/report.js +2 -1
  56. package/dist/cli/commands/report.js.map +1 -1
  57. package/dist/cli/commands/review.d.ts.map +1 -1
  58. package/dist/cli/commands/review.js +13 -25
  59. package/dist/cli/commands/review.js.map +1 -1
  60. package/dist/cli/commands/scaffold-templates.d.ts +73 -0
  61. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -0
  62. package/dist/cli/commands/scaffold-templates.js +424 -0
  63. package/dist/cli/commands/scaffold-templates.js.map +1 -0
  64. package/dist/cli/commands/scaffold.d.ts +105 -0
  65. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  66. package/dist/cli/commands/scaffold.js +263 -0
  67. package/dist/cli/commands/scaffold.js.map +1 -0
  68. package/dist/cli/commands/security.d.ts.map +1 -1
  69. package/dist/cli/commands/security.js +22 -24
  70. package/dist/cli/commands/security.js.map +1 -1
  71. package/dist/cli/commands/spec-baseline-check.d.ts +43 -0
  72. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  73. package/dist/cli/commands/spec-baseline-check.js +109 -34
  74. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  75. package/dist/cli/commands/status.d.ts +8 -0
  76. package/dist/cli/commands/status.d.ts.map +1 -1
  77. package/dist/cli/commands/status.js +5 -7
  78. package/dist/cli/commands/status.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  80. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  82. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  84. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/index.js +86 -27
  86. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  87. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  88. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  89. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  90. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  91. package/dist/cli/commands/validate.d.ts +0 -16
  92. package/dist/cli/commands/validate.d.ts.map +1 -1
  93. package/dist/cli/commands/validate.js +2 -97
  94. package/dist/cli/commands/validate.js.map +1 -1
  95. package/dist/cli/gate-command.d.ts +37 -0
  96. package/dist/cli/gate-command.d.ts.map +1 -0
  97. package/dist/cli/gate-command.js +64 -0
  98. package/dist/cli/gate-command.js.map +1 -0
  99. package/dist/cli/state-io.d.ts +34 -0
  100. package/dist/cli/state-io.d.ts.map +1 -0
  101. package/dist/cli/state-io.js +71 -0
  102. package/dist/cli/state-io.js.map +1 -0
  103. package/dist/completion-checkers/index.d.ts +1 -1
  104. package/dist/completion-checkers/index.d.ts.map +1 -1
  105. package/dist/completion-checkers/index.js +0 -15
  106. package/dist/completion-checkers/index.js.map +1 -1
  107. package/dist/context/knowledge-injector.d.ts +4 -2
  108. package/dist/context/knowledge-injector.d.ts.map +1 -1
  109. package/dist/context/knowledge-injector.js +13 -6
  110. package/dist/context/knowledge-injector.js.map +1 -1
  111. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  112. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  113. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  114. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  115. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  116. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  117. package/dist/core/constraints/check-cache.d.ts +1 -1
  118. package/dist/core/constraints/check-cache.js +1 -1
  119. package/dist/core/constraints/checker.d.ts +8 -3
  120. package/dist/core/constraints/checker.d.ts.map +1 -1
  121. package/dist/core/constraints/checker.js +30 -19
  122. package/dist/core/constraints/checker.js.map +1 -1
  123. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  124. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  125. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  126. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  127. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  128. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  129. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  130. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  131. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  132. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  133. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  134. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  135. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  136. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  137. package/dist/core/constraints/checkers/types.d.ts +10 -6
  138. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  139. package/dist/core/constraints/checkers/types.js +7 -3
  140. package/dist/core/constraints/checkers/types.js.map +1 -1
  141. package/dist/core/constraints/context-builder.d.ts +11 -10
  142. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  143. package/dist/core/constraints/context-builder.js +31 -27
  144. package/dist/core/constraints/context-builder.js.map +1 -1
  145. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  146. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  147. package/dist/core/constraints/context-reconcile.js +161 -0
  148. package/dist/core/constraints/context-reconcile.js.map +1 -0
  149. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  150. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  151. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  152. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  153. package/dist/core/constraints/git-evidence.js +1 -1
  154. package/dist/core/constraints/git-evidence.js.map +1 -1
  155. package/dist/core/constraints/injection-drift.d.ts +7 -2
  156. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  157. package/dist/core/constraints/injection-drift.js +13 -7
  158. package/dist/core/constraints/injection-drift.js.map +1 -1
  159. package/dist/core/constraints/run-env.d.ts +92 -0
  160. package/dist/core/constraints/run-env.d.ts.map +1 -0
  161. package/dist/core/constraints/run-env.js +175 -0
  162. package/dist/core/constraints/run-env.js.map +1 -0
  163. package/dist/core/constraints/triggers.d.ts +22 -0
  164. package/dist/core/constraints/triggers.d.ts.map +1 -0
  165. package/dist/core/constraints/triggers.js +31 -0
  166. package/dist/core/constraints/triggers.js.map +1 -0
  167. package/dist/core/effective-constraints.d.ts +13 -4
  168. package/dist/core/effective-constraints.d.ts.map +1 -1
  169. package/dist/core/effective-constraints.js +16 -6
  170. package/dist/core/effective-constraints.js.map +1 -1
  171. package/dist/core/effective-set.d.ts +8 -15
  172. package/dist/core/effective-set.d.ts.map +1 -1
  173. package/dist/core/effective-set.js +7 -12
  174. package/dist/core/effective-set.js.map +1 -1
  175. package/dist/core/index.d.ts +1 -0
  176. package/dist/core/index.d.ts.map +1 -1
  177. package/dist/core/index.js.map +1 -1
  178. package/dist/core/project-config-loader.d.ts +39 -17
  179. package/dist/core/project-config-loader.d.ts.map +1 -1
  180. package/dist/core/project-config-loader.js +51 -122
  181. package/dist/core/project-config-loader.js.map +1 -1
  182. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  183. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  184. package/dist/core/validators/check-handlers/http.js +12 -7
  185. package/dist/core/validators/check-handlers/http.js.map +1 -1
  186. package/dist/failure/classifier.d.ts +1 -1
  187. package/dist/failure/classifier.d.ts.map +1 -1
  188. package/dist/failure/classifier.js +8 -8
  189. package/dist/failure/classifier.js.map +1 -1
  190. package/dist/failure/index.d.ts +2 -1
  191. package/dist/failure/index.d.ts.map +1 -1
  192. package/dist/failure/index.js +9 -17
  193. package/dist/failure/index.js.map +1 -1
  194. package/dist/failure/recorder.d.ts +1 -1
  195. package/dist/failure/recorder.d.ts.map +1 -1
  196. package/dist/gates/checker-gate.d.ts +2 -1
  197. package/dist/gates/checker-gate.d.ts.map +1 -1
  198. package/dist/gates/checker-gate.js +3 -2
  199. package/dist/gates/checker-gate.js.map +1 -1
  200. package/dist/gates/command.d.ts +10 -4
  201. package/dist/gates/command.d.ts.map +1 -1
  202. package/dist/gates/command.js +36 -62
  203. package/dist/gates/command.js.map +1 -1
  204. package/dist/gates/contract.d.ts.map +1 -1
  205. package/dist/gates/contract.js +0 -4
  206. package/dist/gates/contract.js.map +1 -1
  207. package/dist/gates/definitions.d.ts +2 -1
  208. package/dist/gates/definitions.d.ts.map +1 -1
  209. package/dist/gates/definitions.js +6 -5
  210. package/dist/gates/definitions.js.map +1 -1
  211. package/dist/gates/index.d.ts +0 -4
  212. package/dist/gates/index.d.ts.map +1 -1
  213. package/dist/gates/index.js +1 -6
  214. package/dist/gates/index.js.map +1 -1
  215. package/dist/gates/performance.d.ts.map +1 -1
  216. package/dist/gates/performance.js +0 -4
  217. package/dist/gates/performance.js.map +1 -1
  218. package/dist/gates/runner.d.ts +1 -1
  219. package/dist/gates/runner.js +1 -1
  220. package/dist/gates/security.d.ts +1 -1
  221. package/dist/gates/security.d.ts.map +1 -1
  222. package/dist/gates/security.js +5 -6
  223. package/dist/gates/security.js.map +1 -1
  224. package/dist/gates/types.d.ts +14 -17
  225. package/dist/gates/types.d.ts.map +1 -1
  226. package/dist/gates/types.js.map +1 -1
  227. package/dist/hooks/bootstrap.d.ts +6 -4
  228. package/dist/hooks/bootstrap.d.ts.map +1 -1
  229. package/dist/hooks/bootstrap.js +25 -13
  230. package/dist/hooks/bootstrap.js.map +1 -1
  231. package/dist/hooks/config.d.ts +6 -2
  232. package/dist/hooks/config.d.ts.map +1 -1
  233. package/dist/hooks/config.js +5 -1
  234. package/dist/hooks/config.js.map +1 -1
  235. package/dist/hooks/index.d.ts +1 -1
  236. package/dist/hooks/index.d.ts.map +1 -1
  237. package/dist/hooks/index.js +0 -15
  238. package/dist/hooks/index.js.map +1 -1
  239. package/dist/hooks/pipeline.d.ts +19 -1
  240. package/dist/hooks/pipeline.d.ts.map +1 -1
  241. package/dist/hooks/pipeline.js +42 -4
  242. package/dist/hooks/pipeline.js.map +1 -1
  243. package/dist/hooks/registry.d.ts +11 -8
  244. package/dist/hooks/registry.d.ts.map +1 -1
  245. package/dist/hooks/registry.js +22 -7
  246. package/dist/hooks/registry.js.map +1 -1
  247. package/dist/hooks/types.d.ts +25 -7
  248. package/dist/hooks/types.d.ts.map +1 -1
  249. package/dist/index.d.ts +3 -3
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +3 -4
  252. package/dist/index.js.map +1 -1
  253. package/dist/knowledge/audit-scoring.d.ts +87 -0
  254. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  255. package/dist/knowledge/audit-scoring.js +470 -0
  256. package/dist/knowledge/audit-scoring.js.map +1 -0
  257. package/dist/knowledge/audit.d.ts +11 -59
  258. package/dist/knowledge/audit.d.ts.map +1 -1
  259. package/dist/knowledge/audit.js +47 -522
  260. package/dist/knowledge/audit.js.map +1 -1
  261. package/dist/knowledge/import.d.ts.map +1 -1
  262. package/dist/knowledge/import.js +2 -0
  263. package/dist/knowledge/import.js.map +1 -1
  264. package/dist/knowledge/index-generator.d.ts.map +1 -1
  265. package/dist/knowledge/index-generator.js +4 -5
  266. package/dist/knowledge/index-generator.js.map +1 -1
  267. package/dist/knowledge/index.d.ts +3 -2
  268. package/dist/knowledge/index.d.ts.map +1 -1
  269. package/dist/knowledge/index.js +3 -16
  270. package/dist/knowledge/index.js.map +1 -1
  271. package/dist/knowledge/ingest.js +1 -1
  272. package/dist/knowledge/ingest.js.map +1 -1
  273. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  274. package/dist/knowledge/lifecycle.js +4 -2
  275. package/dist/knowledge/lifecycle.js.map +1 -1
  276. package/dist/knowledge/migration.d.ts +3 -0
  277. package/dist/knowledge/migration.d.ts.map +1 -1
  278. package/dist/knowledge/migration.js +8 -2
  279. package/dist/knowledge/migration.js.map +1 -1
  280. package/dist/knowledge/query.d.ts +6 -0
  281. package/dist/knowledge/query.d.ts.map +1 -1
  282. package/dist/knowledge/query.js +8 -2
  283. package/dist/knowledge/query.js.map +1 -1
  284. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  285. package/dist/knowledge/reference-tracker.js +4 -6
  286. package/dist/knowledge/reference-tracker.js.map +1 -1
  287. package/dist/knowledge/store.d.ts +22 -16
  288. package/dist/knowledge/store.d.ts.map +1 -1
  289. package/dist/knowledge/store.js +53 -12
  290. package/dist/knowledge/store.js.map +1 -1
  291. package/dist/knowledge/tree-walker.d.ts +19 -0
  292. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  293. package/dist/knowledge/tree-walker.js +30 -0
  294. package/dist/knowledge/tree-walker.js.map +1 -0
  295. package/dist/knowledge/types.d.ts +16 -0
  296. package/dist/knowledge/types.d.ts.map +1 -1
  297. package/dist/monitoring/traces.d.ts +5 -3
  298. package/dist/monitoring/traces.d.ts.map +1 -1
  299. package/dist/monitoring/traces.js +19 -5
  300. package/dist/monitoring/traces.js.map +1 -1
  301. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  302. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  303. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  304. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  305. package/dist/tools/index.d.ts +4 -1
  306. package/dist/tools/index.d.ts.map +1 -1
  307. package/dist/tools/index.js +7 -15
  308. package/dist/tools/index.js.map +1 -1
  309. package/dist/types/checkpoint.d.ts +5 -0
  310. package/dist/types/checkpoint.d.ts.map +1 -1
  311. package/dist/types/project-config.d.ts +18 -0
  312. package/dist/types/project-config.d.ts.map +1 -1
  313. package/dist/types/trace.d.ts +13 -4
  314. package/dist/types/trace.d.ts.map +1 -1
  315. package/dist/types/trace.js +4 -3
  316. package/dist/types/trace.js.map +1 -1
  317. package/dist/utils/exec.d.ts +4 -27
  318. package/dist/utils/exec.d.ts.map +1 -1
  319. package/dist/utils/exec.js +4 -94
  320. package/dist/utils/exec.js.map +1 -1
  321. package/dist/utils/jsonl.d.ts +17 -7
  322. package/dist/utils/jsonl.d.ts.map +1 -1
  323. package/dist/utils/jsonl.js +144 -17
  324. package/dist/utils/jsonl.js.map +1 -1
  325. package/dist/utils/numeric-flag.d.ts +44 -0
  326. package/dist/utils/numeric-flag.d.ts.map +1 -0
  327. package/dist/utils/numeric-flag.js +62 -0
  328. package/dist/utils/numeric-flag.js.map +1 -0
  329. package/package.json +1 -1
  330. package/src/CONTEXT.md +5 -2
  331. package/src/__tests__/acceptance-gate.test.ts +7 -4
  332. package/src/__tests__/checker-extra.test.ts +3 -2
  333. package/src/__tests__/checker.test.ts +4 -3
  334. package/src/__tests__/checkpoint.test.ts +26 -9
  335. package/src/__tests__/context-files-resolution.test.ts +29 -15
  336. package/src/__tests__/contract-gate.test.ts +19 -40
  337. package/src/__tests__/error-classifier.test.ts +1 -1
  338. package/src/__tests__/exec.test.ts +45 -107
  339. package/src/__tests__/failure-recorder.test.ts +7 -5
  340. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  341. package/src/__tests__/layering.test.ts +217 -25
  342. package/src/__tests__/passes-gate.test.ts +9 -4
  343. package/src/__tests__/performance-gate.test.ts +3 -15
  344. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  345. package/src/__tests__/project-config-loader.test.ts +6 -3
  346. package/src/__tests__/public-exports.test.ts +0 -1
  347. package/src/__tests__/public-type-surface.test.ts +5 -4
  348. package/src/__tests__/public-value-surface.test.ts +182 -0
  349. package/src/__tests__/review-gate.test.ts +4 -6
  350. package/src/__tests__/security-gate.test.ts +5 -20
  351. package/src/__tests__/skip-semantics.test.ts +5 -3
  352. package/src/__tests__/spec-validator.test.ts +3 -2
  353. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  354. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  355. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  356. package/src/__tests__/trace-analyzer.test.ts +5 -3
  357. package/src/__tests__/trace-collector.test.ts +5 -3
  358. package/src/agents/index.ts +13 -2
  359. package/src/cli/__tests__/gate-command.test.ts +162 -0
  360. package/src/cli/__tests__/state-io.test.ts +50 -0
  361. package/src/cli/commands/CONTEXT.md +30 -5
  362. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  363. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  364. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  365. package/src/cli/commands/__tests__/check-read-count.test.ts +170 -0
  366. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  367. package/src/cli/commands/__tests__/check.test.ts +63 -44
  368. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  369. package/src/cli/commands/__tests__/command.test.ts +33 -17
  370. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  371. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  372. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  373. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  374. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  375. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  376. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  377. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  378. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  379. package/src/cli/commands/__tests__/init.test.ts +82 -6
  380. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  381. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  382. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  383. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  384. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  385. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  386. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  387. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  388. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  389. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  390. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  391. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  392. package/src/cli/commands/__tests__/report.test.ts +23 -1
  393. package/src/cli/commands/__tests__/review.test.ts +10 -17
  394. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  395. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  396. package/src/cli/commands/__tests__/security.test.ts +21 -24
  397. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  398. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  399. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  400. package/src/cli/commands/__tests__/status-extra.test.ts +25 -8
  401. package/src/cli/commands/__tests__/status.test.ts +46 -18
  402. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  403. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  404. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  405. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  406. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  407. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  408. package/src/cli/commands/acceptance.ts +24 -29
  409. package/src/cli/commands/check.ts +60 -35
  410. package/src/cli/commands/command.ts +9 -8
  411. package/src/cli/commands/constraints-retire.ts +57 -70
  412. package/src/cli/commands/contract.ts +44 -47
  413. package/src/cli/commands/definitions.ts +12 -6
  414. package/src/cli/commands/failure.ts +16 -3
  415. package/src/cli/commands/init.ts +242 -389
  416. package/src/cli/commands/knowledge-view.ts +224 -0
  417. package/src/cli/commands/knowledge.ts +643 -448
  418. package/src/cli/commands/passes-gate.ts +12 -3
  419. package/src/cli/commands/performance.ts +31 -38
  420. package/src/cli/commands/report.ts +3 -2
  421. package/src/cli/commands/review.ts +22 -28
  422. package/src/cli/commands/scaffold-templates.ts +406 -0
  423. package/src/cli/commands/scaffold.ts +302 -0
  424. package/src/cli/commands/security.ts +27 -23
  425. package/src/cli/commands/spec-baseline-check.ts +158 -33
  426. package/src/cli/commands/status.ts +12 -7
  427. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  428. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  429. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  430. package/src/cli/commands/sync-docs/index.ts +103 -28
  431. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  432. package/src/cli/commands/validate.ts +1 -104
  433. package/src/cli/gate-command.ts +87 -0
  434. package/src/cli/state-io.ts +53 -0
  435. package/src/completion-checkers/index.ts +10 -1
  436. package/src/context/CONTEXT.md +2 -1
  437. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  438. package/src/context/__tests__/session-manager.test.ts +19 -12
  439. package/src/context/knowledge-injector.ts +14 -7
  440. package/src/core/CONTEXT.md +10 -7
  441. package/src/core/__tests__/effective-set.test.ts +36 -60
  442. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  443. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  444. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  445. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  446. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  447. package/src/core/constraints/check-cache.ts +1 -1
  448. package/src/core/constraints/checker.ts +30 -14
  449. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  450. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  451. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  452. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  453. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  454. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  455. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  456. package/src/core/constraints/checkers/types.ts +14 -8
  457. package/src/core/constraints/context-builder.ts +35 -26
  458. package/src/core/constraints/context-reconcile.ts +200 -0
  459. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  460. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  461. package/src/core/constraints/git-evidence.ts +1 -1
  462. package/src/core/constraints/injection-drift.ts +19 -8
  463. package/src/core/constraints/run-env.ts +207 -0
  464. package/src/core/constraints/triggers.ts +33 -0
  465. package/src/core/effective-constraints.ts +17 -6
  466. package/src/core/effective-set.ts +8 -24
  467. package/src/core/index.ts +2 -0
  468. package/src/core/project-config-loader.ts +54 -102
  469. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  470. package/src/core/validators/check-handlers/http.ts +18 -7
  471. package/src/failure/CONTEXT.md +2 -1
  472. package/src/failure/classifier.ts +2 -2
  473. package/src/failure/index.ts +14 -2
  474. package/src/failure/recorder.ts +1 -1
  475. package/src/gates/CONTEXT.md +13 -7
  476. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  477. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  478. package/src/gates/__tests__/command.test.ts +8 -15
  479. package/src/gates/__tests__/contract.test.ts +5 -5
  480. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  481. package/src/gates/__tests__/performance.test.ts +0 -13
  482. package/src/gates/__tests__/review.test.ts +0 -1
  483. package/src/gates/__tests__/runner.test.ts +1 -1
  484. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  485. package/src/gates/__tests__/security.test.ts +0 -13
  486. package/src/gates/checker-gate.ts +3 -2
  487. package/src/gates/command.ts +51 -74
  488. package/src/gates/contract.ts +0 -5
  489. package/src/gates/definitions.ts +6 -5
  490. package/src/gates/index.ts +0 -5
  491. package/src/gates/performance.ts +1 -6
  492. package/src/gates/runner.ts +1 -1
  493. package/src/gates/security.ts +9 -11
  494. package/src/gates/types.ts +20 -23
  495. package/src/hooks/CONTEXT.md +10 -9
  496. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  497. package/src/hooks/__tests__/pipeline.test.ts +133 -17
  498. package/src/hooks/__tests__/registry.test.ts +51 -3
  499. package/src/hooks/bootstrap.ts +35 -15
  500. package/src/hooks/config.ts +6 -2
  501. package/src/hooks/index.ts +10 -1
  502. package/src/hooks/pipeline.ts +51 -6
  503. package/src/hooks/registry.ts +31 -12
  504. package/src/hooks/types.ts +26 -7
  505. package/src/index.ts +1 -2
  506. package/src/knowledge/CONTEXT.md +14 -7
  507. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  508. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  509. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  510. package/src/knowledge/__tests__/audit.test.ts +291 -208
  511. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  512. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  513. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  514. package/src/knowledge/__tests__/migration.test.ts +35 -6
  515. package/src/knowledge/__tests__/query.test.ts +3 -2
  516. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  517. package/src/knowledge/__tests__/store.test.ts +149 -4
  518. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  519. package/src/knowledge/audit-scoring.ts +589 -0
  520. package/src/knowledge/audit.ts +55 -599
  521. package/src/knowledge/import.ts +2 -0
  522. package/src/knowledge/index-generator.ts +4 -6
  523. package/src/knowledge/index.ts +27 -2
  524. package/src/knowledge/ingest.ts +1 -1
  525. package/src/knowledge/lifecycle.ts +5 -2
  526. package/src/knowledge/migration.ts +8 -2
  527. package/src/knowledge/query.ts +8 -1
  528. package/src/knowledge/reference-tracker.ts +5 -7
  529. package/src/knowledge/store.ts +75 -16
  530. package/src/knowledge/tree-walker.ts +29 -0
  531. package/src/knowledge/types.ts +19 -0
  532. package/src/monitoring/CONTEXT.md +5 -2
  533. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  534. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  535. package/src/monitoring/traces.ts +19 -5
  536. package/src/release/CONTEXT.md +1 -1
  537. package/src/release/__tests__/integrity.test.ts +21 -12
  538. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  539. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  540. package/src/tools/index.ts +5 -1
  541. package/src/types/checkpoint.ts +6 -0
  542. package/src/types/project-config.ts +21 -0
  543. package/src/types/trace.ts +14 -4
  544. package/src/utils/__tests__/file-walk.test.ts +3 -2
  545. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  546. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  547. package/src/utils/__tests__/jsonl.test.ts +61 -14
  548. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  549. package/src/utils/__tests__/package-version.test.ts +2 -2
  550. package/src/utils/exec.ts +4 -64
  551. package/src/utils/jsonl.ts +159 -20
  552. package/src/utils/numeric-flag.ts +64 -0
  553. package/dist/failure/types.d.ts +0 -9
  554. package/dist/failure/types.d.ts.map +0 -1
  555. package/dist/failure/types.js +0 -25
  556. package/dist/failure/types.js.map +0 -1
  557. package/dist/gates/effective-gates.d.ts +0 -30
  558. package/dist/gates/effective-gates.d.ts.map +0 -1
  559. package/dist/gates/effective-gates.js +0 -81
  560. package/dist/gates/effective-gates.js.map +0 -1
  561. package/src/failure/types.ts +0 -9
  562. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  563. package/src/gates/effective-gates.ts +0 -99
@@ -4,16 +4,26 @@
4
4
  * block/warn 行为是 studio safeCallHook 语义在管线侧的无损承载面:
5
5
  * - 'block':hook 失败 → passed=false、blockedBy 记名、停止执行后续 hook
6
6
  * - 'warn':hook 失败 → 记录警告、继续执行后续 hook、passed 不受影响
7
- * - 'ignore'/未声明:静默跳过(历史行为保留)
7
+ *
8
+ * #159 起 `enabled` / `errorStrategy` 的唯一声明点是 HookConfig:
9
+ * HookDefinition 不再携带这两个字段,有效值在注册环节由配置表填充。
8
10
  */
9
11
 
10
12
  import { HookPipeline } from '../pipeline';
11
13
  import { HookRegistry } from '../registry';
12
- import type { HookDefinition } from '../types';
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
+ }
13
19
 
14
- function makeRegistry(hooks: HookDefinition[]): { registry: HookRegistry; pipeline: HookPipeline } {
20
+ function makeRegistry(
21
+ entries: Array<{ hook: HookDefinition; config?: Partial<HookConfig> }>
22
+ ): { registry: HookRegistry; pipeline: HookPipeline } {
15
23
  const registry = new HookRegistry();
16
- registry.registerAll(hooks);
24
+ for (const { hook, config } of entries) {
25
+ registry.register(hook, makeConfig(hook.name, config));
26
+ }
17
27
  return { registry, pipeline: new HookPipeline(registry) };
18
28
  }
19
29
 
@@ -21,8 +31,8 @@ describe('HookPipeline errorStrategy 语义', () => {
21
31
  it('block:失败阻断管线(passed=false、blockedBy 记名、停止后续 hook)', async () => {
22
32
  const second = jest.fn().mockResolvedValue({ passed: true });
23
33
  const { pipeline } = makeRegistry([
24
- { name: 'first', phase: 'before', errorStrategy: 'block', execute: async () => ({ passed: false, error: 'boom' }) },
25
- { name: 'second', phase: 'before', execute: second },
34
+ { hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'boom' }) }, config: { errorStrategy: 'block' } },
35
+ { hook: { name: 'second', phase: 'before', execute: second } },
26
36
  ]);
27
37
 
28
38
  const result = await pipeline.run('before', {});
@@ -35,8 +45,8 @@ describe('HookPipeline errorStrategy 语义', () => {
35
45
  it('warn:失败记录警告并继续(passed 不受影响、后续 hook 照常执行)', async () => {
36
46
  const second = jest.fn().mockResolvedValue({ passed: true });
37
47
  const { pipeline } = makeRegistry([
38
- { name: 'first', phase: 'before', errorStrategy: 'warn', execute: async () => ({ passed: false, error: 'soft' }) },
39
- { name: 'second', phase: 'before', execute: second },
48
+ { hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'soft' }) }, config: { errorStrategy: 'warn' } },
49
+ { hook: { name: 'second', phase: 'before', execute: second } },
40
50
  ]);
41
51
 
42
52
  const result = await pipeline.run('before', {});
@@ -50,8 +60,8 @@ describe('HookPipeline errorStrategy 语义', () => {
50
60
  it('block 失败停在当前 phase,不改变其它 phase 的 hook', async () => {
51
61
  const afterHook = jest.fn().mockResolvedValue({ passed: true });
52
62
  const { pipeline } = makeRegistry([
53
- { name: 'blocker', phase: 'before', errorStrategy: 'block', execute: async () => ({ passed: false }) },
54
- { name: 'afterHook', phase: 'after', execute: afterHook },
63
+ { hook: { name: 'blocker', phase: 'before', execute: async () => ({ passed: false }) }, config: { errorStrategy: 'block' } },
64
+ { hook: { name: 'afterHook', phase: 'after', execute: afterHook } },
55
65
  ]);
56
66
 
57
67
  const before = await pipeline.run('before', {});
@@ -62,18 +72,124 @@ describe('HookPipeline errorStrategy 语义', () => {
62
72
  expect(afterHook).toHaveBeenCalled();
63
73
  });
64
74
 
65
- it('未声明 errorStrategy:静默跳过(历史行为保留)', async () => {
66
- const second = jest.fn().mockResolvedValue({ passed: true });
75
+ it('配置 enabled:false 的 hook 不进入管线执行(配置表是唯一声明点)', async () => {
76
+ const disabledHook = jest.fn().mockResolvedValue({ passed: true });
77
+ const enabledHook = jest.fn().mockResolvedValue({ passed: true });
67
78
  const { pipeline } = makeRegistry([
68
- { name: 'first', phase: 'before', execute: async () => ({ passed: false }) },
69
- { name: 'second', phase: 'before', execute: second },
79
+ { hook: { name: 'disabled', phase: 'before', execute: disabledHook }, config: { enabled: false } },
80
+ { hook: { name: 'enabled', phase: 'before', execute: enabledHook } },
70
81
  ]);
71
82
 
72
83
  const result = await pipeline.run('before', {});
73
84
 
74
85
  expect(result.passed).toBe(true);
75
- expect(result.blockedBy).toEqual([]);
76
- expect(result.warnings).toEqual([]);
77
- expect(second).toHaveBeenCalled();
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
+ }
78
194
  });
79
195
  });
@@ -2,6 +2,10 @@
2
2
  * Hook 注册表闭环双向校验测试(H5,复制 checker 闭环模式)
3
3
  *
4
4
  * 两个失败方向 + 重复声明/注册都必须抛错;闭环通过时不抛错。
5
+ *
6
+ * #159 起注册即配对:`register(hook, config)` 以 HookConfig 填充有效
7
+ * `enabled` / `errorStrategy`——配置表是这两个语义的唯一声明点,
8
+ * HookDefinition 类型上不再存在同名字段(矛盾在构造上不可能出现)。
5
9
  */
6
10
 
7
11
  import { assertHookRegistryClosed, HookRegistry } from '../registry';
@@ -15,8 +19,8 @@ function makeHook(name: string): HookDefinition {
15
19
  };
16
20
  }
17
21
 
18
- function makeConfig(name: string): HookConfig {
19
- return { name, enabled: true, errorStrategy: 'block' };
22
+ function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
23
+ return { name, enabled: true, errorStrategy: 'block', ...overrides };
20
24
  }
21
25
 
22
26
  describe('assertHookRegistryClosed', () => {
@@ -60,8 +64,52 @@ describe('assertHookRegistryClosed', () => {
60
64
 
61
65
  it('与 HookRegistry.listAll 组合可用(consumer 注册点用法)', () => {
62
66
  const registry = new HookRegistry();
63
- registry.registerAll([makeHook('beforeAgentExecute')]);
67
+ registry.registerAll([makeHook('beforeAgentExecute')], [makeConfig('beforeAgentExecute')]);
64
68
  expect(() => assertHookRegistryClosed([makeConfig('beforeAgentExecute')], registry.listAll()))
65
69
  .not.toThrow();
66
70
  });
67
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
+ });
@@ -11,17 +11,18 @@
11
11
  * // harness.checker, harness.config, harness.hooks, harness.sessions
12
12
  * ```
13
13
  *
14
- * checker 的 trace 记录器在此接线(harness#88:core 不上行依赖 monitoring)。
14
+ * checker 的 trace 记录器在此接线(harness#88:core 不上行依赖 monitoring),
15
+ * 且锚在本函数收到的 projectPath 上(#139:落点跟根走,不落调用方 cwd)。
15
16
  */
16
17
 
17
18
  import { ConstraintChecker } from '../core/constraints/checker';
18
19
  import { SessionManager } from '../context/session-manager';
19
20
  import { ProjectConfigLoader } from '../core/project-config-loader';
20
- import { getTraceCollector } from '../monitoring/traces';
21
+ import { TraceCollector } from '../monitoring/traces';
21
22
  import { HookRegistry } from './registry';
22
23
  import { HookPipeline } from './pipeline';
23
24
  import type { MergedConstraintsConfig } from '../types/project-config';
24
- import type { HookDefinition } from './types';
25
+ import type { HookConfig, HookDefinition } from './types';
25
26
 
26
27
  /**
27
28
  * 异步加载项目配置(S9:异步 I/O)
@@ -30,8 +31,8 @@ async function loadConfigAsync(projectPath: string): Promise<{
30
31
  config: ReturnType<ProjectConfigLoader['getConfig']>;
31
32
  mergedConstraints: MergedConstraintsConfig;
32
33
  }> {
33
- // config.yml 解析经 loadRawProjectConfig 进程级 memoize(工单 16),
34
- // 此处不再重复读取文件
34
+ // config.yml 的读取只有 loadRawProjectConfig 这一条路(ADR-0023 决策 2 撤销进程级
35
+ // 缓存后,每次调用读当下内容),此处不重复读文件
35
36
  const loader = new ProjectConfigLoader(projectPath);
36
37
  loader.load();
37
38
 
@@ -59,15 +60,36 @@ export interface HarnessBootstrap {
59
60
  mergedConstraints: MergedConstraintsConfig;
60
61
  }
61
62
 
63
+ /**
64
+ * 注册初始 hook(定义必须配对配置声明表——#159 起 HookConfig 是
65
+ * enabled / errorStrategy 的唯一声明点,缺表即抛错)
66
+ */
67
+ function registerInitialHooks(
68
+ hooks: HookRegistry,
69
+ hookDefinitions: HookDefinition[] | undefined,
70
+ hookConfigs: HookConfig[] | undefined
71
+ ): void {
72
+ if (!hookDefinitions || hookDefinitions.length === 0) return;
73
+ if (!hookConfigs) {
74
+ throw new Error(
75
+ '[harness] bootstrap 失败:注册 hook 定义必须同时提供 HookConfig 声明表' +
76
+ '(enabled / errorStrategy 的唯一声明点)。'
77
+ );
78
+ }
79
+ hooks.registerAll(hookDefinitions, hookConfigs);
80
+ }
81
+
62
82
  /**
63
83
  * 初始化 harness 运行环境(异步,不阻塞事件循环)
64
84
  *
65
85
  * @param projectPath 项目根路径
66
86
  * @param hookDefinitions 可选,初始化时注册的 hook
87
+ * @param hookConfigs 可选,与 hookDefinitions 配对的配置声明表(注册定义时必填)
67
88
  */
68
89
  export async function bootstrapHarness(
69
90
  projectPath?: string,
70
- hookDefinitions?: HookDefinition[]
91
+ hookDefinitions?: HookDefinition[],
92
+ hookConfigs?: HookConfig[]
71
93
  ): Promise<HarnessBootstrap> {
72
94
  const resolvedPath = projectPath || process.cwd();
73
95
 
@@ -75,16 +97,15 @@ export async function bootstrapHarness(
75
97
  const { mergedConstraints } = await loadConfigAsync(resolvedPath);
76
98
 
77
99
  // 2. 初始化核心组件
78
- const checker = new ConstraintChecker(getTraceCollector());
100
+ // trace 记录器锚根构造(#139):本函数本就收 projectPath,落点必须跟着根走
101
+ const checker = new ConstraintChecker(new TraceCollector({ projectPath: resolvedPath }));
79
102
 
80
103
  const sessions = new SessionManager(resolvedPath);
81
104
  const hooks = new HookRegistry();
82
105
  const pipeline = new HookPipeline(hooks);
83
106
 
84
107
  // 3. 注册初始 hook
85
- if (hookDefinitions) {
86
- hooks.registerAll(hookDefinitions);
87
- }
108
+ registerInitialHooks(hooks, hookDefinitions, hookConfigs);
88
109
 
89
110
  return {
90
111
  checker,
@@ -103,7 +124,8 @@ export async function bootstrapHarness(
103
124
  */
104
125
  export function bootstrapHarnessSync(
105
126
  projectPath?: string,
106
- hookDefinitions?: HookDefinition[]
127
+ hookDefinitions?: HookDefinition[],
128
+ hookConfigs?: HookConfig[]
107
129
  ): HarnessBootstrap {
108
130
  const resolvedPath = projectPath || process.cwd();
109
131
 
@@ -111,15 +133,13 @@ export function bootstrapHarnessSync(
111
133
  loader.load();
112
134
  const mergedConstraints = loader.mergeConstraints();
113
135
 
114
- const checker = new ConstraintChecker(getTraceCollector());
136
+ const checker = new ConstraintChecker(new TraceCollector({ projectPath: resolvedPath }));
115
137
 
116
138
  const sessions = new SessionManager(resolvedPath);
117
139
  const hooks = new HookRegistry();
118
140
  const pipeline = new HookPipeline(hooks);
119
141
 
120
- if (hookDefinitions) {
121
- hooks.registerAll(hookDefinitions);
122
- }
142
+ registerInitialHooks(hooks, hookDefinitions, hookConfigs);
123
143
 
124
144
  return {
125
145
  checker,
@@ -2,7 +2,7 @@
2
2
  * Hook 配置归一(G7)
3
3
  *
4
4
  * studio 侧 per-hook 运行时配置(`blocking: boolean`)与 harness 侧
5
- * `HookDefinition.errorStrategy` 双轨归一:以 errorStrategy 为唯一真相,
5
+ * `HookConfig.errorStrategy` 双轨归一:以 errorStrategy 为唯一真相,
6
6
  * `blocking` → errorStrategy 为无损映射。
7
7
  *
8
8
  * 映射语义(确认于 H5):
@@ -12,6 +12,10 @@
12
12
  * (管线结果 warnings 记名,passed 不受影响)——对应 studio safeCallHook
13
13
  * console.warn 继续
14
14
  *
15
+ * #159 起 `HookConfig` 是 `enabled` / `errorStrategy` 的唯一声明点:
16
+ * HookDefinition 不再携带这两个字段,注册环节以配置表填充有效值,
17
+ * 有效策略集合收敛为 'block' | 'warn'(原 'ignore' 退出有效面)。
18
+ *
15
19
  * studio 侧随动(studio #150 A4):hooks/config.ts 删 DEFAULTS/safeCallHook,
16
20
  * blocking→errorStrategy 无损映射进 toHookDef;HARNESS_HOOK_DISABLE 保留 studio。
17
21
  */
@@ -24,6 +28,6 @@ import type { HookErrorStrategy } from './types';
24
28
  * @param blocking studio 侧「失败是否阻断」语义
25
29
  * @returns 'block'(阻断)| 'warn'(警告继续)
26
30
  */
27
- export function toErrorStrategy(blocking: boolean): Extract<HookErrorStrategy, 'block' | 'warn'> {
31
+ export function toErrorStrategy(blocking: boolean): HookErrorStrategy {
28
32
  return blocking ? 'block' : 'warn';
29
33
  }
@@ -5,7 +5,16 @@
5
5
  * 无业务逻辑,consumer 自行定义 hook 名称和语义。
6
6
  */
7
7
 
8
- export * from './types';
8
+ export type {
9
+ HookPhase,
10
+ HookErrorStrategy,
11
+ HookConfig,
12
+ HookDefinition,
13
+ EffectiveHook,
14
+ HookResult,
15
+ HookExecutionRecord,
16
+ PipelineResult,
17
+ } from './types';
9
18
  export { HookRegistry, assertHookRegistryClosed } from './registry';
10
19
  export { HookPipeline } from './pipeline';
11
20
  export { toErrorStrategy } from './config';
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type {
9
- HookDefinition,
9
+ EffectiveHook,
10
10
  HookPhase,
11
11
  HookExecutionRecord,
12
12
  PipelineResult,
@@ -47,16 +47,61 @@ export class HookPipeline<C = unknown> {
47
47
  blockedBy.push(hook.name);
48
48
  break; // blocking hook 失败,停止执行后续 hook
49
49
  }
50
- if (hook.errorStrategy === 'warn') {
51
- warnings.push(hook.name);
52
- }
53
- // 'ignore' 策略静默跳过
50
+ // 'warn':记录警告继续(有效集合仅 block/warn,策略由 HookConfig 必填声明)
51
+ warnings.push(hook.name);
54
52
  }
55
53
  }
56
54
 
57
55
  return { passed, records, blockedBy, warnings };
58
56
  }
59
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
+
60
105
  /**
61
106
  * 执行 before + after 全套管线
62
107
  *
@@ -111,7 +156,7 @@ export class HookPipeline<C = unknown> {
111
156
  * 执行单个 hook(带采样和错误隔离)
112
157
  */
113
158
  private async executeOne(
114
- hook: HookDefinition<C, unknown>,
159
+ hook: EffectiveHook<C, unknown>,
115
160
  context: C
116
161
  ): Promise<HookExecutionRecord> {
117
162
  const startedAt = Date.now();
@@ -4,7 +4,7 @@
4
4
  * 纯簿记,无执行逻辑。线程不安全(Node.js 单线程)。
5
5
  */
6
6
 
7
- import type { HookConfig, HookDefinition, HookPhase } from './types';
7
+ import type { EffectiveHook, HookConfig, HookDefinition, HookPhase } from './types';
8
8
 
9
9
  /**
10
10
  * 注册表闭环双向校验(H5,复制 checker 闭环模式)
@@ -66,21 +66,40 @@ export function assertHookRegistryClosed<C = unknown, R = unknown>(
66
66
  }
67
67
 
68
68
  export class HookRegistry<C = unknown, R = unknown> {
69
- private hooks: Map<string, HookDefinition<C, R>> = new Map();
69
+ private hooks: Map<string, EffectiveHook<C, R>> = new Map();
70
70
 
71
71
  /**
72
72
  * 注册 hook(同名覆盖)
73
+ *
74
+ * 有效 `enabled` / `errorStrategy` 由 HookConfig 填充——配置表是这两个
75
+ * 语义的唯一声明点(#159),定义与配置名称不一致即抛错。
73
76
  */
74
- register(hook: HookDefinition<C, R>): void {
75
- this.hooks.set(hook.name, { ...hook });
77
+ register(hook: HookDefinition<C, R>, config: HookConfig): void {
78
+ if (hook.name !== config.name) {
79
+ throw new Error(
80
+ `[harness] hook 注册失败:定义 "${hook.name}" 与配置 "${config.name}" 名称不一致。`
81
+ );
82
+ }
83
+ this.hooks.set(hook.name, {
84
+ ...hook,
85
+ enabled: config.enabled,
86
+ errorStrategy: config.errorStrategy,
87
+ });
76
88
  }
77
89
 
78
90
  /**
79
- * 批量注册
91
+ * 批量注册(按 name 配对配置;有实现缺配置声明即抛错)
80
92
  */
81
- registerAll(hooks: HookDefinition<C, R>[]): void {
93
+ registerAll(hooks: HookDefinition<C, R>[], configs: readonly HookConfig[]): void {
94
+ const configByName = new Map(configs.map(c => [c.name, c]));
82
95
  for (const hook of hooks) {
83
- this.register(hook);
96
+ const config = configByName.get(hook.name);
97
+ if (!config) {
98
+ throw new Error(
99
+ `[harness] hook 注册失败:hook "${hook.name}" 没有对应的 HookConfig 声明。`
100
+ );
101
+ }
102
+ this.register(hook, config);
84
103
  }
85
104
  }
86
105
 
@@ -92,18 +111,18 @@ export class HookRegistry<C = unknown, R = unknown> {
92
111
  }
93
112
 
94
113
  /**
95
- * 获取单个 hook
114
+ * 获取单个 hook(含有效 enabled / errorStrategy)
96
115
  */
97
- get(name: string): HookDefinition<C, R> | undefined {
116
+ get(name: string): EffectiveHook<C, R> | undefined {
98
117
  return this.hooks.get(name);
99
118
  }
100
119
 
101
120
  /**
102
121
  * 获取指定时机、已启用的 hook,按优先级排序
103
122
  */
104
- getEnabled(phase: HookPhase): HookDefinition<C, R>[] {
123
+ getEnabled(phase: HookPhase): EffectiveHook<C, R>[] {
105
124
  return Array.from(this.hooks.values())
106
- .filter(h => h.phase === phase && h.enabled !== false)
125
+ .filter(h => h.phase === phase && h.enabled)
107
126
  .sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
108
127
  }
109
128
 
@@ -117,7 +136,7 @@ export class HookRegistry<C = unknown, R = unknown> {
117
136
  /**
118
137
  * 列出所有 hook(含禁用)
119
138
  */
120
- listAll(): HookDefinition<C, R>[] {
139
+ listAll(): EffectiveHook<C, R>[] {
121
140
  return Array.from(this.hooks.values());
122
141
  }
123
142