@dommaker/harness 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (563) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/harness.js +17 -1
  3. package/dist/agents/index.d.ts +6 -2
  4. package/dist/agents/index.d.ts.map +1 -1
  5. package/dist/agents/index.js +6 -16
  6. package/dist/agents/index.js.map +1 -1
  7. package/dist/cli/commands/__tests__/gate-decision.d.ts +18 -0
  8. package/dist/cli/commands/__tests__/gate-decision.d.ts.map +1 -0
  9. package/dist/cli/commands/__tests__/gate-decision.js +26 -0
  10. package/dist/cli/commands/__tests__/gate-decision.js.map +1 -0
  11. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  12. package/dist/cli/commands/acceptance.js +17 -28
  13. package/dist/cli/commands/acceptance.js.map +1 -1
  14. package/dist/cli/commands/check.d.ts +19 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +36 -22
  17. package/dist/cli/commands/check.js.map +1 -1
  18. package/dist/cli/commands/command.d.ts +0 -1
  19. package/dist/cli/commands/command.d.ts.map +1 -1
  20. package/dist/cli/commands/command.js +8 -6
  21. package/dist/cli/commands/command.js.map +1 -1
  22. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  23. package/dist/cli/commands/constraints-retire.js +36 -57
  24. package/dist/cli/commands/constraints-retire.js.map +1 -1
  25. package/dist/cli/commands/contract.d.ts.map +1 -1
  26. package/dist/cli/commands/contract.js +36 -42
  27. package/dist/cli/commands/contract.js.map +1 -1
  28. package/dist/cli/commands/definitions.d.ts +2 -0
  29. package/dist/cli/commands/definitions.d.ts.map +1 -1
  30. package/dist/cli/commands/definitions.js +11 -6
  31. package/dist/cli/commands/definitions.js.map +1 -1
  32. package/dist/cli/commands/failure.d.ts.map +1 -1
  33. package/dist/cli/commands/failure.js +16 -2
  34. package/dist/cli/commands/failure.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +14 -8
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +172 -364
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/knowledge-view.d.ts +90 -0
  40. package/dist/cli/commands/knowledge-view.d.ts.map +1 -0
  41. package/dist/cli/commands/knowledge-view.js +198 -0
  42. package/dist/cli/commands/knowledge-view.js.map +1 -0
  43. package/dist/cli/commands/knowledge.d.ts +137 -52
  44. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  45. package/dist/cli/commands/knowledge.js +534 -400
  46. package/dist/cli/commands/knowledge.js.map +1 -1
  47. package/dist/cli/commands/passes-gate.d.ts +4 -1
  48. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  49. package/dist/cli/commands/passes-gate.js +11 -2
  50. package/dist/cli/commands/passes-gate.js.map +1 -1
  51. package/dist/cli/commands/performance.d.ts.map +1 -1
  52. package/dist/cli/commands/performance.js +19 -31
  53. package/dist/cli/commands/performance.js.map +1 -1
  54. package/dist/cli/commands/report.d.ts.map +1 -1
  55. package/dist/cli/commands/report.js +2 -1
  56. package/dist/cli/commands/report.js.map +1 -1
  57. package/dist/cli/commands/review.d.ts.map +1 -1
  58. package/dist/cli/commands/review.js +13 -25
  59. package/dist/cli/commands/review.js.map +1 -1
  60. package/dist/cli/commands/scaffold-templates.d.ts +73 -0
  61. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -0
  62. package/dist/cli/commands/scaffold-templates.js +424 -0
  63. package/dist/cli/commands/scaffold-templates.js.map +1 -0
  64. package/dist/cli/commands/scaffold.d.ts +105 -0
  65. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  66. package/dist/cli/commands/scaffold.js +263 -0
  67. package/dist/cli/commands/scaffold.js.map +1 -0
  68. package/dist/cli/commands/security.d.ts.map +1 -1
  69. package/dist/cli/commands/security.js +22 -24
  70. package/dist/cli/commands/security.js.map +1 -1
  71. package/dist/cli/commands/spec-baseline-check.d.ts +43 -0
  72. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  73. package/dist/cli/commands/spec-baseline-check.js +109 -34
  74. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  75. package/dist/cli/commands/status.d.ts +8 -0
  76. package/dist/cli/commands/status.d.ts.map +1 -1
  77. package/dist/cli/commands/status.js +5 -7
  78. package/dist/cli/commands/status.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  80. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  82. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  84. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/index.js +86 -27
  86. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  87. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  88. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  89. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  90. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  91. package/dist/cli/commands/validate.d.ts +0 -16
  92. package/dist/cli/commands/validate.d.ts.map +1 -1
  93. package/dist/cli/commands/validate.js +2 -97
  94. package/dist/cli/commands/validate.js.map +1 -1
  95. package/dist/cli/gate-command.d.ts +37 -0
  96. package/dist/cli/gate-command.d.ts.map +1 -0
  97. package/dist/cli/gate-command.js +64 -0
  98. package/dist/cli/gate-command.js.map +1 -0
  99. package/dist/cli/state-io.d.ts +34 -0
  100. package/dist/cli/state-io.d.ts.map +1 -0
  101. package/dist/cli/state-io.js +71 -0
  102. package/dist/cli/state-io.js.map +1 -0
  103. package/dist/completion-checkers/index.d.ts +1 -1
  104. package/dist/completion-checkers/index.d.ts.map +1 -1
  105. package/dist/completion-checkers/index.js +0 -15
  106. package/dist/completion-checkers/index.js.map +1 -1
  107. package/dist/context/knowledge-injector.d.ts +4 -2
  108. package/dist/context/knowledge-injector.d.ts.map +1 -1
  109. package/dist/context/knowledge-injector.js +13 -6
  110. package/dist/context/knowledge-injector.js.map +1 -1
  111. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  112. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  113. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  114. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  115. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  116. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  117. package/dist/core/constraints/check-cache.d.ts +1 -1
  118. package/dist/core/constraints/check-cache.js +1 -1
  119. package/dist/core/constraints/checker.d.ts +8 -3
  120. package/dist/core/constraints/checker.d.ts.map +1 -1
  121. package/dist/core/constraints/checker.js +30 -19
  122. package/dist/core/constraints/checker.js.map +1 -1
  123. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  124. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  125. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  126. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  127. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  128. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  129. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  130. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  131. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  132. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  133. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  134. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  135. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  136. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  137. package/dist/core/constraints/checkers/types.d.ts +10 -6
  138. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  139. package/dist/core/constraints/checkers/types.js +7 -3
  140. package/dist/core/constraints/checkers/types.js.map +1 -1
  141. package/dist/core/constraints/context-builder.d.ts +11 -10
  142. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  143. package/dist/core/constraints/context-builder.js +31 -27
  144. package/dist/core/constraints/context-builder.js.map +1 -1
  145. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  146. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  147. package/dist/core/constraints/context-reconcile.js +161 -0
  148. package/dist/core/constraints/context-reconcile.js.map +1 -0
  149. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  150. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  151. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  152. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  153. package/dist/core/constraints/git-evidence.js +1 -1
  154. package/dist/core/constraints/git-evidence.js.map +1 -1
  155. package/dist/core/constraints/injection-drift.d.ts +7 -2
  156. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  157. package/dist/core/constraints/injection-drift.js +13 -7
  158. package/dist/core/constraints/injection-drift.js.map +1 -1
  159. package/dist/core/constraints/run-env.d.ts +92 -0
  160. package/dist/core/constraints/run-env.d.ts.map +1 -0
  161. package/dist/core/constraints/run-env.js +175 -0
  162. package/dist/core/constraints/run-env.js.map +1 -0
  163. package/dist/core/constraints/triggers.d.ts +22 -0
  164. package/dist/core/constraints/triggers.d.ts.map +1 -0
  165. package/dist/core/constraints/triggers.js +31 -0
  166. package/dist/core/constraints/triggers.js.map +1 -0
  167. package/dist/core/effective-constraints.d.ts +13 -4
  168. package/dist/core/effective-constraints.d.ts.map +1 -1
  169. package/dist/core/effective-constraints.js +16 -6
  170. package/dist/core/effective-constraints.js.map +1 -1
  171. package/dist/core/effective-set.d.ts +8 -15
  172. package/dist/core/effective-set.d.ts.map +1 -1
  173. package/dist/core/effective-set.js +7 -12
  174. package/dist/core/effective-set.js.map +1 -1
  175. package/dist/core/index.d.ts +1 -0
  176. package/dist/core/index.d.ts.map +1 -1
  177. package/dist/core/index.js.map +1 -1
  178. package/dist/core/project-config-loader.d.ts +39 -17
  179. package/dist/core/project-config-loader.d.ts.map +1 -1
  180. package/dist/core/project-config-loader.js +51 -122
  181. package/dist/core/project-config-loader.js.map +1 -1
  182. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  183. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  184. package/dist/core/validators/check-handlers/http.js +12 -7
  185. package/dist/core/validators/check-handlers/http.js.map +1 -1
  186. package/dist/failure/classifier.d.ts +1 -1
  187. package/dist/failure/classifier.d.ts.map +1 -1
  188. package/dist/failure/classifier.js +8 -8
  189. package/dist/failure/classifier.js.map +1 -1
  190. package/dist/failure/index.d.ts +2 -1
  191. package/dist/failure/index.d.ts.map +1 -1
  192. package/dist/failure/index.js +9 -17
  193. package/dist/failure/index.js.map +1 -1
  194. package/dist/failure/recorder.d.ts +1 -1
  195. package/dist/failure/recorder.d.ts.map +1 -1
  196. package/dist/gates/checker-gate.d.ts +2 -1
  197. package/dist/gates/checker-gate.d.ts.map +1 -1
  198. package/dist/gates/checker-gate.js +3 -2
  199. package/dist/gates/checker-gate.js.map +1 -1
  200. package/dist/gates/command.d.ts +10 -4
  201. package/dist/gates/command.d.ts.map +1 -1
  202. package/dist/gates/command.js +36 -62
  203. package/dist/gates/command.js.map +1 -1
  204. package/dist/gates/contract.d.ts.map +1 -1
  205. package/dist/gates/contract.js +0 -4
  206. package/dist/gates/contract.js.map +1 -1
  207. package/dist/gates/definitions.d.ts +2 -1
  208. package/dist/gates/definitions.d.ts.map +1 -1
  209. package/dist/gates/definitions.js +6 -5
  210. package/dist/gates/definitions.js.map +1 -1
  211. package/dist/gates/index.d.ts +0 -4
  212. package/dist/gates/index.d.ts.map +1 -1
  213. package/dist/gates/index.js +1 -6
  214. package/dist/gates/index.js.map +1 -1
  215. package/dist/gates/performance.d.ts.map +1 -1
  216. package/dist/gates/performance.js +0 -4
  217. package/dist/gates/performance.js.map +1 -1
  218. package/dist/gates/runner.d.ts +1 -1
  219. package/dist/gates/runner.js +1 -1
  220. package/dist/gates/security.d.ts +1 -1
  221. package/dist/gates/security.d.ts.map +1 -1
  222. package/dist/gates/security.js +5 -6
  223. package/dist/gates/security.js.map +1 -1
  224. package/dist/gates/types.d.ts +14 -17
  225. package/dist/gates/types.d.ts.map +1 -1
  226. package/dist/gates/types.js.map +1 -1
  227. package/dist/hooks/bootstrap.d.ts +6 -4
  228. package/dist/hooks/bootstrap.d.ts.map +1 -1
  229. package/dist/hooks/bootstrap.js +25 -13
  230. package/dist/hooks/bootstrap.js.map +1 -1
  231. package/dist/hooks/config.d.ts +6 -2
  232. package/dist/hooks/config.d.ts.map +1 -1
  233. package/dist/hooks/config.js +5 -1
  234. package/dist/hooks/config.js.map +1 -1
  235. package/dist/hooks/index.d.ts +1 -1
  236. package/dist/hooks/index.d.ts.map +1 -1
  237. package/dist/hooks/index.js +0 -15
  238. package/dist/hooks/index.js.map +1 -1
  239. package/dist/hooks/pipeline.d.ts +19 -1
  240. package/dist/hooks/pipeline.d.ts.map +1 -1
  241. package/dist/hooks/pipeline.js +42 -4
  242. package/dist/hooks/pipeline.js.map +1 -1
  243. package/dist/hooks/registry.d.ts +11 -8
  244. package/dist/hooks/registry.d.ts.map +1 -1
  245. package/dist/hooks/registry.js +22 -7
  246. package/dist/hooks/registry.js.map +1 -1
  247. package/dist/hooks/types.d.ts +25 -7
  248. package/dist/hooks/types.d.ts.map +1 -1
  249. package/dist/index.d.ts +3 -3
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +3 -4
  252. package/dist/index.js.map +1 -1
  253. package/dist/knowledge/audit-scoring.d.ts +87 -0
  254. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  255. package/dist/knowledge/audit-scoring.js +470 -0
  256. package/dist/knowledge/audit-scoring.js.map +1 -0
  257. package/dist/knowledge/audit.d.ts +11 -59
  258. package/dist/knowledge/audit.d.ts.map +1 -1
  259. package/dist/knowledge/audit.js +47 -522
  260. package/dist/knowledge/audit.js.map +1 -1
  261. package/dist/knowledge/import.d.ts.map +1 -1
  262. package/dist/knowledge/import.js +2 -0
  263. package/dist/knowledge/import.js.map +1 -1
  264. package/dist/knowledge/index-generator.d.ts.map +1 -1
  265. package/dist/knowledge/index-generator.js +4 -5
  266. package/dist/knowledge/index-generator.js.map +1 -1
  267. package/dist/knowledge/index.d.ts +3 -2
  268. package/dist/knowledge/index.d.ts.map +1 -1
  269. package/dist/knowledge/index.js +3 -16
  270. package/dist/knowledge/index.js.map +1 -1
  271. package/dist/knowledge/ingest.js +1 -1
  272. package/dist/knowledge/ingest.js.map +1 -1
  273. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  274. package/dist/knowledge/lifecycle.js +4 -2
  275. package/dist/knowledge/lifecycle.js.map +1 -1
  276. package/dist/knowledge/migration.d.ts +3 -0
  277. package/dist/knowledge/migration.d.ts.map +1 -1
  278. package/dist/knowledge/migration.js +8 -2
  279. package/dist/knowledge/migration.js.map +1 -1
  280. package/dist/knowledge/query.d.ts +6 -0
  281. package/dist/knowledge/query.d.ts.map +1 -1
  282. package/dist/knowledge/query.js +8 -2
  283. package/dist/knowledge/query.js.map +1 -1
  284. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  285. package/dist/knowledge/reference-tracker.js +4 -6
  286. package/dist/knowledge/reference-tracker.js.map +1 -1
  287. package/dist/knowledge/store.d.ts +22 -16
  288. package/dist/knowledge/store.d.ts.map +1 -1
  289. package/dist/knowledge/store.js +53 -12
  290. package/dist/knowledge/store.js.map +1 -1
  291. package/dist/knowledge/tree-walker.d.ts +19 -0
  292. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  293. package/dist/knowledge/tree-walker.js +30 -0
  294. package/dist/knowledge/tree-walker.js.map +1 -0
  295. package/dist/knowledge/types.d.ts +16 -0
  296. package/dist/knowledge/types.d.ts.map +1 -1
  297. package/dist/monitoring/traces.d.ts +5 -3
  298. package/dist/monitoring/traces.d.ts.map +1 -1
  299. package/dist/monitoring/traces.js +19 -5
  300. package/dist/monitoring/traces.js.map +1 -1
  301. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  302. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  303. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  304. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  305. package/dist/tools/index.d.ts +4 -1
  306. package/dist/tools/index.d.ts.map +1 -1
  307. package/dist/tools/index.js +7 -15
  308. package/dist/tools/index.js.map +1 -1
  309. package/dist/types/checkpoint.d.ts +5 -0
  310. package/dist/types/checkpoint.d.ts.map +1 -1
  311. package/dist/types/project-config.d.ts +18 -0
  312. package/dist/types/project-config.d.ts.map +1 -1
  313. package/dist/types/trace.d.ts +13 -4
  314. package/dist/types/trace.d.ts.map +1 -1
  315. package/dist/types/trace.js +4 -3
  316. package/dist/types/trace.js.map +1 -1
  317. package/dist/utils/exec.d.ts +4 -27
  318. package/dist/utils/exec.d.ts.map +1 -1
  319. package/dist/utils/exec.js +4 -94
  320. package/dist/utils/exec.js.map +1 -1
  321. package/dist/utils/jsonl.d.ts +17 -7
  322. package/dist/utils/jsonl.d.ts.map +1 -1
  323. package/dist/utils/jsonl.js +144 -17
  324. package/dist/utils/jsonl.js.map +1 -1
  325. package/dist/utils/numeric-flag.d.ts +44 -0
  326. package/dist/utils/numeric-flag.d.ts.map +1 -0
  327. package/dist/utils/numeric-flag.js +62 -0
  328. package/dist/utils/numeric-flag.js.map +1 -0
  329. package/package.json +1 -1
  330. package/src/CONTEXT.md +5 -2
  331. package/src/__tests__/acceptance-gate.test.ts +7 -4
  332. package/src/__tests__/checker-extra.test.ts +3 -2
  333. package/src/__tests__/checker.test.ts +4 -3
  334. package/src/__tests__/checkpoint.test.ts +26 -9
  335. package/src/__tests__/context-files-resolution.test.ts +29 -15
  336. package/src/__tests__/contract-gate.test.ts +19 -40
  337. package/src/__tests__/error-classifier.test.ts +1 -1
  338. package/src/__tests__/exec.test.ts +45 -107
  339. package/src/__tests__/failure-recorder.test.ts +7 -5
  340. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  341. package/src/__tests__/layering.test.ts +217 -25
  342. package/src/__tests__/passes-gate.test.ts +9 -4
  343. package/src/__tests__/performance-gate.test.ts +3 -15
  344. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  345. package/src/__tests__/project-config-loader.test.ts +6 -3
  346. package/src/__tests__/public-exports.test.ts +0 -1
  347. package/src/__tests__/public-type-surface.test.ts +5 -4
  348. package/src/__tests__/public-value-surface.test.ts +182 -0
  349. package/src/__tests__/review-gate.test.ts +4 -6
  350. package/src/__tests__/security-gate.test.ts +5 -20
  351. package/src/__tests__/skip-semantics.test.ts +5 -3
  352. package/src/__tests__/spec-validator.test.ts +3 -2
  353. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  354. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  355. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  356. package/src/__tests__/trace-analyzer.test.ts +5 -3
  357. package/src/__tests__/trace-collector.test.ts +5 -3
  358. package/src/agents/index.ts +13 -2
  359. package/src/cli/__tests__/gate-command.test.ts +162 -0
  360. package/src/cli/__tests__/state-io.test.ts +50 -0
  361. package/src/cli/commands/CONTEXT.md +30 -5
  362. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  363. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  364. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  365. package/src/cli/commands/__tests__/check-read-count.test.ts +170 -0
  366. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  367. package/src/cli/commands/__tests__/check.test.ts +63 -44
  368. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  369. package/src/cli/commands/__tests__/command.test.ts +33 -17
  370. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  371. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  372. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  373. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  374. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  375. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  376. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  377. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  378. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  379. package/src/cli/commands/__tests__/init.test.ts +82 -6
  380. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  381. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  382. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  383. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  384. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  385. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  386. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  387. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  388. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  389. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  390. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  391. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  392. package/src/cli/commands/__tests__/report.test.ts +23 -1
  393. package/src/cli/commands/__tests__/review.test.ts +10 -17
  394. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  395. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  396. package/src/cli/commands/__tests__/security.test.ts +21 -24
  397. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  398. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  399. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  400. package/src/cli/commands/__tests__/status-extra.test.ts +25 -8
  401. package/src/cli/commands/__tests__/status.test.ts +46 -18
  402. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  403. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  404. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  405. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  406. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  407. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  408. package/src/cli/commands/acceptance.ts +24 -29
  409. package/src/cli/commands/check.ts +60 -35
  410. package/src/cli/commands/command.ts +9 -8
  411. package/src/cli/commands/constraints-retire.ts +57 -70
  412. package/src/cli/commands/contract.ts +44 -47
  413. package/src/cli/commands/definitions.ts +12 -6
  414. package/src/cli/commands/failure.ts +16 -3
  415. package/src/cli/commands/init.ts +242 -389
  416. package/src/cli/commands/knowledge-view.ts +224 -0
  417. package/src/cli/commands/knowledge.ts +643 -448
  418. package/src/cli/commands/passes-gate.ts +12 -3
  419. package/src/cli/commands/performance.ts +31 -38
  420. package/src/cli/commands/report.ts +3 -2
  421. package/src/cli/commands/review.ts +22 -28
  422. package/src/cli/commands/scaffold-templates.ts +406 -0
  423. package/src/cli/commands/scaffold.ts +302 -0
  424. package/src/cli/commands/security.ts +27 -23
  425. package/src/cli/commands/spec-baseline-check.ts +158 -33
  426. package/src/cli/commands/status.ts +12 -7
  427. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  428. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  429. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  430. package/src/cli/commands/sync-docs/index.ts +103 -28
  431. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  432. package/src/cli/commands/validate.ts +1 -104
  433. package/src/cli/gate-command.ts +87 -0
  434. package/src/cli/state-io.ts +53 -0
  435. package/src/completion-checkers/index.ts +10 -1
  436. package/src/context/CONTEXT.md +2 -1
  437. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  438. package/src/context/__tests__/session-manager.test.ts +19 -12
  439. package/src/context/knowledge-injector.ts +14 -7
  440. package/src/core/CONTEXT.md +10 -7
  441. package/src/core/__tests__/effective-set.test.ts +36 -60
  442. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  443. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  444. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  445. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  446. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  447. package/src/core/constraints/check-cache.ts +1 -1
  448. package/src/core/constraints/checker.ts +30 -14
  449. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  450. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  451. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  452. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  453. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  454. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  455. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  456. package/src/core/constraints/checkers/types.ts +14 -8
  457. package/src/core/constraints/context-builder.ts +35 -26
  458. package/src/core/constraints/context-reconcile.ts +200 -0
  459. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  460. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  461. package/src/core/constraints/git-evidence.ts +1 -1
  462. package/src/core/constraints/injection-drift.ts +19 -8
  463. package/src/core/constraints/run-env.ts +207 -0
  464. package/src/core/constraints/triggers.ts +33 -0
  465. package/src/core/effective-constraints.ts +17 -6
  466. package/src/core/effective-set.ts +8 -24
  467. package/src/core/index.ts +2 -0
  468. package/src/core/project-config-loader.ts +54 -102
  469. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  470. package/src/core/validators/check-handlers/http.ts +18 -7
  471. package/src/failure/CONTEXT.md +2 -1
  472. package/src/failure/classifier.ts +2 -2
  473. package/src/failure/index.ts +14 -2
  474. package/src/failure/recorder.ts +1 -1
  475. package/src/gates/CONTEXT.md +13 -7
  476. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  477. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  478. package/src/gates/__tests__/command.test.ts +8 -15
  479. package/src/gates/__tests__/contract.test.ts +5 -5
  480. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  481. package/src/gates/__tests__/performance.test.ts +0 -13
  482. package/src/gates/__tests__/review.test.ts +0 -1
  483. package/src/gates/__tests__/runner.test.ts +1 -1
  484. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  485. package/src/gates/__tests__/security.test.ts +0 -13
  486. package/src/gates/checker-gate.ts +3 -2
  487. package/src/gates/command.ts +51 -74
  488. package/src/gates/contract.ts +0 -5
  489. package/src/gates/definitions.ts +6 -5
  490. package/src/gates/index.ts +0 -5
  491. package/src/gates/performance.ts +1 -6
  492. package/src/gates/runner.ts +1 -1
  493. package/src/gates/security.ts +9 -11
  494. package/src/gates/types.ts +20 -23
  495. package/src/hooks/CONTEXT.md +10 -9
  496. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  497. package/src/hooks/__tests__/pipeline.test.ts +133 -17
  498. package/src/hooks/__tests__/registry.test.ts +51 -3
  499. package/src/hooks/bootstrap.ts +35 -15
  500. package/src/hooks/config.ts +6 -2
  501. package/src/hooks/index.ts +10 -1
  502. package/src/hooks/pipeline.ts +51 -6
  503. package/src/hooks/registry.ts +31 -12
  504. package/src/hooks/types.ts +26 -7
  505. package/src/index.ts +1 -2
  506. package/src/knowledge/CONTEXT.md +14 -7
  507. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  508. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  509. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  510. package/src/knowledge/__tests__/audit.test.ts +291 -208
  511. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  512. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  513. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  514. package/src/knowledge/__tests__/migration.test.ts +35 -6
  515. package/src/knowledge/__tests__/query.test.ts +3 -2
  516. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  517. package/src/knowledge/__tests__/store.test.ts +149 -4
  518. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  519. package/src/knowledge/audit-scoring.ts +589 -0
  520. package/src/knowledge/audit.ts +55 -599
  521. package/src/knowledge/import.ts +2 -0
  522. package/src/knowledge/index-generator.ts +4 -6
  523. package/src/knowledge/index.ts +27 -2
  524. package/src/knowledge/ingest.ts +1 -1
  525. package/src/knowledge/lifecycle.ts +5 -2
  526. package/src/knowledge/migration.ts +8 -2
  527. package/src/knowledge/query.ts +8 -1
  528. package/src/knowledge/reference-tracker.ts +5 -7
  529. package/src/knowledge/store.ts +75 -16
  530. package/src/knowledge/tree-walker.ts +29 -0
  531. package/src/knowledge/types.ts +19 -0
  532. package/src/monitoring/CONTEXT.md +5 -2
  533. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  534. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  535. package/src/monitoring/traces.ts +19 -5
  536. package/src/release/CONTEXT.md +1 -1
  537. package/src/release/__tests__/integrity.test.ts +21 -12
  538. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  539. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  540. package/src/tools/index.ts +5 -1
  541. package/src/types/checkpoint.ts +6 -0
  542. package/src/types/project-config.ts +21 -0
  543. package/src/types/trace.ts +14 -4
  544. package/src/utils/__tests__/file-walk.test.ts +3 -2
  545. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  546. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  547. package/src/utils/__tests__/jsonl.test.ts +61 -14
  548. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  549. package/src/utils/__tests__/package-version.test.ts +2 -2
  550. package/src/utils/exec.ts +4 -64
  551. package/src/utils/jsonl.ts +159 -20
  552. package/src/utils/numeric-flag.ts +64 -0
  553. package/dist/failure/types.d.ts +0 -9
  554. package/dist/failure/types.d.ts.map +0 -1
  555. package/dist/failure/types.js +0 -25
  556. package/dist/failure/types.js.map +0 -1
  557. package/dist/gates/effective-gates.d.ts +0 -30
  558. package/dist/gates/effective-gates.d.ts.map +0 -1
  559. package/dist/gates/effective-gates.js +0 -81
  560. package/dist/gates/effective-gates.js.map +0 -1
  561. package/src/failure/types.ts +0 -9
  562. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  563. package/src/gates/effective-gates.ts +0 -99
