@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
@@ -0,0 +1,200 @@
1
+ /**
2
+ * CONTEXT.md「核心导出」节与目录导出面的对照判定(harness#142 / ADR-0025)
3
+ *
4
+ * 纯判定 module:吃「CONTEXT.md 正文 + 目录导出面清单(+ barrel 值符号清单)」,
5
+ * 一次产出双向判定;不做任何 fs IO——文件遍历、正文读取、`export *` 目标解析
6
+ * 全在调用方(cli 侧),与 ADR-0009 的 capabilities-reconcile 同一套组装形状。
7
+ *
8
+ * 方向(语义与 capabilities-reconcile 对齐:幽灵 = 文档说谎,未登记 = 代码有而文档漏):
9
+ * 文档→代码 ghosts(「核心导出」节声明的符号在目录导出面里不存在)
10
+ * 代码→文档 unlistedBarrelExports(index.ts 再导出的值符号未出现在该节)
11
+ *
12
+ * 口径(ADR-0025,两条都是「可操作」而非「越严越好」的取舍):
13
+ * - 声明只取 bullet **行首**的反引号标识符(`/` 连接的多符号逐个取,形参列表与
14
+ * `(type)`注解剥掉)。行首不是标识符的 bullet 是模块/目录条目
15
+ * (`types.ts`、`audit-scoring`、`constraints/`),不属符号声明面,整条跳过。
16
+ * - 覆盖方向只管 barrel 的**值**符号:类型面(interface/type)不要求逐个登记
17
+ * ——否则每次加一个公开类型都要回灌文档,正是本闸要避免的「连注释都要改」。
18
+ * 类型-only 的剔除在采集侧(读得懂 `export type { }` 与声明关键字的那一层)。
19
+ * - 「出现在该节」按标识符边界匹配(inline 提及即算登记),bullet 行首之外的正文同样参与,
20
+ * 但**其它 `## ` 节**的正文不参与——绕过闸需要把符号塞进「核心导出」节,而不是塞满全文件。
21
+ */
22
+
23
+ /** markdown「## 核心导出」节标题(其后括注允许,如 `## 核心导出 (dist/)`) */
24
+ const SECTION_HEADING_REGEX = /^##\s*核心导出/;
25
+ /** 任意二级标题:节的终点(三级标题属节内子结构,不终止) */
26
+ const H2_HEADING_REGEX = /^##\s/;
27
+ /** bullet 行 */
28
+ const BULLET_REGEX = /^\s*[-*]\s*(.*)$/;
29
+ /** bullet 行首(及 `/` 连接的后续)反引号片段 */
30
+ const LEADING_BACKTICK_REGEX = /^`([^`]+)`/;
31
+ /** token 间的 `/` 分隔(可含空白)+ 下一个反引号起点 */
32
+ const NEXT_TOKEN_REGEX = /^(?:\s*\/\s*)?\s*`/;
33
+ /** 合法导出标识符 */
34
+ const IDENTIFIER_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
35
+
36
+ /** 源码导出声明:`export [默认] [形式] 名字` 与 `export { … } [from '…']` */
37
+ const DECLARATION_REGEX =
38
+ /^export\s+(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(function|const|let|var|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
39
+ /** `export { … }` / `export type { … }`(可带 from) */
40
+ const EXPORT_LIST_REGEX = /^export\s+(type\s+)?\{([^}]*)\}(?:\s*from\s*['"]([^'"]+)['"])?/gm;
41
+ /** `export * from '…'` / `export * as ns from '…'` */
42
+ const STAR_EXPORT_REGEX = /^export\s+\*\s*(?:as\s+[A-Za-z_$][\w$]*\s*)?from\s*['"]([^'"]+)['"]/gm;
43
+
44
+ export interface ExportedSymbol {
45
+ /** 导出名(`a as b` 取 b) */
46
+ name: string;
47
+ /** 来源侧的本名(`a as b` 取 a;自身声明与无别名再导出时等于 name)——调用方按它核对正本是否真有此物 */
48
+ localName: string;
49
+ /** 类型-only(interface/type 声明、`export type { }` 块、逐项 `type` 标记) */
50
+ typeOnly: boolean;
51
+ /** 再导出的来源 spec(`from './x'`),模块自身声明为 undefined */
52
+ from?: string;
53
+ }
54
+
55
+ export interface ExportStatementParse {
56
+ symbols: ExportedSymbol[];
57
+ /** 待调用方解析的 `export *` 目标 spec(fs 侧才知道文件在不在) */
58
+ starFrom: string[];
59
+ }
60
+
61
+ export interface ContextReconcileInput {
62
+ /** CONTEXT.md 全文 */
63
+ contextMdContent: string;
64
+ /** 目录导出面全集:目录内所有 .ts 导出的符号名(值 + 类型,含 barrel 与内部 seam) */
65
+ exportSurface: string[];
66
+ /** index.ts barrel 再导出的**值**符号清单;无 index.ts 的目录不传(跳过覆盖方向) */
67
+ barrelExports?: string[];
68
+ }
69
+
70
+ export interface ContextVerdict {
71
+ /** 文档是否含「## 核心导出」节;无节 = 两方向都不参与判定 */
72
+ hasCoreExportsSection: boolean;
73
+ /** 该节解析出的符号声明(去重、按出现序) */
74
+ declaredSymbols: string[];
75
+ /** 幽灵:声明了但目录导出面没有 */
76
+ ghosts: string[];
77
+ /** 未登记:barrel 值符号未出现在该节 */
78
+ unlistedBarrelExports: string[];
79
+ }
80
+
81
+ /**
82
+ * 取「## 核心导出」节正文(不含标题行),到下一个二级标题为止。
83
+ * 无该节 → null。
84
+ */
85
+ export function parseCoreExportsSection(content: string): string | null {
86
+ const lines = content.split(/\r?\n/);
87
+ const start = lines.findIndex((line) => SECTION_HEADING_REGEX.test(line));
88
+ if (start === -1) return null;
89
+
90
+ const body: string[] = [];
91
+ for (let i = start + 1; i < lines.length; i++) {
92
+ if (H2_HEADING_REGEX.test(lines[i])) break;
93
+ body.push(lines[i]);
94
+ }
95
+ return body.join('\n');
96
+ }
97
+
98
+ /** 反引号片段 → 导出符号名;剥形参列表与 `type` 关键字前缀,非标识符返回 null */
99
+ function toSymbolName(token: string): string | null {
100
+ const bare = token.replace(/[((].*$/, '').trim();
101
+ const withoutKeyword = /^type\s+/.test(bare) ? bare.replace(/^type\s+/, '').trim() : bare;
102
+ return IDENTIFIER_REGEX.test(withoutKeyword) ? withoutKeyword : null;
103
+ }
104
+
105
+ /**
106
+ * 解析「## 核心导出」节的符号声明:只认 bullet 行首的反引号标识符
107
+ * (`/` 连接视为多个声明,其后的散文与 inline 提及不参与幽灵判定)。
108
+ */
109
+ export function parseDeclaredExportSymbols(content: string): string[] {
110
+ const section = parseCoreExportsSection(content);
111
+ if (section === null) return [];
112
+
113
+ const out: string[] = [];
114
+ const seen = new Set<string>();
115
+ for (const line of section.split(/\r?\n/)) {
116
+ const bullet = BULLET_REGEX.exec(line);
117
+ if (!bullet) continue;
118
+
119
+ let rest = bullet[1];
120
+ for (;;) {
121
+ const token = LEADING_BACKTICK_REGEX.exec(rest);
122
+ if (!token) break;
123
+ const name = toSymbolName(token[1]);
124
+ if (name && !seen.has(name)) {
125
+ seen.add(name);
126
+ out.push(name);
127
+ }
128
+ rest = rest.slice(token[0].length);
129
+ const separator = NEXT_TOKEN_REGEX.exec(rest);
130
+ if (!separator) break;
131
+ rest = rest.slice(separator[0].length - 1);
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+
137
+ /**
138
+ * 解析单个源文件的导出面(口径正本,采集侧与测试共用):
139
+ * 声明形、`export { }` 列表形(含别名与逐项 `type`)、`export default`、`export *`。
140
+ * 只做正则解析,不解析 spec 指向谁——那是调用方的 fs 活。
141
+ */
142
+ export function parseExportStatements(source: string): ExportStatementParse {
143
+ const symbols: ExportedSymbol[] = [];
144
+
145
+ for (const match of source.matchAll(DECLARATION_REGEX)) {
146
+ symbols.push({
147
+ name: match[2],
148
+ localName: match[2],
149
+ typeOnly: match[1] === 'interface' || match[1] === 'type',
150
+ });
151
+ }
152
+
153
+ for (const match of source.matchAll(EXPORT_LIST_REGEX)) {
154
+ const blockIsType = Boolean(match[1]);
155
+ const from = match[3];
156
+ for (const part of match[2].split(',')) {
157
+ const specifier = part.trim();
158
+ if (!specifier || specifier === 'default') continue;
159
+ const typeOnly = blockIsType || /^type\s+/.test(specifier);
160
+ const cleaned = specifier.replace(/^type\s+/, '').trim();
161
+ const aliased = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(cleaned);
162
+ const single = /^([A-Za-z_$][\w$]*)$/.exec(cleaned);
163
+ if (aliased) symbols.push({ name: aliased[2], localName: aliased[1], typeOnly, ...(from ? { from } : {}) });
164
+ else if (single) symbols.push({ name: single[1], localName: single[1], typeOnly, ...(from ? { from } : {}) });
165
+ }
166
+ }
167
+
168
+ if (/^export\s+default\b/m.test(source)) {
169
+ symbols.push({ name: 'default', localName: 'default', typeOnly: false });
170
+ }
171
+
172
+ return {
173
+ symbols,
174
+ starFrom: [...source.matchAll(STAR_EXPORT_REGEX)].map((m) => m[1]),
175
+ };
176
+ }
177
+
178
+ /** 符号是否在该节正文中出现(标识符边界匹配) */
179
+ function mentionedInSection(section: string, symbol: string): boolean {
180
+ const escaped = symbol.replace(/[$]/g, '\\$');
181
+ return new RegExp(`(?<![\\w$])${escaped}(?![\\w$])`).test(section);
182
+ }
183
+
184
+ /**
185
+ * CONTEXT.md 与目录导出面的双向判定(唯一真相源,sync-docs 与任何后续消费方共用)。
186
+ */
187
+ export function reconcileContext(input: ContextReconcileInput): ContextVerdict {
188
+ const section = parseCoreExportsSection(input.contextMdContent);
189
+ const hasCoreExportsSection = section !== null && section.trim() !== '';
190
+ const declaredSymbols = parseDeclaredExportSymbols(input.contextMdContent);
191
+ const surface = new Set(input.exportSurface);
192
+ const ghosts = declaredSymbols.filter((symbol) => !surface.has(symbol));
193
+
194
+ const barrelExports = [...new Set(input.barrelExports ?? [])];
195
+ const unlistedBarrelExports = hasCoreExportsSection
196
+ ? barrelExports.filter((symbol) => !mentionedInSection(section as string, symbol))
197
+ : [];
198
+
199
+ return { hasCoreExportsSection, declaredSymbols, ghosts, unlistedBarrelExports };
200
+ }
@@ -248,6 +248,31 @@ describe('FreshnessRunner', () => {
248
248
  expect(results[0].message).toContain('unlisted');
249
249
  });
250
250
 
251
+ it('推不出任何根时用调用方注入的源码根兜底,本模块不再自探(ADR-0023 决策 4)', () => {
252
+ setupFixture('dirdir-roots-injected', {
253
+ // 条目是单段目录 `src`:docDirs 非空(否则会先走「未找到目录引用」早退),
254
+ // 但推不出任何根(根 = 去掉最后一段),落到兜底分支
255
+ 'CLAUDE.md': ['## Key Subsystems', '', '| `src` | Core |'].join('\n'),
256
+ });
257
+ mkdirSync(join(TEST_ROOT, 'dirdir-roots-injected', 'src'), { recursive: true });
258
+ mkdirSync(join(TEST_ROOT, 'dirdir-roots-injected', 'lib', 'nested'), { recursive: true });
259
+
260
+ let injectedCalls = 0;
261
+ const config: DocFreshnessConfig = {
262
+ checks: [{ type: 'doc_dir_check', doc: 'CLAUDE.md', section: 'Key Subsystems' }],
263
+ };
264
+ const results = runner.runAll(config, join(TEST_ROOT, 'dirdir-roots-injected'), {
265
+ sourceRoots: () => {
266
+ injectedCalls++;
267
+ return ['lib'];
268
+ },
269
+ });
270
+
271
+ expect(injectedCalls).toBe(1);
272
+ expect(results[0].pass).toBe(false);
273
+ expect(results[0].message).toContain('lib/nested');
274
+ });
275
+
251
276
  it('passes for uncovered dir when skip_reverse_check is set', () => {
252
277
  setupFixture('dirdir-skip-reverse', {
253
278
  'CLAUDE.md': [
@@ -41,6 +41,12 @@ interface RunContext {
41
41
  projectPath: string;
42
42
  /** 外部提供的 required_dirs(来自 governance.context_files,用于 context_docs 检查的默认 dirs) */
43
43
  requiredDirs?: string[];
44
+ /**
45
+ * 源码根的**懒取**提供者(ADR-0023 决策 4):只在 doc_dir_check 从文档目录
46
+ * 推断不出根时用到。调用方传 `() => env.sourceRoots()` 即与本 run 其余消费方
47
+ * 共用一次探测;不传(sync-docs 等一次性调用方)则本模块自探一次。
48
+ */
49
+ sourceRoots?: () => string[];
44
50
  /** 外部提供的约束计数(用于内置 harner harness 的 doc_regex_count) */
45
51
  constraintCounts?: {
46
52
  ironLaws?: number;
@@ -58,7 +64,11 @@ export class FreshnessRunner {
58
64
  runAll(
59
65
  config: DocFreshnessConfig,
60
66
  projectPath: string,
61
- ctx?: { requiredDirs?: string[]; constraintCounts?: { ironLaws?: number; guidelines?: number } }
67
+ ctx?: {
68
+ requiredDirs?: string[];
69
+ sourceRoots?: () => string[];
70
+ constraintCounts?: { ironLaws?: number; guidelines?: number };
71
+ }
62
72
  ): FreshnessCheckResult[] {
63
73
  if (config.enabled === false) return [];
64
74
  if (!config.checks || config.checks.length === 0) return [];
@@ -66,6 +76,7 @@ export class FreshnessRunner {
66
76
  const context: RunContext = {
67
77
  projectPath,
68
78
  requiredDirs: ctx?.requiredDirs,
79
+ sourceRoots: ctx?.sourceRoots,
69
80
  constraintCounts: ctx?.constraintCounts,
70
81
  };
71
82
 
@@ -238,7 +249,7 @@ export class FreshnessRunner {
238
249
  // 2. fs → doc: 每个实际目录必须在文档中被覆盖
239
250
  if (!check.skip_reverse_check) {
240
251
  // 从文档目录中推断要扫描的根目录集
241
- const rootDirs = this.inferRootDirs(docDirs, ctx.projectPath);
252
+ const rootDirs = this.inferRootDirs(docDirs, ctx);
242
253
  const excludeSet = new Set(check.exclude || DEFAULT_SKIP_DIRS);
243
254
 
244
255
  for (const rootDir of rootDirs) {
@@ -287,7 +298,7 @@ export class FreshnessRunner {
287
298
  * 从文档目录集合推断要扫描的根目录
288
299
  * 例如 ["src/core", "src/cli/commands"] → ["src"]
289
300
  */
290
- private inferRootDirs(docDirs: Set<string>, projectPath: string): string[] {
301
+ private inferRootDirs(docDirs: Set<string>, ctx: RunContext): string[] {
291
302
  const roots = new Set<string>();
292
303
 
293
304
  for (const dir of docDirs) {
@@ -315,7 +326,10 @@ export class FreshnessRunner {
315
326
  }
316
327
  }
317
328
 
318
- return merged.size > 0 ? [...merged] : detectSourceRoots(projectPath);
329
+ if (merged.size > 0) return [...merged];
330
+ // 兜底 = 文档一个目录条目都没登记:按项目实际源码根全量比对(删掉它等于静默放行空文档)。
331
+ // 取根优先用调用方注入的懒取 provider(与本 run 其余消费方共用一次探测,ADR-0023 决策 4)
332
+ return ctx.sourceRoots ? ctx.sourceRoots() : detectSourceRoots(ctx.projectPath);
319
333
  }
320
334
 
321
335
  // ── doc_regex_count ──────────────────────────────────────────────
@@ -43,7 +43,7 @@ export interface GitEvidence {
43
43
  headDirs(): Set<string> | null;
44
44
  }
45
45
 
46
- /** utils/exec.runCommand 同一缓冲上限(大 diff 不 ENOBUFS) */
46
+ /** diff 不 ENOBUFS 的缓冲上限(1MB) */
47
47
  const GIT_MAX_BUFFER = 1024 * 1024;
48
48
 
49
49
  /**
@@ -18,7 +18,9 @@
18
18
  */
19
19
 
20
20
  import * as path from 'path';
21
+ import type { Constraint } from '../../types/constraint';
21
22
  import { getEffectiveConstraints } from '../effective-constraints';
23
+ import { resolveRunEnv, type RunTarget } from './run-env';
22
24
  import { getHarnessPackageVersion } from '../../utils/package-version';
23
25
  import { CONSTRAINTS_END_MARKER, renderConstraintsSection } from './injection-renderer';
24
26
  import {
@@ -72,13 +74,19 @@ function significantLines(section: string): string[] {
72
74
  /**
73
75
  * 检测约束注入段漂移
74
76
  *
75
- * @param projectRoot 项目根路径
77
+ * @param target 项目根路径,或本 run 的运行级观察面(CLI check 传入即与本 run 共用
78
+ * 同一份 config.yml 读取,漂移侧的生效集计算不再重读文件——ADR-0023 决策 2)
76
79
  * @param currentVersion 当前 harness 版本(缺省经 getHarnessPackageVersion 正本读取;测试可显式传入)
80
+ * @param expectedConstraints 本 run 已算好的生效集(CLI check 传入 → 生效集链路一次运行只算
81
+ * 一遍,且比对用的就是本 run 实际执法的那份)。不传则按 target 自取,行为与改前逐字一致
77
82
  */
78
83
  export function detectInjectionDrift(
79
- projectRoot: string,
80
- currentVersion: string = getHarnessPackageVersion()
84
+ target: RunTarget,
85
+ currentVersion: string = getHarnessPackageVersion(),
86
+ expectedConstraints?: Constraint[]
81
87
  ): InjectionDrift {
88
+ const env = resolveRunEnv(target);
89
+ const projectRoot = env.projectPath;
82
90
  const result: InjectionDrift = {
83
91
  hasDrift: false,
84
92
  notInjected: false,
@@ -86,8 +94,8 @@ export function detectInjectionDrift(
86
94
  fixHint: INJECTION_DRIFT_FIX_HINT,
87
95
  };
88
96
 
89
- const target = resolveInjectionTarget(projectRoot);
90
- if (!target) {
97
+ const injected = resolveInjectionTarget(projectRoot);
98
+ if (!injected) {
91
99
  // 两处均无标记段 = 未注入,不算漂移(check 不警告)。
92
100
  // 重复章节仍如实记录供 report 提示:旧落点 CLAUDE.md 优先,其次 AGENTS.md。
93
101
  const legacy =
@@ -100,8 +108,8 @@ export function detectInjectionDrift(
100
108
  return result;
101
109
  }
102
110
 
103
- result.injectionFile = target.file;
104
- const { content, startIdx, endIdx } = target;
111
+ result.injectionFile = injected.file;
112
+ const { content, startIdx, endIdx } = injected;
105
113
 
106
114
  // 重复章节:全文统计治理标题数(严格计数 injection-writer.countGovernanceHeadings,#83)
107
115
  const headingCount = countGovernanceHeadings(content);
@@ -117,7 +125,10 @@ export function detectInjectionDrift(
117
125
  }
118
126
 
119
127
  // 2. 内容漂移(条目级:期望渲染 vs 实际段)
120
- const expectedSection = renderConstraintsSection(getEffectiveConstraints(projectRoot), currentVersion);
128
+ const expectedSection = renderConstraintsSection(
129
+ expectedConstraints ?? getEffectiveConstraints(env),
130
+ currentVersion
131
+ );
121
132
  const expectedLines = new Set(significantLines(expectedSection));
122
133
  const actualLines = new Set(significantLines(actualSection));
123
134
  const missing = [...expectedLines].filter(l => !actualLines.has(l));
@@ -0,0 +1,207 @@
1
+ /**
2
+ * 运行级观察面(ADR-0023 决策 1)
3
+ *
4
+ * 一次 `harness check` 一份、跑完即弃:把「本项目有哪些源码根」「最近的 trace 记录」
5
+ * 「`.harness/config.yml` 与自定义约束文件写了什么」这类上行数据的读取收在此处,
6
+ * 同一次运行内每个文件至多读一次。
7
+ *
8
+ * 为什么不是一个对象而是两个:checker 拿到的 `CheckEnv` 里含 `context`,而 `context` 正是
9
+ * context-builder **通过本对象读文件算出来的**——同一对象不能既是构造者的输入又是它的产物。
10
+ * 故这里只放「不需要 context 就能造」的部分,`CheckEnv` 之后从它派生(见 checkers/types.ts)。
11
+ *
12
+ * 与 #87 的 git 证据同一形状:一次运行一份、经参数显式传下去,不做进程级单例——
13
+ * 测试在同进程内反复改文件,常驻缓存会把「先改后读」读成老内容。
14
+ */
15
+
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import * as yaml from 'js-yaml';
19
+ import { detectSourceRoots } from '../../utils/detect-source-roots';
20
+ import { reconcileCapabilities, type CapabilityVerdict } from './capabilities-reconcile';
21
+ import { readJsonlWindow, type JsonlWindow, type JsonlReadResult } from '../../utils/jsonl';
22
+ import { DEFAULT_TRACE_FILE, type ExecutionTrace } from '../../types/trace';
23
+ import type { CustomConstraintDefinition } from '../../types/project-config';
24
+
25
+ /** config.yml 在项目根下的相对路径(读取口径唯一落点) */
26
+ const CONFIG_FILE_REL = path.join('.harness', 'config.yml');
27
+
28
+ /** 能力表在项目根下的落点(checker 侧存在性探测与本读面共用此常量) */
29
+ export const CAPABILITIES_FILE_REL = 'CAPABILITIES.md';
30
+
31
+ /**
32
+ * 尾部窗口上限 = 本 run 内最大的尾部消费方(有无失败记录看 20 条,见 context-builder)
33
+ *
34
+ * 更小的窗口(验证证据看 10 条)从同一份行文本上截,不再二次读文件。
35
+ */
36
+ const TRACE_TAIL_WINDOW = 20;
37
+
38
+ export interface RunEnv {
39
+ /** 项目根(生产代码的每个 IO 点都用它,不再各自取 cwd——harness#95) */
40
+ readonly projectPath: string;
41
+ /** 最近 limit 条 trace 记录(≤ TRACE_TAIL_WINDOW;一次运行内至多读文件一次) */
42
+ traceTail(limit: number): JsonlReadResult<ExecutionTrace>;
43
+ /** 源码根相对路径列表(一次运行内至多探测一次) */
44
+ sourceRoots(): string[];
45
+ /**
46
+ * `.harness/config.yml` 的解析结果(一次运行内至多读一次)
47
+ *
48
+ * 口径与改前的 `loadRawProjectConfig` 逐字一致:文件缺失 → undefined;
49
+ * 空文件 → `{}`;YAML 解析失败 → **抛出**(兜底与否属各消费方的判定,不在读面上替它决定)。
50
+ */
51
+ rawConfig(): Record<string, unknown> | undefined;
52
+ /**
53
+ * `.harness/<fileName>` 里的自定义约束定义(一次运行内同名文件至多读一次)
54
+ *
55
+ * 文件名由消费方(ProjectConfigLoader)从合并后的 config 解析后传入——
56
+ * 「`custom_constraints_file` 怎么写才算数」属加载器口径,不在读面上判。
57
+ * 文件缺失或无 `custom_constraints` 段 → `{}`(与改前的直读逐字一致)。
58
+ */
59
+ customConstraints(fileName: string): Record<string, CustomConstraintDefinition>;
60
+ /**
61
+ * `CAPABILITIES.md` 的读取 + 解析 + 与代码实况的对照判定(ADR-0023 决策 4)
62
+ *
63
+ * 此前 capability_sync 与 docs_freshness 各读一遍文档、各跑一遍 `reconcileCapabilities`
64
+ * (studio 量得单次 7.6–15.9ms,两边解析输入逐字节相同)。此处一次供给两消费方。
65
+ * 文档缺失 → `undefined`(消费方据此 skip / 无幽灵)。
66
+ *
67
+ * **memo 只记第一次传入的 `populationFiles`**:同一 run 内两消费方必须给同一份代码实况清单。
68
+ * 二者一律经 `collectPopulationFiles(projectPath, roots, scan)` 取数,形状由构造保证;
69
+ * 出现第三个消费方且清单不同,就必须改成按输入分列的缓存而不是静默复用。
70
+ */
71
+ capabilities(populationFiles: string[]): ProjectCapabilities | undefined;
72
+ }
73
+
74
+ /**
75
+ * 一次运行内的能力表面貌:原文 + 源码根 + 代码实况清单 + 对照判定
76
+ *
77
+ * `verdict.deadEntries` 按「文件系统中真实存在也算活」的严口径算(带存在性 oracle),
78
+ * capability_sync 不读它、docs_freshness 只读它;`verdict.uncoveredChanges` 在此恒空
79
+ * ——本观察面不收 git 变更清单(属证据面,#87),增量覆盖由 capability_sync 自己按
80
+ * `coverageEntries` 算。直调 `reconcileCapabilities` 的消费方(如 sync-docs)仍可传 changedFiles。
81
+ */
82
+ export interface ProjectCapabilities {
83
+ readonly content: string;
84
+ readonly sourceRoots: string[];
85
+ readonly populationFiles: string[];
86
+ readonly verdict: CapabilityVerdict;
87
+ }
88
+
89
+ /**
90
+ * 构造运行级观察面
91
+ *
92
+ * 懒建:没有消费方就不碰文件(`harness check` 在干净树与脏树上读的并不一样多)。
93
+ */
94
+ export function createRunEnv(projectPath: string): RunEnv {
95
+ const traceFile = path.join(projectPath, DEFAULT_TRACE_FILE);
96
+ let window: JsonlWindow<ExecutionTrace> | null = null;
97
+ let sourceRoots: string[] | null = null;
98
+ let configLoaded = false;
99
+ let configRaw: Record<string, unknown> | undefined;
100
+ const customDefs = new Map<string, Record<string, CustomConstraintDefinition>>();
101
+ /** 能力表 memo:undefined = 未算,null = 项目无 CAPABILITIES.md */
102
+ let caps: ProjectCapabilities | null | undefined;
103
+
104
+ return {
105
+ projectPath,
106
+ traceTail(limit: number) {
107
+ if (!window) {
108
+ // 计数去向:豁免(harness#100)——本观察面只供给「最近有无 fail / 有无 pass」两个
109
+ // 布尔证据的消费方,它们不读 skippedLines;坏行占尾部槽位只会让证据变少(方向保守),
110
+ // 告知需要改判定形状,属行为变更不在本票(口径与改前的两处独立 tail 读逐字一致)
111
+ window = readJsonlWindow<ExecutionTrace>(traceFile, 'skip', TRACE_TAIL_WINDOW);
112
+ }
113
+ return window.take(limit);
114
+ },
115
+ sourceRoots: roots,
116
+ rawConfig() {
117
+ if (!configLoaded) {
118
+ configRaw = readYamlFile(path.join(projectPath, CONFIG_FILE_REL));
119
+ configLoaded = true;
120
+ }
121
+ return configRaw;
122
+ },
123
+ customConstraints(fileName: string) {
124
+ let defs = customDefs.get(fileName);
125
+ if (!defs) {
126
+ defs = readCustomConstraints(path.join(projectPath, '.harness', fileName));
127
+ customDefs.set(fileName, defs);
128
+ }
129
+ return defs;
130
+ },
131
+ capabilities(populationFiles: string[]) {
132
+ if (caps === undefined) {
133
+ const content = readCapabilitiesContent();
134
+ caps = content === undefined
135
+ ? null
136
+ : {
137
+ content,
138
+ sourceRoots: roots(),
139
+ populationFiles,
140
+ verdict: reconcileCapabilities({
141
+ content,
142
+ populationFiles,
143
+ sourceRoots: roots(),
144
+ fileExists: rel => fs.existsSync(path.join(projectPath, rel)),
145
+ }),
146
+ };
147
+ }
148
+ return caps ?? undefined;
149
+ },
150
+ };
151
+
152
+ /** 源根探测 memo(sourceRoots() 与能力表共用同一份) */
153
+ function roots(): string[] {
154
+ if (!sourceRoots) {
155
+ sourceRoots = detectSourceRoots(projectPath);
156
+ }
157
+ return sourceRoots;
158
+ }
159
+
160
+ /** 读 CAPABILITIES.md 原文;缺失 → undefined(不算异常,未采用该约定的项目占多数) */
161
+ function readCapabilitiesContent(): string | undefined {
162
+ const file = path.join(projectPath, CAPABILITIES_FILE_REL);
163
+ return fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : undefined;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 读一个自定义约束文件并取出 `custom_constraints` 段
169
+ *
170
+ * 缺失 → `{}`。文档解析成空值(空 yaml 文件)时取段会抛——改前直读就是这个行为,
171
+ * 不在此新增兜底:畸形配置文件该炸在配置上,不该被读面抹平。
172
+ */
173
+ function readCustomConstraints(absPath: string): Record<string, CustomConstraintDefinition> {
174
+ if (!fs.existsSync(absPath)) return {};
175
+ const loaded = yaml.load(fs.readFileSync(absPath, 'utf-8')) as {
176
+ custom_constraints?: Record<string, CustomConstraintDefinition>;
177
+ };
178
+ return loaded.custom_constraints ?? {};
179
+ }
180
+
181
+ /**
182
+ * 读并解析一个 YAML 配置文件
183
+ *
184
+ * 缺失 → undefined、空文件 → `{}`,两者都不算异常;解析失败照抛(消费方各自兜底)。
185
+ * 不判 mtime/size 指纹——ADR-0023 决策 2 撤销了那套进程级口径,新鲜度只由「一次运行一份」保证。
186
+ */
187
+ function readYamlFile(absPath: string): Record<string, unknown> | undefined {
188
+ if (!fs.existsSync(absPath)) return undefined;
189
+ const loaded = yaml.load(fs.readFileSync(absPath, 'utf-8'));
190
+ return (loaded ?? {}) as Record<string, unknown>;
191
+ }
192
+
193
+ /**
194
+ * 运行级访问器的入参形状:观察面,或项目根路径
195
+ *
196
+ * 传路径 = 「只此一次」的便利形状:自造一枚用完即弃的观察面,读到的是当下的文件内容。
197
+ * 想在本 run 内共享读取(`harness check` 的全部消费方)就把同一枚 RunEnv 传下去。
198
+ */
199
+ export type RunTarget = RunEnv | string;
200
+
201
+ /** 归一 RunTarget(非 RunEnv 即按项目根自造一枚一次性观察面) */
202
+ export function resolveRunEnv(target?: RunTarget): RunEnv {
203
+ if (typeof target === 'object' && target !== null) return target;
204
+ return createRunEnv(target || process.cwd());
205
+ }
206
+
207
+ export { TRACE_TAIL_WINDOW };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 约束 trigger 归一与匹配(harness#105 单点)
3
+ *
4
+ * 约束域谓词,不是通用工具:`normalizeTriggers` 的「undefined → [] 不匹配任何操作」
5
+ * 语义只在 trigger 字段上成立。此前二者住在 `utils/exec.ts`、由 core/constraints
6
+ * 跨模块拉用,utils 层因此挂着一枚只有约束域会调的假接缝(harness#137 归位)。
7
+ * checker 与 prompt 渲染共用本模块,禁止就地重写。
8
+ */
9
+
10
+ /**
11
+ * 将单值或数组统一为数组(用于 trigger 等字段)
12
+ */
13
+ export function normalizeTriggers<T>(
14
+ value: T | T[] | undefined,
15
+ fallback: T[] = []
16
+ ): T[] {
17
+ if (value === undefined || value === null) return fallback;
18
+ return Array.isArray(value) ? value : [value];
19
+ }
20
+
21
+ /**
22
+ * 判断约束是否匹配当前操作集合(#105:trigger 匹配语义单一来源)
23
+ *
24
+ * 语义 = normalizeTriggers(undefined → [] 不匹配任何操作)+ 交集判定,
25
+ * 任一 operation 命中任一 trigger 即匹配。
26
+ */
27
+ export function matchesTrigger<T>(
28
+ constraint: { trigger: T | T[] | undefined },
29
+ operations: T[]
30
+ ): boolean {
31
+ const triggers = normalizeTriggers(constraint.trigger);
32
+ return operations.some(op => triggers.includes(op));
33
+ }
@@ -11,6 +11,7 @@ import type { Constraint } from '../types/constraint';
11
11
  import type { MergedConstraintsConfig } from '../types/project-config';
12
12
  import { PROMPTS } from './constraints/definitions';
13
13
  import { ProjectConfigLoader } from './project-config-loader';
14
+ import type { RunTarget } from './constraints/run-env';
14
15
 
15
16
  /**
16
17
  * 获取项目当前生效的约束集(check + prompt,带 kind)
@@ -21,15 +22,25 @@ import { ProjectConfigLoader } from './project-config-loader';
21
22
  * (带 appliesTo 的 prompt 仅当 config.yml `scenes` 与其交集非空时保留,
22
23
  * 缺省 scenes=[] 即场景专属 prompt 默认不进入生效集)。
23
24
  *
24
- * @param projectRoot 项目根路径(缺省 process.cwd())
25
+ * @param target 项目根路径,或本 run 的运行级观察面(缺省 process.cwd())
26
+ * 传观察面 = config.yml 读取与本 run 其余消费方共用同一份(ADR-0023 决策 2)
25
27
  * @param options.preset 覆盖 config.yml 的 preset;仅在项目无自定义配置时生效
26
28
  * (与 getMergedConstraintsConfig 同一优先级规则),不传则尊重 config.yml
27
29
  */
28
30
  export function getEffectiveConstraints(
29
- projectRoot: string = process.cwd(),
31
+ target: RunTarget = process.cwd(),
30
32
  options?: { preset?: string }
31
33
  ): Constraint[] {
32
- const merged = getMergedConstraintsConfig(projectRoot, options);
34
+ return constraintsFromMerged(getMergedConstraintsConfig(target, options));
35
+ }
36
+
37
+ /**
38
+ * 从已算好的合并配置取出生效集清单(与 getEffectiveConstraints 同一形状)
39
+ *
40
+ * 给已经持有 `MergedConstraintsConfig` 的消费方复用(CLI check 把这一份同时交给
41
+ * 漂移检测,避免生效集链路在一次运行里算两遍,ADR-0023 步骤 4.5)。
42
+ */
43
+ export function constraintsFromMerged(merged: MergedConstraintsConfig): Constraint[] {
33
44
  return [
34
45
  ...Object.values(merged.ironLaws),
35
46
  ...Object.values(merged.guidelines),
@@ -47,13 +58,13 @@ export function getEffectiveConstraints(
47
58
  * preset(工单 23 语义:项目自定义配置优先于 CLI 预设);不传 preset 时
48
59
  * 完全尊重 config.yml 的 preset 键。
49
60
  *
50
- * @param projectRoot 项目根路径(缺省 process.cwd())
61
+ * @param target 项目根路径,或本 run 的运行级观察面(缺省 process.cwd())
51
62
  */
52
63
  export function getMergedConstraintsConfig(
53
- projectRoot: string = process.cwd(),
64
+ target: RunTarget = process.cwd(),
54
65
  options?: { preset?: string }
55
66
  ): MergedConstraintsConfig {
56
- const loader = new ProjectConfigLoader(projectRoot);
67
+ const loader = new ProjectConfigLoader(target);
57
68
  loader.load();
58
69
  if (loader.hasCustomConfig() || !options?.preset) {
59
70
  return loader.mergeConstraints();