@dommaker/harness 1.3.0 → 1.4.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 (517) hide show
  1. package/CHANGELOG.md +8 -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 +76 -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 +29 -214
  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 +21 -15
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +214 -199
  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 +2 -1
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +31 -45
  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 +64 -115
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +4 -3
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +29 -69
  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 +37 -63
  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/no-test-simplification.d.ts.map +1 -1
  160. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  161. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  162. package/dist/core/constraints/checkers/types.d.ts +2 -2
  163. package/dist/core/constraints/context-builder.d.ts +15 -14
  164. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  165. package/dist/core/constraints/context-builder.js +32 -96
  166. package/dist/core/constraints/context-builder.js.map +1 -1
  167. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  168. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  169. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  170. package/dist/core/constraints/git-evidence.d.ts +60 -0
  171. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  172. package/dist/core/constraints/git-evidence.js +88 -0
  173. package/dist/core/constraints/git-evidence.js.map +1 -0
  174. package/dist/core/constraints/injection-drift.d.ts +3 -20
  175. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  176. package/dist/core/constraints/injection-drift.js +10 -57
  177. package/dist/core/constraints/injection-drift.js.map +1 -1
  178. package/dist/core/constraints/injection-writer.d.ts +113 -0
  179. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  180. package/dist/core/constraints/injection-writer.js +228 -0
  181. package/dist/core/constraints/injection-writer.js.map +1 -0
  182. package/dist/core/constraints/usage-report.d.ts +22 -1
  183. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  184. package/dist/core/constraints/usage-report.js +27 -20
  185. package/dist/core/constraints/usage-report.js.map +1 -1
  186. package/dist/core/index.d.ts +2 -2
  187. package/dist/core/index.d.ts.map +1 -1
  188. package/dist/core/index.js +4 -2
  189. package/dist/core/index.js.map +1 -1
  190. package/dist/core/project-config-loader.d.ts +32 -1
  191. package/dist/core/project-config-loader.d.ts.map +1 -1
  192. package/dist/core/project-config-loader.js +39 -9
  193. package/dist/core/project-config-loader.js.map +1 -1
  194. package/dist/core/validators/passes-gate.d.ts +15 -64
  195. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  196. package/dist/core/validators/passes-gate.js +84 -222
  197. package/dist/core/validators/passes-gate.js.map +1 -1
  198. package/dist/core/validators/test-output.d.ts +58 -0
  199. package/dist/core/validators/test-output.d.ts.map +1 -0
  200. package/dist/core/validators/test-output.js +109 -0
  201. package/dist/core/validators/test-output.js.map +1 -0
  202. package/dist/failure/recorder.d.ts +6 -0
  203. package/dist/failure/recorder.d.ts.map +1 -1
  204. package/dist/failure/recorder.js +12 -8
  205. package/dist/failure/recorder.js.map +1 -1
  206. package/dist/gates/acceptance.d.ts +1 -48
  207. package/dist/gates/acceptance.d.ts.map +1 -1
  208. package/dist/gates/acceptance.js +6 -38
  209. package/dist/gates/acceptance.js.map +1 -1
  210. package/dist/gates/checker-gate.d.ts.map +1 -1
  211. package/dist/gates/checker-gate.js +6 -11
  212. package/dist/gates/checker-gate.js.map +1 -1
  213. package/dist/gates/command.d.ts.map +1 -1
  214. package/dist/gates/command.js +10 -22
  215. package/dist/gates/command.js.map +1 -1
  216. package/dist/gates/contract.d.ts.map +1 -1
  217. package/dist/gates/contract.js +22 -61
  218. package/dist/gates/contract.js.map +1 -1
  219. package/dist/gates/performance.d.ts.map +1 -1
  220. package/dist/gates/performance.js +13 -31
  221. package/dist/gates/performance.js.map +1 -1
  222. package/dist/gates/review.d.ts.map +1 -1
  223. package/dist/gates/review.js +21 -53
  224. package/dist/gates/review.js.map +1 -1
  225. package/dist/gates/security.d.ts.map +1 -1
  226. package/dist/gates/security.js +14 -32
  227. package/dist/gates/security.js.map +1 -1
  228. package/dist/gates/types.d.ts +28 -5
  229. package/dist/gates/types.d.ts.map +1 -1
  230. package/dist/gates/types.js +38 -1
  231. package/dist/gates/types.js.map +1 -1
  232. package/dist/hooks/bootstrap.d.ts +2 -0
  233. package/dist/hooks/bootstrap.d.ts.map +1 -1
  234. package/dist/hooks/bootstrap.js +5 -2
  235. package/dist/hooks/bootstrap.js.map +1 -1
  236. package/dist/index.d.ts +1 -2
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js.map +1 -1
  239. package/dist/knowledge/audit.d.ts +7 -2
  240. package/dist/knowledge/audit.d.ts.map +1 -1
  241. package/dist/knowledge/audit.js +59 -79
  242. package/dist/knowledge/audit.js.map +1 -1
  243. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  244. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  245. package/dist/knowledge/flywheel-metrics.js +47 -0
  246. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  247. package/dist/knowledge/import.d.ts.map +1 -1
  248. package/dist/knowledge/import.js +4 -14
  249. package/dist/knowledge/import.js.map +1 -1
  250. package/dist/knowledge/index-generator.d.ts.map +1 -1
  251. package/dist/knowledge/index-generator.js +17 -14
  252. package/dist/knowledge/index-generator.js.map +1 -1
  253. package/dist/knowledge/ingest.d.ts.map +1 -1
  254. package/dist/knowledge/ingest.js +9 -4
  255. package/dist/knowledge/ingest.js.map +1 -1
  256. package/dist/knowledge/migration.d.ts.map +1 -1
  257. package/dist/knowledge/migration.js +11 -8
  258. package/dist/knowledge/migration.js.map +1 -1
  259. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  260. package/dist/knowledge/reference-tracker.js +12 -9
  261. package/dist/knowledge/reference-tracker.js.map +1 -1
  262. package/dist/knowledge/store.d.ts +15 -0
  263. package/dist/knowledge/store.d.ts.map +1 -1
  264. package/dist/knowledge/store.js +59 -10
  265. package/dist/knowledge/store.js.map +1 -1
  266. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  267. package/dist/monitoring/context-tracker.js +11 -20
  268. package/dist/monitoring/context-tracker.js.map +1 -1
  269. package/dist/monitoring/index.d.ts +1 -1
  270. package/dist/monitoring/index.d.ts.map +1 -1
  271. package/dist/monitoring/index.js +1 -2
  272. package/dist/monitoring/index.js.map +1 -1
  273. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  274. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  275. package/dist/monitoring/trace-analyzer.js +19 -2
  276. package/dist/monitoring/trace-analyzer.js.map +1 -1
  277. package/dist/monitoring/traces.d.ts +21 -2
  278. package/dist/monitoring/traces.d.ts.map +1 -1
  279. package/dist/monitoring/traces.js +38 -29
  280. package/dist/monitoring/traces.js.map +1 -1
  281. package/dist/release/index.d.ts +2 -2
  282. package/dist/release/index.d.ts.map +1 -1
  283. package/dist/release/index.js +1 -5
  284. package/dist/release/index.js.map +1 -1
  285. package/dist/sdd/index-generator.js +10 -11
  286. package/dist/sdd/index-generator.js.map +1 -1
  287. package/dist/test-setup/project-fixture.d.ts +48 -0
  288. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  289. package/dist/test-setup/project-fixture.js +106 -0
  290. package/dist/test-setup/project-fixture.js.map +1 -0
  291. package/dist/types/constraint.d.ts +0 -18
  292. package/dist/types/constraint.d.ts.map +1 -1
  293. package/dist/types/constraint.js.map +1 -1
  294. package/dist/types/passes-gate.d.ts +10 -25
  295. package/dist/types/passes-gate.d.ts.map +1 -1
  296. package/dist/types/project-config.d.ts +1 -1
  297. package/dist/types/session.d.ts +6 -5
  298. package/dist/types/session.d.ts.map +1 -1
  299. package/dist/utils/exec.d.ts +10 -0
  300. package/dist/utils/exec.d.ts.map +1 -1
  301. package/dist/utils/exec.js +12 -0
  302. package/dist/utils/exec.js.map +1 -1
  303. package/dist/utils/file-walk.d.ts +9 -1
  304. package/dist/utils/file-walk.d.ts.map +1 -1
  305. package/dist/utils/file-walk.js +10 -1
  306. package/dist/utils/file-walk.js.map +1 -1
  307. package/dist/utils/frontmatter.d.ts +73 -0
  308. package/dist/utils/frontmatter.d.ts.map +1 -0
  309. package/dist/utils/frontmatter.js +141 -0
  310. package/dist/utils/frontmatter.js.map +1 -0
  311. package/dist/utils/jsonl.d.ts +92 -0
  312. package/dist/utils/jsonl.d.ts.map +1 -0
  313. package/dist/utils/jsonl.js +166 -0
  314. package/dist/utils/jsonl.js.map +1 -0
  315. package/dist/utils/package-version.d.ts +16 -0
  316. package/dist/utils/package-version.d.ts.map +1 -0
  317. package/dist/utils/package-version.js +63 -0
  318. package/dist/utils/package-version.js.map +1 -0
  319. package/package.json +1 -1
  320. package/src/CONTEXT.md +7 -2
  321. package/src/__tests__/acceptance-gate.test.ts +3 -1
  322. package/src/__tests__/checker-extra.test.ts +7 -1218
  323. package/src/__tests__/context-files-resolution.test.ts +116 -0
  324. package/src/__tests__/effective-constraints.test.ts +10 -13
  325. package/src/__tests__/exec.test.ts +35 -1
  326. package/src/__tests__/layering.test.ts +152 -0
  327. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  328. package/src/__tests__/passes-gate.test.ts +142 -106
  329. package/src/__tests__/skip-semantics.test.ts +5 -9
  330. package/src/cli/__tests__/command-contract.test.ts +170 -0
  331. package/src/cli/command-contract.ts +108 -0
  332. package/src/cli/commands/CONTEXT.md +22 -7
  333. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  334. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  335. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +101 -0
  336. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  337. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  338. package/src/cli/commands/__tests__/check.test.ts +378 -382
  339. package/src/cli/commands/__tests__/command.test.ts +85 -70
  340. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  341. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  342. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  343. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  344. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  345. package/src/cli/commands/__tests__/init.test.ts +77 -48
  346. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  347. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  348. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  349. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  350. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  351. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  352. package/src/cli/commands/__tests__/project-path-convention.test.ts +169 -0
  353. package/src/cli/commands/__tests__/registry.test.ts +69 -35
  354. package/src/cli/commands/__tests__/release.test.ts +209 -80
  355. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  356. package/src/cli/commands/__tests__/report.test.ts +25 -25
  357. package/src/cli/commands/__tests__/review.test.ts +33 -31
  358. package/src/cli/commands/__tests__/security.test.ts +23 -27
  359. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  360. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  361. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  362. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  363. package/src/cli/commands/__tests__/status.test.ts +34 -34
  364. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  365. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  366. package/src/cli/commands/__tests__/update-user-model.test.ts +62 -33
  367. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  368. package/src/cli/commands/acceptance.ts +35 -24
  369. package/src/cli/commands/analyze-sessions.ts +25 -24
  370. package/src/cli/commands/check.ts +88 -65
  371. package/src/cli/commands/command.ts +30 -17
  372. package/src/cli/commands/constraints-report.ts +37 -34
  373. package/src/cli/commands/constraints-retire.ts +58 -55
  374. package/src/cli/commands/constraints.ts +10 -27
  375. package/src/cli/commands/contract.ts +51 -42
  376. package/src/cli/commands/definitions.ts +36 -222
  377. package/src/cli/commands/failure.ts +33 -26
  378. package/src/cli/commands/init.ts +166 -220
  379. package/src/cli/commands/knowledge.ts +225 -203
  380. package/src/cli/commands/passes-gate.ts +58 -68
  381. package/src/cli/commands/performance.ts +23 -18
  382. package/src/cli/commands/posteval-plan.ts +29 -15
  383. package/src/cli/commands/release.ts +70 -59
  384. package/src/cli/commands/report.ts +9 -5
  385. package/src/cli/commands/review.ts +39 -29
  386. package/src/cli/commands/sdd.ts +7 -5
  387. package/src/cli/commands/security.ts +35 -26
  388. package/src/cli/commands/spec-baseline-check.ts +16 -11
  389. package/src/cli/commands/spec.ts +66 -53
  390. package/src/cli/commands/status.ts +59 -53
  391. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  392. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  393. package/src/cli/commands/sync-docs/index.ts +73 -100
  394. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  395. package/src/cli/commands/update-user-model.ts +33 -45
  396. package/src/cli/commands/validate.ts +31 -25
  397. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  398. package/src/cli/session-mining/transcript.ts +26 -8
  399. package/src/context/session-manager.ts +19 -33
  400. package/src/core/CONTEXT.md +9 -6
  401. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  402. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  403. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  404. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  405. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  406. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  407. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  408. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  409. package/src/core/constraints/capabilities-parser.ts +53 -35
  410. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  411. package/src/core/constraints/checker.ts +75 -130
  412. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +214 -0
  413. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  414. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +117 -0
  415. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  416. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  417. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  418. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  419. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  420. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +145 -0
  421. package/src/core/constraints/checkers/capability-sync.ts +29 -88
  422. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  423. package/src/core/constraints/checkers/docs-freshness.ts +40 -69
  424. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  425. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  426. package/src/core/constraints/checkers/types.ts +2 -2
  427. package/src/core/constraints/context-builder.ts +39 -91
  428. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  429. package/src/core/constraints/git-evidence.ts +120 -0
  430. package/src/core/constraints/injection-drift.ts +13 -60
  431. package/src/core/constraints/injection-writer.ts +212 -0
  432. package/src/core/constraints/usage-report.ts +36 -17
  433. package/src/core/index.ts +4 -2
  434. package/src/core/project-config-loader.ts +49 -9
  435. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  436. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  437. package/src/core/validators/passes-gate.ts +86 -248
  438. package/src/core/validators/test-output.ts +128 -0
  439. package/src/failure/__tests__/recorder.test.ts +168 -0
  440. package/src/failure/recorder.ts +12 -10
  441. package/src/gates/CONTEXT.md +6 -3
  442. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  443. package/src/gates/__tests__/acceptance.test.ts +4 -86
  444. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  445. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  446. package/src/gates/acceptance.ts +19 -84
  447. package/src/gates/checker-gate.ts +8 -7
  448. package/src/gates/command.ts +10 -16
  449. package/src/gates/contract.ts +22 -61
  450. package/src/gates/performance.ts +10 -22
  451. package/src/gates/review.ts +21 -41
  452. package/src/gates/security.ts +10 -22
  453. package/src/gates/types.ts +70 -5
  454. package/src/hooks/CONTEXT.md +1 -1
  455. package/src/hooks/bootstrap.ts +5 -2
  456. package/src/index.ts +0 -4
  457. package/src/knowledge/CONTEXT.md +8 -3
  458. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  459. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  460. package/src/knowledge/__tests__/import.test.ts +44 -0
  461. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  462. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  463. package/src/knowledge/__tests__/migration.test.ts +47 -0
  464. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  465. package/src/knowledge/__tests__/store.test.ts +194 -0
  466. package/src/knowledge/audit.ts +67 -71
  467. package/src/knowledge/flywheel-metrics.ts +71 -0
  468. package/src/knowledge/import.ts +4 -18
  469. package/src/knowledge/index-generator.ts +19 -14
  470. package/src/knowledge/ingest.ts +9 -4
  471. package/src/knowledge/migration.ts +11 -8
  472. package/src/knowledge/reference-tracker.ts +12 -8
  473. package/src/knowledge/store.ts +64 -11
  474. package/src/monitoring/CONTEXT.md +8 -3
  475. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  476. package/src/monitoring/__tests__/traces.test.ts +57 -0
  477. package/src/monitoring/context-tracker.ts +11 -23
  478. package/src/monitoring/index.ts +1 -1
  479. package/src/monitoring/trace-analyzer.ts +21 -2
  480. package/src/monitoring/traces.ts +39 -36
  481. package/src/release/CONTEXT.md +1 -0
  482. package/src/release/__tests__/barrel.test.ts +60 -0
  483. package/src/release/index.ts +2 -13
  484. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  485. package/src/sdd/index-generator.ts +9 -9
  486. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  487. package/src/test-setup/project-fixture.ts +87 -0
  488. package/src/tools/CONTEXT.md +6 -0
  489. package/src/tools/__tests__/registry.test.ts +221 -0
  490. package/src/types/__tests__/types.test.ts +4 -5
  491. package/src/types/constraint.ts +0 -25
  492. package/src/types/passes-gate.ts +10 -30
  493. package/src/types/project-config.ts +1 -1
  494. package/src/types/session.ts +5 -5
  495. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  496. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  497. package/src/utils/__tests__/jsonl.test.ts +187 -0
  498. package/src/utils/__tests__/package-version.test.ts +97 -0
  499. package/src/utils/exec.ts +15 -0
  500. package/src/utils/file-walk.ts +11 -2
  501. package/src/utils/frontmatter.ts +121 -0
  502. package/src/utils/jsonl.ts +167 -0
  503. package/src/utils/package-version.ts +27 -0
  504. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  505. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  506. package/dist/cli/commands/doc-freshness-check.js +0 -352
  507. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  508. package/dist/types/index.d.ts +0 -14
  509. package/dist/types/index.d.ts.map +0 -1
  510. package/dist/types/index.js +0 -39
  511. package/dist/types/index.js.map +0 -1
  512. package/src/__tests__/long-running.test.ts +0 -17
  513. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  514. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  515. package/src/cli/commands/doc-freshness-check.ts +0 -386
  516. package/src/spec/CONTEXT.md +0 -11
  517. package/src/types/index.ts +0 -47