@@ -0,0 +1,170 @@
1
+ /**
2
+ * 一次 check 运行内「每个项目文件至多读一次」计数闸(ADR-0023 决策 5 ①)
3
+ *
4
+ * 形状照 `check.test.ts:78-106` 的 `recordingEvidence()`:真执行、只在中间记一笔,
5
+ * 不改任何行为。这里数的不是「观察口被请求几次」(memo 在观察面上,请求多次本就应当),
6
+ * 而是**底层文件读取发生了几次** —— 那才是本 ADR 承诺的口径。
7
+ *
8
+ * 夹具必须让两个吃能力表的 checker 都真评估(触发条件 module_modification +
9
+ * 有 CAPABILITIES.md + 有自定义约束文件),否则闸门是空跑的。
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+ import { execFileSync } from 'child_process';
16
+ import { check } from '../check';
17
+ import { captureIO, type CapturingIO } from '../../command-contract';
18
+
19
+ type ReadCall = { file: string; via: 'readFileSync' | 'open' };
20
+
21
+ // 计数器挂在 globalThis:jest.mock 工厂会被提前调用,闭包外提会撞 TDZ
22
+ jest.mock('fs', () => {
23
+ const actual = jest.requireActual<typeof import('fs')>('fs');
24
+ const g = globalThis as unknown as { __ADR0023_READS__?: ReadCall[] };
25
+ if (!g.__ADR0023_READS__) g.__ADR0023_READS__ = [];
26
+ const bucket = g.__ADR0023_READS__;
27
+ const name = (p: unknown): string =>
28
+ Buffer.isBuffer(p) ? p.toString('utf8') : String(p);
29
+ const passthrough = (fn: string) =>
30
+ (actual as unknown as Record<string, (...args: unknown[]) => unknown>)[fn];
31
+
32
+ return {
33
+ ...actual,
34
+ readFileSync: jest.fn((p: unknown, ...rest: unknown[]) => {
35
+ bucket.push({ file: name(p), via: 'readFileSync' });
36
+ return passthrough('readFileSync')(p, ...rest);
37
+ }),
38
+ // 有界读走 open/fstat/read:只记只读打开(写 trace 的 append 不算读取)
39
+ openSync: jest.fn((p: unknown, flags?: unknown, ...rest: unknown[]) => {
40
+ if (flags === undefined || flags === 'r' || flags === 'rs') {
41
+ bucket.push({ file: name(p), via: 'open' });
42
+ }
43
+ return passthrough('openSync')(p, flags, ...rest);
44
+ }),
45
+ };
46
+ });
47
+
48
+ const reads = () =>
49
+ ((globalThis as unknown as { __ADR0023_READS__?: ReadCall[] }).__ADR0023_READS__ ||= []);
50
+
51
+ jest.mock('chalk', () => ({
52
+ blue: (s: string) => s,
53
+ yellow: (s: string) => s,
54
+ green: (s: string) => s,
55
+ gray: (s: string) => s,
56
+ red: (s: string) => s,
57
+ }));
58
+
59
+ function git(dir: string, ...args: string[]): void {
60
+ execFileSync('git', args, { cwd: dir, stdio: 'pipe' });
61
+ }
62
+
63
+ function write(dir: string, rel: string, content: string): void {
64
+ const target = path.join(dir, rel);
65
+ fs.mkdirSync(path.dirname(target), { recursive: true });
66
+ fs.writeFileSync(target, content, 'utf-8');
67
+ }
68
+
69
+ const TABLE = ['| 能力 | 路径 | 说明 |', '|---|---|---|'];
70
+
71
+ /** 一个「两 checker 都会真评估」的项目:源码根下两个已登记文件 + 自定义约束 + 一条 trace */
72
+ function fixtureRepo(): string {
73
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-read-count-'));
74
+ git(dir, 'init', '-q');
75
+ git(dir, 'config', 'user.email', 'fixture@example.com');
76
+ git(dir, 'config', 'user.name', 'Fixture');
77
+ write(dir, 'README.md', '# fixture\n');
78
+ write(dir, '.harness/config.yml', 'preset: standard\n');
79
+ write(
80
+ dir,
81
+ '.harness/custom-constraints.yml',
82
+ 'custom_constraints:\n ab_custom_rule:\n level: guideline\n rule: 自定义规则\n message: 自定义提示\n trigger: manual\n'
83
+ );
84
+ write(
85
+ dir,
86
+ 'CAPABILITIES.md',
87
+ ['# CAPABILITIES.md', '', ...TABLE, '| 既有 | src/existing.ts | 在 |', '| 嵌套 | src/nested/deep.ts | 在 |'].join('\n') + '\n'
88
+ );
89
+ write(dir, 'AGENTS.md', '<!-- PRESERVE:governance -->\n## Governance Rules\n\n条款\n\n<!-- /PRESERVE:governance -->\n');
90
+ write(dir, 'src/existing.ts', 'export const a = 1;\n');
91
+ write(dir, 'src/nested/deep.ts', 'export const d = 1;\n');
92
+ write(dir, '.harness/logs/traces.log', '{"constraintId":"fixture","result":"pass"}\n');
93
+ // ADR-0026:状态文件进夹具,智能提示的状态读取随之进计数表(一次运行至多读一次)
94
+ write(dir, '.harness/.state.json', '{}');
95
+ git(dir, 'add', '.');
96
+ git(dir, 'commit', '-q', '-m', 'baseline');
97
+ // 改一个已登记的源文件并 stage → 触发条件 module_modification
98
+ write(dir, 'src/existing.ts', 'export const a = 2;\n');
99
+ git(dir, 'add', '--', 'src/existing.ts');
100
+ return dir;
101
+ }
102
+
103
+ describe('一次 check 的文件读取计数闸(ADR-0023 决策 5 ①)', () => {
104
+ let io: CapturingIO;
105
+ let dir: string;
106
+
107
+ /** 相对项目根的文件名 → 读取次数(只读语义的 readFileSync 与只读 open 都算一次) */
108
+ function readCounts(): Map<string, number> {
109
+ const counts = new Map<string, number>();
110
+ for (const call of reads()) {
111
+ const rel = path.relative(dir, call.file);
112
+ if (!rel || rel.startsWith('..')) continue; // 非本项目文件(模块加载等)不计
113
+ counts.set(rel, (counts.get(rel) ?? 0) + 1);
114
+ }
115
+ return counts;
116
+ }
117
+
118
+ beforeEach(() => {
119
+ // 原地清空:mock 工厂在装载时就captured了这个数组引用,换新数组计数会落到旧的去向(假绿)
120
+ reads().length = 0;
121
+ io = captureIO();
122
+ dir = fixtureRepo();
123
+ // trace 自 #139 起锚在 projectPath 上写入(本夹具内),写入不经读取计数器
124
+ });
125
+
126
+ afterEach(() => {
127
+ fs.rmSync(dir, { recursive: true, force: true });
128
+ });
129
+
130
+ it('整张读取表如实冻结:本次运行读了哪些项目文件、各读几次', async () => {
131
+ const result = await check({ staged: true, projectPath: dir }, io);
132
+ expect(result.kind).toBe('ok');
133
+ // 正对照:两个吃能力表的 checker 确实跑了(否则下面的断言是空跑)
134
+ expect(io.outText()).toContain('自定义约束: 1 条');
135
+ expect(io.outText()).toContain('触发条件: module_modification');
136
+
137
+ expect([...readCounts().entries()].sort()).toEqual(
138
+ [
139
+ // —— 一次运行一份:本 ADR 的承诺面 ——
140
+ ['.harness/config.yml', 1],
141
+ ['.harness/custom-constraints.yml', 1],
142
+ ['CAPABILITIES.md', 1],
143
+ ['.harness/logs/traces.log', 2],
144
+ ['.harness/.state.json', 1], // ADR-0026:智能提示经 StateIO 读状态,恰一次
145
+ // —— 已知例外(改动需明写理由)——
146
+ // traces.log = 2:head 50(智能提示的阈值判定,决策 3)与 tail 20(有无失败证据,
147
+ // 决策 4)是两个不同窗口的**有界**读;并成一个窗口就等于回到整读。
148
+ // AGENTS.md = 3 / CLAUDE.md = 2:决策表明示本 ADR 不处理治理文档的重复读
149
+ // (1.5–25KB,<1ms)。CLAUDE.md 在本夹具里并不存在,2 = 落点路由与旧落点兜底
150
+ // 各尝试读一次(读失败的尝试同样计入)。
151
+ ['AGENTS.md', 3],
152
+ ['CLAUDE.md', 2],
153
+ ].sort()
154
+ );
155
+ });
156
+
157
+ it('除上表点名例外外的项目文件,一次运行内一律至多读一次', async () => {
158
+ await check({ staged: true, projectPath: dir }, io);
159
+
160
+ const except = new Set([
161
+ path.join('.harness', 'logs', 'traces.log'), // 两个窗口的有界读,见上表理由
162
+ 'AGENTS.md', // 治理文档,ADR-0023 决策表明示不处理
163
+ 'CLAUDE.md',
164
+ ]);
165
+ const offenders = [...readCounts()].filter(
166
+ ([file, n]) => !except.has(file) && n > 1
167
+ );
168
+ expect(offenders).toEqual([]);
169
+ });
170
+ });
@@ -34,8 +34,11 @@ jest.mock('../../../core/constraints/checker', () => ({
34
34
  }),
35
35
  }));
