@dommaker/harness 1.3.0 → 1.5.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 (532) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +104 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +27 -219
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +17 -30
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +183 -273
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +9 -3
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +56 -61
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +68 -117
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +20 -8
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +66 -77
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +49 -66
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/index.d.ts +2 -2
  160. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/index.js +2 -1
  162. package/dist/core/constraints/checkers/index.js.map +1 -1
  163. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  165. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  166. package/dist/core/constraints/checkers/types.d.ts +43 -4
  167. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/types.js +33 -0
  169. package/dist/core/constraints/checkers/types.js.map +1 -1
  170. package/dist/core/constraints/context-builder.d.ts +15 -14
  171. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  172. package/dist/core/constraints/context-builder.js +32 -96
  173. package/dist/core/constraints/context-builder.js.map +1 -1
  174. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  175. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  176. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  177. package/dist/core/constraints/git-evidence.d.ts +60 -0
  178. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  179. package/dist/core/constraints/git-evidence.js +88 -0
  180. package/dist/core/constraints/git-evidence.js.map +1 -0
  181. package/dist/core/constraints/injection-drift.d.ts +3 -20
  182. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  183. package/dist/core/constraints/injection-drift.js +10 -57
  184. package/dist/core/constraints/injection-drift.js.map +1 -1
  185. package/dist/core/constraints/injection-writer.d.ts +113 -0
  186. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  187. package/dist/core/constraints/injection-writer.js +228 -0
  188. package/dist/core/constraints/injection-writer.js.map +1 -0
  189. package/dist/core/constraints/usage-report.d.ts +22 -1
  190. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  191. package/dist/core/constraints/usage-report.js +27 -20
  192. package/dist/core/constraints/usage-report.js.map +1 -1
  193. package/dist/core/index.d.ts +2 -2
  194. package/dist/core/index.d.ts.map +1 -1
  195. package/dist/core/index.js +4 -2
  196. package/dist/core/index.js.map +1 -1
  197. package/dist/core/project-config-loader.d.ts +32 -1
  198. package/dist/core/project-config-loader.d.ts.map +1 -1
  199. package/dist/core/project-config-loader.js +39 -9
  200. package/dist/core/project-config-loader.js.map +1 -1
  201. package/dist/core/validators/passes-gate.d.ts +15 -64
  202. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  203. package/dist/core/validators/passes-gate.js +84 -222
  204. package/dist/core/validators/passes-gate.js.map +1 -1
  205. package/dist/core/validators/test-output.d.ts +58 -0
  206. package/dist/core/validators/test-output.d.ts.map +1 -0
  207. package/dist/core/validators/test-output.js +109 -0
  208. package/dist/core/validators/test-output.js.map +1 -0
  209. package/dist/failure/recorder.d.ts +6 -0
  210. package/dist/failure/recorder.d.ts.map +1 -1
  211. package/dist/failure/recorder.js +12 -8
  212. package/dist/failure/recorder.js.map +1 -1
  213. package/dist/gates/acceptance.d.ts +1 -48
  214. package/dist/gates/acceptance.d.ts.map +1 -1
  215. package/dist/gates/acceptance.js +6 -38
  216. package/dist/gates/acceptance.js.map +1 -1
  217. package/dist/gates/checker-gate.d.ts +2 -1
  218. package/dist/gates/checker-gate.d.ts.map +1 -1
  219. package/dist/gates/checker-gate.js +11 -13
  220. package/dist/gates/checker-gate.js.map +1 -1
  221. package/dist/gates/command.d.ts.map +1 -1
  222. package/dist/gates/command.js +10 -22
  223. package/dist/gates/command.js.map +1 -1
  224. package/dist/gates/contract.d.ts.map +1 -1
  225. package/dist/gates/contract.js +22 -61
  226. package/dist/gates/contract.js.map +1 -1
  227. package/dist/gates/performance.d.ts.map +1 -1
  228. package/dist/gates/performance.js +13 -31
  229. package/dist/gates/performance.js.map +1 -1
  230. package/dist/gates/review.d.ts.map +1 -1
  231. package/dist/gates/review.js +21 -53
  232. package/dist/gates/review.js.map +1 -1
  233. package/dist/gates/security.d.ts.map +1 -1
  234. package/dist/gates/security.js +14 -32
  235. package/dist/gates/security.js.map +1 -1
  236. package/dist/gates/types.d.ts +28 -5
  237. package/dist/gates/types.d.ts.map +1 -1
  238. package/dist/gates/types.js +38 -1
  239. package/dist/gates/types.js.map +1 -1
  240. package/dist/hooks/bootstrap.d.ts +2 -0
  241. package/dist/hooks/bootstrap.d.ts.map +1 -1
  242. package/dist/hooks/bootstrap.js +5 -2
  243. package/dist/hooks/bootstrap.js.map +1 -1
  244. package/dist/index.d.ts +1 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/knowledge/audit.d.ts +7 -2
  248. package/dist/knowledge/audit.d.ts.map +1 -1
  249. package/dist/knowledge/audit.js +59 -79
  250. package/dist/knowledge/audit.js.map +1 -1
  251. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  252. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  253. package/dist/knowledge/flywheel-metrics.js +47 -0
  254. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  255. package/dist/knowledge/import.d.ts.map +1 -1
  256. package/dist/knowledge/import.js +4 -14
  257. package/dist/knowledge/import.js.map +1 -1
  258. package/dist/knowledge/index-generator.d.ts.map +1 -1
  259. package/dist/knowledge/index-generator.js +17 -14
  260. package/dist/knowledge/index-generator.js.map +1 -1
  261. package/dist/knowledge/ingest.d.ts.map +1 -1
  262. package/dist/knowledge/ingest.js +9 -4
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/migration.d.ts.map +1 -1
  265. package/dist/knowledge/migration.js +11 -8
  266. package/dist/knowledge/migration.js.map +1 -1
  267. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  268. package/dist/knowledge/reference-tracker.js +12 -9
  269. package/dist/knowledge/reference-tracker.js.map +1 -1
  270. package/dist/knowledge/store.d.ts +15 -0
  271. package/dist/knowledge/store.d.ts.map +1 -1
  272. package/dist/knowledge/store.js +59 -10
  273. package/dist/knowledge/store.js.map +1 -1
  274. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  275. package/dist/monitoring/context-tracker.js +11 -20
  276. package/dist/monitoring/context-tracker.js.map +1 -1
  277. package/dist/monitoring/index.d.ts +1 -1
  278. package/dist/monitoring/index.d.ts.map +1 -1
  279. package/dist/monitoring/index.js +1 -2
  280. package/dist/monitoring/index.js.map +1 -1
  281. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  282. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  283. package/dist/monitoring/trace-analyzer.js +19 -2
  284. package/dist/monitoring/trace-analyzer.js.map +1 -1
  285. package/dist/monitoring/traces.d.ts +21 -2
  286. package/dist/monitoring/traces.d.ts.map +1 -1
  287. package/dist/monitoring/traces.js +38 -29
  288. package/dist/monitoring/traces.js.map +1 -1
  289. package/dist/release/index.d.ts +2 -2
  290. package/dist/release/index.d.ts.map +1 -1
  291. package/dist/release/index.js +1 -5
  292. package/dist/release/index.js.map +1 -1
  293. package/dist/sdd/index-generator.js +10 -11
  294. package/dist/sdd/index-generator.js.map +1 -1
  295. package/dist/test-setup/project-fixture.d.ts +48 -0
  296. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  297. package/dist/test-setup/project-fixture.js +106 -0
  298. package/dist/test-setup/project-fixture.js.map +1 -0
  299. package/dist/types/constraint.d.ts +8 -18
  300. package/dist/types/constraint.d.ts.map +1 -1
  301. package/dist/types/constraint.js +6 -1
  302. package/dist/types/constraint.js.map +1 -1
  303. package/dist/types/passes-gate.d.ts +10 -25
  304. package/dist/types/passes-gate.d.ts.map +1 -1
  305. package/dist/types/project-config.d.ts +1 -1
  306. package/dist/types/session.d.ts +6 -5
  307. package/dist/types/session.d.ts.map +1 -1
  308. package/dist/types/trace.d.ts +9 -0
  309. package/dist/types/trace.d.ts.map +1 -1
  310. package/dist/utils/exec.d.ts +10 -0
  311. package/dist/utils/exec.d.ts.map +1 -1
  312. package/dist/utils/exec.js +12 -0
  313. package/dist/utils/exec.js.map +1 -1
  314. package/dist/utils/file-walk.d.ts +9 -1
  315. package/dist/utils/file-walk.d.ts.map +1 -1
  316. package/dist/utils/file-walk.js +10 -1
  317. package/dist/utils/file-walk.js.map +1 -1
  318. package/dist/utils/frontmatter.d.ts +73 -0
  319. package/dist/utils/frontmatter.d.ts.map +1 -0
  320. package/dist/utils/frontmatter.js +141 -0
  321. package/dist/utils/frontmatter.js.map +1 -0
  322. package/dist/utils/jsonl.d.ts +92 -0
  323. package/dist/utils/jsonl.d.ts.map +1 -0
  324. package/dist/utils/jsonl.js +166 -0
  325. package/dist/utils/jsonl.js.map +1 -0
  326. package/dist/utils/package-version.d.ts +16 -0
  327. package/dist/utils/package-version.d.ts.map +1 -0
  328. package/dist/utils/package-version.js +63 -0
  329. package/dist/utils/package-version.js.map +1 -0
  330. package/package.json +1 -1
  331. package/src/CONTEXT.md +8 -2
  332. package/src/__tests__/acceptance-gate.test.ts +3 -1
  333. package/src/__tests__/checker-extra.test.ts +7 -1218
  334. package/src/__tests__/context-files-resolution.test.ts +116 -0
  335. package/src/__tests__/effective-constraints.test.ts +10 -13
  336. package/src/__tests__/exec.test.ts +35 -1
  337. package/src/__tests__/layering.test.ts +152 -0
  338. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  339. package/src/__tests__/passes-gate.test.ts +130 -101
  340. package/src/__tests__/skip-semantics.test.ts +5 -9
  341. package/src/cli/__tests__/command-contract.test.ts +170 -0
  342. package/src/cli/command-contract.ts +108 -0
  343. package/src/cli/commands/CONTEXT.md +23 -8
  344. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  345. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  346. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +107 -0
  347. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  348. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  349. package/src/cli/commands/__tests__/check.test.ts +448 -378
  350. package/src/cli/commands/__tests__/command.test.ts +85 -70
  351. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  352. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  353. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  354. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  355. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  356. package/src/cli/commands/__tests__/init.test.ts +77 -48
  357. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  358. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  359. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  360. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  361. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  362. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  363. package/src/cli/commands/__tests__/project-path-convention.test.ts +231 -0
  364. package/src/cli/commands/__tests__/registry.test.ts +75 -35
  365. package/src/cli/commands/__tests__/release.test.ts +209 -80
  366. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  367. package/src/cli/commands/__tests__/report.test.ts +25 -25
  368. package/src/cli/commands/__tests__/review.test.ts +33 -31
  369. package/src/cli/commands/__tests__/security.test.ts +23 -27
  370. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  371. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  372. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  373. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  374. package/src/cli/commands/__tests__/status.test.ts +34 -34
  375. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  376. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  377. package/src/cli/commands/__tests__/update-user-model.test.ts +97 -34
  378. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  379. package/src/cli/commands/acceptance.ts +35 -24
  380. package/src/cli/commands/analyze-sessions.ts +25 -24
  381. package/src/cli/commands/check.ts +127 -66
  382. package/src/cli/commands/command.ts +30 -17
  383. package/src/cli/commands/constraints-report.ts +37 -34
  384. package/src/cli/commands/constraints-retire.ts +58 -55
  385. package/src/cli/commands/constraints.ts +10 -27
  386. package/src/cli/commands/contract.ts +51 -42
  387. package/src/cli/commands/definitions.ts +34 -227
  388. package/src/cli/commands/failure.ts +33 -26
  389. package/src/cli/commands/init.ts +166 -220
  390. package/src/cli/commands/knowledge.ts +192 -289
  391. package/src/cli/commands/passes-gate.ts +58 -68
  392. package/src/cli/commands/performance.ts +23 -18
  393. package/src/cli/commands/posteval-plan.ts +29 -15
  394. package/src/cli/commands/release.ts +70 -59
  395. package/src/cli/commands/report.ts +9 -5
  396. package/src/cli/commands/review.ts +39 -29
  397. package/src/cli/commands/sdd.ts +7 -5
  398. package/src/cli/commands/security.ts +35 -26
  399. package/src/cli/commands/spec-baseline-check.ts +16 -11
  400. package/src/cli/commands/spec.ts +66 -53
  401. package/src/cli/commands/status.ts +59 -53
  402. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  403. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  404. package/src/cli/commands/sync-docs/index.ts +73 -100
  405. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  406. package/src/cli/commands/update-user-model.ts +64 -61
  407. package/src/cli/commands/validate.ts +31 -25
  408. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  409. package/src/cli/session-mining/transcript.ts +26 -8
  410. package/src/context/session-manager.ts +19 -33
  411. package/src/core/CONTEXT.md +10 -6
  412. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  413. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  414. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  415. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  416. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  417. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  418. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  419. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  420. package/src/core/constraints/capabilities-parser.ts +53 -35
  421. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  422. package/src/core/constraints/checker.ts +82 -133
  423. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +273 -0
  424. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  425. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  426. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +126 -0
  427. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  428. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  429. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  430. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  431. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  432. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +153 -0
  433. package/src/core/constraints/checkers/capability-sync.ts +75 -96
  434. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  435. package/src/core/constraints/checkers/docs-freshness.ts +54 -70
  436. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  437. package/src/core/constraints/checkers/index.ts +9 -2
  438. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  439. package/src/core/constraints/checkers/types.ts +60 -4
  440. package/src/core/constraints/context-builder.ts +39 -91
  441. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  442. package/src/core/constraints/git-evidence.ts +120 -0
  443. package/src/core/constraints/injection-drift.ts +13 -60
  444. package/src/core/constraints/injection-writer.ts +212 -0
  445. package/src/core/constraints/usage-report.ts +36 -17
  446. package/src/core/index.ts +4 -2
  447. package/src/core/project-config-loader.ts +49 -9
  448. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  449. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  450. package/src/core/validators/passes-gate.ts +86 -248
  451. package/src/core/validators/test-output.ts +128 -0
  452. package/src/failure/__tests__/recorder.test.ts +168 -0
  453. package/src/failure/recorder.ts +12 -10
  454. package/src/gates/CONTEXT.md +7 -4
  455. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  456. package/src/gates/__tests__/acceptance.test.ts +4 -86
  457. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  458. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  459. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  460. package/src/gates/acceptance.ts +19 -84
  461. package/src/gates/checker-gate.ts +12 -15
  462. package/src/gates/command.ts +10 -16
  463. package/src/gates/contract.ts +22 -61
  464. package/src/gates/performance.ts +10 -22
  465. package/src/gates/review.ts +21 -41
  466. package/src/gates/security.ts +10 -22
  467. package/src/gates/types.ts +70 -5
  468. package/src/hooks/CONTEXT.md +1 -1
  469. package/src/hooks/bootstrap.ts +5 -2
  470. package/src/index.ts +0 -4
  471. package/src/knowledge/CONTEXT.md +8 -3
  472. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  473. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  474. package/src/knowledge/__tests__/import.test.ts +44 -0
  475. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  476. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  477. package/src/knowledge/__tests__/migration.test.ts +47 -0
  478. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  479. package/src/knowledge/__tests__/store.test.ts +194 -0
  480. package/src/knowledge/audit.ts +67 -71
  481. package/src/knowledge/flywheel-metrics.ts +71 -0
  482. package/src/knowledge/import.ts +4 -18
  483. package/src/knowledge/index-generator.ts +19 -14
  484. package/src/knowledge/ingest.ts +9 -4
  485. package/src/knowledge/migration.ts +11 -8
  486. package/src/knowledge/reference-tracker.ts +12 -8
  487. package/src/knowledge/store.ts +64 -11
  488. package/src/monitoring/CONTEXT.md +8 -3
  489. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  490. package/src/monitoring/__tests__/traces.test.ts +57 -0
  491. package/src/monitoring/context-tracker.ts +11 -23
  492. package/src/monitoring/index.ts +1 -1
  493. package/src/monitoring/trace-analyzer.ts +21 -2
  494. package/src/monitoring/traces.ts +39 -36
  495. package/src/release/CONTEXT.md +1 -0
  496. package/src/release/__tests__/barrel.test.ts +60 -0
  497. package/src/release/index.ts +2 -13
  498. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  499. package/src/sdd/index-generator.ts +9 -9
  500. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  501. package/src/test-setup/project-fixture.ts +87 -0
  502. package/src/tools/CONTEXT.md +6 -0
  503. package/src/tools/__tests__/registry.test.ts +221 -0
  504. package/src/types/__tests__/types.test.ts +4 -5
  505. package/src/types/constraint.ts +16 -27
  506. package/src/types/passes-gate.ts +10 -30
  507. package/src/types/project-config.ts +1 -1
  508. package/src/types/session.ts +5 -5
  509. package/src/types/trace.ts +10 -0
  510. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  511. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  512. package/src/utils/__tests__/jsonl.test.ts +187 -0
  513. package/src/utils/__tests__/package-version.test.ts +97 -0
  514. package/src/utils/exec.ts +15 -0
  515. package/src/utils/file-walk.ts +11 -2
  516. package/src/utils/frontmatter.ts +121 -0
  517. package/src/utils/jsonl.ts +167 -0
  518. package/src/utils/package-version.ts +27 -0
  519. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  520. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  521. package/dist/cli/commands/doc-freshness-check.js +0 -352
  522. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -14
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -39
  526. package/dist/types/index.js.map +0 -1
  527. package/src/__tests__/long-running.test.ts +0 -17
  528. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  529. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  530. package/src/cli/commands/doc-freshness-check.ts +0 -386
  531. package/src/spec/CONTEXT.md +0 -11
  532. package/src/types/index.ts +0 -47