@@ -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);
@@ -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,60 +20,59 @@ 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('report', () => {
35
35
  it('应该生成 JSON 格式报告', async () => {
36
- await report({ format: 'json' });
37
- expect(consoleSpy).toHaveBeenCalled();
38
- const output = consoleSpy.mock.calls.map(c => c[0]).join('\n');
36
+ await report({ format: 'json' }, io);
37
+ expect(io.outText()).not.toBe('');
38
+ const output = io.outText();
39
39
  expect(output).toContain('timestamp');
40
40
  });
41
41
 
42
42
  it('应该生成 Markdown 格式报告', async () => {
43
- await report({ format: 'markdown' });
44
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Harness 检查报告'));
43
+ await report({ format: 'markdown' }, io);
44
+ expect(io.outText()).toContain('Harness 检查报告');
45
45
  });
46
46
 
47
47
  it('应该生成 HTML 格式报告', async () => {
48
- await report({ format: 'html' });
49
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('<!DOCTYPE html>'));
48
+ await report({ format: 'html' }, io);
49
+ expect(io.outText()).toContain('<!DOCTYPE html>');
50
50
  });
51
51
 
52
52
  it('应该保存报告到文件', async () => {
53
53
  mockFs.writeFile.mockResolvedValue(undefined);
54
54
 
55
- await report({ format: 'json', output: 'report.json' });
55
+ await report({ format: 'json', output: 'report.json' }, io);
56
56
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.json', expect.any(String), 'utf-8');
57
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('报告已保存'));
57
+ expect(io.outText()).toContain('报告已保存');
58
58
  });
