@dommaker/harness 1.6.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 (560) hide show
  1. package/CHANGELOG.md +52 -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 +2 -1
  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/core/validators/index.d.ts +1 -1
  179. package/dist/core/validators/index.d.ts.map +1 -1
  180. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  181. package/dist/core/validators/passes-gate.js +2 -2
  182. package/dist/core/validators/passes-gate.js.map +1 -1
  183. package/dist/failure/classifier.d.ts +1 -1
  184. package/dist/failure/classifier.d.ts.map +1 -1
  185. package/dist/failure/classifier.js +8 -8
  186. package/dist/failure/classifier.js.map +1 -1
  187. package/dist/failure/index.d.ts +2 -1
  188. package/dist/failure/index.d.ts.map +1 -1
  189. package/dist/failure/index.js +9 -17
  190. package/dist/failure/index.js.map +1 -1
  191. package/dist/failure/recorder.d.ts +1 -1
  192. package/dist/failure/recorder.d.ts.map +1 -1
  193. package/dist/gates/checker-gate.d.ts +2 -1
  194. package/dist/gates/checker-gate.d.ts.map +1 -1
  195. package/dist/gates/checker-gate.js +3 -2
  196. package/dist/gates/checker-gate.js.map +1 -1
  197. package/dist/gates/command.d.ts +10 -4
  198. package/dist/gates/command.d.ts.map +1 -1
  199. package/dist/gates/command.js +36 -62
  200. package/dist/gates/command.js.map +1 -1
  201. package/dist/gates/contract.d.ts.map +1 -1
  202. package/dist/gates/contract.js +0 -4
  203. package/dist/gates/contract.js.map +1 -1
  204. package/dist/gates/definitions.d.ts +2 -1
  205. package/dist/gates/definitions.d.ts.map +1 -1
  206. package/dist/gates/definitions.js +6 -5
  207. package/dist/gates/definitions.js.map +1 -1
  208. package/dist/gates/index.d.ts +0 -4
  209. package/dist/gates/index.d.ts.map +1 -1
  210. package/dist/gates/index.js +1 -6
  211. package/dist/gates/index.js.map +1 -1
  212. package/dist/gates/performance.d.ts.map +1 -1
  213. package/dist/gates/performance.js +0 -4
  214. package/dist/gates/performance.js.map +1 -1
  215. package/dist/gates/runner.d.ts +1 -1
  216. package/dist/gates/runner.js +1 -1
  217. package/dist/gates/security.d.ts +1 -1
  218. package/dist/gates/security.d.ts.map +1 -1
  219. package/dist/gates/security.js +5 -6
  220. package/dist/gates/security.js.map +1 -1
  221. package/dist/gates/types.d.ts +14 -17
  222. package/dist/gates/types.d.ts.map +1 -1
  223. package/dist/gates/types.js.map +1 -1
  224. package/dist/hooks/bootstrap.d.ts +6 -4
  225. package/dist/hooks/bootstrap.d.ts.map +1 -1
  226. package/dist/hooks/bootstrap.js +25 -13
  227. package/dist/hooks/bootstrap.js.map +1 -1
  228. package/dist/hooks/config.d.ts +6 -2
  229. package/dist/hooks/config.d.ts.map +1 -1
  230. package/dist/hooks/config.js +5 -1
  231. package/dist/hooks/config.js.map +1 -1
  232. package/dist/hooks/index.d.ts +1 -1
  233. package/dist/hooks/index.d.ts.map +1 -1
  234. package/dist/hooks/index.js +0 -15
  235. package/dist/hooks/index.js.map +1 -1
  236. package/dist/hooks/pipeline.d.ts.map +1 -1
  237. package/dist/hooks/pipeline.js +2 -4
  238. package/dist/hooks/pipeline.js.map +1 -1
  239. package/dist/hooks/registry.d.ts +11 -8
  240. package/dist/hooks/registry.d.ts.map +1 -1
  241. package/dist/hooks/registry.js +22 -7
  242. package/dist/hooks/registry.js.map +1 -1
  243. package/dist/hooks/types.d.ts +23 -7
  244. package/dist/hooks/types.d.ts.map +1 -1
  245. package/dist/index.d.ts +4 -4
  246. package/dist/index.d.ts.map +1 -1
  247. package/dist/index.js +3 -4
  248. package/dist/index.js.map +1 -1
  249. package/dist/knowledge/audit-scoring.d.ts +87 -0
  250. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  251. package/dist/knowledge/audit-scoring.js +470 -0
  252. package/dist/knowledge/audit-scoring.js.map +1 -0
  253. package/dist/knowledge/audit.d.ts +11 -59
  254. package/dist/knowledge/audit.d.ts.map +1 -1
  255. package/dist/knowledge/audit.js +47 -522
  256. package/dist/knowledge/audit.js.map +1 -1
  257. package/dist/knowledge/import.d.ts.map +1 -1
  258. package/dist/knowledge/import.js +2 -0
  259. package/dist/knowledge/import.js.map +1 -1
  260. package/dist/knowledge/index-generator.d.ts.map +1 -1
  261. package/dist/knowledge/index-generator.js +4 -5
  262. package/dist/knowledge/index-generator.js.map +1 -1
  263. package/dist/knowledge/index.d.ts +3 -2
  264. package/dist/knowledge/index.d.ts.map +1 -1
  265. package/dist/knowledge/index.js +3 -16
  266. package/dist/knowledge/index.js.map +1 -1
  267. package/dist/knowledge/ingest.js +1 -1
  268. package/dist/knowledge/ingest.js.map +1 -1
  269. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  270. package/dist/knowledge/lifecycle.js +4 -2
  271. package/dist/knowledge/lifecycle.js.map +1 -1
  272. package/dist/knowledge/migration.d.ts +3 -0
  273. package/dist/knowledge/migration.d.ts.map +1 -1
  274. package/dist/knowledge/migration.js +8 -2
  275. package/dist/knowledge/migration.js.map +1 -1
  276. package/dist/knowledge/query.d.ts +6 -0
  277. package/dist/knowledge/query.d.ts.map +1 -1
  278. package/dist/knowledge/query.js +8 -2
  279. package/dist/knowledge/query.js.map +1 -1
  280. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  281. package/dist/knowledge/reference-tracker.js +4 -6
  282. package/dist/knowledge/reference-tracker.js.map +1 -1
  283. package/dist/knowledge/store.d.ts +22 -16
  284. package/dist/knowledge/store.d.ts.map +1 -1
  285. package/dist/knowledge/store.js +53 -12
  286. package/dist/knowledge/store.js.map +1 -1
  287. package/dist/knowledge/tree-walker.d.ts +19 -0
  288. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  289. package/dist/knowledge/tree-walker.js +30 -0
  290. package/dist/knowledge/tree-walker.js.map +1 -0
  291. package/dist/knowledge/types.d.ts +16 -0
  292. package/dist/knowledge/types.d.ts.map +1 -1
  293. package/dist/monitoring/traces.d.ts +5 -3
  294. package/dist/monitoring/traces.d.ts.map +1 -1
  295. package/dist/monitoring/traces.js +19 -5
  296. package/dist/monitoring/traces.js.map +1 -1
  297. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  298. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  299. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  300. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  301. package/dist/tools/index.d.ts +4 -1
  302. package/dist/tools/index.d.ts.map +1 -1
  303. package/dist/tools/index.js +7 -15
  304. package/dist/tools/index.js.map +1 -1
  305. package/dist/types/checkpoint.d.ts +5 -0
  306. package/dist/types/checkpoint.d.ts.map +1 -1
  307. package/dist/types/passes-gate.d.ts +0 -13
  308. package/dist/types/passes-gate.d.ts.map +1 -1
  309. package/dist/types/project-config.d.ts +18 -0
  310. package/dist/types/project-config.d.ts.map +1 -1
  311. package/dist/types/trace.d.ts +13 -4
  312. package/dist/types/trace.d.ts.map +1 -1
  313. package/dist/types/trace.js +4 -3
  314. package/dist/types/trace.js.map +1 -1
  315. package/dist/utils/exec.d.ts +4 -27
  316. package/dist/utils/exec.d.ts.map +1 -1
  317. package/dist/utils/exec.js +4 -94
  318. package/dist/utils/exec.js.map +1 -1
  319. package/dist/utils/jsonl.d.ts +17 -7
  320. package/dist/utils/jsonl.d.ts.map +1 -1
  321. package/dist/utils/jsonl.js +144 -17
  322. package/dist/utils/jsonl.js.map +1 -1
  323. package/dist/utils/numeric-flag.d.ts +44 -0
  324. package/dist/utils/numeric-flag.d.ts.map +1 -0
  325. package/dist/utils/numeric-flag.js +62 -0
  326. package/dist/utils/numeric-flag.js.map +1 -0
  327. package/package.json +1 -1
  328. package/src/CONTEXT.md +5 -2
  329. package/src/__tests__/acceptance-gate.test.ts +7 -4
  330. package/src/__tests__/checker-extra.test.ts +3 -2
  331. package/src/__tests__/checker.test.ts +4 -3
  332. package/src/__tests__/checkpoint.test.ts +26 -9
  333. package/src/__tests__/context-files-resolution.test.ts +29 -15
  334. package/src/__tests__/contract-gate.test.ts +19 -40
  335. package/src/__tests__/error-classifier.test.ts +1 -1
  336. package/src/__tests__/exec.test.ts +45 -107
  337. package/src/__tests__/failure-recorder.test.ts +7 -5
  338. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  339. package/src/__tests__/layering.test.ts +217 -25
  340. package/src/__tests__/passes-gate.test.ts +9 -4
  341. package/src/__tests__/performance-gate.test.ts +3 -15
  342. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  343. package/src/__tests__/project-config-loader.test.ts +6 -3
  344. package/src/__tests__/public-exports.test.ts +27 -6
  345. package/src/__tests__/public-type-surface.test.ts +360 -0
  346. package/src/__tests__/public-value-surface.test.ts +182 -0
  347. package/src/__tests__/review-gate.test.ts +4 -6
  348. package/src/__tests__/security-gate.test.ts +5 -20
  349. package/src/__tests__/skip-semantics.test.ts +5 -3
  350. package/src/__tests__/spec-validator.test.ts +3 -2
  351. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  352. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  353. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  354. package/src/__tests__/trace-analyzer.test.ts +5 -3
  355. package/src/__tests__/trace-collector.test.ts +5 -3
  356. package/src/agents/index.ts +13 -2
  357. package/src/cli/__tests__/gate-command.test.ts +162 -0
  358. package/src/cli/commands/CONTEXT.md +29 -5
  359. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  360. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  361. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  362. package/src/cli/commands/__tests__/check-read-count.test.ts +167 -0
  363. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  364. package/src/cli/commands/__tests__/check.test.ts +43 -36
  365. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  366. package/src/cli/commands/__tests__/command.test.ts +33 -17
  367. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  368. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  369. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  370. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  371. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  372. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  373. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  374. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  375. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  376. package/src/cli/commands/__tests__/init.test.ts +82 -6
  377. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  378. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  379. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  380. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  381. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  382. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  383. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +89 -1
  384. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  385. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  386. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  387. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  388. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  389. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  390. package/src/cli/commands/__tests__/report.test.ts +23 -1
  391. package/src/cli/commands/__tests__/review.test.ts +10 -17
  392. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  393. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  394. package/src/cli/commands/__tests__/security.test.ts +21 -24
  395. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  396. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  397. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  398. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  399. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  400. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  401. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  402. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  403. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  404. package/src/cli/commands/acceptance.ts +24 -29
  405. package/src/cli/commands/check.ts +40 -17
  406. package/src/cli/commands/command.ts +9 -8
  407. package/src/cli/commands/constraints-retire.ts +57 -70
  408. package/src/cli/commands/contract.ts +44 -47
  409. package/src/cli/commands/definitions.ts +12 -6
  410. package/src/cli/commands/failure.ts +16 -3
  411. package/src/cli/commands/init.ts +242 -389
  412. package/src/cli/commands/knowledge-view.ts +224 -0
  413. package/src/cli/commands/knowledge.ts +643 -448
  414. package/src/cli/commands/passes-gate.ts +12 -3
  415. package/src/cli/commands/performance.ts +31 -38
  416. package/src/cli/commands/report.ts +3 -2
  417. package/src/cli/commands/review.ts +22 -28
  418. package/src/cli/commands/scaffold-templates.ts +406 -0
  419. package/src/cli/commands/scaffold.ts +302 -0
  420. package/src/cli/commands/security.ts +27 -23
  421. package/src/cli/commands/spec-baseline-check.ts +158 -33
  422. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  423. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  424. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  425. package/src/cli/commands/sync-docs/index.ts +103 -28
  426. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  427. package/src/cli/commands/validate.ts +1 -104
  428. package/src/cli/gate-command.ts +87 -0
  429. package/src/completion-checkers/index.ts +10 -1
  430. package/src/context/CONTEXT.md +2 -1
  431. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  432. package/src/context/__tests__/session-manager.test.ts +19 -12
  433. package/src/context/knowledge-injector.ts +14 -7
  434. package/src/core/CONTEXT.md +10 -7
  435. package/src/core/__tests__/effective-set.test.ts +36 -60
  436. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  437. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  438. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  439. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  440. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  441. package/src/core/constraints/check-cache.ts +1 -1
  442. package/src/core/constraints/checker.ts +30 -14
  443. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  444. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  445. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  446. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  447. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  448. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  449. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  450. package/src/core/constraints/checkers/types.ts +14 -8
  451. package/src/core/constraints/context-builder.ts +35 -26
  452. package/src/core/constraints/context-reconcile.ts +200 -0
  453. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  454. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  455. package/src/core/constraints/git-evidence.ts +1 -1
  456. package/src/core/constraints/injection-drift.ts +19 -8
  457. package/src/core/constraints/run-env.ts +207 -0
  458. package/src/core/constraints/triggers.ts +33 -0
  459. package/src/core/effective-constraints.ts +17 -6
  460. package/src/core/effective-set.ts +8 -24
  461. package/src/core/index.ts +2 -1
  462. package/src/core/project-config-loader.ts +54 -102
  463. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  464. package/src/core/validators/check-handlers/http.ts +18 -7
  465. package/src/core/validators/index.ts +0 -1
  466. package/src/core/validators/passes-gate.ts +2 -3
  467. package/src/failure/CONTEXT.md +2 -1
  468. package/src/failure/classifier.ts +2 -2
  469. package/src/failure/index.ts +14 -2
  470. package/src/failure/recorder.ts +1 -1
  471. package/src/gates/CONTEXT.md +13 -7
  472. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  473. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  474. package/src/gates/__tests__/command.test.ts +8 -15
  475. package/src/gates/__tests__/contract.test.ts +5 -5
  476. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  477. package/src/gates/__tests__/performance.test.ts +0 -13
  478. package/src/gates/__tests__/review.test.ts +0 -1
  479. package/src/gates/__tests__/runner.test.ts +1 -1
  480. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  481. package/src/gates/__tests__/security.test.ts +0 -13
  482. package/src/gates/checker-gate.ts +3 -2
  483. package/src/gates/command.ts +51 -74
  484. package/src/gates/contract.ts +0 -5
  485. package/src/gates/definitions.ts +6 -5
  486. package/src/gates/index.ts +0 -5
  487. package/src/gates/performance.ts +1 -6
  488. package/src/gates/runner.ts +1 -1
  489. package/src/gates/security.ts +9 -11
  490. package/src/gates/types.ts +20 -23
  491. package/src/hooks/CONTEXT.md +10 -9
  492. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  493. package/src/hooks/__tests__/pipeline.test.ts +28 -17
  494. package/src/hooks/__tests__/registry.test.ts +51 -3
  495. package/src/hooks/bootstrap.ts +35 -15
  496. package/src/hooks/config.ts +6 -2
  497. package/src/hooks/index.ts +10 -1
  498. package/src/hooks/pipeline.ts +4 -6
  499. package/src/hooks/registry.ts +31 -12
  500. package/src/hooks/types.ts +24 -7
  501. package/src/index.ts +1 -3
  502. package/src/knowledge/CONTEXT.md +14 -7
  503. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  504. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  505. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  506. package/src/knowledge/__tests__/audit.test.ts +291 -208
  507. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  508. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  509. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  510. package/src/knowledge/__tests__/migration.test.ts +35 -6
  511. package/src/knowledge/__tests__/query.test.ts +3 -2
  512. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  513. package/src/knowledge/__tests__/store.test.ts +149 -4
  514. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  515. package/src/knowledge/audit-scoring.ts +589 -0
  516. package/src/knowledge/audit.ts +55 -599
  517. package/src/knowledge/import.ts +2 -0
  518. package/src/knowledge/index-generator.ts +4 -6
  519. package/src/knowledge/index.ts +27 -2
  520. package/src/knowledge/ingest.ts +1 -1
  521. package/src/knowledge/lifecycle.ts +5 -2
  522. package/src/knowledge/migration.ts +8 -2
  523. package/src/knowledge/query.ts +8 -1
  524. package/src/knowledge/reference-tracker.ts +5 -7
  525. package/src/knowledge/store.ts +75 -16
  526. package/src/knowledge/tree-walker.ts +29 -0
  527. package/src/knowledge/types.ts +19 -0
  528. package/src/monitoring/CONTEXT.md +5 -2
  529. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  530. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  531. package/src/monitoring/traces.ts +19 -5
  532. package/src/release/CONTEXT.md +1 -1
  533. package/src/release/__tests__/integrity.test.ts +21 -12
  534. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  535. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  536. package/src/tools/index.ts +5 -1
  537. package/src/types/checkpoint.ts +6 -0
  538. package/src/types/passes-gate.ts +0 -17
  539. package/src/types/project-config.ts +21 -0
  540. package/src/types/trace.ts +14 -4
  541. package/src/utils/__tests__/file-walk.test.ts +3 -2
  542. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  543. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  544. package/src/utils/__tests__/jsonl.test.ts +61 -14
  545. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  546. package/src/utils/__tests__/package-version.test.ts +2 -2
  547. package/src/utils/exec.ts +4 -64
  548. package/src/utils/jsonl.ts +159 -20
  549. package/src/utils/numeric-flag.ts +64 -0
  550. package/dist/failure/types.d.ts +0 -9
  551. package/dist/failure/types.d.ts.map +0 -1
  552. package/dist/failure/types.js +0 -25
  553. package/dist/failure/types.js.map +0 -1
  554. package/dist/gates/effective-gates.d.ts +0 -30
  555. package/dist/gates/effective-gates.d.ts.map +0 -1
  556. package/dist/gates/effective-gates.js +0 -81
  557. package/dist/gates/effective-gates.js.map +0 -1
  558. package/src/failure/types.ts +0 -9
  559. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  560. package/src/gates/effective-gates.ts +0 -99