@@ -36,7 +36,7 @@ const EXPECTED_TOP_LEVEL_COMMANDS = [
36
36
  'check', 'validate', 'passes-gate', 'init', 'report', 'status', 'spec',
37
37
  'sync-docs', 'knowledge', 'sdd', 'failure', 'posteval-plan',
38
38
  'update-user-model', 'release', 'analyze-sessions', 'constraints',
39
- 'doc-freshness-check', 'spec-baseline-check',
39
+ 'spec-baseline-check',
40
40
  // 6 门禁命令(GATE_DEFINITIONS 驱动)
41
41
  'acceptance', 'command', 'contract', 'performance', 'review', 'security',
42
42
  ];
@@ -66,6 +66,28 @@ describe('命令注册表闭环', () => {
66
66
  }
67
67
  });
68
68
 
69
+ it('全部命令实现声明 CommandResult 返回(候选7:判定经返回值外溢)', () => {
70
+ const refs = collectRefs([
71
+ ...COMMAND_DEFINITIONS,
72
+ ...GATE_DEFINITIONS.map(d => d.cli),
73
+ ]);
74
+ for (const ref of refs) {
75
+ const file = fs.existsSync(path.join(repoCommandsDir, ref.module + '.ts'))
76
+ ? path.join(repoCommandsDir, ref.module + '.ts')
77
+ : path.join(repoCommandsDir, ref.module, 'index.ts');
78
+ const source = fs.readFileSync(file, 'utf-8');
79
+ const declAt = source.search(new RegExp(`function\\s+${ref.export}\\s*\\(`));
80
+ expect(declAt).toBeGreaterThanOrEqual(0);
81
+ const decl = source.slice(declAt, declAt + 900);
82
+ expect(decl).toMatch(/\)\s*:\s*(?:Promise<)?CommandResult/);
83
+ // bin 的调用约定:任何路由/子命令/action 至少传 options(mapActionArgs 编组)
84
+ // → 首参不得是 io,否则 options 会被当成 io 传入(写流即崩,退出码 1)
85
+ const params = source.slice(declAt + ref.export.length, declAt + 900);
86
+ const firstParam = (params.slice(params.indexOf('(') + 1).match(/^\s*([A-Za-z_$][\w$]*)/) || [''])[1];
87
+ expect(['io', '_io']).not.toContain(firstParam);
88
+ }
89
+ });
90
+
69
91
  it('顶层命令面与预期 24 命令一致(含 6 门禁)', () => {
70
92
  const names = [
71
93
  ...COMMAND_DEFINITIONS.map(d => d.command.split(' ')[0]),
@@ -80,45 +102,42 @@ describe('命令注册表闭环', () => {
80
102
  expect(new Set(names).size).toBe(names.length);
81
103
  });
82
104
 
83
- it('全部命令定义的路由构造可执行(mapActionArgs/subcommands.args/optionRoutes.args/afterRun 冒烟)', () => {
84
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
85
- try {
86
- const walk = (def: CommandDefinition) => {
87
- if (def.mapActionArgs) {
88
- def.mapActionArgs(['positional'], {});
89
- def.mapActionArgs([], {});
105
+ it('子命令别名唯一:别名不与主名/其他别名碰撞(候选7:别名是数据,冲突即行为歧义)', () => {
106
+ const walk = (def: CommandDefinition) => {
107
+ const entries = Object.entries(def.subcommands || {});
108
+ const seen = new Set<string>();
109
+ for (const [name, entry] of entries) {
110
+ expect(seen.has(name)).toBe(false);
111
+ seen.add(name);
112
+ for (const alias of entry.aliases || []) {
113
+ expect(seen.has(alias)).toBe(false);
114
+ seen.add(alias);
90
115
  }
91
- for (const entry of Object.values(def.subcommands || {})) {
92
- if (entry.args) {
93
- entry.args(['positional'], {});
94
- entry.args([], {});
95
- }
96
- }
97
- for (const route of def.optionRoutes || []) {
98
- if (route.args) {
99
- route.args([], {});
100
- route.args(['positional'], {});
101
- }
102
- }
103
- if (def.afterRun) {
104
- def.afterRun(true, {});
105
- def.afterRun(false, { check: true });
106
- }
107
- for (const child of def.children || []) walk(child);
108
- };
109
- for (const def of [
110
- ...COMMAND_DEFINITIONS,
111
- ...GATE_DEFINITIONS.map(d => d.cli),
112
- ]) walk(def);
113
- // sync-docs --check 失败路径确实会请求退出
114
- expect(exitSpy).toHaveBeenCalled();
115
- } finally {
116
- exitSpy.mockRestore();
117
- }
116
+ }
117
+ for (const child of def.children || []) walk(child);
118
+ };
119
+ for (const def of [...COMMAND_DEFINITIONS, ...GATE_DEFINITIONS.map(d => d.cli)]) walk(def);
120
+ });
121
+
122
+ it('全部命令定义的路由构造可执行(mapActionArgs 冒烟;候选7 后定义表零副作用闭包)', () => {
123
+ const walk = (def: CommandDefinition) => {
124
+ if (def.mapActionArgs) {
125
+ def.mapActionArgs(['positional'], {});
126
+ def.mapActionArgs([], {});
127
+ }
128
+ // 候选7:定义表不得再持有退出码处理闭包(afterRun 已废除,判定进返回值)
129
+ expect((def as unknown as Record<string, unknown>).afterRun).toBeUndefined();
130
+ for (const child of def.children || []) walk(child);
131
+ };
132
+ for (const def of [
133
+ ...COMMAND_DEFINITIONS,
134
+ ...GATE_DEFINITIONS.map(d => d.cli),
135
+ ]) walk(def);
118
136
  });
119
137
  });
120
138
 
121
139
  const repoRoot = path.join(__dirname, '..', '..', '..', '..');
140
+ const repoCommandsDir = path.join(repoRoot, 'src', 'cli', 'commands');
122
141
  const distBin = path.join(repoRoot, 'bin', 'harness.js');
123
142
  const distCommands = path.join(repoRoot, 'dist', 'cli', 'commands');
124
143
  const hasDist = fs.existsSync(distCommands);
@@ -156,6 +175,12 @@ function runWithModuleProbe(argv: string[]): { status: number | null; implModule
156
175
  }
157
176
  }
158
177
 
178
+ // CI 上 dist 缺失时显式失败而非静默 skip(harness#99):bin 懒加载/路由的
179
+ // 端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则证据隐形。
180
+ if (!hasDist && process.env.CI) {
181
+ throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
182
+ }
183
+
159
184
  const smoke = hasDist ? describe : describe.skip;
160
185
 
161
186
  smoke('bin/harness.js 端到端(dist 存在时)', () => {
@@ -200,4 +225,19 @@ smoke('bin/harness.js 端到端(dist 存在时)', () => {
200
225
  expect(r.status).toBe(1);
201
226
  expect(r.stderr).toContain('未知子命令: bogus');
202
227
  });
228
+
229
+ it('别名子命令解析到同一实现(knowledge ls = list,候选7)', () => {
230
+ const r = runWithModuleProbe(['knowledge', 'ls', '--json']);
231
+ expect(r.status).toBe(0);
232
+ expect(JSON.parse(r.stdout)).toHaveProperty('total');
233
+ expect(r.implModules).toEqual([
234
+ expect.stringContaining('/dist/cli/commands/knowledge.js'),
235
+ ]);
236
+ });
237
+
238
+ it('search 编组闸门随别名命令名生效且提示逐字不变(kb s 缺参 → exit 1)', () => {
239
+ const r = runWithModuleProbe(['kb', 's']);
240
+ expect(r.status).toBe(1);
241
+ expect(r.stderr).toContain('请提供搜索关键词');
242
+ });
203
243
  });
@@ -1,19 +1,22 @@
1
1
  /**
2
- * release 命令测试(O6)
2
+ * release 命令测试(O6 + 架构评审候选7
3
3
  *
4
- * Seam:release(options) 公开入口。
4
+ * Seam:release(options, io) 公开入口。
5
5
  * 隔离面:
6
- * - child_process.execSync → 按命令分发的 fixture(git/npm/tsc 全部 mock,绝不真执行)
6
+ * - child_process.execSync → 按命令前缀分发的 fixture(git/npm/tsc 全部 mock,绝不真执行)
7
7
  * - fs.existsSync / readFileSync → fixture 包(package.json + dist 关键文件)
8
- * - process.exit抛异常断言化(exit code 可断言,且阻断后续真实副作用)
8
+ * - 输出注入 captureIO(不再 spyOn(process,'exit') / 断言彩色字符串)
9
9
  *
10
- * 测试止于 dry-run 与「发布前闸门」(分支/同步/干净树/tag 冲突),
11
- * 不覆盖真实 npm version/publish 路径——禁止真发版。
10
+ * 判定结果经返回值外溢:每个前置闸门的失败原因含闸门标识(gate <id>),
11
+ * 测试断言 kind + reason + 「首败即停」(后续闸门副作用未发生)。
12
+ *
13
+ * 测试止于 dry-run 与「发布前闸门」,不覆盖真实 npm version/publish 路径——禁止真发版。
12
14
  */
13
15
 
14
16
  import * as fs from 'fs';
15
17
  import * as path from 'path';
16
18
  import { execSync } from 'child_process';
19
+ import { captureIO, type CapturingIO } from '../../../cli/command-contract';
17
20
  import { release } from '../release';
18
21
 
19
22
  jest.mock('child_process', () => ({
@@ -62,22 +65,53 @@ const PKG_JSON = JSON.stringify({
62
65
  exports: { '.': './dist/index.js' },
63
66
  });
64
67
 
68
+ /** 抛一个带 stderr 的子进程失败(execSync 失败对象的形状) */
69
+ function fail(cmd: string, stderr = cmd): never {
70
+ const err: any = new Error(`${cmd} exited`);
71
+ err.stderr = stderr;
72
+ throw err;
73
+ }
74
+
75
+ type Override = [prefix: string, handler: (cmd: string) => string];
76
+
77
+ /**
78
+ * 全闸门通过的默认流(master / 已同步 / 干净树 / tsc ok / dist ok /
79
+ * npm version → v0.18.1 / npm view → 0.18.1),overrides 按前缀优先命中。
80
+ */
81
+ function flow(overrides: Override[] = []): void {
82
+ mockExecSync.mockImplementation((cmd) => {
83
+ const c = String(cmd);
84
+ for (const [prefix, handler] of overrides) {
85
+ if (c.startsWith(prefix)) return handler(c);
86
+ }
87
+ if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
88
+ if (c.startsWith('git rev-list --count')) return '0';
89
+ if (c.startsWith('git status --porcelain')) return '';
90
+ if (c.startsWith('git tag -l')) return '';
91
+ if (c.startsWith('npx tsc')) return 'build ok';
92
+ if (c.startsWith('npm version')) return 'v0.18.1';
93
+ if (c.startsWith('npm view')) return '0.18.1';
94
+ return '';
95
+ });
96
+ }
97
+
98
+ function execCommands(): string[] {
99
+ return mockExecSync.mock.calls.map(c => String(c[0]));
100
+ }
101
+
102
+ /** 断言 fail 结果的 reason 定位到指定闸门 */
103
+ function gateFail(gate: string) {
104
+ return { kind: 'fail' as const, reason: expect.stringContaining(`gate ${gate}`) };
105
+ }
106
+
65
107
  describe('release command', () => {
66
- let consoleSpy: jest.SpyInstance;
67
- let errorSpy: jest.SpyInstance;
68
- let exitSpy: jest.SpyInstance;
108
+ let io: CapturingIO;
69
109
  let pkgExists: boolean;
70
- let tagListResult: string;
71
110
 
72
111
  beforeEach(() => {
73
112
  jest.clearAllMocks();
113
+ io = captureIO();
74
114
  pkgExists = true;
75
- tagListResult = '';
76
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
77
- errorSpy = jest.spyOn(console, 'error').mockImplementation();
78
- exitSpy = jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null) => {
79
- throw new Error(`__exit_${code}__`);
80
- });
81
115
 
82
116
  mockFs.existsSync.mockImplementation((p) => {
83
117
  if (String(p).endsWith('package.json')) return pkgExists;
@@ -85,99 +119,194 @@ describe('release command', () => {
85
119
  });
86
120
  mockFs.readFileSync.mockReturnValue(PKG_JSON);
87
121
 
88
- mockExecSync.mockImplementation((cmd: string) => {
89
- const c = String(cmd);
90
- if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
91
- if (c.startsWith('git rev-list --count')) return '0';
92
- if (c.startsWith('git status --porcelain')) return '';
93
- if (c.startsWith('git tag -l')) return tagListResult;
94
- if (c.startsWith('npx tsc')) return 'build ok';
95
- return '';
96
- });
122
+ flow();
97
123
  });
98
124
 
99
- afterEach(() => {
100
- consoleSpy.mockRestore();
101
- errorSpy.mockRestore();
102
- exitSpy.mockRestore();
125
+ test('闸门 1 非包目录:fail 定位 package 闸门,零子进程调用', async () => {
126
+ pkgExists = false;
127
+
128
+ const result = await release({}, io);
129
+
130
+ expect(result).toEqual(gateFail('package'));
131
+ expect(io.errLines().join('\n')).toContain('Not a package');
132
+ expect(execCommands()).toEqual([]);
103
133
  });
104
134
 
105
- test('非包目录:exit 1 并提示 Not a package', async () => {
106
- pkgExists = false;
135
+ test('闸门 2 master/main 分支:fail 定位 branch 闸门,后续闸门不执行', async () => {
136
+ flow([['git rev-parse --abbrev-ref', () => 'feat/x']]);
137
+
138
+ const result = await release({}, io);
107
139
 
108
- await expect(release({})).rejects.toThrow('__exit_1__');
109
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Not a package'));
140
+ expect(result).toEqual(gateFail('branch'));
141
+ expect(io.errText()).toContain('Must be on master or main branch');
142
+ const commands = execCommands();
143
+ expect(commands.some(c => c.startsWith('git fetch'))).toBe(false);
144
+ expect(commands.some(c => c.startsWith('npx tsc'))).toBe(false);
110
145
  });
111
146
 
112
- test(' master/main 分支:exit 1 并提示', async () => {
113
- mockExecSync.mockImplementation((cmd: string) =>
114
- String(cmd).startsWith('git rev-parse --abbrev-ref') ? 'feat/x' : '');
147
+ test('闸门 3 落后于远程:fail 定位 remote-sync 闸门', async () => {
148
+ flow([['git rev-list --count', () => '3']]);
115
149
 
116
- await expect(release({})).rejects.toThrow('__exit_1__');
117
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Must be on master or main branch'));
150
+ const result = await release({}, io);
151
+
152
+ expect(result).toEqual(gateFail('remote-sync'));
153
+ expect(io.errText()).toContain('behind origin');
154
+ expect(execCommands().some(c => c.startsWith('npx tsc'))).toBe(false);
118
155
  });
119
156
 
120
- test('落后于远程:exit 1 并提示 behind', async () => {
121
- mockExecSync.mockImplementation((cmd: string) => {
122
- const c = String(cmd);
123
- if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
124
- if (c.startsWith('git rev-list --count')) return '3';
125
- return '';
126
- });
157
+ test('闸门 4 工作树不干净:fail 定位 clean-tree 闸门', async () => {
158
+ flow([['git status --porcelain', () => ' M package.json']]);
127
159
 
128
- await expect(release({})).rejects.toThrow('__exit_1__');
129
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('behind origin'));
160
+ const result = await release({}, io);
161
+
162
+ expect(result).toEqual(gateFail('clean-tree'));
163
+ expect(io.errText()).toContain('Uncommitted changes');
164
+ expect(execCommands().some(c => c.startsWith('npx tsc'))).toBe(false);
130
165
  });
131
166
 
132
- test('工作树不干净:exit 1 并提示 Uncommitted changes', async () => {
133
- mockExecSync.mockImplementation((cmd: string) => {
134
- const c = String(cmd);
135
- if (c.startsWith('git rev-parse --abbrev-ref')) return 'master';
136
- if (c.startsWith('git rev-list --count')) return '0';
137
- if (c.startsWith('git status --porcelain')) return ' M package.json';
138
- return '';
139
- });
167
+ test('闸门 5 tsc 构建失败:fail 定位 build 闸门', async () => {
168
+ flow([['npx tsc', (c) => fail(c, 'error TS2304: cannot find name')]]);
169
+
170
+ const result = await release({}, io);
140
171
 
141
- await expect(release({})).rejects.toThrow('__exit_1__');
142
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Uncommitted changes'));
172
+ expect(result).toEqual(gateFail('build'));
173
+ expect(io.errText()).toContain('tsc failed');
174
+ expect(io.errText()).toContain('TS2304');
143
175
  });
144
176
 
145
- test('dist 关键发布物缺失:发布前闸门 exit 1(不触 npm version)', async () => {
177
+ test('闸门 6 dist 关键发布物缺失:fail 定位 dist-artifacts 闸门(不触 npm version)', async () => {
146
178
  mockFs.existsSync.mockImplementation((p) => {
147
179
  if (String(p).endsWith('package.json')) return true;
148
180
  if (String(p).includes(`${path.sep}dist${path.sep}`)) return false;
149
181
  return true;
150
182
  });
151
183
 
152
- await expect(release({ bumpType: 'patch' })).rejects.toThrow('__exit_1__');
153
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('dist missing critical artifacts'));
154
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('dist/index.js'));
155
- const commands = mockExecSync.mock.calls.map(c => String(c[0]));
156
- expect(commands.some(c => c.startsWith('npm version'))).toBe(false);
157
- });
184
+ const result = await release({ bumpType: 'patch' }, io);
158
185
 
159
- test('dry-run patch:计算目标版本 0.18.1,exit 0', async () => {
160
- await expect(release({ bumpType: 'patch', dryRun: 'true' })).rejects.toThrow('__exit_0__');
161
- expect(consoleSpy).toHaveBeenCalledWith(
162
- expect.stringContaining('Dry-run complete. Would publish: @dommaker/harness@0.18.1'),
163
- );
186
+ expect(result).toEqual(gateFail('dist-artifacts'));
187
+ expect(io.errText()).toContain('dist missing critical artifacts');
188
+ expect(io.errText()).toContain('dist/index.js');
189
+ expect(execCommands().some(c => c.startsWith('npm version'))).toBe(false);
164
190
  });
165
191
 
166
- test('dry-run minor:计算目标版本 0.19.0', async () => {
167
- await expect(release({ bumpType: 'minor', dryRun: 'true' })).rejects.toThrow('__exit_0__');
168
- expect(consoleSpy).toHaveBeenCalledWith(
169
- expect.stringContaining('Dry-run complete. Would publish: @dommaker/harness@0.19.0'),
170
- );
192
+ test.each([
193
+ ['patch', '0.18.1'],
194
+ ['minor', '0.19.0'],
195
+ ['major', '1.0.0'],
196
+ ] as const)('dry-run %s:ok 且提示目标版本 %s(不触 npm version)', async (bumpType, expected) => {
197
+ const result = await release({ bumpType, dryRun: 'true' }, io);
198
+
199
+ expect(result).toEqual({ kind: 'ok' });
200
+ expect(io.outText()).toContain(`Dry-run complete. Would publish: @dommaker/harness@${expected}`);
201
+ expect(execCommands().some(c => c.startsWith('npm version'))).toBe(false);
171
202
  });
172
203
 
173
- test('目标 tag 已存在:发布前闸门 exit 1(不触 npm version)', async () => {
174
- tagListResult = 'v0.18.1';
204
+ test('闸门 7 目标 tag 已存在:fail 定位 tag-conflict 闸门(不触 npm version/publish)', async () => {
205
+ flow([['git tag -l', () => 'v0.18.1']]);
206
+
207
+ const result = await release({ bumpType: 'patch' }, io);
175
208
 
176
- await expect(release({ bumpType: 'patch' })).rejects.toThrow('__exit_1__');
177
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('already exists'));
178
- // 闸门阻断:npm version / npm publish 从未执行
179
- const commands = mockExecSync.mock.calls.map(c => String(c[0]));
209
+ expect(result).toEqual(gateFail('tag-conflict'));
210
+ expect(io.errText()).toContain('already exists');
211
+ const commands = execCommands();
180
212
  expect(commands.some(c => c.startsWith('npm version'))).toBe(false);
181
213
  expect(commands.some(c => c.startsWith('npm publish'))).toBe(false);
182
214
  });
215
+
216
+ test('闸门 8 npm version 失败:fail 定位 version-bump 闸门(不触 push)', async () => {
217
+ flow([['npm version', (c) => fail(c, 'npm ERR! Version prefix required')]]);
218
+
219
+ const result = await release({ bumpType: 'patch' }, io);
220
+
221
+ expect(result).toEqual(gateFail('version-bump'));
222
+ expect(io.errText()).toContain('npm version failed');
223
+ expect(execCommands().some(c => c.startsWith('git push'))).toBe(false);
224
+ });
225
+
226
+ test('闸门 9 git push 失败(非保护分支):fail 定位 git-push 闸门', async () => {
227
+ flow([['git push origin master', (c) => fail(c, 'error: failed to push some refs')]]);
228
+
229
+ const result = await release({ bumpType: 'patch' }, io);
230
+
231
+ expect(result).toEqual(gateFail('git-push'));
232
+ expect(io.errText()).toContain('git push failed');
233
+ expect(execCommands().some(c => c.startsWith('npm publish'))).toBe(false);
234
+ });
235
+
236
+ test('闸门 10 保护分支且 PR 创建失败:fail 定位 pr-create 闸门', async () => {
237
+ flow([
238
+ ['git push origin master', (c) => fail(c, 'GH006: push rejected to protected branch')],
239
+ ['gh pr create', (c) => fail(c, 'could not create pull request')],
240
+ ]);
241
+
242
+ const result = await release({ bumpType: 'patch' }, io);
243
+
244
+ expect(result).toEqual(gateFail('pr-create'));
245
+ expect(io.errText()).toContain('PR creation failed');
246
+ expect(execCommands().some(c => c.startsWith('npm publish'))).toBe(false);
247
+ });
248
+
249
+ test('保护分支且 PR 创建成功:ok(不走 npm publish,打印后续步骤)', async () => {
250
+ flow([
251
+ ['git push origin master', (c) => fail(c, 'GH006: push rejected to protected branch')],
252
+ ['gh pr create', () => 'https://github.com/x/y/pull/9'],
253
+ ]);
254
+
255
+ const result = await release({ bumpType: 'patch' }, io);
256
+
257
+ expect(result).toEqual({ kind: 'ok' });
258
+ expect(io.outText()).toContain('PR created: https://github.com/x/y/pull/9');
259
+ expect(io.outText()).toContain('Next steps');
260
+ expect(execCommands().some(c => c.startsWith('npm publish'))).toBe(false);
261
+ });
262
+
263
+ test('闸门 11 package.json private=true:fail 定位 private-flag 闸门(不触 npm publish)', async () => {
264
+ mockFs.readFileSync.mockReturnValue(JSON.stringify({
265
+ name: '@dommaker/harness',
266
+ version: '0.18.0',
267
+ private: true,
268
+ main: './dist/index.js',
269
+ }));
270
+
271
+ const result = await release({ bumpType: 'patch' }, io);
272
+
273
+ expect(result).toEqual(gateFail('private-flag'));
274
+ expect(io.errText()).toContain('"private": true');
275
+ expect(execCommands().some(c => c.startsWith('npm publish'))).toBe(false);
276
+ });
277
+
278
+ test('闸门 12 npm 版本校验不一致:fail 定位 npm-publish-verify 闸门', async () => {
279
+ flow([['npm view', () => '0.0.1']]);
280
+
281
+ const result = await release({ bumpType: 'patch' }, io);
282
+
283
+ expect(result).toEqual(gateFail('npm-publish-verify'));
284
+ expect(io.errText()).toContain('npm publish verification failed');
285
+ expect(io.errText()).toContain('Expected 0.18.1, registry has 0.0.1');
286
+ });
287
+
288
+ test('发布全流程通过:ok,GitHub Release 失败为非致命告警', async () => {
289
+ flow([['gh release create', (c) => fail(c, 'gh: could not generate notes')]]);
290
+
291
+ const result = await release({ bumpType: 'patch' }, io);
292
+
293
+ expect(result).toEqual({ kind: 'ok' });
294
+ expect(io.outText()).toContain('Released @dommaker/harness@0.18.1');
295
+ expect(io.outText()).toContain('release may have failed (non-fatal)');
296
+ expect(io.outText()).toContain('npm: published @dommaker/harness@0.18.1');
297
+ });
298
+
299
+ test('闸门标识唯一且覆盖全部前置闸门(失败原因可逐闸门断言)', () => {
300
+ // 本文件 mock 了 fs,读源码要走真实 fs
301
+ const source: string = jest.requireActual('fs').readFileSync(path.join(__dirname, '..', 'release.ts'), 'utf-8');
302
+ expect(source).toContain('export async function release');
303
+ const gateIds = [...source.matchAll(/gateFail\('([a-z0-9-]+)'/g)].map(m => m[1]);
304
+ expect(gateIds.length).toBeGreaterThanOrEqual(12);
305
+ expect(new Set(gateIds).size).toBe(gateIds.length);
306
+ expect(gateIds).toEqual(expect.arrayContaining([
307
+ 'package', 'branch', 'remote-sync', 'clean-tree', 'build', 'dist-artifacts',
308
+ 'tag-conflict', 'version-bump', 'git-push', 'pr-create', 'private-flag', 'npm-publish-verify',
309
+ ]));
310
+ expect(source).not.toContain('process.exit');
311
+ });
183
312
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { report } from '../report';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs/promises';
7
8
 
8
9
  // Mock fs/promises
@@ -19,30 +20,29 @@ jest.mock('chalk', () => ({
19
20
 
20
21
  const mockFs = fs as jest.Mocked<typeof fs>;
21
22
 
23
+ let io: CapturingIO;
24
+ beforeEach(() => {
25
+ io = captureIO();
26
+ });
27
+
22
28
  describe('report command - 补充覆盖', () => {
23
- let consoleSpy: jest.SpyInstance;
24
29
 
25
30
  beforeEach(() => {
26
31
  jest.clearAllMocks();
27
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
28
- });
29
-
30
- afterEach(() => {
31
- consoleSpy.mockRestore();
32
32
  });
33
33
 
34
34
  describe('format 边界情况', () => {
35
35
  it('未知格式应该默认输出 JSON', async () => {
36
- await report({ format: 'json' });
37
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
36
+ await report({ format: 'json' }, io);
37
+ const output = io.outText();
38
38
  expect(output).toContain('timestamp');
39
39
  });
40
40
  });
41
41
 
42
42
  describe('Markdown 报告完整内容', () => {
43
43
  it('Markdown 报告应该包含所有区块', async () => {
44
- await report({ format: 'markdown' });
45
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
44
+ await report({ format: 'markdown' }, io);
45
+ const output = io.outText();
46
46
 
47
47
  expect(output).toContain('# Harness 检查报告');
48
48
  expect(output).toContain('## 约束检查');
@@ -54,8 +54,8 @@ describe('report command - 补充覆盖', () => {
54
54
 
55
55
  describe('HTML 报告完整内容', () => {
56
56
  it('HTML 报告应该包含所有区块', async () => {
57
- await report({ format: 'html' });
58
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
57
+ await report({ format: 'html' }, io);
58
+ const output = io.outText();
59
59
 
60
60
  expect(output).toContain('<!DOCTYPE html>');
61
61
  expect(output).toContain('<title>Harness 检查报告</title>');
@@ -68,22 +68,22 @@ describe('report command - 补充覆盖', () => {
68
68
  it('Markdown 输出到文件', async () => {
69
69
  mockFs.writeFile.mockResolvedValue(undefined);
70
70
 
71
- await report({ format: 'markdown', output: 'report.md' });
71
+ await report({ format: 'markdown', output: 'report.md' }, io);
72
72
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
73
73
  });
74
74
 
75
75
  it('HTML 输出到文件', async () => {
76
76
  mockFs.writeFile.mockResolvedValue(undefined);
77
77
 
78
- await report({ format: 'html', output: 'report.html' });
78
+ await report({ format: 'html', output: 'report.html' }, io);
79
79
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
80
80
  });
81
81
  });
82
82
 
83
83
  describe('JSON 报告内容', () => {
84
84
  it('JSON 应该包含完整数据结构', async () => {
85
- await report({ format: 'json' });
86
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
85
+ await report({ format: 'json' }, io);
86
+ const output = io.outText();
87
87
  const jsonStart = output.indexOf('{');
88
88
  const jsonStr = output.slice(jsonStart);
89
89
  const data = JSON.parse(jsonStr);