@dommaker/harness 1.7.0 → 1.8.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 (549) hide show
  1. package/CHANGELOG.md +37 -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 +11 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +26 -11
  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/sync-docs/context-syncer.d.ts +21 -2
  76. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  77. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  78. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  80. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/index.js +86 -27
  82. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  84. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  86. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  87. package/dist/cli/commands/validate.d.ts +0 -16
  88. package/dist/cli/commands/validate.d.ts.map +1 -1
  89. package/dist/cli/commands/validate.js +2 -97
  90. package/dist/cli/commands/validate.js.map +1 -1
  91. package/dist/cli/gate-command.d.ts +37 -0
  92. package/dist/cli/gate-command.d.ts.map +1 -0
  93. package/dist/cli/gate-command.js +64 -0
  94. package/dist/cli/gate-command.js.map +1 -0
  95. package/dist/completion-checkers/index.d.ts +1 -1
  96. package/dist/completion-checkers/index.d.ts.map +1 -1
  97. package/dist/completion-checkers/index.js +0 -15
  98. package/dist/completion-checkers/index.js.map +1 -1
  99. package/dist/context/knowledge-injector.d.ts +4 -2
  100. package/dist/context/knowledge-injector.d.ts.map +1 -1
  101. package/dist/context/knowledge-injector.js +13 -6
  102. package/dist/context/knowledge-injector.js.map +1 -1
  103. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  104. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  105. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  106. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  107. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  108. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  109. package/dist/core/constraints/check-cache.d.ts +1 -1
  110. package/dist/core/constraints/check-cache.js +1 -1
  111. package/dist/core/constraints/checker.d.ts +8 -3
  112. package/dist/core/constraints/checker.d.ts.map +1 -1
  113. package/dist/core/constraints/checker.js +30 -19
  114. package/dist/core/constraints/checker.js.map +1 -1
  115. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  116. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  117. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  118. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  119. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  120. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  121. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  122. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  123. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  124. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  125. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  126. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  127. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  128. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  129. package/dist/core/constraints/checkers/types.d.ts +10 -6
  130. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  131. package/dist/core/constraints/checkers/types.js +7 -3
  132. package/dist/core/constraints/checkers/types.js.map +1 -1
  133. package/dist/core/constraints/context-builder.d.ts +11 -10
  134. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  135. package/dist/core/constraints/context-builder.js +31 -27
  136. package/dist/core/constraints/context-builder.js.map +1 -1
  137. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  138. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  139. package/dist/core/constraints/context-reconcile.js +161 -0
  140. package/dist/core/constraints/context-reconcile.js.map +1 -0
  141. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  142. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  143. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  144. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  145. package/dist/core/constraints/git-evidence.js +1 -1
  146. package/dist/core/constraints/git-evidence.js.map +1 -1
  147. package/dist/core/constraints/injection-drift.d.ts +7 -2
  148. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  149. package/dist/core/constraints/injection-drift.js +13 -7
  150. package/dist/core/constraints/injection-drift.js.map +1 -1
  151. package/dist/core/constraints/run-env.d.ts +92 -0
  152. package/dist/core/constraints/run-env.d.ts.map +1 -0
  153. package/dist/core/constraints/run-env.js +175 -0
  154. package/dist/core/constraints/run-env.js.map +1 -0
  155. package/dist/core/constraints/triggers.d.ts +22 -0
  156. package/dist/core/constraints/triggers.d.ts.map +1 -0
  157. package/dist/core/constraints/triggers.js +31 -0
  158. package/dist/core/constraints/triggers.js.map +1 -0
  159. package/dist/core/effective-constraints.d.ts +13 -4
  160. package/dist/core/effective-constraints.d.ts.map +1 -1
  161. package/dist/core/effective-constraints.js +16 -6
  162. package/dist/core/effective-constraints.js.map +1 -1
  163. package/dist/core/effective-set.d.ts +8 -15
  164. package/dist/core/effective-set.d.ts.map +1 -1
  165. package/dist/core/effective-set.js +7 -12
  166. package/dist/core/effective-set.js.map +1 -1
  167. package/dist/core/index.d.ts +1 -0
  168. package/dist/core/index.d.ts.map +1 -1
  169. package/dist/core/index.js.map +1 -1
  170. package/dist/core/project-config-loader.d.ts +39 -17
  171. package/dist/core/project-config-loader.d.ts.map +1 -1
  172. package/dist/core/project-config-loader.js +51 -122
  173. package/dist/core/project-config-loader.js.map +1 -1
  174. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  175. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  176. package/dist/core/validators/check-handlers/http.js +12 -7
  177. package/dist/core/validators/check-handlers/http.js.map +1 -1
  178. package/dist/failure/classifier.d.ts +1 -1
  179. package/dist/failure/classifier.d.ts.map +1 -1
  180. package/dist/failure/classifier.js +8 -8
  181. package/dist/failure/classifier.js.map +1 -1
  182. package/dist/failure/index.d.ts +2 -1
  183. package/dist/failure/index.d.ts.map +1 -1
  184. package/dist/failure/index.js +9 -17
  185. package/dist/failure/index.js.map +1 -1
  186. package/dist/failure/recorder.d.ts +1 -1
  187. package/dist/failure/recorder.d.ts.map +1 -1
  188. package/dist/gates/checker-gate.d.ts +2 -1
  189. package/dist/gates/checker-gate.d.ts.map +1 -1
  190. package/dist/gates/checker-gate.js +3 -2
  191. package/dist/gates/checker-gate.js.map +1 -1
  192. package/dist/gates/command.d.ts +10 -4
  193. package/dist/gates/command.d.ts.map +1 -1
  194. package/dist/gates/command.js +36 -62
  195. package/dist/gates/command.js.map +1 -1
  196. package/dist/gates/contract.d.ts.map +1 -1
  197. package/dist/gates/contract.js +0 -4
  198. package/dist/gates/contract.js.map +1 -1
  199. package/dist/gates/definitions.d.ts +2 -1
  200. package/dist/gates/definitions.d.ts.map +1 -1
  201. package/dist/gates/definitions.js +6 -5
  202. package/dist/gates/definitions.js.map +1 -1
  203. package/dist/gates/index.d.ts +0 -4
  204. package/dist/gates/index.d.ts.map +1 -1
  205. package/dist/gates/index.js +1 -6
  206. package/dist/gates/index.js.map +1 -1
  207. package/dist/gates/performance.d.ts.map +1 -1
  208. package/dist/gates/performance.js +0 -4
  209. package/dist/gates/performance.js.map +1 -1
  210. package/dist/gates/runner.d.ts +1 -1
  211. package/dist/gates/runner.js +1 -1
  212. package/dist/gates/security.d.ts +1 -1
  213. package/dist/gates/security.d.ts.map +1 -1
  214. package/dist/gates/security.js +5 -6
  215. package/dist/gates/security.js.map +1 -1
  216. package/dist/gates/types.d.ts +14 -17
  217. package/dist/gates/types.d.ts.map +1 -1
  218. package/dist/gates/types.js.map +1 -1
  219. package/dist/hooks/bootstrap.d.ts +6 -4
  220. package/dist/hooks/bootstrap.d.ts.map +1 -1
  221. package/dist/hooks/bootstrap.js +25 -13
  222. package/dist/hooks/bootstrap.js.map +1 -1
  223. package/dist/hooks/config.d.ts +6 -2
  224. package/dist/hooks/config.d.ts.map +1 -1
  225. package/dist/hooks/config.js +5 -1
  226. package/dist/hooks/config.js.map +1 -1
  227. package/dist/hooks/index.d.ts +1 -1
  228. package/dist/hooks/index.d.ts.map +1 -1
  229. package/dist/hooks/index.js +0 -15
  230. package/dist/hooks/index.js.map +1 -1
  231. package/dist/hooks/pipeline.d.ts.map +1 -1
  232. package/dist/hooks/pipeline.js +2 -4
  233. package/dist/hooks/pipeline.js.map +1 -1
  234. package/dist/hooks/registry.d.ts +11 -8
  235. package/dist/hooks/registry.d.ts.map +1 -1
  236. package/dist/hooks/registry.js +22 -7
  237. package/dist/hooks/registry.js.map +1 -1
  238. package/dist/hooks/types.d.ts +23 -7
  239. package/dist/hooks/types.d.ts.map +1 -1
  240. package/dist/index.d.ts +3 -3
  241. package/dist/index.d.ts.map +1 -1
  242. package/dist/index.js +3 -4
  243. package/dist/index.js.map +1 -1
  244. package/dist/knowledge/audit-scoring.d.ts +87 -0
  245. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  246. package/dist/knowledge/audit-scoring.js +470 -0
  247. package/dist/knowledge/audit-scoring.js.map +1 -0
  248. package/dist/knowledge/audit.d.ts +11 -59
  249. package/dist/knowledge/audit.d.ts.map +1 -1
  250. package/dist/knowledge/audit.js +47 -522
  251. package/dist/knowledge/audit.js.map +1 -1
  252. package/dist/knowledge/import.d.ts.map +1 -1
  253. package/dist/knowledge/import.js +2 -0
  254. package/dist/knowledge/import.js.map +1 -1
  255. package/dist/knowledge/index-generator.d.ts.map +1 -1
  256. package/dist/knowledge/index-generator.js +4 -5
  257. package/dist/knowledge/index-generator.js.map +1 -1
  258. package/dist/knowledge/index.d.ts +3 -2
  259. package/dist/knowledge/index.d.ts.map +1 -1
  260. package/dist/knowledge/index.js +3 -16
  261. package/dist/knowledge/index.js.map +1 -1
  262. package/dist/knowledge/ingest.js +1 -1
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  265. package/dist/knowledge/lifecycle.js +4 -2
  266. package/dist/knowledge/lifecycle.js.map +1 -1
  267. package/dist/knowledge/migration.d.ts +3 -0
  268. package/dist/knowledge/migration.d.ts.map +1 -1
  269. package/dist/knowledge/migration.js +8 -2
  270. package/dist/knowledge/migration.js.map +1 -1
  271. package/dist/knowledge/query.d.ts +6 -0
  272. package/dist/knowledge/query.d.ts.map +1 -1
  273. package/dist/knowledge/query.js +8 -2
  274. package/dist/knowledge/query.js.map +1 -1
  275. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  276. package/dist/knowledge/reference-tracker.js +4 -6
  277. package/dist/knowledge/reference-tracker.js.map +1 -1
  278. package/dist/knowledge/store.d.ts +22 -16
  279. package/dist/knowledge/store.d.ts.map +1 -1
  280. package/dist/knowledge/store.js +53 -12
  281. package/dist/knowledge/store.js.map +1 -1
  282. package/dist/knowledge/tree-walker.d.ts +19 -0
  283. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  284. package/dist/knowledge/tree-walker.js +30 -0
  285. package/dist/knowledge/tree-walker.js.map +1 -0
  286. package/dist/knowledge/types.d.ts +16 -0
  287. package/dist/knowledge/types.d.ts.map +1 -1
  288. package/dist/monitoring/traces.d.ts +5 -3
  289. package/dist/monitoring/traces.d.ts.map +1 -1
  290. package/dist/monitoring/traces.js +19 -5
  291. package/dist/monitoring/traces.js.map +1 -1
  292. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  293. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  294. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  295. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  296. package/dist/tools/index.d.ts +4 -1
  297. package/dist/tools/index.d.ts.map +1 -1
  298. package/dist/tools/index.js +7 -15
  299. package/dist/tools/index.js.map +1 -1
  300. package/dist/types/checkpoint.d.ts +5 -0
  301. package/dist/types/checkpoint.d.ts.map +1 -1
  302. package/dist/types/project-config.d.ts +18 -0
  303. package/dist/types/project-config.d.ts.map +1 -1
  304. package/dist/types/trace.d.ts +13 -4
  305. package/dist/types/trace.d.ts.map +1 -1
  306. package/dist/types/trace.js +4 -3
  307. package/dist/types/trace.js.map +1 -1
  308. package/dist/utils/exec.d.ts +4 -27
  309. package/dist/utils/exec.d.ts.map +1 -1
  310. package/dist/utils/exec.js +4 -94
  311. package/dist/utils/exec.js.map +1 -1
  312. package/dist/utils/jsonl.d.ts +17 -7
  313. package/dist/utils/jsonl.d.ts.map +1 -1
  314. package/dist/utils/jsonl.js +144 -17
  315. package/dist/utils/jsonl.js.map +1 -1
  316. package/dist/utils/numeric-flag.d.ts +44 -0
  317. package/dist/utils/numeric-flag.d.ts.map +1 -0
  318. package/dist/utils/numeric-flag.js +62 -0
  319. package/dist/utils/numeric-flag.js.map +1 -0
  320. package/package.json +1 -1
  321. package/src/CONTEXT.md +5 -2
  322. package/src/__tests__/acceptance-gate.test.ts +7 -4
  323. package/src/__tests__/checker-extra.test.ts +3 -2
  324. package/src/__tests__/checker.test.ts +4 -3
  325. package/src/__tests__/checkpoint.test.ts +26 -9
  326. package/src/__tests__/context-files-resolution.test.ts +29 -15
  327. package/src/__tests__/contract-gate.test.ts +19 -40
  328. package/src/__tests__/error-classifier.test.ts +1 -1
  329. package/src/__tests__/exec.test.ts +45 -107
  330. package/src/__tests__/failure-recorder.test.ts +7 -5
  331. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  332. package/src/__tests__/layering.test.ts +217 -25
  333. package/src/__tests__/passes-gate.test.ts +9 -4
  334. package/src/__tests__/performance-gate.test.ts +3 -15
  335. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  336. package/src/__tests__/project-config-loader.test.ts +6 -3
  337. package/src/__tests__/public-exports.test.ts +0 -1
  338. package/src/__tests__/public-type-surface.test.ts +5 -4
  339. package/src/__tests__/public-value-surface.test.ts +182 -0
  340. package/src/__tests__/review-gate.test.ts +4 -6
  341. package/src/__tests__/security-gate.test.ts +5 -20
  342. package/src/__tests__/skip-semantics.test.ts +5 -3
  343. package/src/__tests__/spec-validator.test.ts +3 -2
  344. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  345. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  346. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  347. package/src/__tests__/trace-analyzer.test.ts +5 -3
  348. package/src/__tests__/trace-collector.test.ts +5 -3
  349. package/src/agents/index.ts +13 -2
  350. package/src/cli/__tests__/gate-command.test.ts +162 -0
  351. package/src/cli/commands/CONTEXT.md +29 -5
  352. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  353. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  354. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  355. package/src/cli/commands/__tests__/check-read-count.test.ts +167 -0
  356. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  357. package/src/cli/commands/__tests__/check.test.ts +43 -36
  358. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  359. package/src/cli/commands/__tests__/command.test.ts +33 -17
  360. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  361. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  362. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  363. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  364. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  365. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  366. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  367. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  368. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  369. package/src/cli/commands/__tests__/init.test.ts +82 -6
  370. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  371. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  372. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  373. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  374. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  375. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  376. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  377. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  378. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  379. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  380. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  381. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  382. package/src/cli/commands/__tests__/report.test.ts +23 -1
  383. package/src/cli/commands/__tests__/review.test.ts +10 -17
  384. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  385. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  386. package/src/cli/commands/__tests__/security.test.ts +21 -24
  387. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  388. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  389. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  390. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  391. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  392. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  393. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  394. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  395. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  396. package/src/cli/commands/acceptance.ts +24 -29
  397. package/src/cli/commands/check.ts +40 -17
  398. package/src/cli/commands/command.ts +9 -8
  399. package/src/cli/commands/constraints-retire.ts +57 -70
  400. package/src/cli/commands/contract.ts +44 -47
  401. package/src/cli/commands/definitions.ts +12 -6
  402. package/src/cli/commands/failure.ts +16 -3
  403. package/src/cli/commands/init.ts +242 -389
  404. package/src/cli/commands/knowledge-view.ts +224 -0
  405. package/src/cli/commands/knowledge.ts +643 -448
  406. package/src/cli/commands/passes-gate.ts +12 -3
  407. package/src/cli/commands/performance.ts +31 -38
  408. package/src/cli/commands/report.ts +3 -2
  409. package/src/cli/commands/review.ts +22 -28
  410. package/src/cli/commands/scaffold-templates.ts +406 -0
  411. package/src/cli/commands/scaffold.ts +302 -0
  412. package/src/cli/commands/security.ts +27 -23
  413. package/src/cli/commands/spec-baseline-check.ts +158 -33
  414. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  415. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  416. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  417. package/src/cli/commands/sync-docs/index.ts +103 -28
  418. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  419. package/src/cli/commands/validate.ts +1 -104
  420. package/src/cli/gate-command.ts +87 -0
  421. package/src/completion-checkers/index.ts +10 -1
  422. package/src/context/CONTEXT.md +2 -1
  423. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  424. package/src/context/__tests__/session-manager.test.ts +19 -12
  425. package/src/context/knowledge-injector.ts +14 -7
  426. package/src/core/CONTEXT.md +10 -7
  427. package/src/core/__tests__/effective-set.test.ts +36 -60
  428. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  429. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  430. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  431. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  432. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  433. package/src/core/constraints/check-cache.ts +1 -1
  434. package/src/core/constraints/checker.ts +30 -14
  435. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  436. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  437. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  438. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  439. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  440. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  441. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  442. package/src/core/constraints/checkers/types.ts +14 -8
  443. package/src/core/constraints/context-builder.ts +35 -26
  444. package/src/core/constraints/context-reconcile.ts +200 -0
  445. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  446. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  447. package/src/core/constraints/git-evidence.ts +1 -1
  448. package/src/core/constraints/injection-drift.ts +19 -8
  449. package/src/core/constraints/run-env.ts +207 -0
  450. package/src/core/constraints/triggers.ts +33 -0
  451. package/src/core/effective-constraints.ts +17 -6
  452. package/src/core/effective-set.ts +8 -24
  453. package/src/core/index.ts +2 -0
  454. package/src/core/project-config-loader.ts +54 -102
  455. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  456. package/src/core/validators/check-handlers/http.ts +18 -7
  457. package/src/failure/CONTEXT.md +2 -1
  458. package/src/failure/classifier.ts +2 -2
  459. package/src/failure/index.ts +14 -2
  460. package/src/failure/recorder.ts +1 -1
  461. package/src/gates/CONTEXT.md +13 -7
  462. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  463. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  464. package/src/gates/__tests__/command.test.ts +8 -15
  465. package/src/gates/__tests__/contract.test.ts +5 -5
  466. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  467. package/src/gates/__tests__/performance.test.ts +0 -13
  468. package/src/gates/__tests__/review.test.ts +0 -1
  469. package/src/gates/__tests__/runner.test.ts +1 -1
  470. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  471. package/src/gates/__tests__/security.test.ts +0 -13
  472. package/src/gates/checker-gate.ts +3 -2
  473. package/src/gates/command.ts +51 -74
  474. package/src/gates/contract.ts +0 -5
  475. package/src/gates/definitions.ts +6 -5
  476. package/src/gates/index.ts +0 -5
  477. package/src/gates/performance.ts +1 -6
  478. package/src/gates/runner.ts +1 -1
  479. package/src/gates/security.ts +9 -11
  480. package/src/gates/types.ts +20 -23
  481. package/src/hooks/CONTEXT.md +10 -9
  482. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  483. package/src/hooks/__tests__/pipeline.test.ts +28 -17
  484. package/src/hooks/__tests__/registry.test.ts +51 -3
  485. package/src/hooks/bootstrap.ts +35 -15
  486. package/src/hooks/config.ts +6 -2
  487. package/src/hooks/index.ts +10 -1
  488. package/src/hooks/pipeline.ts +4 -6
  489. package/src/hooks/registry.ts +31 -12
  490. package/src/hooks/types.ts +24 -7
  491. package/src/index.ts +1 -2
  492. package/src/knowledge/CONTEXT.md +14 -7
  493. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  494. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  495. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  496. package/src/knowledge/__tests__/audit.test.ts +291 -208
  497. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  498. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  499. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  500. package/src/knowledge/__tests__/migration.test.ts +35 -6
  501. package/src/knowledge/__tests__/query.test.ts +3 -2
  502. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  503. package/src/knowledge/__tests__/store.test.ts +149 -4
  504. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  505. package/src/knowledge/audit-scoring.ts +589 -0
  506. package/src/knowledge/audit.ts +55 -599
  507. package/src/knowledge/import.ts +2 -0
  508. package/src/knowledge/index-generator.ts +4 -6
  509. package/src/knowledge/index.ts +27 -2
  510. package/src/knowledge/ingest.ts +1 -1
  511. package/src/knowledge/lifecycle.ts +5 -2
  512. package/src/knowledge/migration.ts +8 -2
  513. package/src/knowledge/query.ts +8 -1
  514. package/src/knowledge/reference-tracker.ts +5 -7
  515. package/src/knowledge/store.ts +75 -16
  516. package/src/knowledge/tree-walker.ts +29 -0
  517. package/src/knowledge/types.ts +19 -0
  518. package/src/monitoring/CONTEXT.md +5 -2
  519. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  520. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  521. package/src/monitoring/traces.ts +19 -5
  522. package/src/release/CONTEXT.md +1 -1
  523. package/src/release/__tests__/integrity.test.ts +21 -12
  524. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  525. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  526. package/src/tools/index.ts +5 -1
  527. package/src/types/checkpoint.ts +6 -0
  528. package/src/types/project-config.ts +21 -0
  529. package/src/types/trace.ts +14 -4
  530. package/src/utils/__tests__/file-walk.test.ts +3 -2
  531. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  532. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  533. package/src/utils/__tests__/jsonl.test.ts +61 -14
  534. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  535. package/src/utils/__tests__/package-version.test.ts +2 -2
  536. package/src/utils/exec.ts +4 -64
  537. package/src/utils/jsonl.ts +159 -20
  538. package/src/utils/numeric-flag.ts +64 -0
  539. package/dist/failure/types.d.ts +0 -9
  540. package/dist/failure/types.d.ts.map +0 -1
  541. package/dist/failure/types.js +0 -25
  542. package/dist/failure/types.js.map +0 -1
  543. package/dist/gates/effective-gates.d.ts +0 -30
  544. package/dist/gates/effective-gates.d.ts.map +0 -1
  545. package/dist/gates/effective-gates.js +0 -81
  546. package/dist/gates/effective-gates.js.map +0 -1
  547. package/src/failure/types.ts +0 -9
  548. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  549. package/src/gates/effective-gates.ts +0 -99
