@dommaker/harness 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/harness.js +17 -1
  3. package/dist/agents/index.d.ts +6 -2
  4. package/dist/agents/index.d.ts.map +1 -1
  5. package/dist/agents/index.js +6 -16
  6. package/dist/agents/index.js.map +1 -1
  7. package/dist/cli/commands/__tests__/gate-decision.d.ts +18 -0
  8. package/dist/cli/commands/__tests__/gate-decision.d.ts.map +1 -0
  9. package/dist/cli/commands/__tests__/gate-decision.js +26 -0
  10. package/dist/cli/commands/__tests__/gate-decision.js.map +1 -0
  11. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  12. package/dist/cli/commands/acceptance.js +17 -28
  13. package/dist/cli/commands/acceptance.js.map +1 -1
  14. package/dist/cli/commands/check.d.ts +11 -3
  15. package/dist/cli/commands/check.d.ts.map +1 -1
  16. package/dist/cli/commands/check.js +26 -11
  17. package/dist/cli/commands/check.js.map +1 -1
  18. package/dist/cli/commands/command.d.ts +0 -1
  19. package/dist/cli/commands/command.d.ts.map +1 -1
  20. package/dist/cli/commands/command.js +8 -6
  21. package/dist/cli/commands/command.js.map +1 -1
  22. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  23. package/dist/cli/commands/constraints-retire.js +36 -57
  24. package/dist/cli/commands/constraints-retire.js.map +1 -1
  25. package/dist/cli/commands/contract.d.ts.map +1 -1
  26. package/dist/cli/commands/contract.js +36 -42
  27. package/dist/cli/commands/contract.js.map +1 -1
  28. package/dist/cli/commands/definitions.d.ts +2 -0
  29. package/dist/cli/commands/definitions.d.ts.map +1 -1
  30. package/dist/cli/commands/definitions.js +11 -6
  31. package/dist/cli/commands/definitions.js.map +1 -1
  32. package/dist/cli/commands/failure.d.ts.map +1 -1
  33. package/dist/cli/commands/failure.js +16 -2
  34. package/dist/cli/commands/failure.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +14 -8
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +172 -364
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/knowledge-view.d.ts +90 -0
  40. package/dist/cli/commands/knowledge-view.d.ts.map +1 -0
  41. package/dist/cli/commands/knowledge-view.js +198 -0
  42. package/dist/cli/commands/knowledge-view.js.map +1 -0
  43. package/dist/cli/commands/knowledge.d.ts +137 -52
  44. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  45. package/dist/cli/commands/knowledge.js +534 -400
  46. package/dist/cli/commands/knowledge.js.map +1 -1
  47. package/dist/cli/commands/passes-gate.d.ts +4 -1
  48. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  49. package/dist/cli/commands/passes-gate.js +11 -2
  50. package/dist/cli/commands/passes-gate.js.map +1 -1
  51. package/dist/cli/commands/performance.d.ts.map +1 -1
  52. package/dist/cli/commands/performance.js +19 -31
  53. package/dist/cli/commands/performance.js.map +1 -1
  54. package/dist/cli/commands/report.d.ts.map +1 -1
  55. package/dist/cli/commands/report.js +2 -1
  56. package/dist/cli/commands/report.js.map +1 -1
  57. package/dist/cli/commands/review.d.ts.map +1 -1
  58. package/dist/cli/commands/review.js +13 -25
  59. package/dist/cli/commands/review.js.map +1 -1
  60. package/dist/cli/commands/scaffold-templates.d.ts +73 -0
  61. package/dist/cli/commands/scaffold-templates.d.ts.map +1 -0
  62. package/dist/cli/commands/scaffold-templates.js +424 -0
  63. package/dist/cli/commands/scaffold-templates.js.map +1 -0
  64. package/dist/cli/commands/scaffold.d.ts +105 -0
  65. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  66. package/dist/cli/commands/scaffold.js +263 -0
  67. package/dist/cli/commands/scaffold.js.map +1 -0
  68. package/dist/cli/commands/security.d.ts.map +1 -1
  69. package/dist/cli/commands/security.js +22 -24
  70. package/dist/cli/commands/security.js.map +1 -1
  71. package/dist/cli/commands/spec-baseline-check.d.ts +43 -0
  72. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  73. package/dist/cli/commands/spec-baseline-check.js +109 -34
  74. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  75. package/dist/cli/commands/sync-docs/context-syncer.d.ts +21 -2
  76. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  77. package/dist/cli/commands/sync-docs/context-syncer.js +112 -28
  78. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  79. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  80. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  81. package/dist/cli/commands/sync-docs/index.js +86 -27
  82. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  83. package/dist/cli/commands/sync-docs/project-reader.d.ts +24 -1
  84. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  85. package/dist/cli/commands/sync-docs/project-reader.js +33 -7
  86. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  87. package/dist/cli/commands/validate.d.ts +0 -16
  88. package/dist/cli/commands/validate.d.ts.map +1 -1
  89. package/dist/cli/commands/validate.js +2 -97
  90. package/dist/cli/commands/validate.js.map +1 -1
  91. package/dist/cli/gate-command.d.ts +37 -0
  92. package/dist/cli/gate-command.d.ts.map +1 -0
  93. package/dist/cli/gate-command.js +64 -0
  94. package/dist/cli/gate-command.js.map +1 -0
  95. package/dist/completion-checkers/index.d.ts +1 -1
  96. package/dist/completion-checkers/index.d.ts.map +1 -1
  97. package/dist/completion-checkers/index.js +0 -15
  98. package/dist/completion-checkers/index.js.map +1 -1
  99. package/dist/context/knowledge-injector.d.ts +4 -2
  100. package/dist/context/knowledge-injector.d.ts.map +1 -1
  101. package/dist/context/knowledge-injector.js +13 -6
  102. package/dist/context/knowledge-injector.js.map +1 -1
  103. package/dist/core/constraints/agent-prompt-renderer.js +2 -2
  104. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  105. package/dist/core/constraints/capabilities-reconcile.d.ts +20 -0
  106. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -1
  107. package/dist/core/constraints/capabilities-reconcile.js +26 -4
  108. package/dist/core/constraints/capabilities-reconcile.js.map +1 -1
  109. package/dist/core/constraints/check-cache.d.ts +1 -1
  110. package/dist/core/constraints/check-cache.js +1 -1
  111. package/dist/core/constraints/checker.d.ts +8 -3
  112. package/dist/core/constraints/checker.d.ts.map +1 -1
  113. package/dist/core/constraints/checker.js +30 -19
  114. package/dist/core/constraints/checker.js.map +1 -1
  115. package/dist/core/constraints/checkers/capability-sync.d.ts +3 -1
  116. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  117. package/dist/core/constraints/checkers/capability-sync.js +17 -24
  118. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  119. package/dist/core/constraints/checkers/context-doc-sync.d.ts +1 -1
  120. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  121. package/dist/core/constraints/checkers/context-doc-sync.js +9 -6
  122. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  123. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  124. package/dist/core/constraints/checkers/docs-freshness.js +24 -30
  125. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  126. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  127. package/dist/core/constraints/checkers/governance-presence.js +10 -4
  128. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  129. package/dist/core/constraints/checkers/types.d.ts +10 -6
  130. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  131. package/dist/core/constraints/checkers/types.js +7 -3
  132. package/dist/core/constraints/checkers/types.js.map +1 -1
  133. package/dist/core/constraints/context-builder.d.ts +11 -10
  134. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  135. package/dist/core/constraints/context-builder.js +31 -27
  136. package/dist/core/constraints/context-builder.js.map +1 -1
  137. package/dist/core/constraints/context-reconcile.d.ts +75 -0
  138. package/dist/core/constraints/context-reconcile.d.ts.map +1 -0
  139. package/dist/core/constraints/context-reconcile.js +161 -0
  140. package/dist/core/constraints/context-reconcile.js.map +1 -0
  141. package/dist/core/constraints/doc-freshness/runner.d.ts +7 -0
  142. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  143. package/dist/core/constraints/doc-freshness/runner.js +8 -3
  144. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  145. package/dist/core/constraints/git-evidence.js +1 -1
  146. package/dist/core/constraints/git-evidence.js.map +1 -1
  147. package/dist/core/constraints/injection-drift.d.ts +7 -2
  148. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  149. package/dist/core/constraints/injection-drift.js +13 -7
  150. package/dist/core/constraints/injection-drift.js.map +1 -1
  151. package/dist/core/constraints/run-env.d.ts +92 -0
  152. package/dist/core/constraints/run-env.d.ts.map +1 -0
  153. package/dist/core/constraints/run-env.js +175 -0
  154. package/dist/core/constraints/run-env.js.map +1 -0
  155. package/dist/core/constraints/triggers.d.ts +22 -0
  156. package/dist/core/constraints/triggers.d.ts.map +1 -0
  157. package/dist/core/constraints/triggers.js +31 -0
  158. package/dist/core/constraints/triggers.js.map +1 -0
  159. package/dist/core/effective-constraints.d.ts +13 -4
  160. package/dist/core/effective-constraints.d.ts.map +1 -1
  161. package/dist/core/effective-constraints.js +16 -6
  162. package/dist/core/effective-constraints.js.map +1 -1
  163. package/dist/core/effective-set.d.ts +8 -15
  164. package/dist/core/effective-set.d.ts.map +1 -1
  165. package/dist/core/effective-set.js +7 -12
  166. package/dist/core/effective-set.js.map +1 -1
  167. package/dist/core/index.d.ts +2 -1
  168. package/dist/core/index.d.ts.map +1 -1
  169. package/dist/core/index.js.map +1 -1
  170. package/dist/core/project-config-loader.d.ts +39 -17
  171. package/dist/core/project-config-loader.d.ts.map +1 -1
  172. package/dist/core/project-config-loader.js +51 -122
  173. package/dist/core/project-config-loader.js.map +1 -1
  174. package/dist/core/validators/check-handlers/http.d.ts +2 -2
  175. package/dist/core/validators/check-handlers/http.d.ts.map +1 -1
  176. package/dist/core/validators/check-handlers/http.js +12 -7
  177. package/dist/core/validators/check-handlers/http.js.map +1 -1
  178. package/dist/core/validators/index.d.ts +1 -1
  179. package/dist/core/validators/index.d.ts.map +1 -1
  180. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  181. package/dist/core/validators/passes-gate.js +2 -2
  182. package/dist/core/validators/passes-gate.js.map +1 -1
  183. package/dist/failure/classifier.d.ts +1 -1
  184. package/dist/failure/classifier.d.ts.map +1 -1
  185. package/dist/failure/classifier.js +8 -8
  186. package/dist/failure/classifier.js.map +1 -1
  187. package/dist/failure/index.d.ts +2 -1
  188. package/dist/failure/index.d.ts.map +1 -1
  189. package/dist/failure/index.js +9 -17
  190. package/dist/failure/index.js.map +1 -1
  191. package/dist/failure/recorder.d.ts +1 -1
  192. package/dist/failure/recorder.d.ts.map +1 -1
  193. package/dist/gates/checker-gate.d.ts +2 -1
  194. package/dist/gates/checker-gate.d.ts.map +1 -1
  195. package/dist/gates/checker-gate.js +3 -2
  196. package/dist/gates/checker-gate.js.map +1 -1
  197. package/dist/gates/command.d.ts +10 -4
  198. package/dist/gates/command.d.ts.map +1 -1
  199. package/dist/gates/command.js +36 -62
  200. package/dist/gates/command.js.map +1 -1
  201. package/dist/gates/contract.d.ts.map +1 -1
  202. package/dist/gates/contract.js +0 -4
  203. package/dist/gates/contract.js.map +1 -1
  204. package/dist/gates/definitions.d.ts +2 -1
  205. package/dist/gates/definitions.d.ts.map +1 -1
  206. package/dist/gates/definitions.js +6 -5
  207. package/dist/gates/definitions.js.map +1 -1
  208. package/dist/gates/index.d.ts +0 -4
  209. package/dist/gates/index.d.ts.map +1 -1
  210. package/dist/gates/index.js +1 -6
  211. package/dist/gates/index.js.map +1 -1
  212. package/dist/gates/performance.d.ts.map +1 -1
  213. package/dist/gates/performance.js +0 -4
  214. package/dist/gates/performance.js.map +1 -1
  215. package/dist/gates/runner.d.ts +1 -1
  216. package/dist/gates/runner.js +1 -1
  217. package/dist/gates/security.d.ts +1 -1
  218. package/dist/gates/security.d.ts.map +1 -1
  219. package/dist/gates/security.js +5 -6
  220. package/dist/gates/security.js.map +1 -1
  221. package/dist/gates/types.d.ts +14 -17
  222. package/dist/gates/types.d.ts.map +1 -1
  223. package/dist/gates/types.js.map +1 -1
  224. package/dist/hooks/bootstrap.d.ts +6 -4
  225. package/dist/hooks/bootstrap.d.ts.map +1 -1
  226. package/dist/hooks/bootstrap.js +25 -13
  227. package/dist/hooks/bootstrap.js.map +1 -1
  228. package/dist/hooks/config.d.ts +6 -2
  229. package/dist/hooks/config.d.ts.map +1 -1
  230. package/dist/hooks/config.js +5 -1
  231. package/dist/hooks/config.js.map +1 -1
  232. package/dist/hooks/index.d.ts +1 -1
  233. package/dist/hooks/index.d.ts.map +1 -1
  234. package/dist/hooks/index.js +0 -15
  235. package/dist/hooks/index.js.map +1 -1
  236. package/dist/hooks/pipeline.d.ts.map +1 -1
  237. package/dist/hooks/pipeline.js +2 -4
  238. package/dist/hooks/pipeline.js.map +1 -1
  239. package/dist/hooks/registry.d.ts +11 -8
  240. package/dist/hooks/registry.d.ts.map +1 -1
  241. package/dist/hooks/registry.js +22 -7
  242. package/dist/hooks/registry.js.map +1 -1
  243. package/dist/hooks/types.d.ts +23 -7
  244. package/dist/hooks/types.d.ts.map +1 -1
  245. package/dist/index.d.ts +4 -4
  246. package/dist/index.d.ts.map +1 -1
  247. package/dist/index.js +3 -4
  248. package/dist/index.js.map +1 -1
  249. package/dist/knowledge/audit-scoring.d.ts +87 -0
  250. package/dist/knowledge/audit-scoring.d.ts.map +1 -0
  251. package/dist/knowledge/audit-scoring.js +470 -0
  252. package/dist/knowledge/audit-scoring.js.map +1 -0
  253. package/dist/knowledge/audit.d.ts +11 -59
  254. package/dist/knowledge/audit.d.ts.map +1 -1
  255. package/dist/knowledge/audit.js +47 -522
  256. package/dist/knowledge/audit.js.map +1 -1
  257. package/dist/knowledge/import.d.ts.map +1 -1
  258. package/dist/knowledge/import.js +2 -0
  259. package/dist/knowledge/import.js.map +1 -1
  260. package/dist/knowledge/index-generator.d.ts.map +1 -1
  261. package/dist/knowledge/index-generator.js +4 -5
  262. package/dist/knowledge/index-generator.js.map +1 -1
  263. package/dist/knowledge/index.d.ts +3 -2
  264. package/dist/knowledge/index.d.ts.map +1 -1
  265. package/dist/knowledge/index.js +3 -16
  266. package/dist/knowledge/index.js.map +1 -1
  267. package/dist/knowledge/ingest.js +1 -1
  268. package/dist/knowledge/ingest.js.map +1 -1
  269. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  270. package/dist/knowledge/lifecycle.js +4 -2
  271. package/dist/knowledge/lifecycle.js.map +1 -1
  272. package/dist/knowledge/migration.d.ts +3 -0
  273. package/dist/knowledge/migration.d.ts.map +1 -1
  274. package/dist/knowledge/migration.js +8 -2
  275. package/dist/knowledge/migration.js.map +1 -1
  276. package/dist/knowledge/query.d.ts +6 -0
  277. package/dist/knowledge/query.d.ts.map +1 -1
  278. package/dist/knowledge/query.js +8 -2
  279. package/dist/knowledge/query.js.map +1 -1
  280. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  281. package/dist/knowledge/reference-tracker.js +4 -6
  282. package/dist/knowledge/reference-tracker.js.map +1 -1
  283. package/dist/knowledge/store.d.ts +22 -16
  284. package/dist/knowledge/store.d.ts.map +1 -1
  285. package/dist/knowledge/store.js +53 -12
  286. package/dist/knowledge/store.js.map +1 -1
  287. package/dist/knowledge/tree-walker.d.ts +19 -0
  288. package/dist/knowledge/tree-walker.d.ts.map +1 -0
  289. package/dist/knowledge/tree-walker.js +30 -0
  290. package/dist/knowledge/tree-walker.js.map +1 -0
  291. package/dist/knowledge/types.d.ts +16 -0
  292. package/dist/knowledge/types.d.ts.map +1 -1
  293. package/dist/monitoring/traces.d.ts +5 -3
  294. package/dist/monitoring/traces.d.ts.map +1 -1
  295. package/dist/monitoring/traces.js +19 -5
  296. package/dist/monitoring/traces.js.map +1 -1
  297. package/dist/test-setup/jsonl-fake-fs.d.ts +30 -0
  298. package/dist/test-setup/jsonl-fake-fs.d.ts.map +1 -0
  299. package/dist/test-setup/jsonl-fake-fs.js +49 -0
  300. package/dist/test-setup/jsonl-fake-fs.js.map +1 -0
  301. package/dist/tools/index.d.ts +4 -1
  302. package/dist/tools/index.d.ts.map +1 -1
  303. package/dist/tools/index.js +7 -15
  304. package/dist/tools/index.js.map +1 -1
  305. package/dist/types/checkpoint.d.ts +5 -0
  306. package/dist/types/checkpoint.d.ts.map +1 -1
  307. package/dist/types/passes-gate.d.ts +0 -13
  308. package/dist/types/passes-gate.d.ts.map +1 -1
  309. package/dist/types/project-config.d.ts +18 -0
  310. package/dist/types/project-config.d.ts.map +1 -1
  311. package/dist/types/trace.d.ts +13 -4
  312. package/dist/types/trace.d.ts.map +1 -1
  313. package/dist/types/trace.js +4 -3
  314. package/dist/types/trace.js.map +1 -1
  315. package/dist/utils/exec.d.ts +4 -27
  316. package/dist/utils/exec.d.ts.map +1 -1
  317. package/dist/utils/exec.js +4 -94
  318. package/dist/utils/exec.js.map +1 -1
  319. package/dist/utils/jsonl.d.ts +17 -7
  320. package/dist/utils/jsonl.d.ts.map +1 -1
  321. package/dist/utils/jsonl.js +144 -17
  322. package/dist/utils/jsonl.js.map +1 -1
  323. package/dist/utils/numeric-flag.d.ts +44 -0
  324. package/dist/utils/numeric-flag.d.ts.map +1 -0
  325. package/dist/utils/numeric-flag.js +62 -0
  326. package/dist/utils/numeric-flag.js.map +1 -0
  327. package/package.json +1 -1
  328. package/src/CONTEXT.md +5 -2
  329. package/src/__tests__/acceptance-gate.test.ts +7 -4
  330. package/src/__tests__/checker-extra.test.ts +3 -2
  331. package/src/__tests__/checker.test.ts +4 -3
  332. package/src/__tests__/checkpoint.test.ts +26 -9
  333. package/src/__tests__/context-files-resolution.test.ts +29 -15
  334. package/src/__tests__/contract-gate.test.ts +19 -40
  335. package/src/__tests__/error-classifier.test.ts +1 -1
  336. package/src/__tests__/exec.test.ts +45 -107
  337. package/src/__tests__/failure-recorder.test.ts +7 -5
  338. package/src/__tests__/hooks-pipeline.test.ts +34 -47
  339. package/src/__tests__/layering.test.ts +217 -25
  340. package/src/__tests__/passes-gate.test.ts +9 -4
  341. package/src/__tests__/performance-gate.test.ts +3 -15
  342. package/src/__tests__/project-config-loader-extra.test.ts +31 -57
  343. package/src/__tests__/project-config-loader.test.ts +6 -3
  344. package/src/__tests__/public-exports.test.ts +27 -6
  345. package/src/__tests__/public-type-surface.test.ts +360 -0
  346. package/src/__tests__/public-value-surface.test.ts +182 -0
  347. package/src/__tests__/review-gate.test.ts +4 -6
  348. package/src/__tests__/security-gate.test.ts +5 -20
  349. package/src/__tests__/skip-semantics.test.ts +5 -3
  350. package/src/__tests__/spec-validator.test.ts +3 -2
  351. package/src/__tests__/sub-barrels-explicit.test.ts +87 -0
  352. package/src/__tests__/test-fixture-hygiene.test.ts +53 -0
  353. package/src/__tests__/trace-analyzer-extra.test.ts +5 -3
  354. package/src/__tests__/trace-analyzer.test.ts +5 -3
  355. package/src/__tests__/trace-collector.test.ts +5 -3
  356. package/src/agents/index.ts +13 -2
  357. package/src/cli/__tests__/gate-command.test.ts +162 -0
  358. package/src/cli/commands/CONTEXT.md +29 -5
  359. package/src/cli/commands/__tests__/acceptance.test.ts +24 -25
  360. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +15 -0
  361. package/src/cli/commands/__tests__/check-drift.test.ts +42 -1
  362. package/src/cli/commands/__tests__/check-read-count.test.ts +167 -0
  363. package/src/cli/commands/__tests__/check-skip-output.test.ts +4 -1
  364. package/src/cli/commands/__tests__/check.test.ts +43 -36
  365. package/src/cli/commands/__tests__/command-gate-dead-options.test.ts +60 -0
  366. package/src/cli/commands/__tests__/command.test.ts +33 -17
  367. package/src/cli/commands/__tests__/constraints-retire.test.ts +52 -0
  368. package/src/cli/commands/__tests__/context-syncer.test.ts +141 -0
  369. package/src/cli/commands/__tests__/contract.test.ts +20 -20
  370. package/src/cli/commands/__tests__/failure.test.ts +5 -4
  371. package/src/cli/commands/__tests__/gate-decision.ts +30 -0
  372. package/src/cli/commands/__tests__/init-dead-options.test.ts +50 -0
  373. package/src/cli/commands/__tests__/init-governance-io.test.ts +139 -0
  374. package/src/cli/commands/__tests__/init-injection.test.ts +3 -3
  375. package/src/cli/commands/__tests__/init-ondisk.test.ts +867 -0
  376. package/src/cli/commands/__tests__/init.test.ts +82 -6
  377. package/src/cli/commands/__tests__/knowledge-audit-threshold.test.ts +146 -0
  378. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +1 -1
  379. package/src/cli/commands/__tests__/knowledge-tone-mapping.test.ts +244 -0
  380. package/src/cli/commands/__tests__/knowledge-view.test.ts +839 -0
  381. package/src/cli/commands/__tests__/knowledge.test.ts +33 -15
  382. package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +368 -0
  383. package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +89 -1
  384. package/src/cli/commands/__tests__/performance.test.ts +19 -21
  385. package/src/cli/commands/__tests__/posteval-plan.test.ts +5 -3
  386. package/src/cli/commands/__tests__/pre-push-hook.test.ts +181 -0
  387. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +95 -2
  388. package/src/cli/commands/__tests__/project-path-convention.test.ts +103 -25
  389. package/src/cli/commands/__tests__/registry.test.ts +2 -0
  390. package/src/cli/commands/__tests__/report.test.ts +23 -1
  391. package/src/cli/commands/__tests__/review.test.ts +10 -17
  392. package/src/cli/commands/__tests__/scaffold.test.ts +505 -0
  393. package/src/cli/commands/__tests__/security-default-scan.test.ts +75 -0
  394. package/src/cli/commands/__tests__/security.test.ts +21 -24
  395. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +3 -2
  396. package/src/cli/commands/__tests__/spec-baseline-index-cap.test.ts +131 -0
  397. package/src/cli/commands/__tests__/spec-baseline-read-count.test.ts +325 -0
  398. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +19 -4
  399. package/src/cli/commands/__tests__/sync-docs-context-drift.test.ts +232 -0
  400. package/src/cli/commands/__tests__/sync-docs-module-order.test.ts +111 -0
  401. package/src/cli/commands/__tests__/sync-docs-read-count.test.ts +192 -0
  402. package/src/cli/commands/__tests__/sync-docs.test.ts +3 -2
  403. package/src/cli/commands/__tests__/validate.test.ts +1 -48
  404. package/src/cli/commands/acceptance.ts +24 -29
  405. package/src/cli/commands/check.ts +40 -17
  406. package/src/cli/commands/command.ts +9 -8
  407. package/src/cli/commands/constraints-retire.ts +57 -70
  408. package/src/cli/commands/contract.ts +44 -47
  409. package/src/cli/commands/definitions.ts +12 -6
  410. package/src/cli/commands/failure.ts +16 -3
  411. package/src/cli/commands/init.ts +242 -389
  412. package/src/cli/commands/knowledge-view.ts +224 -0
  413. package/src/cli/commands/knowledge.ts +643 -448
  414. package/src/cli/commands/passes-gate.ts +12 -3
  415. package/src/cli/commands/performance.ts +31 -38
  416. package/src/cli/commands/report.ts +3 -2
  417. package/src/cli/commands/review.ts +22 -28
  418. package/src/cli/commands/scaffold-templates.ts +406 -0
  419. package/src/cli/commands/scaffold.ts +302 -0
  420. package/src/cli/commands/security.ts +27 -23
  421. package/src/cli/commands/spec-baseline-check.ts +158 -33
  422. package/src/cli/commands/sync-docs/__tests__/context-export-surface.test.ts +102 -0
  423. package/src/cli/commands/sync-docs/__tests__/module-descriptions.test.ts +196 -0
  424. package/src/cli/commands/sync-docs/context-syncer.ts +137 -31
  425. package/src/cli/commands/sync-docs/index.ts +103 -28
  426. package/src/cli/commands/sync-docs/project-reader.ts +51 -7
  427. package/src/cli/commands/validate.ts +1 -104
  428. package/src/cli/gate-command.ts +87 -0
  429. package/src/completion-checkers/index.ts +10 -1
  430. package/src/context/CONTEXT.md +2 -1
  431. package/src/context/__tests__/knowledge-injector.test.ts +27 -0
  432. package/src/context/__tests__/session-manager.test.ts +19 -12
  433. package/src/context/knowledge-injector.ts +14 -7
  434. package/src/core/CONTEXT.md +10 -7
  435. package/src/core/__tests__/effective-set.test.ts +36 -60
  436. package/src/core/constraints/__tests__/context-reconcile.test.ts +246 -0
  437. package/src/core/constraints/__tests__/run-env.test.ts +377 -0
  438. package/src/core/constraints/__tests__/triggers.test.ts +70 -0
  439. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  440. package/src/core/constraints/capabilities-reconcile.ts +28 -4
  441. package/src/core/constraints/check-cache.ts +1 -1
  442. package/src/core/constraints/checker.ts +30 -14
  443. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +8 -3
  444. package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +16 -7
  445. package/src/core/constraints/checkers/__tests__/no-hardcoded-credentials.test.ts +2 -1
  446. package/src/core/constraints/checkers/capability-sync.ts +27 -28
  447. package/src/core/constraints/checkers/context-doc-sync.ts +9 -7
  448. package/src/core/constraints/checkers/docs-freshness.ts +28 -33
  449. package/src/core/constraints/checkers/governance-presence.ts +10 -6
  450. package/src/core/constraints/checkers/types.ts +14 -8
  451. package/src/core/constraints/context-builder.ts +35 -26
  452. package/src/core/constraints/context-reconcile.ts +200 -0
  453. package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +25 -0
  454. package/src/core/constraints/doc-freshness/runner.ts +18 -4
  455. package/src/core/constraints/git-evidence.ts +1 -1
  456. package/src/core/constraints/injection-drift.ts +19 -8
  457. package/src/core/constraints/run-env.ts +207 -0
  458. package/src/core/constraints/triggers.ts +33 -0
  459. package/src/core/effective-constraints.ts +17 -6
  460. package/src/core/effective-set.ts +8 -24
  461. package/src/core/index.ts +2 -1
  462. package/src/core/project-config-loader.ts +54 -102
  463. package/src/core/validators/__tests__/http-retry-sleep.test.ts +231 -0
  464. package/src/core/validators/check-handlers/http.ts +18 -7
  465. package/src/core/validators/index.ts +0 -1
  466. package/src/core/validators/passes-gate.ts +2 -3
  467. package/src/failure/CONTEXT.md +2 -1
  468. package/src/failure/classifier.ts +2 -2
  469. package/src/failure/index.ts +14 -2
  470. package/src/failure/recorder.ts +1 -1
  471. package/src/gates/CONTEXT.md +13 -7
  472. package/src/gates/__tests__/checker-gate.test.ts +1 -1
  473. package/src/gates/__tests__/command-single-predicate.test.ts +135 -0
  474. package/src/gates/__tests__/command.test.ts +8 -15
  475. package/src/gates/__tests__/contract.test.ts +5 -5
  476. package/src/gates/__tests__/gate-interface.test.ts +0 -17
  477. package/src/gates/__tests__/performance.test.ts +0 -13
  478. package/src/gates/__tests__/review.test.ts +0 -1
  479. package/src/gates/__tests__/runner.test.ts +1 -1
  480. package/src/gates/__tests__/security-default-scan.test.ts +97 -0
  481. package/src/gates/__tests__/security.test.ts +0 -13
  482. package/src/gates/checker-gate.ts +3 -2
  483. package/src/gates/command.ts +51 -74
  484. package/src/gates/contract.ts +0 -5
  485. package/src/gates/definitions.ts +6 -5
  486. package/src/gates/index.ts +0 -5
  487. package/src/gates/performance.ts +1 -6
  488. package/src/gates/runner.ts +1 -1
  489. package/src/gates/security.ts +9 -11
  490. package/src/gates/types.ts +20 -23
  491. package/src/hooks/CONTEXT.md +10 -9
  492. package/src/hooks/__tests__/bootstrap.test.ts +29 -6
  493. package/src/hooks/__tests__/pipeline.test.ts +28 -17
  494. package/src/hooks/__tests__/registry.test.ts +51 -3
  495. package/src/hooks/bootstrap.ts +35 -15
  496. package/src/hooks/config.ts +6 -2
  497. package/src/hooks/index.ts +10 -1
  498. package/src/hooks/pipeline.ts +4 -6
  499. package/src/hooks/registry.ts +31 -12
  500. package/src/hooks/types.ts +24 -7
  501. package/src/index.ts +1 -3
  502. package/src/knowledge/CONTEXT.md +14 -7
  503. package/src/knowledge/__tests__/audit-labels.test.ts +8 -5
  504. package/src/knowledge/__tests__/audit-scoring.test.ts +230 -0
  505. package/src/knowledge/__tests__/audit-write-count.test.ts +131 -0
  506. package/src/knowledge/__tests__/audit.test.ts +291 -208
  507. package/src/knowledge/__tests__/doctor.test.ts +3 -2
  508. package/src/knowledge/__tests__/ingest.test.ts +3 -2
  509. package/src/knowledge/__tests__/lifecycle.test.ts +57 -2
  510. package/src/knowledge/__tests__/migration.test.ts +35 -6
  511. package/src/knowledge/__tests__/query.test.ts +3 -2
  512. package/src/knowledge/__tests__/reference-tracker.test.ts +23 -2
  513. package/src/knowledge/__tests__/store.test.ts +149 -4
  514. package/src/knowledge/__tests__/tree-walker.test.ts +132 -0
  515. package/src/knowledge/audit-scoring.ts +589 -0
  516. package/src/knowledge/audit.ts +55 -599
  517. package/src/knowledge/import.ts +2 -0
  518. package/src/knowledge/index-generator.ts +4 -6
  519. package/src/knowledge/index.ts +27 -2
  520. package/src/knowledge/ingest.ts +1 -1
  521. package/src/knowledge/lifecycle.ts +5 -2
  522. package/src/knowledge/migration.ts +8 -2
  523. package/src/knowledge/query.ts +8 -1
  524. package/src/knowledge/reference-tracker.ts +5 -7
  525. package/src/knowledge/store.ts +75 -16
  526. package/src/knowledge/tree-walker.ts +29 -0
  527. package/src/knowledge/types.ts +19 -0
  528. package/src/monitoring/CONTEXT.md +5 -2
  529. package/src/monitoring/__tests__/context-tracker.test.ts +16 -8
  530. package/src/monitoring/__tests__/trace-file-anchoring.test.ts +80 -0
  531. package/src/monitoring/traces.ts +19 -5
  532. package/src/release/CONTEXT.md +1 -1
  533. package/src/release/__tests__/integrity.test.ts +21 -12
  534. package/src/test-setup/__tests__/jsonl-fake-fs.test.ts +98 -0
  535. package/src/test-setup/jsonl-fake-fs.ts +59 -0
  536. package/src/tools/index.ts +5 -1
  537. package/src/types/checkpoint.ts +6 -0
  538. package/src/types/passes-gate.ts +0 -17
  539. package/src/types/project-config.ts +21 -0
  540. package/src/types/trace.ts +14 -4
  541. package/src/utils/__tests__/file-walk.test.ts +3 -2
  542. package/src/utils/__tests__/jsonl-bounded-read.test.ts +106 -0
  543. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +10 -6
  544. package/src/utils/__tests__/jsonl.test.ts +61 -14
  545. package/src/utils/__tests__/numeric-flag.test.ts +80 -0
  546. package/src/utils/__tests__/package-version.test.ts +2 -2
  547. package/src/utils/exec.ts +4 -64
  548. package/src/utils/jsonl.ts +159 -20
  549. package/src/utils/numeric-flag.ts +64 -0
  550. package/dist/failure/types.d.ts +0 -9
  551. package/dist/failure/types.d.ts.map +0 -1
  552. package/dist/failure/types.js +0 -25
  553. package/dist/failure/types.js.map +0 -1
  554. package/dist/gates/effective-gates.d.ts +0 -30
  555. package/dist/gates/effective-gates.d.ts.map +0 -1
  556. package/dist/gates/effective-gates.js +0 -81
  557. package/dist/gates/effective-gates.js.map +0 -1
  558. package/src/failure/types.ts +0 -9
  559. package/src/gates/__tests__/effective-gates.test.ts +0 -77
  560. package/src/gates/effective-gates.ts +0 -99