@@ -0,0 +1,377 @@
1
+ /**
2
+ * 运行级观察面测试(ADR-0023 决策 1/4)
3
+ *
4
+ * 钉五件事:① 一次运行内同一份 trace 至多读一次(懒建 + memo);② 不同尾部窗口的口径
5
+ * 与 `readJsonl(..., {tail})` 逐字一致(含坏行占槽位)——这是"合并两处读"不改变判定的前提;
6
+ * ③ context-builder 用注入的 env,而不是自己再读一遍;
7
+ * ④ 项目配置(config.yml 与自定义约束文件)一次运行一份快照(ADR-0023 决策 2:
8
+ * 进程级缓存撤销后的口径);
9
+ * ⑤ 能力表一次解析多消费——两个 checker 共用同一枚 env 时文档只读一次、判定不变
10
+ * (ADR-0023 决策 4)。
11
+ */
12
+
13
+ import { jest } from '@jest/globals';
14
+ import * as fs from 'fs';
15
+ import * as os from 'os';
16
+ import * as path from 'path';
17
+ import { createRunEnv, resolveRunEnv, TRACE_TAIL_WINDOW, type RunEnv } from '../run-env';
18
+ import { buildConstraintContext } from '../context-builder';
19
+ import { buildCheckEnv, normalizeCheckOutcome } from '../checkers/types';
20
+ import { docsFreshness } from '../checkers/docs-freshness';
21
+ import { capabilitySync } from '../checkers/capability-sync';
22
+ import { collectSourceFiles, reconcileCapabilities } from '../capabilities-reconcile';
23
+ import { loadRawProjectConfig } from '../../project-config-loader';
24
+ import { createProjectFixture, writeProjectConfig } from '../../../test-setup/project-fixture';
25
+ import { readJsonl } from '../../../utils/jsonl';
26
+ import { DEFAULT_TRACE_FILE, type ExecutionTrace } from '../../../types/trace';
27
+ import type { GitEvidence } from '../git-evidence';
28
+
29
+ // ts-jest 的 namespace 导入属性不可重定义(jest.spyOn 会抛),只包一层 readFileSync
30
+ // 做读计数——其余 fs 能力用真实实现,夹具搭建与被检代码读取都不受影响
31
+ // (同一手法先例:src/__tests__/context-files-resolution.test.ts)
32
+ jest.mock('fs', () => {
33
+ const actual = jest.requireActual<typeof import('fs')>('fs');
34
+ return { ...actual, readFileSync: jest.fn(actual.readFileSync) };
35
+ });
36
+
37
+ const readSpy = (fs as unknown as { readFileSync: jest.Mock }).readFileSync;
38
+ const capsReads = () =>
39
+ readSpy.mock.calls.filter(call => String(call[0]).endsWith('CAPABILITIES.md')).length;
40
+
41
+ const tracePathOf = (projectPath: string) => path.join(projectPath, DEFAULT_TRACE_FILE);
42
+
43
+ function writeTraces(projectPath: string, lines: string[]): void {
44
+ const file = tracePathOf(projectPath);
45
+ fs.mkdirSync(path.dirname(file), { recursive: true });
46
+ fs.writeFileSync(file, lines.join('\n') + '\n', 'utf-8');
47
+ }
48
+
49
+ const traceLine = (id: string, result: 'pass' | 'fail'): string =>
50
+ JSON.stringify({ constraintId: id, level: 'iron_law', timestamp: 1, result });
51
+
52
+ const CORRUPT = '{"constraintId":"broken"';
53
+
54
+ describe('createRunEnv', () => {
55
+ let dir: string;
56
+
57
+ beforeEach(() => {
58
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-run-env-'));
59
+ });
60
+
61
+ afterEach(() => {
62
+ fs.rmSync(dir, { recursive: true, force: true });
63
+ });
64
+
65
+ it('一次运行内同一份 trace 至多读一次:取过之后再改文件,仍是老内容', () => {
66
+ writeTraces(dir, [traceLine('a', 'pass')]);
67
+ const env = createRunEnv(dir);
68
+ expect(env.traceTail(1).records.map(r => r.constraintId)).toEqual(['a']);
69
+
70
+ // 追加一条 fail:若没有 memo,第二次取会读到新尾部并判出 fail
71
+ writeTraces(dir, [traceLine('a', 'pass'), traceLine('b', 'fail')]);
72
+ expect(env.traceTail(2).records.map(r => r.constraintId)).toEqual(['a']);
73
+ });
74
+
75
+ it('懒建:造 env 不碰文件,之后落的数据才读得到', () => {
76
+ const env = createRunEnv(dir);
77
+ writeTraces(dir, [traceLine('late', 'fail')]);
78
+ expect(env.traceTail(5).records.map(r => r.constraintId)).toEqual(['late']);
79
+ });
80
+
81
+ it('缺 trace 文件返回空窗口,不抛', () => {
82
+ expect(createRunEnv(dir).traceTail(10)).toEqual({ records: [], skippedLines: 0 });
83
+ });
84
+
85
+ it('窗口口径与 readJsonl 的 tail 逐字一致(含坏行占槽位)', () => {
86
+ const lines = [
87
+ traceLine('old', 'fail'),
88
+ traceLine('x1', 'pass'),
89
+ CORRUPT,
90
+ traceLine('x2', 'pass'),
91
+ traceLine('x3', 'pass'),
92
+ ];
93
+ writeTraces(dir, lines);
94
+ const env = createRunEnv(dir);
95
+ const file = tracePathOf(dir);
96
+
97
+ for (const limit of [1, 2, 3, 4, 5]) {
98
+ expect(env.traceTail(limit)).toEqual(readJsonl<ExecutionTrace>(file, 'skip', { tail: limit }));
99
+ }
100
+ // 坏行占住尾部槽位:limit=3 时最近三条含一条坏行 → 有效记录只剩 2 条
101
+ expect(env.traceTail(3).records.map(r => r.constraintId)).toEqual(['x2', 'x3']);
102
+ expect(env.traceTail(3).skippedLines).toBe(1);
103
+ });
104
+
105
+ it('超过窗口上限直接抛,不静默少给', () => {
106
+ writeTraces(dir, Array.from({ length: 40 }, (_, i) => traceLine(`t${i}`, 'pass')));
107
+ const env = createRunEnv(dir);
108
+ expect(() => env.traceTail(TRACE_TAIL_WINDOW + 1)).toThrow(`尾部窗口上限 ${TRACE_TAIL_WINDOW} 行`);
109
+ });
110
+
111
+ it('projectPath 原样带出(每个 IO 点锚同一个根,harness#95)', () => {
112
+ expect(createRunEnv(dir).projectPath).toBe(dir);
113
+ });
114
+
115
+ it('sourceRoots 一次运行内至多探测一次', () => {
116
+ fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
117
+ fs.writeFileSync(path.join(dir, 'src', 'a.ts'), 'export const a = 1;\n');
118
+ const env = createRunEnv(dir);
119
+ expect(env.sourceRoots()).toContain('src');
120
+
121
+ // 运行中途新增源码根:memo 生效,同一份结果不再走盘
122
+ fs.mkdirSync(path.join(dir, 'packages'), { recursive: true });
123
+ fs.writeFileSync(path.join(dir, 'packages', 'b.ts'), 'export const b = 1;\n');
124
+ expect(env.sourceRoots()).not.toContain('packages');
125
+ });
126
+ });
127
+
128
+ describe('配置一次装载 — rawConfig / customConstraints(ADR-0023 决策 2)', () => {
129
+ let dir: string;
130
+
131
+ const writeConfig = (content: string) => writeProjectConfig(dir, content);
132
+
133
+ /** 落 `.harness/<fileName>`(夹具可能还没有 .harness 目录,先补上) */
134
+ const writeHarnessFile = (fileName: string, body: string) => {
135
+ const target = path.join(dir, '.harness', fileName);
136
+ fs.mkdirSync(path.dirname(target), { recursive: true });
137
+ fs.writeFileSync(target, body, 'utf-8');
138
+ };
139
+
140
+ beforeEach(() => {
141
+ dir = createProjectFixture({ name: 'run-env-config' });
142
+ });
143
+
144
+ afterEach(() => {
145
+ fs.rmSync(dir, { recursive: true, force: true });
146
+ });
147
+
148
+ it('run 内至多读一次:取过之后再改文件,仍是老内容', () => {
149
+ writeConfig('preset: standard\n');
150
+ const env = createRunEnv(dir);
151
+ expect(env.rawConfig()).toEqual({ preset: 'standard' });
152
+
153
+ writeConfig('preset: strict\n');
154
+ expect(env.rawConfig()).toEqual({ preset: 'standard' });
155
+ });
156
+
157
+ it('懒建:造 env 不碰文件,之后落的配置才读得到', () => {
158
+ const env = createRunEnv(dir);
159
+ writeConfig('preset: minimal\n');
160
+ expect(env.rawConfig()).toEqual({ preset: 'minimal' });
161
+ });
162
+
163
+ it('无 config.yml = undefined(并记住这个结论,不反复探测)', () => {
164
+ const env = createRunEnv(dir);
165
+ expect(env.rawConfig()).toBeUndefined();
166
+ expect(env.rawConfig()).toBeUndefined();
167
+ });
168
+
169
+ it('空文件回落 {},与改前 loadRawProjectConfig 逐字一致', () => {
170
+ writeConfig('');
171
+ expect(createRunEnv(dir).rawConfig()).toEqual({});
172
+ });
173
+
174
+ it('YAML 解析失败照抛——兜与否属消费方的判定,读面不替它决定', () => {
175
+ writeConfig('governance: [\n broken: {{\n');
176
+ const env = createRunEnv(dir);
177
+ expect(() => env.rawConfig()).toThrow();
178
+ // 抛错不入 memo:修好文件后同一枚 env 也能读到正确内容
179
+ writeConfig('preset: standard\n');
180
+ expect(env.rawConfig()).toEqual({ preset: 'standard' });
181
+ });
182
+
183
+ it('resolveRunEnv:传 env 原样复用,传路径各自读当下内容(无进程级残留)', () => {
184
+ writeConfig('preset: standard\n');
185
+ const env = createRunEnv(dir);
186
+ expect(resolveRunEnv(env)).toBe(env);
187
+ expect(resolveRunEnv(dir).rawConfig()).toEqual({ preset: 'standard' });
188
+
189
+ writeConfig('preset: strict\n');
190
+ expect(loadRawProjectConfig(dir)).toEqual({ preset: 'strict' });
191
+ expect(resolveRunEnv().projectPath).toBe(process.cwd());
192
+ });
193
+
194
+ it('customConstraints 取 custom_constraints 段,run 内同名文件至多读一次', () => {
195
+ const write = (body: string) => writeHarnessFile('custom-constraints.yml', body);
196
+ write('custom_constraints:\n a:\n level: iron_law\n rule: 只此一份\n');
197
+ const env = createRunEnv(dir);
198
+ expect(Object.keys(env.customConstraints('custom-constraints.yml'))).toEqual(['a']);
199
+
200
+ write('custom_constraints:\n b:\n level: guideline\n rule: 后来的\n');
201
+ expect(Object.keys(env.customConstraints('custom-constraints.yml'))).toEqual(['a']);
202
+ });
203
+
204
+ it('customConstraints:文件缺失 = {};不同文件名各自 memo', () => {
205
+ const env = createRunEnv(dir);
206
+ expect(env.customConstraints('missing.yml')).toEqual({});
207
+
208
+ writeHarnessFile(
209
+ 'other.yml',
210
+ 'custom_constraints:\n c:\n level: guideline\n rule: r\n'
211
+ );
212
+ expect(Object.keys(env.customConstraints('other.yml'))).toEqual(['c']);
213
+ expect(env.customConstraints('missing.yml')).toEqual({});
214
+ });
215
+ });
216
+
217
+ describe('context-builder 经注入的观察面取证据', () => {
218
+ let dir: string;
219
+
220
+ const fakeGit = (projectPath: string, changed: string[]): GitEvidence => ({
221
+ projectPath,
222
+ stagedDiff: () => '',
223
+ changedFileNames: () => changed.join('\n'),
224
+ headDirs: () => new Set<string>(),
225
+ });
226
+
227
+ /** 只记次数的假观察面:records 固定,用于断言两个证据探测都从 env 取数 */
228
+ function countingEnv(records: ExecutionTrace[]) {
229
+ const limits: number[] = [];
230
+ const env: RunEnv = {
231
+ projectPath: dir,
232
+ traceTail: (limit: number) => {
233
+ limits.push(limit);
234
+ return { records: limit >= records.length ? records : records.slice(-limit), skippedLines: 0 };
235
+ },
236
+ sourceRoots: () => ['src'],
237
+ rawConfig: () => undefined,
238
+ customConstraints: () => ({}),
239
+ capabilities: () => undefined,
240
+ };
241
+ return { env, limits };
242
+ }
243
+
244
+ beforeEach(() => {
245
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-ctxbuilder-env-'));
246
+ });
247
+
248
+ afterEach(() => {
249
+ fs.rmSync(dir, { recursive: true, force: true });
250
+ });
251
+
252
+ it('hasFailingTest / hasVerificationEvidence 各按自己的窗口向 env 取数,不另开读', async () => {
253
+ const { env, limits } = countingEnv([
254
+ { constraintId: 'p', level: 'iron_law', timestamp: 1, result: 'pass' },
255
+ { constraintId: 'f', level: 'iron_law', timestamp: 2, result: 'fail' },
256
+ ]);
257
+ const ctx = await buildConstraintContext({
258
+ projectPath: dir,
259
+ staged: false,
260
+ evidence: fakeGit(dir, ['README.md']),
261
+ runEnv: env,
262
+ });
263
+
264
+ expect(limits).toEqual([TRACE_TAIL_WINDOW, 10]);
265
+ expect(ctx.hasFailingTest).toBe(true);
266
+ expect(ctx.hasVerificationEvidence).toBe(true);
267
+ });
268
+
269
+ it('窗口里没有 pass 时验证证据为 false(合并读取不改判定)', async () => {
270
+ const { env } = countingEnv([
271
+ { constraintId: 'f', level: 'iron_law', timestamp: 2, result: 'fail' },
272
+ ]);
273
+ const ctx = await buildConstraintContext({
274
+ projectPath: dir,
275
+ staged: false,
276
+ evidence: fakeGit(dir, ['README.md']),
277
+ runEnv: env,
278
+ });
279
+ expect(ctx.hasFailingTest).toBe(true);
280
+ expect(ctx.hasVerificationEvidence).toBe(false);
281
+ });
282
+
283
+ it('不注入 env 时自造一份并按 projectPath 锚定读取', async () => {
284
+ writeTraces(dir, [traceLine('a', 'fail')]);
285
+ const ctx = await buildConstraintContext({
286
+ projectPath: dir,
287
+ staged: false,
288
+ evidence: fakeGit(dir, ['README.md']),
289
+ });
290
+ expect(ctx.hasFailingTest).toBe(true);
291
+ });
292
+ });
293
+
294
+ describe('capabilities — 能力表一次解析多消费(ADR-0023 决策 4)', () => {
295
+ const TABLE = '| 能力 | 路径 | 说明 |\n|---|---|---|\n';
296
+ /** 幽灵条目 src/gone.ts + 一个真实源文件 src/live.ts */
297
+ const CAPS_DOC = `# C\n\n${TABLE}| 幽灵 | src/gone.ts | 已删 |\n`;
298
+
299
+ let dir: string;
300
+
301
+ beforeEach(() => {
302
+ dir = createProjectFixture({
303
+ name: 'run-env-caps',
304
+ files: { 'CAPABILITIES.md': CAPS_DOC, 'src/live.ts': 'export const x = 1;\n' },
305
+ });
306
+ readSpy.mockClear();
307
+ });
308
+
309
+ afterEach(() => {
310
+ fs.rmSync(dir, { recursive: true, force: true });
311
+ });
312
+
313
+ /** 两个 checker 共用同一枚 env(= 一次 check run 的形状) */
314
+ async function evaluateBoth(env: RunEnv) {
315
+ const scan = collectSourceFiles(dir, env.sourceRoots());
316
+ const checkEnv = buildCheckEnv(
317
+ { operation: 'module_modification', projectPath: dir },
318
+ { stagedDiff: async () => '', stagedDiffNames: async () => '', srcScan: () => scan },
319
+ env
320
+ );
321
+ return {
322
+ docs: normalizeCheckOutcome(await docsFreshness.evaluate(checkEnv)),
323
+ caps: normalizeCheckOutcome(await capabilitySync.evaluate(checkEnv)),
324
+ };
325
+ }
326
+
327
+ it('一次 run 内两个 checker 共用同一份读取:CAPABILITIES.md 只读一次', async () => {
328
+ const { docs, caps } = await evaluateBoth(createRunEnv(dir));
329
+
330
+ // 判定照旧:幽灵条目由 docs_freshness 点名,capability_sync 正常评估不 skip
331
+ expect(docs.satisfied).toBe(false);
332
+ expect(docs.evidence.join('\n')).toContain('src/gone.ts');
333
+ expect(caps.skipped).toBe(false);
334
+ expect(capsReads()).toBe(1);
335
+ });
336
+
337
+ it('取过之后再改文档 = 同一份内容(run 内 memo,不重复解析)', () => {
338
+ const env = createRunEnv(dir);
339
+ const first = env.capabilities(['src/live.ts']);
340
+ expect(capsReads()).toBe(1);
341
+
342
+ fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), `# C\n\n${TABLE}| 新 | src/live.ts | 在 |\n`, 'utf-8');
343
+ expect(env.capabilities(['src/live.ts'])).toBe(first);
344
+ expect(capsReads()).toBe(1);
345
+ });
346
+
347
+ it('无 CAPABILITIES.md → undefined;有文档时判定与直调对照模块同输入同结果', () => {
348
+ const noDoc = createProjectFixture({ name: 'run-env-caps-none', files: { 'src/a.ts': 'x\n' } });
349
+ try {
350
+ expect(createRunEnv(noDoc).capabilities(['src/a.ts'])).toBeUndefined();
351
+ } finally {
352
+ fs.rmSync(noDoc, { recursive: true, force: true });
353
+ }
354
+
355
+ const env = createRunEnv(dir);
356
+ const caps = env.capabilities(['src/live.ts']);
357
+ expect(caps?.verdict.deadEntries).toEqual(
358
+ reconcileCapabilities({
359
+ content: caps!.content,
360
+ populationFiles: ['src/live.ts'],
361
+ sourceRoots: caps!.sourceRoots,
362
+ fileExists: rel => fs.existsSync(path.join(dir, rel)),
363
+ }).deadEntries
364
+ );
365
+ // 增量那一维不在上行数据面(变更清单属 git 证据 #87),共享形状里恒空
366
+ expect(caps?.verdict.uncoveredChanges).toEqual([]);
367
+ });
368
+
369
+ it('源根探测与能力表共用同一份 memo:取过之后再改目录树不影响', () => {
370
+ const env = createRunEnv(dir);
371
+ const before = env.capabilities(['src/live.ts'])?.sourceRoots;
372
+
373
+ fs.mkdirSync(path.join(dir, 'packages'), { recursive: true });
374
+ fs.writeFileSync(path.join(dir, 'packages', 'p.ts'), 'export const p = 1;\n', 'utf-8');
375
+ expect(env.capabilities(['src/live.ts'])?.sourceRoots).toBe(before);
376
+ });
377
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 约束 trigger 归一与匹配谓词测试(harness#105 语义单点)
3
+ *
4
+ * 用例自 `src/__tests__/exec.test.ts` 随迁(harness#137):二者是约束域谓词,
5
+ * 归位到 core/constraints 后测试跟着正本走。
6
+ */
7
+
8
+ import { describe, it, expect } from '@jest/globals';
9
+ import { normalizeTriggers, matchesTrigger } from '../triggers';
10
+
11
+ describe('normalizeTriggers', () => {
12
+ it('应该返回 fallback 当值为 null', () => {
13
+ const result = normalizeTriggers(null, ['default']);
14
+ expect(result).toEqual(['default']);
15
+ });
16
+
17
+ it('应该返回 fallback 当值为 undefined', () => {
18
+ const result = normalizeTriggers(undefined, ['default']);
19
+ expect(result).toEqual(['default']);
20
+ });
21
+
22
+ it('应该将单值包装为数组', () => {
23
+ const result = normalizeTriggers('single');
24
+ expect(result).toEqual(['single']);
25
+ });
26
+
27
+ it('应该直接返回数组', () => {
28
+ const result = normalizeTriggers(['a', 'b']);
29
+ expect(result).toEqual(['a', 'b']);
30
+ });
31
+
32
+ it('应该使用空数组作为默认 fallback', () => {
33
+ const result = normalizeTriggers(undefined);
34
+ expect(result).toEqual([]);
35
+ });
36
+ });
37
+
38
+ describe('matchesTrigger', () => {
39
+ it('单值 trigger 命中操作返回 true', () => {
40
+ expect(matchesTrigger({ trigger: 'a' }, ['a'])).toBe(true);
41
+ });
42
+
43
+ it('单值 trigger 未命中返回 false', () => {
44
+ expect(matchesTrigger({ trigger: 'a' }, ['b'])).toBe(false);
45
+ });
46
+
47
+ it('数组 trigger 任一命中即返回 true', () => {
48
+ expect(matchesTrigger({ trigger: ['a', 'b'] }, ['b'])).toBe(true);
49
+ });
50
+
51
+ it('数组 trigger 全部未命中返回 false', () => {
52
+ expect(matchesTrigger({ trigger: ['a', 'b'] }, ['c', 'd'])).toBe(false);
53
+ });
54
+
55
+ it('operations 中任一命中即返回 true', () => {
56
+ expect(matchesTrigger({ trigger: 'a' }, ['x', 'a'])).toBe(true);
57
+ });
58
+
59
+ it('trigger 为 undefined 时不匹配任何操作', () => {
60
+ expect(matchesTrigger({ trigger: undefined }, ['a'])).toBe(false);
61
+ });
62
+
63
+ it('trigger 为空数组时不匹配任何操作', () => {
64
+ expect(matchesTrigger({ trigger: [] }, ['a'])).toBe(false);
65
+ });
66
+
67
+ it('operations 为空数组时返回 false', () => {
68
+ expect(matchesTrigger({ trigger: 'a' }, [])).toBe(false);
69
+ });
70
+ });
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import type { ConstraintLevel, ConstraintTrigger } from '../../types/constraint';
15
- import { matchesTrigger } from '../../utils/exec';
15
+ import { matchesTrigger } from './triggers';
16
16
  import { getEffectiveConstraints } from '../effective-constraints';
17
17
 
18
18
  /**
@@ -16,6 +16,7 @@
16
16
  * module 读 uncoveredDirs(目录聚合),file 读 uncoveredFiles(逐文件)。
17
17
  */
18
18
 
19
+ import { existsSync } from 'fs';
19
20
  import { join, relative } from 'path';
20
21
  import {
21
22
  aggregateToSourceSubdir,
@@ -67,8 +68,11 @@ export interface CapabilityVerdict {
67
68
  * 目录条目(以 / 结尾)前缀匹配;文件条目精确匹配或路径边界后缀匹配
68
69
  * (兼容 basename 条目,但拒绝 xfoo.ts 命中 foo.ts、docs/src/foo.tsx 命中 src/foo.ts
69
70
  * 这类 endsWith/includes 模糊匹配造成的碰撞)
71
+ *
72
+ * 对外导出(ADR-0023 步骤 4):共享对照判定的消费方需要按自己的增量清单再算一次覆盖,
73
+ * 这条规则不得在别处重写。
70
74
  */
71
- function isCoveredBy(entries: string[], file: string): boolean {
75
+ export function isCoveredByEntries(entries: string[], file: string): boolean {
72
76
  return entries.some((listed) =>
73
77
  listed.endsWith('/')
74
78
  ? file.startsWith(listed)
@@ -100,7 +104,7 @@ export function reconcileCapabilities(input: ReconcileInput): CapabilityVerdict
100
104
  const dirEntries = allEntries.filter((e) => e.endsWith('/'));
101
105
  const coverageEntries = [...fileEntries, ...dirEntries];
102
106
 
103
- const uncoveredFiles = populationFiles.filter((f) => !isCoveredBy(coverageEntries, f));
107
+ const uncoveredFiles = populationFiles.filter((f) => !isCoveredByEntries(coverageEntries, f));
104
108
  const uncoveredDirs = [...new Set(
105
109
  uncoveredFiles.map((file) => {
106
110
  // 与 sync-docs 历史聚合同语义:归属第一个能前缀匹配的来源根,兜底首根
@@ -111,7 +115,7 @@ export function reconcileCapabilities(input: ReconcileInput): CapabilityVerdict
111
115
 
112
116
  // 条目在代码实况中仍有对应文件即算活(裸文件名条目无法做存在性判定的兜底)
113
117
  const aliveByPopulation = (entry: string): boolean =>
114
- populationFiles.some((f) => isCoveredBy([entry], f));
118
+ populationFiles.some((f) => isCoveredByEntries([entry], f));
115
119
 
116
120
  const deadEntries = allEntries.filter((entry) => {
117
121
  if (entryExistsOnDisk(entry, fileExists, sourceRoots)) return false;
@@ -124,7 +128,7 @@ export function reconcileCapabilities(input: ReconcileInput): CapabilityVerdict
124
128
  coverageEntries,
125
129
  hasTable: TABLE_SEPARATOR_REGEX.test(content),
126
130
  listingFormat: isCapabilityListingFormat(content),
127
- uncoveredChanges: changedFiles.filter((f) => !isCoveredBy(coverageEntries, f)),
131
+ uncoveredChanges: changedFiles.filter((f) => !isCoveredByEntries(coverageEntries, f)),
128
132
  uncoveredFiles,
129
133
  uncoveredDirs,
130
134
  deadEntries,
@@ -164,3 +168,23 @@ export function collectSourceFiles(
164
168
  }
165
169
  return files;
166
170
  }
171
+
172
+ /**
173
+ * 「代码实况清单」的唯一取法:只收真实存在的源码根下的文件(项目相对路径)
174
+ *
175
+ * 抽出的理由(ADR-0023 步骤 4):capability_sync 与 docs_freshness 现在把同一份清单
176
+ * 喂给 run 内共享的对照判定(`RunEnv.capabilities()`,memo 只记第一次的输入),
177
+ * 两边必须逐字相同,否则共享的就是一份错的东西。扫描本身仍经调用方注入的 `scan`
178
+ * (保留 checker 侧 srcScan 证据接缝与测试可替身性)。
179
+ */
180
+ export function collectPopulationFiles(
181
+ projectPath: string,
182
+ roots: string[],
183
+ scan: (root: string) => string[]
184
+ ): string[] {
185
+ const files: string[] = [];
186
+ for (const root of roots) {
187
+ if (existsSync(join(projectPath, root))) files.push(...scan(root));
188
+ }
189
+ return files;
190
+ }
@@ -14,7 +14,7 @@
14
14
  * ```typescript
15
15
  * const cache = new CheckCache({ ttlMs: 5000 });
16
16
  * // 普通 TTL 缓存
17
- * const diff = await cache.get('git_diff', projectPath, () => runCommand('git diff --cached', projectPath));
17
+ * const diff = await cache.get('git_diff', projectPath, () => createGitEvidence(projectPath).stagedDiff());
18
18
  * // 计数采样:每 3 次调用执行 1 次完整检查
19
19
  * const ok = await cache.get('goal_check', key, check, { sampleRate: 3, defaultValueOnMiss: true });
20
20
  * ```
@@ -18,12 +18,13 @@ import {
18
18
  import type { ExecutionTrace } from '../../types/trace';
19
19
  import { IRON_LAWS, GUIDELINES, PROMPTS } from './definitions';
20
20
  import type { MergedConstraintsConfig } from '../../types/project-config';
21
- import { matchesTrigger } from '../../utils/exec';
21
+ import { matchesTrigger } from './triggers';
22
22
  import { join, relative } from 'path';
23
23
  import { CheckCache } from './check-cache';
24
24
  import { findTsSourceFiles } from '../../utils/file-walk';
25
25
  import { getConstraintCheck, buildCheckEnv, normalizeCheckOutcome, type CheckOutcome } from './checkers';
26
26
  import { createGitEvidence, type GitEvidence } from './git-evidence';
27
+ import { createRunEnv, type RunEnv } from './run-env';
27
28
 
28
29
  /**
29
30
  * trace 记录器最小接口(工单 15 decycle 收尾,harness#88)
@@ -94,11 +95,13 @@ export class ConstraintChecker {
94
95
  * 检查单个约束
95
96
  *
96
97
  * @param evidence 可选,本 run 的 git 证据实例(#87);不传 = 本次调用独占一份真 git 证据
98
+ * @param runEnv 可选,本 run 的运行级观察面(ADR-0023);不传 = 本次调用独占一份
97
99
  */
98
100
  async check(
99
101
  constraint: Constraint,
100
102
  context: ConstraintContext,
101
- evidence?: GitEvidence
103
+ evidence?: GitEvidence,
104
+ runEnv?: RunEnv
102
105
  ): Promise<ConstraintResult> {
103
106
  // prompt 类约束不参与 checker 执行(ADR-0001:仅参与注入)
104
107
  if (constraint.kind === 'prompt') {
@@ -113,7 +116,7 @@ export class ConstraintChecker {
113
116
 
114
117
  // 检查前置条件('skip' = 约定未采用/证据未接线:satisfied 置 true 但不计 pass/fail)
115
118
  const outcome = normalizeCheckOutcome(
116
- await this.checkPrecondition(constraint, context, evidence)
119
+ await this.checkPrecondition(constraint, context, evidence, runEnv)
117
120
  );
118
121
 
119
122
  if (outcome.skipped) {
@@ -187,11 +190,13 @@ export class ConstraintChecker {
187
190
  *
188
191
  * git 证据(stagedDiff/stagedDiffNames)单一来源 = GitEvidence adapter(#87):
189
192
  * 一次 run 内调用方传同一实例即至多取证一次,run 外调用独占一份。
193
+ * 上行数据(config.yml / trace 尾部 / 源根)同一形状 = RunEnv(ADR-0023)。
190
194
  */
191
195
  private async checkPrecondition(
192
196
  constraint: Constraint,
193
197
  context: ConstraintContext,
194
- evidence?: GitEvidence
198
+ evidence?: GitEvidence,
199
+ runEnv?: RunEnv
195
200
  ): Promise<CheckOutcome> {
196
201
  const impl = getConstraintCheck(constraint.id);
197
202
  if (!impl) {
@@ -212,7 +217,7 @@ export class ConstraintChecker {
212
217
  relative(projectPath, f)
213
218
  )
214
219
  ),
215
- });
220
+ }, runEnv);
216
221
 
217
222
  return await impl.evaluate(env);
218
223
  }
@@ -258,13 +263,16 @@ export class ConstraintChecker {
258
263
  * @param evidence 可选,本 run 的 git 证据 adapter(#87)。调用方(CLI check)与
259
264
  * buildConstraintContext 传同一实例 → 一次 run 内 git 取证单一来源;
260
265
  * 不传则本 run 自建一份,run 级 memo 照样生效(memo 在实例层,非隐式全局)。
266
+ * @param runEnv 可选,本 run 的运行级观察面(ADR-0023)。调用方(CLI check)与
267
+ * buildConstraintContext 传同一实例 → 一次 run 内同一项目文件至多读一次。
261
268
  */
262
269
  async checkConstraints(
263
270
  context: ConstraintContext,
264
271
  customConfig?: MergedConstraintsConfig | null,
265
- evidence?: GitEvidence
272
+ evidence?: GitEvidence,
273
+ runEnv?: RunEnv
266
274
  ): Promise<ConstraintCheckResult> {
267
- return this.runAllConstraints(context, customConfig, evidence, 'block');
275
+ return this.runAllConstraints(context, customConfig, evidence, runEnv, 'block');
268
276
  }
269
277
 
270
278
  /**
@@ -278,9 +286,10 @@ export class ConstraintChecker {
278
286
  async collectConstraints(
279
287
  context: ConstraintContext,
280
288
  customConfig?: MergedConstraintsConfig | null,
281
- evidence?: GitEvidence
289
+ evidence?: GitEvidence,
290
+ runEnv?: RunEnv
282
291
  ): Promise<ConstraintCheckResult> {
283
- return this.runAllConstraints(context, customConfig, evidence, 'collect');
292
+ return this.runAllConstraints(context, customConfig, evidence, runEnv, 'collect');
284
293
  }
285
294
 
286
295
  /**
@@ -291,11 +300,15 @@ export class ConstraintChecker {
291
300
  context: ConstraintContext,
292
301
  customConfig: MergedConstraintsConfig | null | undefined,
293
302
  evidence: GitEvidence | undefined,
303
+ runEnv: RunEnv | undefined,
294
304
  mode: 'block' | 'collect'
295
305
  ): Promise<ConstraintCheckResult> {
296
306
  // run 起始:重置 src 扫描缓存(S7);git 证据 = 本 run 独占的 adapter 实例(工单 18 → #87)
297
307
  this.cache.invalidate();
298
- const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());
308
+ const projectPath = context.projectPath || process.cwd();
309
+ const run = evidence ?? createGitEvidence(projectPath);
310
+ // 观察面同一形状(ADR-0023):调用方传了就共用,没传也只造一枚——逐条 checker 不再各读一遍配置
311
+ const env = runEnv ?? createRunEnv(projectPath);
299
312
 
300
313
  const result: ConstraintCheckResult = {
301
314
  ironLaws: [],
@@ -312,7 +325,7 @@ export class ConstraintChecker {
312
325
  for (const constraint of Object.values(constraints.ironLaws)) {
313
326
  if (!matchesTrigger(constraint, operations)) continue;
314
327
 
315
- const checkResult = await this.check(constraint, context, run);
328
+ const checkResult = await this.check(constraint, context, run, env);
316
329
  result.ironLaws.push(checkResult);
317
330
  this.recordTrace(constraint, checkResult, context);
318
331
 
@@ -328,7 +341,7 @@ export class ConstraintChecker {
328
341
  for (const constraint of Object.values(constraints.guidelines)) {
329
342
  if (!matchesTrigger(constraint, operations)) continue;
330
343
 
331
- const checkResult = await this.check(constraint, context, run);
344
+ const checkResult = await this.check(constraint, context, run, env);
332
345
  result.guidelines.push(checkResult);
333
346
  this.recordTrace(constraint, checkResult, context);
334
347
 
@@ -353,14 +366,17 @@ export class ConstraintChecker {
353
366
  customConfig?: MergedConstraintsConfig | null,
354
367
  evidence?: GitEvidence
355
368
  ): Promise<void> {
356
- const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());
369
+ const projectPath = context.projectPath || process.cwd();
370
+ const run = evidence ?? createGitEvidence(projectPath);
371
+ // 独立 run 入口(ADR-0023):本函数内所有 checker 共用一枚观察面
372
+ const env = createRunEnv(projectPath);
357
373
  const constraints = this.getConstraints(customConfig);
358
374
  const operations = [context.operation, ...(context.extraTriggers ?? [])];
359
375
 
360
376
  for (const constraint of Object.values(constraints.ironLaws)) {
361
377
  if (!matchesTrigger(constraint, operations)) continue;
362
378
 
363
- const result = await this.check(constraint, context, run);
379
+ const result = await this.check(constraint, context, run, env);
364
380
  if (!result.satisfied) {
365
381
  throw new ConstraintViolationError(result);
366
382
  }