@@ -1,50 +1,34 @@
1
1
  /**
2
2
  * 生效集共享筛选器(ADR-0001 收口)
3
3
  *
4
- * 「config.yml 引用了哪些 id、哪些被禁用」的判定,约束侧与门禁侧共用一份:
5
- * - 约束侧(ProjectConfigLoader.mergeConstraints):未知 id 静默忽略,
6
- * 收集进结果 unknownIds 供诊断(历史配置可能存在已移除约束的禁用残留)
7
- * - 门禁侧(getEffectiveGates):未知 id 抛错(闭环校验;gates 配置段为
8
- * 新增面,不存在历史残留配置)
4
+ * 「config.yml 引用了哪些 id、哪些被 enabled:false 裁剪」的判定唯一实现。
5
+ * 未知 id 静默忽略并收集进结果 `unknownIds` 供诊断——历史配置可能残留已移除条目的禁用键,
6
+ * 故不能抛错。唯一生产调用方:`ProjectConfigLoader.mergeConstraints`。
9
7
  *
10
- * 行为差异仅收敛为 onUnknownId 一个开关,筛选语义两侧一致。
8
+ * 去向指引:本函数曾带一个按语义分的 `'throw'` 模式(引用未注册即抛错),其唯一生产调用方
9
+ * 消失后已按 ADR-0022 判据删除——背景见 ADR-0002 文末「后续变更」,票 harness#141。
11
10
  */