@@ -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' });
@@ -0,0 +1,141 @@
1
+ /**
2
+ * sync-docs 的 CONTEXT.md 落点测试(harness#150)
3
+ *
4
+ * #132 把 init/validate 的脚手架三态收进 scaffold 模块,但 sync-docs 的
5
+ * `createContextMd` 是票口径外的第 9 处同形写手:自带一份 CONTEXT.md 模板正文,
6
+ * 与正本 `renderContextDoc` 已漂移。本票把它改消费正本(contextDocFile + runPlan)。
7
+ *
8
+ * 这里零真实文件系统(注入内存 fs 替身,手法照 scaffold.test.ts)钉三件事:
9
+ * 1. 对撞:同一目录经 init(contextDocFile)与 sync-docs(createContextMd)生成的
10
+ * CONTEXT.md 骨架逐字相同(两条路径归一到单一正本)。
11
+ * 2. 正本文案:并入副本独有的两行引导、四节占位取 HTML 注释形。
12
+ * 3. 只在缺失时写:present → exists 分支不覆盖,正常 contextMissing 只走 created。
13
+ */
14
+
15
+ import * as path from 'path';
16
+ import chalk from 'chalk';
17
+ import { captureIO, type CapturingIO } from '../../command-contract';
18
+ import { contextDocFile, type ScaffoldFileSystem } from '../scaffold';
19
+ import { createContextMd } from '../sync-docs/context-syncer';
20
+
21
+ jest.mock('chalk', () => ({
22
+ blue: jest.fn((s: string) => s),
23
+ yellow: jest.fn((s: string) => s),
24
+ green: jest.fn((s: string) => s),
25
+ gray: jest.fn((s: string) => s),
26
+ red: jest.fn((s: string) => s),
27
+ cyan: jest.fn((s: string) => s),
28
+ bold: jest.fn((s: string) => s),
29
+ }));
30
+
31
+ /** 内存 fs 替身:写盘内容、建过的目录可断言 */
32
+ class MemoryFs implements ScaffoldFileSystem {
33
+ readonly files = new Map<string, string>();
34
+ readonly dirs: string[] = [];
35
+
36
+ seed(target: string, content: string): void {
37
+ this.files.set(target, content);
38
+ }
39
+ async exists(target: string): Promise<boolean> {
40
+ return this.files.has(target);
41
+ }
42
+ async mkdir(dir: string): Promise<void> {
43
+ this.dirs.push(dir);
44
+ }
45
+ async writeFile(target: string, content: string): Promise<void> {
46
+ this.files.set(target, content);
47
+ }
48
+ async chmod(): Promise<void> {
49
+ /* CONTEXT.md 无权限位 */
50
+ }
51
+ }
52
+
53
+ const PROJECT = '/srv/project';
54
+ const USER_CONTENT = '# src\n\n我自己写的,不许覆盖\n';
55
+
56
+ /**
57
+ * CONTEXT.md 骨架正文的冻结字面量(harness#158):正本 `renderContextDoc('src')` 被改必红。
58
+ * 上面的对撞闸只证明「init 与 sync-docs 同源」——两侧一起漂它看不见(复审 M3:给正本
59
+ * 加一行 → 60/60 全绿)。骨架正文整体由此处逐字钉住,改骨架必须显式改这份字面量。
60
+ */
61
+ const CONTEXT_MD_SKELETON = `# src
62
+
63
+ > 此文件描述 src 目录的职责和上下文
64
+ > 请阅读本目录的源代码,然后填写以下各节。
65
+ > 如果使用 AI 编码助手,将本文件内容作为 prompt 请求它分析并填写。
66
+
67
+ ## 职责
68
+
69
+ <!-- 本目录的核心职责是什么 -->
70
+
71
+ ## 核心导出
72
+
73
+ <!-- 本目录对外暴露的主要模块/函数 -->
74
+
75
+ ## 依赖关系
76
+
77
+ <!-- 本目录依赖哪些其他模块,谁依赖本目录 -->
78
+
79
+ ## 注意事项
80
+
81
+ <!-- 开发时需要注意的约束或约定 -->
82
+ `;
83
+
84
+ let fs: MemoryFs;
85
+ let io: CapturingIO;
86
+
87
+ beforeEach(() => {
88
+ jest.clearAllMocks();
89
+ fs = new MemoryFs();
90
+ io = captureIO();
91
+ });
92
+
93
+ describe('createContextMd 消费 scaffold 正本(harness#150)', () => {
94
+ it('对撞:sync-docs 落点与 init 站点生成的骨架逐字相同', async () => {
95
+ await expect(createContextMd(PROJECT, 'src', io, fs)).resolves.toBe('created');
96
+ const written = fs.files.get(path.join(PROJECT, 'src', 'CONTEXT.md'));
97
+
98
+ // init 侧经由同一个 contextDocFile 工厂落盘(见 init.ts 的 contextDocPlan)——
99
+ // 两侧共用单一正本,任何一份偷偷漂移都会在这里撞红
100
+ expect(written).toBe(contextDocFile(PROJECT, 'src').content);
101
+ });
102
+
103
+ it('骨架正文逐字冻结:正本 renderContextDoc 被改必红(#158,M3 假闸补钉)', () => {
104
+ expect(contextDocFile(PROJECT, 'src').content).toBe(CONTEXT_MD_SKELETON);
105
+ });
106
+
107
+ it('正本文案钉住:并入的两行引导 + 注释形占位(散文式提问已消失)', async () => {
108
+ const content = contextDocFile(PROJECT, 'src').content;
109
+
110
+ expect(content).toContain('> 请阅读本目录的源代码,然后填写以下各节。');
111
+ expect(content).toContain(
112
+ '> 如果使用 AI 编码助手,将本文件内容作为 prompt 请求它分析并填写。',
113
+ );
114
+ expect(content).toContain('<!-- 本目录的核心职责是什么 -->');
115
+ expect(content).not.toContain('本目录的核心职责是?');
116
+ });
117
+
118
+ it('缺失才写:present → exists 分支,用户内容一字节不动', async () => {
119
+ const target = path.join(PROJECT, 'src', 'CONTEXT.md');
120
+ fs.seed(target, USER_CONTENT);
121
+
122
+ await expect(createContextMd(PROJECT, 'src', io, fs)).resolves.toBe('exists');
123
+
124
+ expect(fs.files.get(target)).toBe(USER_CONTENT);
125
+ expect(fs.dirs).toEqual([]);
126
+ expect(printed(io)).toEqual(['src/CONTEXT.md 已存在']);
127
+ expect(chalk.gray).toHaveBeenCalledWith('src/CONTEXT.md 已存在');
128
+ });
129
+
130
+ it('created 态:绿字文案由 runPlan 打印,与 init 落盘态同源', async () => {
131
+ await createContextMd(PROJECT, 'src', io, fs);
132
+
133
+ expect(printed(io)).toEqual(['✅ 已创建 src/CONTEXT.md']);
134
+ expect(chalk.green).toHaveBeenCalledWith('✅ 已创建 src/CONTEXT.md');
135
+ });
136
+ });
137
+
138
+ /** stdout 每次写入的文本(去掉 log 补的换行) */
139
+ function printed(capture: CapturingIO): string[] {
140
+ return capture.outRecords().map(chunk => chunk.replace(/\n$/, ''));
141
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { contract, validateSchema } from '../contract';
6
6
  import { captureIO, type CapturingIO } from '../../command-contract';
7
+ import { decide, fakeGate, throwingGate } from './gate-decision';
7
8
  import * as fs from 'fs/promises';
8
9
  import { ContractGate } from '../../../gates/contract';
9
10
 
@@ -14,7 +15,7 @@ jest.mock('fs/promises', () => ({
14
15
 
15
16
  jest.mock('../../../gates/contract', () => ({
16
17
  ContractGate: jest.fn().mockImplementation(() => ({
17
- check: jest.fn(),
18
+ evaluate: jest.fn(),
18
19
  })),
19
20
  }));
20
21
 
@@ -46,16 +47,15 @@ describe('contract command', () => {
46
47
  describe('contract', () => {
47
48
  it('should print success when check passes', async () => {
48
49
  mockFs.access.mockResolvedValue(undefined);
49
- const mockCheck = jest.fn().mockResolvedValue({
50
- passed: true,
51
- message: 'ok',
52
- details: { endpoints: 10, breakingChanges: false },
53
- });
54
- MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
50
+ MockGate.mockImplementation(() =>
51
+ fakeGate(decide('contract', true, 'ok', { endpoints: 10, breakingChanges: false })) as any
52
+ );
55
53
 
56
54
  const result = await contract({}, io);
57
55
 
58
56
  expect(io.outText()).toContain('契约门控检查通过');
57
+ expect(io.outText()).toContain(' 端点数: 10');
58
+ expect(io.outText()).toContain(' 破坏性变更: 无');
59
59
  expect(result.kind).toBe('ok');
60
60
  });
61
61
 
@@ -70,31 +70,31 @@ describe('contract command', () => {
70
70
 
71
71
  it('should print failure and exit 1 when check fails', async () => {
72
72
  mockFs.access.mockResolvedValue(undefined);
73
- const mockCheck = jest.fn().mockResolvedValue({
74
- passed: false,
75
- message: 'validation errors',
76
- details: {
77
- errors: ['missing field X', 'invalid type Y'],
78
- breakingChanges: [{ type: 'removed', path: '/api/v1/users' }],
79
- },
80
- });
81
- MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
73
+ MockGate.mockImplementation(() =>
74
+ fakeGate(
75
+ decide('contract', false, 'validation errors', {
76
+ errors: ['missing field X', 'invalid type Y'],
77
+ breakingChanges: [{ type: 'removed', path: '/api/v1/users' }],
78
+ })
79
+ ) as any
80
+ );
82
81
 
83
82
  const result = await contract({}, io);
84
83
 
85
84
  expect(io.outText()).toContain('契约门控检查失败');
86
- expect(result.kind).toBe('fail');
85
+ expect(io.outText()).toContain(' - missing field X');
86
+ expect(io.outText()).toContain(' - removed: /api/v1/users');
87
+ expect(result).toEqual({ kind: 'fail', reason: 'contract gate denied: validation errors' });
87
88
  });
88
89
 
89
90
  it('should handle thrown errors and exit 1', async () => {
90
91
  mockFs.access.mockResolvedValue(undefined);
91
- const mockCheck = jest.fn().mockRejectedValue(new Error('gate error'));
92
- MockGate.mockImplementation(() => ({ check: mockCheck }) as any);
92
+ MockGate.mockImplementation(() => throwingGate(new Error('gate error')) as any);
93
93
 
94
94
  const result = await contract({}, io);
95
95
 
96
96
  expect(io.outText()).toContain('契约门控检查出错');
97
- expect(result.kind).toBe('fail');
97
+ expect(result).toEqual({ kind: 'fail', reason: 'contract gate error: gate error' });
98
98
  });
99
99
  });
100
100
 
@@ -5,9 +5,10 @@
5
5
  import * as fs from 'fs';
6
6
  import { captureIO, type CapturingIO } from '../../command-contract';
7
7
  import * as path from 'path';
8
+ import * as os from 'os';
8
9
  import { FailureRecorder } from '../../../failure/recorder';
9
- import { ErrorType, FailureLevel } from '../../../failure/types';
10
- import type { FailureRecord } from '../../../failure/types';
10
+ import { ErrorType, FailureLevel } from '../../../types/failure';
11
+ import type { FailureRecord } from '../../../types/failure';
11
12
 
12
13
  // Mock FailureRecorder
13
14
  jest.mock('../../../failure/recorder');
@@ -18,7 +19,7 @@ beforeEach(() => {
18
19
  });
19
20
 
20
21
  describe('failure CLI commands', () => {
21
- const tempDir = path.join(process.cwd(), 'temp-test-failure-cli');
22
+ let tempDir: string;
22
23
  let mockRecorderInstance: {
23
24
  record: jest.Mock;
24
25
  getHistory: jest.Mock;
@@ -39,7 +40,7 @@ describe('failure CLI commands', () => {
39
40
  };
40
41
 
41
42
  beforeAll(() => {
42
- fs.mkdirSync(tempDir, { recursive: true });
43
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'temp-test-failure-cli-'));
43
44
  });
44
45
 
45
46
  afterAll(() => {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 门禁命令测试的决策替身
3
+ *
4
+ * 命令的唯一入口是 `evaluate()`,所以替身是「产一枚决策的假门禁」——
5
+ * 映射与报告构造器用真实现(decisionFromResult / gateResult),
6
+ * 六个命令的测试因此不需要 git、gh 或真扫描。
7
+ */
8
+
9
+ import { decisionFromResult } from '../../../gates/decision';
10
+ import { gateResult } from '../../../gates/types';
11
+ import type { GateDecision } from '../../../gates/types';
12
+
13
+ export function decide(
14
+ gateId: string,
15
+ passed: boolean,
16
+ message: string,
17
+ details?: Record<string, any>
18
+ ): GateDecision {
19
+ return decisionFromResult(gateResult(gateId, passed, message, Date.now(), details));
20
+ }
21
+
22
+ /** 恒产同一枚决策的假门禁(evaluate 记录调用参数,供断言上下文) */
23
+ export function fakeGate(decision: GateDecision): { evaluate: jest.Mock } {
24
+ return { evaluate: jest.fn().mockResolvedValue(decision) };
25
+ }
26
+
27
+ /** evaluate 直接抛出的假门禁:命令出错分支测的是门禁之外的失败(git / fs) */
28
+ export function throwingGate(error: unknown): { evaluate: jest.Mock } {
29
+ return { evaluate: jest.fn().mockRejectedValue(error) };
30
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * init 死选项 `-t/--type` 已删除(harness#132 grilling 落定)
3
+ *
4
+ * `-t, --type <type>` 自始只有定义表一条与 `InitOptions.type` 一个字段,全仓无读取方——
5
+ * `harness init -t nextjs-app` 恒不生效。scaffold 收口时按 ADR-0018 判据(只执法有真实现的
6
+ * 维度)连旗帜与字段一并删除。手法同 passes-gate-dead-options.test.ts:编译期
7
+ * `@ts-expect-error` 钉字段不存在 + 运行期钉定义表旗帜面。
8
+ */
9
+
10
+ import { COMMAND_DEFINITIONS } from '../definitions';
11
+ import type { InitOptions } from '../init';
12
+
13
+ const initCommand = COMMAND_DEFINITIONS.find(d => d.command === 'init');
14
+
15
+ describe('init 的 -t/--type 已删除(#132)', () => {
16
+ it('定义表已注册 init(前提)', () => {
17
+ expect(initCommand).toBeDefined();
18
+ });
19
+
20
+ it('init 旗帜面逐字冻结,不含 --type', () => {
21
+ expect((initCommand?.options || []).map(o => o.flags)).toEqual([
22
+ '-p, --preset <preset>',
23
+ '-g, --governance <level>',
24
+ '--project-path <path>',
25
+ '--no-git-hooks',
26
+ '--ci <platform>',
27
+ '--no-github-actions',
28
+ '--print-snippets',
29
+ ]);
30
+ });
31
+
32
+ it('InitOptions 的 type 字段已从类型面消失(编译期钉)', () => {
33
+ // @ts-expect-error -t/--type 无实现,随 #132 从 InitOptions 删除
34
+ const removed: InitOptions = { preset: 'standard', type: 'nextjs-app' };
35
+ expect(removed).toBeDefined();
36
+ });
37
+
38
+ it('活旗帜仍可赋值(防止删多)', () => {
39
+ const live: InitOptions = {
40
+ preset: 'strict',
41
+ governance: 'minimal',
42
+ projectPath: '/tmp/p',
43
+ gitHooks: false,
44
+ githubActions: false,
45
+ ci: 'gitlab',
46
+ printSnippets: true,
47
+ };
48
+ expect(Object.keys(live)).toHaveLength(7);
49
+ });
50
+ });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * 治理链路的 io 注入不逃逸(harness#149)
3
+ *
4
+ * setupGovernance 手握 io,却在两个调用点把它丢了(`setupClaudeMdOutputStyle(projectPath)`
5
+ * / `setupGovernanceConstraints(projectPath)`),被调方一律 `io: CommandIO = processIO`
6
+ * 兜成真实 stdout。真机输出正常,但注入面的测试看不见那 14 条提示——`⚠️ …标记残缺…`
7
+ * 这类唯一提示写坏了没有测试会红。
8
+ *
9
+ * 本文件不 mock 任何 IO:在临时目录里真跑 init,断言治理段提示落在**注入面**。
10
+ * 改造前这些断言逐条为红(文本逃到了 process.stdout)。
11
+ */
12
+
13
+ import * as fs from 'fs/promises';
14
+ import * as fsSync from 'fs';
15
+ import * as os from 'os';
16
+ import * as path from 'path';
17
+ import { init, type InitOptions } from '../init';
18
+ import { captureIO, type CapturingIO } from '../../command-contract';
19
+
20
+ jest.mock('chalk', () => ({
21
+ blue: jest.fn((s: string) => s),
22
+ yellow: jest.fn((s: string) => s),
23
+ green: jest.fn((s: string) => s),
24
+ gray: jest.fn((s: string) => s),
25
+ red: jest.fn((s: string) => s),
26
+ cyan: jest.fn((s: string) => s),
27
+ bold: jest.fn((s: string) => s),
28
+ }));
29
+
30
+ const INIT_SOURCE = fsSync.readFileSync(path.join(__dirname, '..', 'init.ts'), 'utf-8');
31
+
32
+ const INIT_OPTIONS: Partial<InitOptions> = { preset: 'standard', governance: 'standard' };
33
+
34
+ /** 真临时工程根:带 .git 与 src/,让 init 走完治理链路而不必先落别的文件 */
35
+ async function makeProject(files: Record<string, string> = {}): Promise<string> {
36
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'harness-governance-io-'));
37
+ await fs.mkdir(path.join(root, 'src'));
38
+ await fs.writeFile(path.join(root, 'src', 'index.ts'), 'export const x = 1;\n');
39
+ await fs.mkdir(path.join(root, '.git'));
40
+ for (const [rel, content] of Object.entries(files)) {
41
+ await fs.writeFile(path.join(root, rel), content, 'utf-8');
42
+ }
43
+ return root;
44
+ }
45
+
46
+ async function runInit(files: Record<string, string>): Promise<CapturingIO> {
47
+ const root = await makeProject(files);
48
+ roots.push(root);
49
+ const io = captureIO();
50
+ expect(await init({ ...INIT_OPTIONS, projectPath: root } as InitOptions, io)).toEqual({ kind: 'ok' });
51
+ return io;
52
+ }
53
+
54
+ const roots: string[] = [];
55
+
56
+ afterAll(async () => {
57
+ await Promise.all(roots.map(root => fs.rm(root, { recursive: true, force: true })));
58
+ });
59
+
60
+ describe('init 治理链路的提示落在注入面(harness#149)', () => {
61
+ it('Output Style 段(setupClaudeMdOutputStyle 调用点曾丢 io):写入提示进捕获 stdout', async () => {
62
+ const io = await runInit({ 'CLAUDE.md': '# CLAUDE.md\n\n项目说明\n' });
63
+ expect(io.outText()).toContain('✅ 已在 CLAUDE.md 顶部写入 Output Style 段');
64
+ });
65
+
66
+ it('治理契约追加到既有 AGENTS.md:`已追加治理契约` 进捕获 stdout', async () => {
67
+ const io = await runInit({ 'AGENTS.md': '# AGENTS.md\n\n既有导读\n' });
68
+ expect(io.outText()).toContain('✅ 已追加治理契约 PRESERVE:governance 段到 AGENTS.md');
69
+ });
70
+
71
+ it('标记残缺(唯一提示):`标记残缺…请人工修复` 进捕获 stdout', async () => {
72
+ const io = await runInit({ 'AGENTS.md': '# AGENTS.md\n\n<!-- PRESERVE:governance -->\n残缺段\n' });
73
+ expect(io.outText()).toContain('⚠️ AGENTS.md 中 PRESERVE:governance 标记残缺(只有单边),跳过治理契约写入,请人工修复');
74
+ });
75
+
76
+ // #158 补钉:这类提示在 init.ts 实测有四处,#149 只钉住上面一条——其余三处各补一条
77
+ // 捕获面断言。四处分支不同文件/不同段位,fixture 各自单建,不共用残缺文件。
78
+
79
+ it('标记残缺四处之二:CLAUDE.md 的 HARNESS_OUTPUT_STYLE 单边 → Output Style 注入告警', async () => {
80
+ const io = await runInit({ 'CLAUDE.md': '# CLAUDE.md\n\n<!-- HARNESS_OUTPUT_STYLE_START -->\n半残段\n' });
81
+ expect(io.outText()).toContain('⚠️ CLAUDE.md 中 HARNESS_OUTPUT_STYLE 标记残缺(单边或乱序),跳过 Output Style 注入,请人工修复');
82
+ });
83
+
84
+ it('标记残缺四处之三:AGENTS.md 的 PRESERVE 段内 HARNESS_CONSTRAINTS 单边 → 段内告警', async () => {
85
+ const broken = [
86
+ '# AGENTS.md',
87
+ '',
88
+ '<!-- PRESERVE:governance -->',
89
+ '手写契约',
90
+ '',
91
+ '<!-- HARNESS_CONSTRAINTS_START -->',
92
+ '半残注入',
93
+ '<!-- /PRESERVE:governance -->',
94
+ '',
95
+ ].join('\n');
96
+ const io = await runInit({ 'AGENTS.md': broken });
97
+ expect(io.outText()).toContain('⚠️ AGENTS.md PRESERVE:governance 段内 HARNESS_CONSTRAINTS 标记残缺(单边或乱序),跳过治理契约写入,请人工修复');
98
+ });
99
+
100
+ it('标记残缺四处之四:CLAUDE.md 的 HARNESS_CONSTRAINTS 单边 → 治理约束注入告警(旧模型仓落点)', async () => {
101
+ const io = await runInit({ 'CLAUDE.md': '# CLAUDE.md\n\n<!-- HARNESS_CONSTRAINTS_START -->\n半残段\n' });
102
+ expect(io.outText()).toContain('⚠️ CLAUDE.md 中 HARNESS_CONSTRAINTS 标记残缺(单边或乱序),跳过治理约束注入,请人工修复');
103
+ });
104
+
105
+ it('新仓创建 AGENTS.md 治理段:创建提示进捕获 stdout', async () => {
106
+ const io = await runInit({});
107
+ expect(io.outText()).toContain('✅ 已创建 AGENTS.md 并写入治理契约 PRESERVE:governance 段');
108
+ });
109
+ });
110
+
111
+ describe('writer 层 io 必传(源形状闸,防缺省兜底再回来)', () => {
112
+ it('init.ts 里 `io: CommandIO = processIO` 只剩 init() 一处(对外入口保留缺省)', () => {
113
+ const sites = INIT_SOURCE.split('\n').filter(line => line.includes('io: CommandIO = processIO'));
114
+ expect(sites).toHaveLength(1);
115
+ expect(sites[0]).toMatch(/^export async function init\(/);
116
+ });
117
+
118
+ it('三个 writer 与落点路由的 io 均为必传形参', () => {
119
+ for (const writer of [
120
+ 'setupClaudeMdOutputStyle',
121
+ 'setupAgentsMdConstraints',
122
+ 'setupClaudeMdConstraints',
123
+ 'setupGovernanceConstraints',
124
+ ]) {
125
+ expect(INIT_SOURCE).toMatch(
126
+ new RegExp(`export async function ${writer}\\(projectPath: string, io: CommandIO\\)`),
127
+ );
128
+ }
129
+ });
130
+
131
+ it('setupGovernance 的调用点逐个传 io(不留裸 projectPath 调用)', () => {
132
+ const governanceBody = INIT_SOURCE.slice(
133
+ INIT_SOURCE.indexOf('async function setupGovernance('),
134
+ INIT_SOURCE.indexOf('/**\n * 目录 CONTEXT.md 骨架的 plan'),
135
+ );
136
+ expect(governanceBody).toMatch(/await setupClaudeMdOutputStyle\(projectPath, io\)/);
137
+ expect(governanceBody).toMatch(/await setupGovernanceConstraints\(projectPath, io\)/);
138
+ });
139
+ });