59
59
 
60
60
  it('应该使用自定义项目路径', async () => {
61
- await report({ format: 'json', projectPath: '/custom/path' });
62
- expect(consoleSpy).toHaveBeenCalled();
61
+ await report({ format: 'json', projectPath: '/custom/path' }, io);
62
+ expect(io.outText()).not.toBe('');
63
63
  });
64
64
 
65
65
  it('Markdown 报告应包含表格结构', async () => {
66
- await report({ format: 'markdown' });
67
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
66
+ await report({ format: 'markdown' }, io);
67
+ const output = io.outText();
68
68
  expect(output).toContain('| 指标 | 数值 |');
69
69
  expect(output).toContain('| 总约束 |');
70
70
  expect(output).toContain('报告由 @dommaker/harness 生成');
71
71
  });
72
72
 
73
73
  it('HTML 报告应包含完整结构', async () => {
74
- await report({ format: 'html' });
75
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
74
+ await report({ format: 'html' }, io);
75
+ const output = io.outText();
76
76
  expect(output).toContain('<html lang="zh-CN">');
77
77
  expect(output).toContain('</html>');
78
78
  expect(output).toContain('Harness 检查报告');
@@ -80,8 +80,8 @@ describe('report command', () => {
80
80
  });
81
81
 
82
82
  it('JSON 报告应包含约束统计', async () => {
83
- await report({ format: 'json' });
84
- const output = consoleSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n');
83
+ await report({ format: 'json' }, io);
84
+ const output = io.outText();
85
85
  // 过滤掉 chalk 的 emoji 行,只取 JSON 部分
86
86
  const jsonStart = output.indexOf('{');
87
87
  const jsonEnd = output.lastIndexOf('}');
@@ -96,13 +96,13 @@ describe('report command', () => {
96
96
 
97
97
  it('应该支持输出到文件 (markdown)', async () => {
98
98
  mockFs.writeFile.mockResolvedValue(undefined);
99
- await report({ format: 'markdown', output: 'report.md' });
99
+ await report({ format: 'markdown', output: 'report.md' }, io);
100
100
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.md', expect.stringContaining('# Harness'), 'utf-8');
101
101
  });
102
102
 
103
103
  it('应该支持输出到文件 (html)', async () => {
104
104
  mockFs.writeFile.mockResolvedValue(undefined);
105
- await report({ format: 'html', output: 'report.html' });
105
+ await report({ format: 'html', output: 'report.html' }, io);
106
106
  expect(mockFs.writeFile).toHaveBeenCalledWith('report.html', expect.stringContaining('<!DOCTYPE html>'), 'utf-8');
107
107
  });
108
108
  });