12
11
 
13
- export interface FilterEnabledEntriesOptions {
14
- /** 未知 id 处理:'collect' 收集进结果 unknownIds;'throw' 立即抛错 */
15
- onUnknownId: 'collect' | 'throw';
16
-
17
- /** onUnknownId='throw' 时的错误消息定制(如附带可用 id 清单) */
18
- unknownIdError?: (id: string) => string;
19
- }
20
-
21
12
  export interface FilterEnabledEntriesResult {
22
13
  /** enabled === false 的 id(未知 id 若带 enabled:false 同样计入,与历史行为一致) */
23
14
  disabledIds: string[];
24
15
 
25
- /** 未知 id(仅 onUnknownId='collect' 时非空) */
16
+ /** config 条目里未注册的 id,供调用方诊断 */
26
17
  unknownIds: string[];
27
18
  }
28
19
 
29
20
  /**
30
- * 按 config 条目筛选:未知 id onUnknownId 处理,enabled:false 收进 disabledIds
21
+ * 按 config 条目筛选:未知 id 收集进 unknownIds,enabled:false 收进 disabledIds
31
22
  */
32
23
  export function filterEnabledEntries(
33
24
  knownIds: ReadonlySet<string>,
34
- entries: Record<string, { enabled?: boolean } | undefined>,
35
- options: FilterEnabledEntriesOptions
25
+ entries: Record<string, { enabled?: boolean } | undefined>
36
26
  ): FilterEnabledEntriesResult {
37
27
  const disabledIds: string[] = [];
38
28
  const unknownIds: string[] = [];
39
29
 
40
30
  for (const [id, entry] of Object.entries(entries)) {
41
31
  if (!knownIds.has(id)) {
42
- if (options.onUnknownId === 'throw') {
43
- throw new Error(
44
- options.unknownIdError?.(id) ??
45
- `[harness] 配置引用了未注册的 id "${id}"。`
46
- );
47
- }
48
32
  unknownIds.push(id);
49
33
  }
50
34
  if (entry?.enabled === false) {
package/src/core/index.ts CHANGED
@@ -81,6 +81,8 @@ export {
81
81
  getCapabilitiesMode,
82
82
  } from './project-config-loader';
83
83
  export type { CapabilitiesMode, ContextFilesResolution } from './project-config-loader';
84
+ // 运行级观察面(ADR-0023 决策 1/2):上面两组访问器与 ProjectConfigLoader 的入参形状
85
+ export type { RunEnv, RunTarget } from './constraints/run-env';
84
86
 
85
87
  // 生效约束集(ADR-0001:唯一生效集来源)
86
88
  export {
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * 项目配置加载器
3
3
  *
4
- * 加载 .harness/config.yml 和 .harness/custom-constraints.yml
5
- * 合并内置约束和项目自定义约束
4
+ * 解读 `.harness/config.yml` 与自定义约束文件、合并内置与项目自定义约束。
5
+ * 本模块不碰文件系统:两份配置的读取口径 = 运行级观察面(`constraints/run-env.ts`,
6
+ * ADR-0023 决策 2),一次运行内各至多读一次。
6
7
  */
7
8
 
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
- import * as yaml from 'js-yaml';
11
9
  import type { Constraint, ConstraintTrigger } from '../types/constraint';
12
10
  import type {
13
11
  ProjectConfig,
@@ -19,6 +17,7 @@ import type {
19
17
  import { IRON_LAWS, GUIDELINES, PROMPTS } from './constraints/definitions';
20
18
  import { PRESETS_BY_NAME, STANDARD_PRESET } from '../presets';
21
19
  import { filterEnabledEntries } from './effective-set';
20
+ import { resolveRunEnv, type RunEnv, type RunTarget } from './constraints/run-env';
22
21
 
23
22
  /**
24
23
  * 默认配置
@@ -29,48 +28,17 @@ const DEFAULT_CONFIG: ProjectConfig = {
29
28
  };
30
29
 
31
30
  /**
32
- * config.yml 原始解析结果的进程级缓存(工单 16)
31
+ * 读取并解析 `.harness/config.yml`
33
32
  *
34
- * 键为解析后的项目路径,条目带 mtimeMs+size 指纹:文件未变则直接复用,
35
- * 避免单次 harness check 内多处读取者重复 yaml 解析;文件变更自动失效。
36
- */
37
- const rawConfigCache = new Map<string, { mtimeMs: number; size: number; raw: Record<string, unknown> }>();
38
-
39
- /**
40
- * 读取并解析 .harness/config.yml(进程级 memoize)
33
+ * 实面 = `RunEnv.rawConfig()`(ADR-0023 决策 2),本函数只是它的「只此一次」形状:
34
+ * 传项目根字符串即自造一枚用完即弃的观察面(每次调用都读当下内容),
35
+ * 传调用方那枚 RunEnv 则与本 run 的其余消费方共用同一份读取。
36
+ * 原进程级 `rawConfigCache` + mtime/size 指纹已撤销——见 ADR-0023 决策 2 与工单 16 的撤销记录。
41
37
  *
42
38
  * 文件不存在时返回 undefined;解析失败向上抛出(由各调用方自行兜底)。
43
39
  */
44
- export function loadRawProjectConfig(projectPath: string): Record<string, unknown> | undefined {
45
- const key = path.resolve(projectPath);
46
- const configPath = path.join(key, '.harness', 'config.yml');
47
-
48
- let stat: fs.Stats | undefined;
49
- try {
50
- stat = fs.statSync(configPath);
51
- } catch {
52
- stat = undefined;
53
- }
54
- // 文件缺失,或 stat 不可用(如测试 mock)且文件不存在 → 无配置
55
- if (!stat?.mtimeMs && !fs.existsSync(configPath)) {
56
- rawConfigCache.delete(key);
57
- return undefined;
58
- }
59
-
60
- // stat 指纹可用时走缓存;不可用则每次直读(不缓存,避免脏数据)
61
- if (stat?.mtimeMs !== undefined) {
62
- const cached = rawConfigCache.get(key);
63
- if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
64
- return cached.raw;
65
- }
66
- }
67
-
68
- const loaded = yaml.load(fs.readFileSync(configPath, 'utf-8')) ?? {};
69
- const raw = loaded as Record<string, unknown>;
70
- if (stat?.mtimeMs !== undefined) {
71
- rawConfigCache.set(key, { mtimeMs: stat.mtimeMs, size: stat.size, raw });
72
- }
73
- return raw;
40
+ export function loadRawProjectConfig(target: RunTarget): Record<string, unknown> | undefined {
41
+ return resolveRunEnv(target).rawConfig();
74
42
  }
75
43
 
76
44
  /**
@@ -81,12 +49,12 @@ export type CapabilitiesMode = NonNullable<CapabilitiesConfig['mode']>;
81
49
  /**
82
50
  * 读取 governance 段(全仓唯一手写钻取点,工单 84)
83
51
  *
84
- * 基于 loadRawProjectConfig 的进程级 memoize,不引入第二次 yaml 解析。
85
52
  * 配置缺失 / 解析失败 / 形状不符时返回 undefined,由调用方按未配置处理。
53
+ * 入参形状见 loadRawProjectConfig:传 RunEnv 即与本 run 其余消费方共用同一份 config.yml 读取。
86
54
  */
87
- export function getGovernanceConfig(projectPath: string): GovernanceConfig | undefined {
55
+ export function getGovernanceConfig(target: RunTarget): GovernanceConfig | undefined {
88
56
  try {
89
- const raw = loadRawProjectConfig(projectPath);
57
+ const raw = loadRawProjectConfig(target);
90
58
  const governance = raw?.governance;
91
59
  if (governance === null || typeof governance !== 'object') return undefined;
92
60
  return governance as GovernanceConfig;
@@ -115,8 +83,8 @@ export type ContextFilesResolution =
115
83
  * init / 扫描类工具流的自动探测回落保留在调用方,不进本访问器。
116
84
  * 元素类型在此收口:脏配置(如 required_dirs: [1])不得流入调用方 path.join。
117
85
  */
118
- export function resolveContextFiles(projectPath: string): ContextFilesResolution {
119
- const contextFiles = getGovernanceConfig(projectPath)?.context_files;
86
+ export function resolveContextFiles(target: RunTarget): ContextFilesResolution {
87
+ const contextFiles = getGovernanceConfig(target)?.context_files;
120
88
  if (!contextFiles?.enabled) return { state: 'unconfigured' };
121
89
  const dirs = contextFiles.required_dirs;
122
90
  if (!Array.isArray(dirs) || dirs.length === 0) return { state: 'enabled-empty' };
@@ -129,22 +97,47 @@ export function resolveContextFiles(projectPath: string): ContextFilesResolution
129
97
  *
130
98
  * 配置缺失/解析失败/取值非法时一律回落 'file'。
131
99
  */
132
- export function getCapabilitiesMode(projectPath: string): CapabilitiesMode {
133
- const mode = getGovernanceConfig(projectPath)?.capabilities?.mode;
100
+ export function getCapabilitiesMode(target: RunTarget): CapabilitiesMode {
101
+ const mode = getGovernanceConfig(target)?.capabilities?.mode;
134
102
  if (mode === 'file' || mode === 'module' || mode === 'listing') return mode;
135
103
  return 'file';
136
104
  }
137
105
 
106
+ /**
107
+ * 退役判定单点(studio#82 D6 一处真相)
108
+ *
109
+ * 两个落点合成一条规则:禁用(config.yml `constraints.<id>.enabled: false`,内置约束的
110
+ * 退役落点)或 custom 条目自带 `retired` 元数据(custom 约束的退役落点,不拆 config.yml
111
+ * 第二处)。`mergeConstraints` 的 custom 追加跳过与 `harness constraints retire` 的
112
+ * already_retired 检查共用本函数——后者此前是这条规则的手写复印(harness#137 收口)。
113
+ *
114
+ * `disabled` 由调用方给:mergeConstraints 传的是含 preset 裁剪的完整禁用集,
115
+ * retire 传的是 config.yml 单点(退役落点只有这一处,preset 语义不掺进退役判定)。
116
+ * `retired` 按 truthiness 判,与 mergeConstraints 历史口径逐字一致。
117
+ */
118
+ export function isConstraintRetired(
119
+ customDef: { retired?: unknown } | undefined,
120
+ disabled: boolean
121
+ ): boolean {
122
+ return disabled || Boolean(customDef?.retired);
123
+ }
124
+
138
125
  /**
139
126
  * 项目配置加载器
140
127
  */
141
128
  export class ProjectConfigLoader {
129
+ private env: RunEnv;
142
130
  private projectPath: string;
143
131
  private config: ProjectConfig;
144
132
  private customConstraints: Record<string, CustomConstraintDefinition>;
145
133
 
146
- constructor(projectPath?: string) {
147
- this.projectPath = projectPath || process.cwd();
134
+ /**
135
+ * @param target 项目根路径,或本 run 已构造的运行级观察面(ADR-0023 决策 2)
136
+ * 传观察面时 config.yml 与本 run 其余消费方共用同一份读取;传路径 = 自造一枚一次性观察面
137
+ */
138
+ constructor(target?: RunTarget) {
139
+ this.env = resolveRunEnv(target);
140
+ this.projectPath = this.env.projectPath;
148
141
  this.config = { ...DEFAULT_CONFIG };
149
142
  this.customConstraints = {};
150
143
  }
@@ -153,8 +146,8 @@ export class ProjectConfigLoader {
153
146
  * 加载项目配置
154
147
  */
155
148
  load(): ProjectConfig {
156
- // 1. 加载主配置(经进程级 memoize,避免重复 yaml 解析)
157
- const raw = loadRawProjectConfig(this.projectPath);
149
+ // 1. 加载主配置(run memo 在观察面上,同一份解析结果供全部消费方共用)
150
+ const raw = this.env.rawConfig();
158
151
  if (raw) {
159
152
  this.config = { ...DEFAULT_CONFIG, ...(raw as Partial<ProjectConfig>) };
160
153
  }
@@ -167,24 +160,15 @@ export class ProjectConfigLoader {
167
160
 
168
161
  /**
169
162
  * 加载自定义约束
163
+ *
164
+ * 文件读取经观察面(ADR-0023 决策 2):同一次运行内多个加载器共用同一份内容,
165
+ * 「哪个文件名算数」与合并顺序仍由本函数决定(方式 1 文件 → 方式 2 主配置内联,后者覆盖)。
170
166
  */
171
167
  private loadCustomConstraints(): void {
172
168
  // 方式 1:从单独文件加载
173
169
  if (this.config.custom_constraints_file) {
174
- const customPath = path.join(
175
- this.projectPath,
176
- '.harness',
177
- this.config.custom_constraints_file
178
- );
179
- if (fs.existsSync(customPath)) {
180
- const content = fs.readFileSync(customPath, 'utf-8');
181
- const loaded = yaml.load(content) as {
182
- custom_constraints?: Record<string, CustomConstraintDefinition>;
183
- };
184
- if (loaded.custom_constraints) {
185
- this.customConstraints = { ...this.customConstraints, ...loaded.custom_constraints };
186
- }
187
- }
170
+ const defs = this.env.customConstraints(this.config.custom_constraints_file);
171
+ this.customConstraints = { ...this.customConstraints, ...defs };
188
172
  }
189
173
 
190
174
  // 方式 2:从主配置文件中加载
@@ -253,9 +237,7 @@ export class ProjectConfigLoader {
253
237
  ...Object.keys(PROMPTS),
254
238
  ...Object.keys(this.customConstraints),
255
239
  ]);
256
- const filtered = filterEnabledEntries(knownIds, this.config.constraints, {
257
- onUnknownId: 'collect',
258
- });
240
+ const filtered = filterEnabledEntries(knownIds, this.config.constraints);
259
241
  unknownIds.push(...filtered.unknownIds);
260
242
  for (const constraintId of filtered.disabledIds) {
261
243
  result.disabled.push(constraintId);
@@ -270,7 +252,7 @@ export class ProjectConfigLoader {
270
252
  // 收集进 disabled,custom 在 step 1 时尚未入桶,须在此兜底跳过;
271
253
  // 条目带 retired 元数据的同样不追加——studio#82 D6 退役落点在条目自身)
272
254
  for (const [id, customDef] of Object.entries(this.customConstraints)) {
273
- if (result.disabled.includes(id) || customDef.retired) {
255
+ if (isConstraintRetired(customDef, result.disabled.includes(id))) {
274
256
  continue;
275
257
  }
276
258
  const constraint = this.toConstraint(customDef, id);
@@ -345,36 +327,6 @@ export class ProjectConfigLoader {
345
327
  return this.customConstraints;
346
328
  }
347
329
 
348
- /**
349
- * 检查约束是否启用
350
- */
351
- isConstraintEnabled(constraintId: string): boolean {
352
- // 1. 检查是否在禁用列表
353
- if (this.config.constraints?.[constraintId]?.enabled === false) {
354
- return false;
355
- }
356
-
357
- // 2. 检查自定义约束是否禁用
358
- if (this.customConstraints[constraintId]?.enabled === false) {
359
- return false;
360
- }
361
-
362
- return true;
363
- }
364
-
365
- /**
366
- * 获取约束来源
367
- */
368
- getConstraintSource(constraintId: string): 'built-in' | 'custom' | 'disabled' {
369
- if (this.config.constraints?.[constraintId]?.enabled === false) {
370
- return 'disabled';
371
- }
372
- if (this.customConstraints[constraintId]) {
373
- return 'custom';
374
- }
375
- return 'built-in';
376
- }
377
-
378
330
  /**
379
331
  * 检查是否有自定义配置
380
332
  */
@@ -0,0 +1,231 @@
1
+ /**
2
+ * http 检查项的重试退避等待注入(工单 #136)
3
+ *
4
+ * 退避等待经 CheckpointContext.sleep 注入后,重试语义(尝试次数、退避序列)由记录
5
+ * 替身钉住,不再靠真 sleep 证明;未注入的缺省路径同时钉住「走真定时器、序列不变」。
6
+ */
7
+
8
+ import { describe, it, expect, afterEach, jest } from '@jest/globals';
9
+ import { CheckpointValidator } from '../checkpoint';
10
+ import { checkHttpBody, checkHttpStatus } from '../check-handlers/http';
11
+ import type { CheckConfig, CheckType } from '../../../types/checkpoint';
12
+ import type { CheckpointCheck, CheckpointContext } from '../../../types/checkpoint';
13
+
14
+ const realFetch = globalThis.fetch;
15
+
16
+ afterEach(() => {
17
+ globalThis.fetch = realFetch;
18
+ });
19
+
20
+ function makeContext(overrides: Partial<CheckpointContext> = {}): CheckpointContext {
21
+ return { projectPath: process.cwd(), workdir: process.cwd(), ...overrides };
22
+ }
23
+
24
+ function makeCheck(id: string, type: CheckType, config: CheckConfig): CheckpointCheck {
25
+ return { id, type, config };
26
+ }
27
+
28
+ type InjectedSleep = NonNullable<CheckpointContext['sleep']>;
29
+
30
+ /** 记录型零等待替身:记下每次退避请求的毫秒数 */
31
+ function recordingSleep(): { waits: number[]; sleep: InjectedSleep } {
32
+ const waits: number[] = [];
33
+ const sleep: InjectedSleep = async (ms: number) => {
34
+ waits.push(ms);
35
+ };
36
+ return { waits, sleep };
37
+ }
38
+
39
+ /** 按脚本依次出牌的 fetch 替身:条目耗尽后重复最后一张;返回调用记录 */
40
+ function scriptedFetch(script: Array<number | Error>): string[] {
41
+ const calls: string[] = [];
42
+ globalThis.fetch = (async (url: string | URL) => {
43
+ calls.push(String(url));
44
+ const next = script[Math.min(calls.length - 1, script.length - 1)];
45
+ if (next instanceof Error) throw next;
46
+ return new Response(null, { status: next });
47
+ }) as unknown as typeof fetch;
48
+ return calls;
49
+ }
50
+
51
+ const URL_OK = 'https://example.com';
52
+
53
+ describe('http_status 重试退避(注入 sleep)', () => {
54
+ it('fetch 持续抛错:尝试 4 次、退避 2000/4000/6000、全程零真等待', async () => {
55
+ const { waits, sleep } = recordingSleep();
56
+ const calls = scriptedFetch([new Error('Network error')]);
57
+
58
+ const startedAt = Date.now();
59
+ const result = await checkHttpStatus(
60
+ makeCheck('c-reject', 'http_status', { url: URL_OK, expectedStatus: 200 }),
61
+ makeContext({ sleep })
62
+ );
63
+ const elapsed = Date.now() - startedAt;
64
+
65
+ expect(elapsed).toBeLessThan(2000);
66
+ expect(calls).toHaveLength(4);
67
+ expect(waits).toEqual([2000, 4000, 6000]);
68
+ expect(result.passed).toBe(false);
69
+ expect(result.message).toContain('请求失败');
70
+ });
71
+
72
+ it('5xx 后恢复:尝试 2 次、只退避一次 2000', async () => {
73
+ const { waits, sleep } = recordingSleep();
74
+ const calls = scriptedFetch([503, 200]);
75
+
76
+ const result = await checkHttpStatus(
77
+ makeCheck('c-503', 'http_status', { url: URL_OK, expectedStatus: 200 }),
78
+ makeContext({ sleep })
79
+ );
80
+
81
+ expect(calls).toHaveLength(2);
82
+ expect(waits).toEqual([2000]);
83
+ expect(result.passed).toBe(true);
84
+ });
85
+
86
+ it('429 后恢复:尝试 2 次、只退避一次 2000', async () => {
87
+ const { waits, sleep } = recordingSleep();
88
+ const calls = scriptedFetch([429, 200]);
89
+
90
+ const result = await checkHttpStatus(
91
+ makeCheck('c-429', 'http_status', { url: URL_OK, expectedStatus: 200 }),
92
+ makeContext({ sleep })
93
+ );
94
+
95
+ expect(calls).toHaveLength(2);
96
+ expect(waits).toEqual([2000]);
97
+ expect(result.passed).toBe(true);
98
+ });
99
+
100
+ it('5xx 不恢复:退避用尽后返回最后响应,判状态码不匹配', async () => {
101
+ const { waits, sleep } = recordingSleep();
102
+ const calls = scriptedFetch([500]);
103
+
104
+ const result = await checkHttpStatus(
105
+ makeCheck('c-500', 'http_status', { url: URL_OK, expectedStatus: 200 }),
106
+ makeContext({ sleep })
107
+ );
108
+
109
+ expect(calls).toHaveLength(4);
110
+ expect(waits).toEqual([2000, 4000, 6000]);
111
+ expect(result.passed).toBe(false);
112
+ expect(result.actual).toBe(500);
113
+ expect(result.message).toContain('不匹配');
114
+ });
115
+
116
+ it('404 非可重试状态码:只尝试 1 次、零退避', async () => {
117
+ const { waits, sleep } = recordingSleep();
118
+ const calls = scriptedFetch([404]);
119
+
120
+ const result = await checkHttpStatus(
121
+ makeCheck('c-404', 'http_status', { url: URL_OK, expectedStatus: 200 }),
122
+ makeContext({ sleep })
123
+ );
124
+
125
+ expect(calls).toHaveLength(1);
126
+ expect(waits).toEqual([]);
127
+ expect(result.passed).toBe(false);
128
+ expect(result.message).toContain('不匹配');
129
+ });
130
+
131
+ it('首次即成功:不产生任何退避', async () => {
132
+ const { waits, sleep } = recordingSleep();
133
+ const calls = scriptedFetch([200]);
134
+
135
+ const result = await checkHttpStatus(
136
+ makeCheck('c-200', 'http_status', { url: URL_OK, expectedStatus: 200 }),
137
+ makeContext({ sleep })
138
+ );
139
+
140
+ expect(calls).toHaveLength(1);
141
+ expect(waits).toEqual([]);
142
+ expect(result.passed).toBe(true);
143
+ });
144
+ });
145
+
146
+ describe('http_body 重试退避(注入 sleep)', () => {
147
+ it('fetch 持续抛错:尝试 4 次、退避 2000/4000/6000、全程零真等待', async () => {
148
+ const { waits, sleep } = recordingSleep();
149
+ const calls = scriptedFetch([new Error('Network error')]);
150
+
151
+ const startedAt = Date.now();
152
+ const result = await checkHttpBody(
153
+ makeCheck('c-body-reject', 'http_body', { url: URL_OK, expected: 'test' }),
154
+ makeContext({ sleep })
155
+ );
156
+ const elapsed = Date.now() - startedAt;
157
+
158
+ expect(elapsed).toBeLessThan(2000);
159
+ expect(calls).toHaveLength(4);
160
+ expect(waits).toEqual([2000, 4000, 6000]);
161
+ expect(result.passed).toBe(false);
162
+ expect(result.message).toContain('请求失败');
163
+ });
164
+
165
+ it('响应体不含期望值:不重试、只尝试 1 次', async () => {
166
+ const { waits, sleep } = recordingSleep();
167
+ globalThis.fetch = (async () => new Response('{"data": "test"}', { status: 200 })) as unknown as typeof fetch;
168
+
169
+ const result = await checkHttpBody(
170
+ makeCheck('c-body-miss', 'http_body', { url: URL_OK, expected: 'NONEXISTENT' }),
171
+ makeContext({ sleep })
172
+ );
173
+
174
+ expect(waits).toEqual([]);
175
+ expect(result.passed).toBe(false);
176
+ expect(result.message).toContain('不包含');
177
+ });
178
+ });
179
+
180
+ describe('未注入 sleep 的缺省路径(生产行为逐字不变)', () => {
181
+ it('退避仍走真 setTimeout,且序列为 2000/4000/6000', async () => {
182
+ // 替身把 setTimeout 改成同步触发:缺省路径的「真定时器 + 退避毫秒数」一并钉住且不真等
183
+ // (AbortSignal.timeout 走 Node 内部计时器、不经 globalThis.setTimeout,实测收不到干扰)
184
+ const delays: number[] = [];
185
+ const spy = jest.spyOn(globalThis, 'setTimeout').mockImplementation((
186
+ (callback: (...args: unknown[]) => void, ms?: number) => {
187
+ delays.push(ms ?? 0);
188
+ callback();
189
+ return 0 as unknown as ReturnType<typeof setTimeout>;
190
+ }
191
+ ) as unknown as typeof setTimeout);
192
+
193
+ try {
194
+ const calls = scriptedFetch([new Error('Network error')]);
195
+ const startedAt = Date.now();
196
+ const result = await checkHttpStatus(
197
+ makeCheck('c-default', 'http_status', { url: URL_OK, expectedStatus: 200 }),
198
+ makeContext()
199
+ );
200
+ const elapsed = Date.now() - startedAt;
201
+
202
+ expect(calls).toHaveLength(4);
203
+ expect(delays).toEqual([2000, 4000, 6000]);
204
+ expect(elapsed).toBeLessThan(2000);
205
+ expect(result.passed).toBe(false);
206
+ expect(result.message).toContain('请求失败');
207
+ } finally {
208
+ spy.mockRestore();
209
+ }
210
+ });
211
+ });
212
+
213
+ describe('CheckpointValidator → http handler 的 context 接线', () => {
214
+ it('validate() 传入的 sleep 抵达 handler:重试全程零真等待', async () => {
215
+ const { waits, sleep } = recordingSleep();
216
+ const calls = scriptedFetch([new Error('Network error')]);
217
+
218
+ const result = await CheckpointValidator.getInstance().validate(
219
+ {
220
+ id: 'cp-wiring',
221
+ checks: [makeCheck('c-wiring', 'http_status', { url: URL_OK, expectedStatus: 200 })],
222
+ },
223
+ makeContext({ sleep })
224
+ );
225
+
226
+ expect(calls).toHaveLength(4);
227
+ expect(waits).toEqual([2000, 4000, 6000]);
228
+ expect(result.passed).toBe(false);
229
+ expect(result.checks[0].message).toContain('请求失败');
230
+ });
231
+ });
@@ -4,34 +4,45 @@
4
4
 
5
5
  import type { CheckpointCheck, CheckResult, CheckpointContext } from '../../../types/checkpoint';
6
6
 
7
+ /**
8
+ * 缺省退避等待:真定时器,生产行为
9
+ */
10
+ const realSleep = (ms: number): Promise<void> => new Promise(r => setTimeout(r, ms));
11
+
7
12
  /**
8
13
  * HTTP 调用重试(处理 httpbin rate-limit 503/429)
14
+ * 等待经 sleep 注入(工单 #136):缺省真定时器,测试注入零等待替身
9
15
  */
10
- async function fetchWithRetry(url: string, init?: RequestInit, retries = 3): Promise<Response> {
16
+ async function fetchWithRetry(
17
+ url: string,
18
+ init?: RequestInit,
19
+ sleep: (ms: number) => Promise<void> = realSleep,
20
+ retries = 3
21
+ ): Promise<Response> {
11
22
  let lastError: unknown;
12
23
  for (let i = 0; i <= retries; i++) {
13
24
  try {
14
25
  const response = await fetch(url, init);
15
26
  // 5xx / 429 → 重试,指数退避
16
27
  if (!response.ok && i < retries && (response.status >= 500 || response.status === 429)) {
17
- await new Promise(r => setTimeout(r, 2000 * (i + 1)));
28
+ await sleep(2000 * (i + 1));
18
29
  continue;
19
30
  }
20
31
  return response;
21
32
  } catch (e) {
22
33
  lastError = e;
23
- if (i < retries) await new Promise(r => setTimeout(r, 2000 * (i + 1)));
34
+ if (i < retries) await sleep(2000 * (i + 1));
24
35
  }
25
36
  }
26
37
  throw lastError;
27
38
  }
28
39
 
29
- export async function checkHttpStatus(check: CheckpointCheck, _context: CheckpointContext): Promise<CheckResult> {
40
+ export async function checkHttpStatus(check: CheckpointCheck, context: CheckpointContext): Promise<CheckResult> {
30
41
  const url = check.config.url || '';
31
42
  const expected = check.config.expectedStatus || check.config.expected || 200;
32
43
 
33
44
  try {
34
- const response = await fetchWithRetry(url, { signal: AbortSignal.timeout(10000) });
45
+ const response = await fetchWithRetry(url, { signal: AbortSignal.timeout(10000) }, context.sleep);
35
46
  const actual = response.status;
36
47
  const matches = actual === expected;
37
48
 
@@ -54,7 +65,7 @@ export async function checkHttpStatus(check: CheckpointCheck, _context: Checkpoi
54
65
  }
55
66
  }
56
67
 
57
- export async function checkHttpBody(check: CheckpointCheck, _context: CheckpointContext): Promise<CheckResult> {
68
+ export async function checkHttpBody(check: CheckpointCheck, context: CheckpointContext): Promise<CheckResult> {
58
69
  const url = check.config.url || '';
59
70
  const expected = String(check.config.expected || '');
60
71
 
@@ -64,7 +75,7 @@ export async function checkHttpBody(check: CheckpointCheck, _context: Checkpoint
64
75
  headers: check.config.headers,
65
76
  body: check.config.body ? JSON.stringify(check.config.body) : undefined,
66
77
  signal: AbortSignal.timeout(10000),
67
- });
78
+ }, context.sleep);
68
79
  const actual = await response.text();
69
80
  const contains = actual.includes(expected);
70
81
 
@@ -6,7 +6,8 @@
6
6
  ## 核心导出
7
7
  - `ErrorClassifier` — 错误分类器(可配置规则)
8
8
  - `FailureRecorder` — 失败记录器(文件存储)
9
- - `types.ts` 错误分类/记录类型定义
9
+ - `index.ts` 直连类型正本 `src/types/failure.ts`(ErrorType/FailureLevel/FailureRecord/分类规则数据)
10
+ - barrel 公开面的其余出口:`createErrorClassifier(config?)`、`classifyError(error)`(免建实例的快速分类,取 `ErrorType`)、`getFailureLevel(type)`(查 `DEFAULT_LEVEL_MAPPING`)、`createFailureRecorder(config)`(注意 config 必填,与 classifier 工厂不对称);数据常量 `DEFAULT_FAILURE_LOG_FILE`(`.harness/logs/failures.log`)、`DEFAULT_CLASSIFICATION_RULES`、`DEFAULT_LEVEL_MAPPING` 正本同在 `src/types/failure.ts`
10
11
 
11
12
  ## 依赖关系
12
13
  - 依赖 `src/types/` 错误分类/记录类型
@@ -9,13 +9,13 @@ import type {
9
9
  FailureLevel,
10
10
  ErrorClassificationRule,
11
11
  ClassificationResult,
12
- } from './types';
12
+ } from '../types/failure';
13
13
  import {
14
14
  ErrorType as ErrorTypeEnum,
15
15
  FailureLevel as FailureLevelEnum,
16
16
  DEFAULT_CLASSIFICATION_RULES,
17
17
  DEFAULT_LEVEL_MAPPING,
18
- } from './types';
18
+ } from '../types/failure';
19
19
 
20
20
  /**
21
21
  * 错误分类器配置
@@ -5,8 +5,20 @@
5
5
  * 不包含业务逻辑
6
6
  */
7
7
 
8
- // 类型
9
- export * from './types';
8
+ // 类型与分类数据(正本 src/types/failure.ts;harness#137 删掉 ./types 兼容 shim,
9
+ // barrel 直连正本并把星号改成显式清单——ADR-0003 的「对外给了什么」按名字可答)
10
+ export {
11
+ DEFAULT_FAILURE_LOG_FILE,
12
+ ErrorType,
13
+ FailureLevel,
14
+ DEFAULT_CLASSIFICATION_RULES,
15
+ DEFAULT_LEVEL_MAPPING,
16
+ } from '../types/failure';
17
+ export type {
18
+ ErrorClassificationRule,
19
+ FailureRecord,
20
+ ClassificationResult,
21
+ } from '../types/failure';
10
22
 
11
23
  // 分类器
12
24
  export {