@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
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import * as fs from 'fs';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as path from 'path';
7
8
  import { FailureRecorder } from '../../../failure/recorder';
8
9
  import { ErrorType, FailureLevel } from '../../../failure/types';
@@ -11,10 +12,13 @@ import type { FailureRecord } from '../../../failure/types';
11
12
  // Mock FailureRecorder
12
13
  jest.mock('../../../failure/recorder');
13
14
 
15
+ let io: CapturingIO;
16
+ beforeEach(() => {
17
+ io = captureIO();
18
+ });
19
+
14
20
  describe('failure CLI commands', () => {
15
21
  const tempDir = path.join(process.cwd(), 'temp-test-failure-cli');
16
- let consoleSpy: jest.SpyInstance;
17
- let consoleErrorSpy: jest.SpyInstance;
18
22
  let mockRecorderInstance: {
19
23
  record: jest.Mock;
20
24
  getHistory: jest.Mock;
@@ -48,8 +52,6 @@ describe('failure CLI commands', () => {
48
52
 
49
53
  beforeEach(() => {
50
54
  jest.clearAllMocks();
51
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
52
- consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
53
55
 
54
56
  // Setup mock instance methods
55
57
  mockRecorderInstance = {
@@ -62,37 +64,32 @@ describe('failure CLI commands', () => {
62
64
  (FailureRecorder as jest.Mock).mockImplementation(() => mockRecorderInstance);
63
65
  });
64
66
 
65
- afterEach(() => {
66
- consoleSpy.mockRestore();
67
- consoleErrorSpy.mockRestore();
68
- });
69
-
70
67
  describe('failureList', () => {
71
68
  it('应该列出所有记录', async () => {
72
69
  const { failureList } = await import('../failure');
73
- await failureList({ projectPath: tempDir });
70
+ await failureList({ projectPath: tempDir }, io);
74
71
 
75
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('失败记录'));
76
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('TEST_FAILED'));
72
+ expect(io.outText()).toContain('失败记录');
73
+ expect(io.outText()).toContain('TEST_FAILED');
77
74
  });
78
75
 
79
76
  it('应该按类型过滤', async () => {
80
77
  const { failureList } = await import('../failure');
81
- await failureList({ projectPath: tempDir, type: 'TEST_FAILED' });
78
+ await failureList({ projectPath: tempDir, type: 'TEST_FAILED' }, io);
82
79
 
83
80
  expect(mockRecorderInstance.getHistory).toHaveBeenCalled();
84
81
  });
85
82
 
86
83
  it('应该按等级过滤', async () => {
87
84
  const { failureList } = await import('../failure');
88
- await failureList({ projectPath: tempDir, level: 'L1' });
85
+ await failureList({ projectPath: tempDir, level: 'L1' }, io);
89
86
 
90
87
  expect(mockRecorderInstance.getHistory).toHaveBeenCalled();
91
88
  });
92
89
 
93
90
  it('应该限制返回数量', async () => {
94
91
  const { failureList } = await import('../failure');
95
- await failureList({ projectPath: tempDir, limit: 2 });
92
+ await failureList({ projectPath: tempDir, limit: 2 }, io);
96
93
 
97
94
  expect(mockRecorderInstance.getHistory).toHaveBeenCalled();
98
95
  });
@@ -100,16 +97,16 @@ describe('failure CLI commands', () => {
100
97
  it('空记录应该输出提示', async () => {
101
98
  mockRecorderInstance.getHistory.mockResolvedValue([]);
102
99
  const { failureList } = await import('../failure');
103
- await failureList({ projectPath: tempDir });
100
+ await failureList({ projectPath: tempDir }, io);
104
101
 
105
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('没有失败记录'));
102
+ expect(io.outText()).toContain('没有失败记录');
106
103
  });
107
104
 
108
105
  it('--json 模式应该输出 JSON', async () => {
109
106
  const { failureList } = await import('../failure');
110
- await failureList({ projectPath: tempDir, json: true });
107
+ await failureList({ projectPath: tempDir, json: true }, io);
111
108
 
112
- const jsonCall = consoleSpy.mock.calls.find(
109
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find(
113
110
  (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
114
111
  );
115
112
  expect(jsonCall).toBeDefined();
@@ -120,9 +117,9 @@ describe('failure CLI commands', () => {
120
117
  it('--json 模式空记录应该输出 total:0', async () => {
121
118
  mockRecorderInstance.getHistory.mockResolvedValue([]);
122
119
  const { failureList } = await import('../failure');
123
- await failureList({ projectPath: tempDir, json: true });
120
+ await failureList({ projectPath: tempDir, json: true }, io);
124
121
 
125
- const jsonCall = consoleSpy.mock.calls.find(
122
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find(
126
123
  (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
127
124
  );
128
125
  expect(jsonCall).toBeDefined();
@@ -134,17 +131,17 @@ describe('failure CLI commands', () => {
134
131
  describe('failureStats', () => {
135
132
  it('应该输出统计信息', async () => {
136
133
  const { failureStats } = await import('../failure');
137
- await failureStats({ projectPath: tempDir });
134
+ await failureStats({ projectPath: tempDir }, io);
138
135
 
139
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('失败统计'));
140
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('TEST_FAILED'));
136
+ expect(io.outText()).toContain('失败统计');
137
+ expect(io.outText()).toContain('TEST_FAILED');
141
138
  });
142
139
 
143
140
  it('--json 模式应该输出 JSON 统计', async () => {
144
141
  const { failureStats } = await import('../failure');
145
- await failureStats({ projectPath: tempDir, json: true });
142
+ await failureStats({ projectPath: tempDir, json: true }, io);
146
143
 
147
- const jsonCall = consoleSpy.mock.calls.find(
144
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find(
148
145
  (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
149
146
  );
150
147
  expect(jsonCall).toBeDefined();
@@ -155,17 +152,17 @@ describe('failure CLI commands', () => {
155
152
  it('空记录应该输出绿色提示', async () => {
156
153
  mockRecorderInstance.getStats.mockResolvedValue({ total: 0, byType: {}, byLevel: {} });
157
154
  const { failureStats } = await import('../failure');
158
- await failureStats({ projectPath: tempDir });
155
+ await failureStats({ projectPath: tempDir }, io);
159
156
 
160
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('没有失败记录'));
157
+ expect(io.outText()).toContain('没有失败记录');
161
158
  });
162
159
 
163
160
  it('--json 模式空记录应该输出空统计', async () => {
164
161
  mockRecorderInstance.getStats.mockResolvedValue({ total: 0, byType: {}, byLevel: {} });
165
162
  const { failureStats } = await import('../failure');
166
- await failureStats({ projectPath: tempDir, json: true });
163
+ await failureStats({ projectPath: tempDir, json: true }, io);
167
164
 
168
- const jsonCall = consoleSpy.mock.calls.find(
165
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find(
169
166
  (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
170
167
  );
171
168
  expect(jsonCall).toBeDefined();
@@ -177,17 +174,17 @@ describe('failure CLI commands', () => {
177
174
  describe('failureClear', () => {
178
175
  it('应该清空记录并输出结果', async () => {
179
176
  const { failureClear } = await import('../failure');
180
- await failureClear({ projectPath: tempDir });
177
+ await failureClear({ projectPath: tempDir }, io);
181
178
 
182
179
  expect(mockRecorderInstance.clear).toHaveBeenCalled();
183
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('已清空'));
180
+ expect(io.outText()).toContain('已清空');
184
181
  });
185
182
 
186
183
  it('--json 模式应该输出清空的记录数', async () => {
187
184
  const { failureClear } = await import('../failure');
188
- await failureClear({ projectPath: tempDir, json: true });
185
+ await failureClear({ projectPath: tempDir, json: true }, io);
189
186
 
190
- const jsonCall = consoleSpy.mock.calls.find(
187
+ const jsonCall = io.outRecords().map(chunk => [chunk] as string[]).find(
191
188
  (call: unknown[]) => typeof call[0] === 'string' && call[0].startsWith('{')
192
189
  );
193
190
  expect(jsonCall).toBeDefined();
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
10
+ import { captureIO, type CapturingIO } from '../../command-contract';
10
11
  import * as fs from 'fs';
11
12
  import * as path from 'path';
12
13
  import {
@@ -38,6 +39,11 @@ const makeConstraint = (overrides: Partial<Constraint>): Constraint => ({
38
39
  ...overrides,
39
40
  });
40
41
 
42
+ let io: CapturingIO;
43
+ beforeEach(() => {
44
+ io = captureIO();
45
+ });
46
+
41
47
  describe('renderConstraintsSection(纯函数)', () => {
42
48
  it('按 level 分组渲染,含标记与版本行', () => {
43
49
  const constraints = [
@@ -101,7 +107,7 @@ describe('setupClaudeMdConstraints(消费生效集)', () => {
101
107
  });
102
108
 
103
109
  it('CLAUDE.md 不存在时创建并写入约束段', async () => {
104
- await setupClaudeMdConstraints(tempDir);
110
+ await setupClaudeMdConstraints(tempDir, io);
105
111
 
106
112
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
107
113
  expect(content).toContain('## Governance Rules');
@@ -112,24 +118,32 @@ describe('setupClaudeMdConstraints(消费生效集)', () => {
112
118
  });
113
119
 
114
120
  it('连跑两次输出一致(幂等)', async () => {
115
- await setupClaudeMdConstraints(tempDir);
121
+ await setupClaudeMdConstraints(tempDir, io);
116
122
  const first = fs.readFileSync(claudeMdPath, 'utf-8');
117
- await setupClaudeMdConstraints(tempDir);
123
+ await setupClaudeMdConstraints(tempDir, io);
118
124
  const second = fs.readFileSync(claudeMdPath, 'utf-8');
119
125
  expect(second).toBe(first);
120
126
  });
121
127
 
122
128
  it('标记区间外内容保持不动', async () => {
123
129
  fs.writeFileSync(claudeMdPath, '# My Project\n\n用户自定义内容\n');
124
- await setupClaudeMdConstraints(tempDir);
130
+ await setupClaudeMdConstraints(tempDir, io);
125
131
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
126
132
  expect(content).toContain('# My Project');
127
133
  expect(content).toContain('用户自定义内容');
128
134
  });
129
135
 
136
+ it('标记残缺(只有单边):拒写不追加并告警(writer 半标记守护,候选1)', async () => {
137
+ const broken = `# My Project\n\n${CONSTRAINTS_START_MARKER}\n半残段\n`;
138
+ fs.writeFileSync(claudeMdPath, broken);
139
+ await setupClaudeMdConstraints(tempDir, io);
140
+ expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(broken);
141
+ expect(io.outText()).toContain('残缺');
142
+ });
143
+
130
144
  it('尊重 config.yml 禁用:注入段不含被禁条目', async () => {
131
145
  writeConfig('constraints:\n no_fuzzy_completion_claim:\n enabled: false\n');
132
- await setupClaudeMdConstraints(tempDir);
146
+ await setupClaudeMdConstraints(tempDir, io);
133
147
 
134
148
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
135
149
  expect(content).not.toContain('no_fuzzy_completion_claim');
@@ -138,12 +152,12 @@ describe('setupClaudeMdConstraints(消费生效集)', () => {
138
152
 
139
153
  it('scenes 缺省不注入场景专属 prompt;配置 scenes 后注入', async () => {
140
154
  writeConfig('preset: standard\n');
141
- await setupClaudeMdConstraints(tempDir);
155
+ await setupClaudeMdConstraints(tempDir, io);
142
156
  let content = fs.readFileSync(claudeMdPath, 'utf-8');
143
157
  expect(content).not.toContain('no_skill_without_test');
144
158
 
145
159
  writeConfig('preset: standard\nscenes:\n - agent-skill\n');
146
- await setupClaudeMdConstraints(tempDir);
160
+ await setupClaudeMdConstraints(tempDir, io);
147
161
  content = fs.readFileSync(claudeMdPath, 'utf-8');
148
162
  expect(content).toContain('- **no_skill_without_test**');
149
163
  expect(content).not.toContain('no_model_for_deterministic');
@@ -151,7 +165,7 @@ describe('setupClaudeMdConstraints(消费生效集)', () => {
151
165
 
152
166
  it('注入段文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
153
167
  writeConfig('constraints:\n no_bypass_checkpoint:\n enabled: false\n');
154
- await setupClaudeMdConstraints(tempDir);
168
+ await setupClaudeMdConstraints(tempDir, io);
155
169
 
156
170
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
157
171
  const start = content.indexOf(CONSTRAINTS_START_MARKER);
@@ -179,13 +193,13 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
179
193
  });
180
194
 
181
195
  it('CLAUDE.md 不存在时不创建(由 constraints 注入负责创建)', async () => {
182
- await setupClaudeMdOutputStyle(tempDir);
196
+ await setupClaudeMdOutputStyle(tempDir, io);
183
197
  expect(fs.existsSync(claudeMdPath)).toBe(false);
184
198
  });
185
199
 
186
200
  it('无 Output Style 段:插入带标记段到文件顶部,二次运行幂等', async () => {
187
201
  fs.writeFileSync(claudeMdPath, '# My Project\n\n正文\n');
188
- await setupClaudeMdOutputStyle(tempDir);
202
+ await setupClaudeMdOutputStyle(tempDir, io);
189
203
 
190
204
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
191
205
  expect(content.startsWith(OUTPUT_STYLE_START)).toBe(true);
@@ -193,7 +207,7 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
193
207
  expect(content).toContain(OUTPUT_STYLE_END);
194
208
  expect(content).toContain('# My Project');
195
209
 
196
- await setupClaudeMdOutputStyle(tempDir);
210
+ await setupClaudeMdOutputStyle(tempDir, io);
197
211
  expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
198
212
  });
199
213
 
@@ -209,7 +223,7 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
209
223
  ].join('\n');
210
224
  fs.writeFileSync(claudeMdPath, legacy);
211
225
 
212
- await setupClaudeMdOutputStyle(tempDir);
226
+ await setupClaudeMdOutputStyle(tempDir, io);
213
227
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
214
228
 
215
229
  expect(content).toContain(OUTPUT_STYLE_START);
@@ -219,19 +233,18 @@ describe('setupClaudeMdOutputStyle(标记化)', () => {
219
233
  // 迁移后只剩一个 Output Style 标题
220
234
  expect(content.match(/^## Output Style$/gm)).toHaveLength(1);
221
235
  // 幂等
222
- await setupClaudeMdOutputStyle(tempDir);
236
+ await setupClaudeMdOutputStyle(tempDir, io);
223
237
  expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
224
238
  });
225
239
 
226
240
  it('用户自写 Output Style 段(特征串不匹配):不动、不追加、console 提示', async () => {
227
241
  const userContent = '# My Project\n\n## Output Style\n\n用户自己的输出风格要求。\n';
228
242
  fs.writeFileSync(claudeMdPath, userContent);
229
- const logSpy = jest.spyOn(console, 'log');
230
243
 
231
- await setupClaudeMdOutputStyle(tempDir);
244
+ await setupClaudeMdOutputStyle(tempDir, io);
232
245
 
233
246
  expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(userContent);
234
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('跳过'));
247
+ expect(io.outText()).toContain('跳过');
235
248
  });
236
249
  });
237
250
 
@@ -254,7 +267,7 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
254
267
  });
255
268
 
256
269
  it('AGENTS.md 不存在时创建骨架并写入 PRESERVE:governance 段', async () => {
257
- await setupAgentsMdConstraints(tempDir);
270
+ await setupAgentsMdConstraints(tempDir, io);
258
271
 
259
272
  const content = fs.readFileSync(agentsMdPath, 'utf-8');
260
273
  expect(content).toContain('# AGENTS.md');
@@ -270,9 +283,9 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
270
283
  });
271
284
 
272
285
  it('连跑两次输出一致(幂等)', async () => {
273
- await setupAgentsMdConstraints(tempDir);
286
+ await setupAgentsMdConstraints(tempDir, io);
274
287
  const first = fs.readFileSync(agentsMdPath, 'utf-8');
275
- await setupAgentsMdConstraints(tempDir);
288
+ await setupAgentsMdConstraints(tempDir, io);
276
289
  expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(first);
277
290
  });
278
291
 
@@ -301,7 +314,7 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
301
314
  '',
302
315
  ].join('\n'));
303
316
 
304
- await setupAgentsMdConstraints(tempDir);
317
+ await setupAgentsMdConstraints(tempDir, io);
305
318
 
306
319
  const content = fs.readFileSync(agentsMdPath, 'utf-8');
307
320
  expect(content).toContain('机器生成导读(保持不变)');
@@ -314,7 +327,7 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
314
327
  expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf('治理契约引言(手写,保留)'));
315
328
  expect(content.indexOf('### 治理变更流程(手写,保留)')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
316
329
  // 幂等
317
- await setupAgentsMdConstraints(tempDir);
330
+ await setupAgentsMdConstraints(tempDir, io);
318
331
  expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
319
332
  });
320
333
 
@@ -330,7 +343,7 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
330
343
  '',
331
344
  ].join('\n'));
332
345
 
333
- await setupAgentsMdConstraints(tempDir);
346
+ await setupAgentsMdConstraints(tempDir, io);
334
347
 
335
348
  const content = fs.readFileSync(agentsMdPath, 'utf-8');
336
349
  expect(content).toContain('治理契约引言(手写,保留)');
@@ -340,14 +353,14 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
340
353
  expect(content.indexOf('### 发布纪律(手写,保留)')).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
341
354
  expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
342
355
  // 幂等
343
- await setupAgentsMdConstraints(tempDir);
356
+ await setupAgentsMdConstraints(tempDir, io);
344
357
  expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
345
358
  });
346
359
 
347
360
  it('AGENTS.md 存在但无 PRESERVE:governance 段:末尾追加,既有内容不动', async () => {
348
361
  fs.writeFileSync(agentsMdPath, '# AGENTS.md\n\n既有导读内容\n');
349
362
 
350
- await setupAgentsMdConstraints(tempDir);
363
+ await setupAgentsMdConstraints(tempDir, io);
351
364
 
352
365
  const content = fs.readFileSync(agentsMdPath, 'utf-8');
353
366
  expect(content).toContain('既有导读内容');
@@ -358,16 +371,33 @@ describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.m
358
371
  it('PRESERVE:governance 标记残缺(只有单边):不写入并告警', async () => {
359
372
  const original = `# AGENTS.md\n\n${GOVERNANCE_PRESERVE_BEGIN}\n残缺的段\n`;
360
373
  fs.writeFileSync(agentsMdPath, original);
361
- const logSpy = jest.spyOn(console, 'log');
362
374
 
363
- await setupAgentsMdConstraints(tempDir);
375
+ await setupAgentsMdConstraints(tempDir, io);
364
376
 
365
377
  expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(original);
366
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('残缺'));
378
+ expect(io.outText()).toContain('残缺');
379
+ });
380
+
381
+ it('PRESERVE 段内 HARNESS 标记残缺(单边):拒写并告警(候选1 守护,旧行为会再追加一份注入段)', async () => {
382
+ const broken = [
383
+ '# AGENTS.md',
384
+ '',
385
+ GOVERNANCE_PRESERVE_BEGIN,
386
+ '手写契约',
387
+ '',
388
+ CONSTRAINTS_START_MARKER,
389
+ '半残注入',
390
+ GOVERNANCE_PRESERVE_END,
391
+ '',
392
+ ].join('\n');
393
+ fs.writeFileSync(agentsMdPath, broken);
394
+ await setupAgentsMdConstraints(tempDir, io);
395
+ expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(broken);
396
+ expect(io.outText()).toContain('残缺');
367
397
  });
368
398
 
369
399
  it('段内注入文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
370
- await setupAgentsMdConstraints(tempDir);
400
+ await setupAgentsMdConstraints(tempDir, io);
371
401
 
372
402
  const content = fs.readFileSync(agentsMdPath, 'utf-8');
373
403
  const start = content.indexOf(CONSTRAINTS_START_MARKER);