36
36
 
37
+ // 命令 per-run 锚根构造收集器(harness#139):构造替身即拦截 trace 落盘
37
38
  jest.mock('../../../monitoring/traces', () => ({
38
- getTraceCollector: jest.fn(() => ({ record: jest.fn() })),
39
+ TraceCollector: jest.fn(function () {
40
+ return { record: jest.fn() };
41
+ }),
39
42
  }));
40
43
 
41
44
  // Mock effective-constraints(ADR-0001:check 经 getMergedConstraintsConfig 走生效集链路)
@@ -7,8 +7,8 @@
7
7
  * 真 ConstraintChecker,断言 CLI 输出形状与 CommandResult。
8
8
  * 取证次数经 createGitEvidence 的计数 runner 观测——执行仍走真 git,不 mock 子进程。
9
9
  *
10
- * trace 记录器由命令侧组合根接线(harness#88):套件把全局收集器重定向到临时文件,
11
- * 既不断言宿主仓写入,也不污染它。
10
+ * trace 记录器由命令侧组合根接线(harness#88)并锚根构造(#139):落点在本用例的
11
+ * 临时项目里(`<projectPath>/.harness/logs/traces.log`),宿主仓 .harness/ 不受影响。
12
12
  */
13
13
 
14
14
  import * as fs from 'fs';
@@ -18,7 +18,8 @@ import { execFileSync } from 'child_process';
18
18
 
19
19
  import { check, listLaws } from '../check';
20
20
  import { captureIO, type CapturingIO } from '../../command-contract';
21
- import { configureTraceCollector } from '../../../monitoring/traces';
21
+ import type { HarnessState, StateIO } from '../../state-io';
22
+ import { DEFAULT_TRACE_FILE } from '../../../types/trace';
22
23
  import {
23
24
  createGitEvidence,
24
25
  realGitCommandRunner,
@@ -75,6 +76,36 @@ function passTraces(dir: string, lines = 1): void {
75
76
  );
76
77
  }
77
78
 
79
+ /**
80
+ * 本项目的 trace 落点记录(harness#139:命令侧收集器锚根构造,写的就是 `<projectPath>/DEFAULT_TRACE_FILE` 正本)
81
+ *
82
+ * 含 `passTraces()` 预置的种子行,只看命令自己写入的用例按行数切掉前缀。
83
+ */
84
+ function projectTraces(
85
+ dir: string
86
+ ): Array<{ constraintId: string; result: string; projectPath?: string; evidence?: string[] }> {
87
+ const file = path.join(dir, DEFAULT_TRACE_FILE);
88
+ if (!fs.existsSync(file)) return [];
89
+ return fs
90
+ .readFileSync(file, 'utf-8')
91
+ .split('\n')
92
+ .filter(Boolean)
93
+ .map(line => JSON.parse(line));
94
+ }
95
+
96
+ /**
97
+ * 内存 StateIO 假件(ADR-0026):状态相关测试经注入面驱动,不碰真文件系统。
98
+ * `snapshot()` 给断言用——读的是假件当前持有的状态,不是磁盘。
99
+ */
100
+ function memoryStateIO(initial: HarnessState = {}): StateIO & { snapshot(): HarnessState } {
101
+ let state = initial;
102
+ return {
103
+ read: () => state,
104
+ write: (next: HarnessState) => { state = next; },
105
+ snapshot: () => state,
106
+ };
107
+ }
108
+
78
109
  /** 计数 git 证据:既记录证据方法请求,也记录实际 spawn 的 git 命令(执行走真 adapter) */
79
110
  function recordingEvidence(projectPath: string): {
80
111
  evidence: GitEvidence;
@@ -118,28 +149,9 @@ function stagedTestDeletion(): string {
118
149
 
119
150
  describe('check command(真 git fixture)', () => {
120
151
  let io: CapturingIO;
121
- let traceDir: string;
122
- let traceFile: string;
123
- let traceSeq = 0;
124
-
125
- beforeAll(() => {
126
- traceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-check-traces-'));
127
- });
128
-
129
- afterAll(() => {
130
- configureTraceCollector({});
131
- try {
132
- fs.rmSync(traceDir, { recursive: true, force: true });
133
- } catch {
134
- // ignore
135
- }
136
- });
137
152
 
138
153
  beforeEach(() => {
139
154
  io = captureIO();
140
- // 命令侧接线的是全局收集器(默认 cwd 相对写入):重定向到套件临时文件,宿主仓 .harness/ 不受影响
141
- traceFile = path.join(traceDir, `trace-${++traceSeq}.log`);
142
- configureTraceCollector({ traceFile });
143
155
  });
144
156
 
145
157
  describe('判定与输出形状', () => {
@@ -240,6 +252,22 @@ describe('check command(真 git fixture)', () => {
240
252
  expect(io.outText()).not.toContain('变更文件');
241
253
  expect(result).toEqual({ kind: 'ok' });
242
254
  });
255
+
256
+ it('不带 -p 时标注预设来源,带 -p 时打印所传值(ADR-0023 步骤 4.5)', async () => {
257
+ const dir = gitRepo();
258
+ stageChange(dir, 'src/existing.ts', 'export const a = 2;\n');
259
+ passTraces(dir);
260
+
261
+ await check({ staged: true, projectPath: dir, trigger: 'code_implementation' }, io);
262
+ await check(
263
+ { preset: 'standard', staged: true, projectPath: dir, trigger: 'code_implementation' },
264
+ io
265
+ );
266
+
267
+ const out = io.outText();
268
+ expect(out).toContain('预设: (按 config.yml,缺省 standard)');
269
+ expect(out).toContain('预设: standard');
270
+ });
243
271
  });
244
272
 
245
273
  describe('判定证据外显(harness#119)', () => {
@@ -255,14 +283,9 @@ describe('check command(真 git fixture)', () => {
255
283
  return dir;
256
284
  }
257
285
 
258
- /** 从本用例 trace 文件里取某条约束的记录 */
259
- function traceOf(constraintId: string) {
260
- return fs
261
- .readFileSync(traceFile, 'utf-8')
262
- .split('\n')
263
- .filter(Boolean)
264
- .map((line) => JSON.parse(line) as { constraintId: string; result: string; evidence?: string[] })
265
- .find((l) => l.constraintId === constraintId);
286
+ /** 从本用例项目的 trace 正本里取某条约束的记录(#139:落点锚在 projectPath) */
287
+ function traceOf(dir: string, constraintId: string) {
288
+ return projectTraces(dir).find(l => l.constraintId === constraintId);
266
289
  }
267
290
 
268
291
  it('变更文件未登记 → 警告随附证据点名该文件', async () => {
@@ -308,7 +331,7 @@ describe('check command(真 git fixture)', () => {
308
331
  io
309
332
  );
310
333
 
311
- const record = traceOf('capability_sync');
334
+ const record = traceOf(dir, 'capability_sync');
312
335
  expect(record?.result).toBe('pass');
313
336
  expect(record?.evidence?.join('\n')).toContain('src/nested/deep.ts');
314
337
  });
@@ -364,17 +387,15 @@ describe('check command(真 git fixture)', () => {
364
387
  const dir = gitRepo();
365
388
  stageChange(dir, 'src/existing.ts', 'export const a = 2;\n');
366
389
  passTraces(dir);
390
+ const seeded = projectTraces(dir).length;
367
391
 
368
392
  await check(
369
393
  { preset: 'standard', staged: true, projectPath: dir, trigger: 'code_implementation' },
370
394
  io
371
395
  );
372
396
 
373
- const traces = fs
374
- .readFileSync(traceFile, 'utf-8')
375
- .split('\n')
376
- .filter(Boolean)
377
- .map(l => JSON.parse(l) as { constraintId: string; result: string; projectPath: string });
397
+ // #139:落点锚在本 run 的 projectPath 上,切掉夹具预置的证据行
398
+ const traces = projectTraces(dir).slice(seeded);
378
399
 
379
400
  expect(traces.map(t => t.constraintId)).toEqual(
380
401
  expect.arrayContaining(['incremental_progress', 'no_implementation_without_requirement'])
@@ -475,27 +496,25 @@ describe('check command(真 git fixture)', () => {
475
496
  });
476
497
  });
477
498
 
478
- describe('智能提示(真 traces.log 与状态文件)', () => {
499
+ describe('智能提示(真 traces.log + 注入 StateIO 假件)', () => {
479
500
  it('记录数首次达到 50 时提示,并落盘已提示状态', async () => {
480
501
  const dir = gitRepo();
481
502
  passTraces(dir, 50);
503
+ const stateIO = memoryStateIO();
482
504
 
483
- await check({ preset: 'standard', staged: true, projectPath: dir, trigger: 'manual' }, io);
505
+ await check({ preset: 'standard', staged: true, projectPath: dir, trigger: 'manual', stateIO }, io);
484
506
 
485
507
  expect(io.outText()).toContain('记录已足够,运行 harness status 查看统计');
486
508
  expect(io.outText()).toContain('────────────────');
487
- const state = JSON.parse(
488
- fs.readFileSync(path.join(dir, '.harness', '.state.json'), 'utf-8')
489
- );
490
- expect(state.shownHints).toContain('trace_50');
509
+ expect(stateIO.snapshot().shownHints).toContain('trace_50');
491
510
  });
492
511
 
493
512
  it('已提示过的不再重复提示', async () => {
494
513
  const dir = gitRepo();
495
514
  passTraces(dir, 50);
496
- write(dir, '.harness/.state.json', JSON.stringify({ shownHints: ['trace_50'] }));
515
+ const stateIO = memoryStateIO({ shownHints: ['trace_50'] });
497
516
 
498
- await check({ preset: 'standard', staged: true, projectPath: dir, trigger: 'manual' }, io);
517
+ await check({ preset: 'standard', staged: true, projectPath: dir, trigger: 'manual', stateIO }, io);
499
518
 
500
519
  expect(io.outText()).not.toContain('记录已足够');
501
520
  expect(io.outText()).not.toContain('────────────────');
@@ -0,0 +1,60 @@
1
+ /**
2
+ * command 门禁的两个死配置位已删除(#135,架构评审候选 6)
3
+ *
4
+ * `CommandGateConfig.strict` 只在构造时读一次、任何判定路径从不使用——`harness command --strict`
5
+ * 与不带它判定完全一致;`CommandGateConfig.customBlacklist` 全仓零生产注入者,运行时扩展点是
6
+ * `addRule()`。triage 裁决两项都**删**(判据同 ADR-0018/0022:不接进判定、不留「配了不生效」),
7
+ * CLI `--strict` 旗帜随之 removal(属 breaking,见 ADR-0024)。
8
+ *
9
+ * 手法照 `passes-gate-dead-options.test.ts` / `init-dead-options.test.ts`:编译期
10
+ * `@ts-expect-error` 钉字段不存在 + 运行期钉定义表旗帜面与配置形状。
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { GATE_DEFINITIONS } from '../../../gates/definitions';
16
+ import { CommandGate } from '../../../gates/command';
17
+ import type { CommandGateConfig } from '../../../gates/types';
18
+ import type { CommandCheckOptions } from '../command';
19
+
20
+ const commandGateCli = GATE_DEFINITIONS.find(gate => gate.id === 'command')!.cli;
21
+
22
+ describe('command 门禁的 strict / customBlacklist 已删除(#135)', () => {
23
+ it('定义表已注册 command 门禁(前提)', () => {
24
+ expect(commandGateCli).toBeDefined();
25
+ });
26
+
27
+ it('command 旗帜面逐字冻结,不含 --strict', () => {
28
+ expect(commandGateCli.options.map(option => option.flags)).toEqual([
29
+ '-l, --level',
30
+ '--list',
31
+ '--json',
32
+ ]);
33
+ });
34
+
35
+ it('CommandGateConfig 与 CommandCheckOptions 的 strict / customBlacklist 已从类型面消失(编译期钉)', () => {
36
+ // @ts-expect-error strict 零消费者(只在构造时读取),随 #135 从 CommandGateConfig 删除
37
+ const removedStrict: CommandGateConfig = { strict: true };
38
+ // @ts-expect-error customBlacklist 零生产注入者,运行时扩展点是 addRule(),随 #135 删除
39
+ const removedCustom: CommandGateConfig = { customBlacklist: [] };
40
+ // @ts-expect-error CLI 同名字段随 --strict 旗帜与装配一并删除
41
+ const removedOption: CommandCheckOptions = { strict: true };
42
+ expect([removedStrict, removedCustom, removedOption]).toHaveLength(3);
43
+ });
44
+
45
+ it('活字段仍可赋值(防止删多)', () => {
46
+ const live: CommandGateConfig = { ignoreCategories: ['database'] };
47
+ const liveCli: CommandCheckOptions = { level: true, list: true, json: false };
48
+ expect(Object.keys(live)).toHaveLength(1);
49
+ expect(Object.keys(liveCli)).toHaveLength(3);
50
+ });
51
+
52
+ it('配置读取面只剩 ignoreCategories(运行期形状钉)', () => {
53
+ expect(Object.keys(new CommandGate().getConfig())).toEqual(['ignoreCategories']);
54
+ });
55
+
56
+ it('CLI 装配点不再引用模块级单例出口(--level 与默认分支同一实例来源)', () => {
57
+ const cliSource = fs.readFileSync(path.join(__dirname, '..', 'command.ts'), 'utf-8');
58
+ expect(cliSource).not.toMatch(/getCommandRiskLevel|getCommandGate\(/);
59
+ });
60
+ });
@@ -6,12 +6,12 @@
6
6
  */
7
7
 
8
8
  import { executeCommand } from '../command';
9
- import { createCommandGate, getCommandRiskLevel } from '../../../gates';
9
+ import { createCommandGate } from '../../../gates';
10
10
  import { captureIO, type CapturingIO } from '../../command-contract';
11
+ import { decide, fakeGate } from './gate-decision';
11
12
 
12
13
  jest.mock('../../../gates', () => ({
13
14
  createCommandGate: jest.fn(),
14
- getCommandRiskLevel: jest.fn(),
15
15
  DEFAULT_COMMAND_BLACKLIST: [
16
16
  { id: 'rule-1', level: 'block', message: 'No rm -rf', category: 'destructive', pattern: 'rm -rf' },
17
17
  { id: 'rule-2', level: 'warn', message: 'No DROP TABLE', category: 'database', pattern: 'DROP TABLE' },
@@ -27,10 +27,21 @@ jest.mock('chalk', () => ({
27
27
  }));
28
28
 
29
29
  const mockCreateGate = createCommandGate as jest.MockedFunction<typeof createCommandGate>;
30
- const mockGetRisk = getCommandRiskLevel as jest.MockedFunction<typeof getCommandRiskLevel>;
31
30
 
32
- function mockGate(check: jest.Mock): void {
33
- mockCreateGate.mockReturnValue({ check } as any);
31
+ /**
32
+ * 替身工厂:命令穿过统一接口,替身产决策而非裸报告。
33
+ * `--level` 与默认分支共用这一台实例(#135),故等级也从替身上取。
34
+ */
35
+ function mockGate(
36
+ report: { passed: boolean; message: string; details?: Record<string, any> },
37
+ level: 'high' | 'medium' | 'low' = 'low',
38
+ ): { evaluate: jest.Mock; getRiskLevel: jest.Mock } {
39
+ const gate = {
40
+ ...fakeGate(decide('command', report.passed, report.message, report.details)),
41
+ getRiskLevel: jest.fn().mockReturnValue(level),
42
+ };
43
+ mockCreateGate.mockReturnValue(gate as any);
44
+ return gate;
34
45
  }
35
46
 
36
47
  /** 从捕获输出里取第一条可解析为该形状的 JSON 行 */
@@ -86,7 +97,7 @@ describe('command command', () => {
86
97
 
87
98
  describe('default mode', () => {
88
99
  it('放行:ok,打印 ✓', async () => {
89
- mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
100
+ mockGate({ passed: true, message: 'OK' });
90
101
 
91
102
  const result = await executeCommand('safe-cmd', {}, io);
92
103
 
@@ -95,7 +106,7 @@ describe('command command', () => {
95
106
  });
96
107
 
97
108
  it('拒绝:fail + 裁决原因,打印 ✗', async () => {
98
- mockGate(jest.fn().mockResolvedValue({ passed: false, message: 'Blocked' }));
109
+ mockGate({ passed: false, message: 'Blocked' });
99
110
 
100
111
  const result = await executeCommand('rm -rf /', {}, io);
101
112
 
@@ -104,7 +115,7 @@ describe('command command', () => {
104
115
  });
105
116
 
106
117
  it('--json 输出裁决详情', async () => {
107
- mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK', details: {} }));
118
+ mockGate({ passed: true, message: 'OK', details: {} });
108
119
 
109
120
  await executeCommand('cmd', { json: true }, io);
110
121
 
@@ -119,8 +130,7 @@ describe('command command', () => {
119
130
 
120
131
  describe('--level', () => {
121
132
  it('low:ok,打印等级', async () => {
122
- mockGetRisk.mockReturnValue('low');
123
- mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
133
+ mockGate({ passed: true, message: 'OK' }, 'low');
124
134
 
125
135
  const result = await executeCommand('safe-cmd', { level: true }, io);
126
136
 
@@ -129,8 +139,7 @@ describe('command command', () => {
129
139
  });
130
140
 
131
141
  it('high:fail + 等级原因(严重级条件式译成 kind)', async () => {
132
- mockGetRisk.mockReturnValue('high');
133
- mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
142
+ mockGate({ passed: true, message: 'OK' }, 'high');
134
143
 
135
144
  const result = await executeCommand('rm -rf /', { level: true }, io);
136
145
 
@@ -138,9 +147,7 @@ describe('command command', () => {
138
147
  });
139
148
 
140
149
  it('high 等级不再执行黑名单裁决分支', async () => {
141
- const mockCheck = jest.fn().mockResolvedValue({ passed: true, message: 'OK' });
142
- mockGetRisk.mockReturnValue('high');
143
- mockGate(mockCheck);
150
+ mockGate({ passed: true, message: 'OK' }, 'high');
144
151
 
145
152
  const result = await executeCommand('rm -rf /', { level: true }, io);
146
153
 
@@ -150,8 +157,7 @@ describe('command command', () => {
150
157
  });
151
158
 
152
159
  it('--json 输出等级', async () => {
153
- mockGetRisk.mockReturnValue('medium');
154
- mockGate(jest.fn().mockResolvedValue({ passed: true, message: 'OK' }));
160
+ mockGate({ passed: true, message: 'OK' }, 'medium');
155
161
 
156
162
  await executeCommand('cmd', { level: true, json: true }, io);
157
163
 
@@ -160,5 +166,15 @@ describe('command command', () => {
160
166
  command: 'cmd',
161
167
  });
162
168
  });
169
+
170
+ it('#135:等级取自本命令创建的那台实例,全程只创建一次', async () => {
171
+ const gate = mockGate({ passed: false, message: 'Blocked' }, 'high');
172
+
173
+ const result = await executeCommand('danger-cmd', { level: true }, io);
174
+
175
+ expect(gate.getRiskLevel).toHaveBeenCalledWith('danger-cmd');
176
+ expect(mockCreateGate).toHaveBeenCalledTimes(1);
177
+ expect(result).toEqual({ kind: 'fail', reason: expect.stringContaining('risk level is high') });
178
+ });
163
179
  });
164
180
  });
@@ -313,6 +313,58 @@ describe('retireConstraint 执行逻辑', () => {
313
313
  });
314
314
  });
315
315
 
316
+ describe('落盘字节与装载次数冻结(harness#137)', () => {
317
+ /**
318
+ * YAML 读-改-写两段合并成 `setYamlEntry` 属内部重构:对外产物逐字不得漂移
319
+ * (票验收 6)。故此处钉的是**字节**而非 yaml.load 后的对象——键序、缩进、
320
+ * 行宽 120 的折叠口径、注释丢弃后的形态都在断言范围内。
321
+ */
322
+ it('内置退役落 config.yml:逐字节冻结', () => {
323
+ const root = createProjectFixture({
324
+ name: 'harness-retire-test',
325
+ config: '# 项目配置\npreset: standard\nscenes:\n - llm-app\n',
326
+ });
327
+
328
+ retireConstraint(root, 'capability_sync', { reason: '逐字节冻结', now: FIXED_NOW });
329
+
330
+ expect(fs.readFileSync(path.join(root, '.harness', 'config.yml'), 'utf-8')).toBe(
331
+ 'preset: standard\nscenes:\n - llm-app\nconstraints:\n capability_sync:\n enabled: false\n' +
332
+ " retired:\n at: '2026-08-08T12:00:00.000Z'\n reason: 逐字节冻结\n" +
333
+ ' stats:\n total: 0\n fail: 0\n failRate: 0\n'
334
+ );
335
+ });
336
+
337
+ it('custom 退役落 custom-constraints.yml:逐字节冻结(规则原文保留、retired 段追加在末尾)', () => {
338
+ const root = createProjectFixture({ name: 'harness-retire-test' });
339
+ writeCustom(root, `# 自定义约束\n${CUSTOM_YML}`);
340
+
341
+ retireConstraint(root, 'my_custom_rule', { reason: '逐字节冻结', now: FIXED_NOW });
342
+
343
+ expect(fs.readFileSync(path.join(root, '.harness', 'custom-constraints.yml'), 'utf-8')).toBe(
344
+ 'custom_constraints:\n my_custom_rule:\n level: iron_law\n rule: 禁止引入 X\n' +
345
+ ' message: X 已由平台能力替代\n promptInjection: 禁止引入 X\n' +
346
+ " retired:\n at: '2026-08-08T12:00:00.000Z'\n reason: 逐字节冻结\n" +
347
+ ' stats:\n total: 0\n fail: 0\n failRate: 0\n'
348
+ );
349
+ });
350
+
351
+ it('一次 retireConstraint 只装载一次项目配置(此前 custom 路径走 3 次 load)', async () => {
352
+ const { ProjectConfigLoader } = await import('../../../core/project-config-loader');
353
+ const loadSpy = jest.spyOn(ProjectConfigLoader.prototype, 'load');
354
+ const root = createProjectFixture({ name: 'harness-retire-test' });
355
+ writeCustom(root, CUSTOM_YML);
356
+ loadSpy.mockClear();
357
+
358
+ retireConstraint(root, 'my_custom_rule', { now: FIXED_NOW });
359
+ expect(loadSpy).toHaveBeenCalledTimes(1);
360
+
361
+ loadSpy.mockClear();
362
+ retireConstraint(root, 'my_custom_rule', { now: new Date('2026-08-09T00:00:00.000Z') });
363
+ expect(loadSpy).toHaveBeenCalledTimes(1);
364
+ loadSpy.mockRestore();
365
+ });
366
+ });
367
+
316
368
  describe('constraintsRetire 非交互直达', () => {
317
369
  it('无 --yes 直达:报错 + 非零退出码 + 不落盘任何文件(#24 人确认闸门)', async () => {
318
370
  const root = createProjectFixture({ name: 'harness-retire-test' });