@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 { init } from '../init';
6
+ import { captureIO, type CapturingIO } from '../../command-contract';
6
7
  import * as fs from 'fs/promises';
7
8
 
8
9
  // Mock fs/promises
@@ -60,19 +61,18 @@ const mockFs = fs as jest.Mocked<typeof fs>;
60
61
  // Use explicit projectPath to avoid CI path mismatch
61
62
  const PROJECT = '/test/project';
62
63
 
64
+ let io: CapturingIO;
65
+ beforeEach(() => {
66
+ io = captureIO();
67
+ });
68
+
63
69
  describe('init command', () => {
64
- let consoleSpy: jest.SpyInstance;
65
70
 
66
71
  beforeEach(() => {
67
72
  jest.clearAllMocks();
68
73
  existingFiles.clear();
69
74
  mockExistsSync.mockReturnValue(false);
70
75
  mockReaddirSync.mockReturnValue([]);
71
- consoleSpy = jest.spyOn(console, 'log').mockImplementation();
72
- });
73
-
74
- afterEach(() => {
75
- consoleSpy.mockRestore();
76
76
  });
77
77
 
78
78
  describe('init', () => {
@@ -80,7 +80,7 @@ describe('init command', () => {
80
80
  mockFs.mkdir.mockResolvedValue(undefined);
81
81
  mockFs.writeFile.mockResolvedValue(undefined);
82
82
 
83
- await init({ preset: 'standard', projectPath: PROJECT });
83
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
84
84
  expect(mockFs.mkdir).toHaveBeenCalled();
85
85
  });
86
86
 
@@ -88,31 +88,31 @@ describe('init command', () => {
88
88
  mockFs.mkdir.mockResolvedValue(undefined);
89
89
  mockFs.writeFile.mockResolvedValue(undefined);
90
90
 
91
- await init({ preset: 'strict', projectPath: PROJECT });
92
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('strict'));
91
+ await init({ preset: 'strict', projectPath: PROJECT }, io);
92
+ expect(io.outText()).toContain('strict');
93
93
  });
94
94
 
95
95
  it('应该使用 relaxed 预设', async () => {
96
96
  mockFs.mkdir.mockResolvedValue(undefined);
97
97
  mockFs.writeFile.mockResolvedValue(undefined);
98
98
 
99
- await init({ preset: 'relaxed', projectPath: PROJECT });
100
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('relaxed'));
99
+ await init({ preset: 'relaxed', projectPath: PROJECT }, io);
100
+ expect(io.outText()).toContain('relaxed');
101
101
  });
102
102
 
103
103
  it('应该支持自定义项目路径', async () => {
104
104
  mockFs.mkdir.mockResolvedValue(undefined);
105
105
  mockFs.writeFile.mockResolvedValue(undefined);
106
106
 
107
- await init({ preset: 'standard', projectPath: '/custom/path' });
107
+ await init({ preset: 'standard', projectPath: '/custom/path' }, io);
108
108
  expect(mockFs.mkdir).toHaveBeenCalled();
109
109
  });
110
110
 
111
111
  it('应该输出代码片段模式', async () => {
112
- await init({ preset: 'standard', printSnippets: true });
113
- expect(consoleSpy).toHaveBeenCalled();
114
- // 打印版与落盘版 plan 匹配模式须一致(#35:模板字面量 \. 运行期退化为 .,防两副本 drift)
115
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("grep -E 'plans/.*\\.md$|\\.plan\\.md$'"));
112
+ await init({ preset: 'standard', printSnippets: true }, io);
113
+ expect(io.outText()).not.toBe('');
114
+ // 打印版与落盘版 plan 匹配模式须一致(#35:模板字面量 \. 运行期退化为 .)
115
+ expect(io.outText()).toContain("grep -E 'plans/.*\\.md$|\\.plan\\.md$'");
116
116
  });
117
117
  });
118
118
 
@@ -121,8 +121,8 @@ describe('init command', () => {
121
121
  mockFs.mkdir.mockResolvedValue(undefined);
122
122
  mockFs.writeFile.mockResolvedValue(undefined);
123
123
 
124
- await init({ preset: 'standard', projectPath: PROJECT });
125
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('未检测到 Git 仓库'));
124
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
125
+ expect(io.outText()).toContain('未检测到 Git 仓库');
126
126
  });
127
127
 
128
128
  it('应该提示手动添加当 pre-commit 已存在', async () => {
@@ -132,10 +132,10 @@ describe('init command', () => {
132
132
  existingFiles.add(`${PROJECT}/.git/hooks`);
133
133
  existingFiles.add(`${PROJECT}/.git/hooks/pre-commit`);
134
134
 
135
- await init({ preset: 'standard', projectPath: PROJECT });
136
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('pre-commit 已存在'));
135
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
136
+ expect(io.outText()).toContain('pre-commit 已存在');
137
137
  // 手动添加提示的片段同样 scoped 化(#36)
138
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('npx @dommaker/harness check --staged'));
138
+ expect(io.outText()).toContain('npx @dommaker/harness check --staged');
139
139
  });
140
140
 
141
141
  it('应该创建 pre-commit hook', async () => {
@@ -144,8 +144,8 @@ describe('init command', () => {
144
144
  existingFiles.add(`${PROJECT}/.git`);
145
145
  existingFiles.add(`${PROJECT}/.git/hooks`);
146
146
 
147
- await init({ preset: 'standard', projectPath: PROJECT });
148
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('已创建 .git/hooks/pre-commit'));
147
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
148
+ expect(io.outText()).toContain('已创建 .git/hooks/pre-commit');
149
149
  });
150
150
 
151
151
  it('落盘 pre-commit 使用 scoped 包名(#36)', async () => {
@@ -154,16 +154,45 @@ describe('init command', () => {
154
154
  existingFiles.add(`${PROJECT}/.git`);
155
155
  existingFiles.add(`${PROJECT}/.git/hooks`);
156
156
 
157
- await init({ preset: 'standard', projectPath: PROJECT });
157
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
158
158
  const writeCalls = mockFs.writeFile.mock.calls;
159
159
  const hookCall = writeCalls.find((c: any[]) => String(c[0]).includes('pre-commit'));
160
160
  expect(hookCall).toBeDefined();
161
161
  expect(hookCall![1]).toContain('npx @dommaker/harness check --staged');
162
162
  expect(hookCall![1]).toContain('npx @dommaker/harness posteval-plan');
163
163
  expect(hookCall![1]).not.toMatch(/npx harness /);
164
- // plan 匹配模式须为转义点(#35:模板字面量 \. 运行期退化为 .,与打印版防 drift)
164
+ // plan 匹配模式须为转义点(#35:模板字面量 \. 运行期退化为 .)
165
165
  expect(hookCall![1]).toContain("grep -E 'plans/.*\\.md$|\\.plan\\.md$'");
166
166
  });
167
+
168
+ it('pre-commit 片段与落盘 hook 同源(#103:差异行并入共享片段,无第二份文本)', async () => {
169
+ mockFs.mkdir.mockResolvedValue(undefined);
170
+ mockFs.writeFile.mockResolvedValue(undefined);
171
+ existingFiles.add(`${PROJECT}/.git`);
172
+ existingFiles.add(`${PROJECT}/.git/hooks`);
173
+
174
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
175
+ const hookCall = mockFs.writeFile.mock.calls.find((c: any[]) => String(c[0]).includes('pre-commit'));
176
+ const hookContent = String(hookCall![1]);
177
+ expect(hookContent.startsWith('#!/bin/sh')).toBe(true);
178
+
179
+ const io2 = captureIO();
180
+ await init({ preset: 'standard', printSnippets: true }, io2);
181
+
182
+ // 落盘 hook = shebang 头 + 共享片段:去头后必须逐字出现在打印片段输出里
183
+ const body = hookContent.replace(/^#!\/bin\/sh\n# Harness pre-commit hook\n/, '');
184
+ expect(body.length).toBeGreaterThan(0);
185
+ expect(io2.outText()).toContain(body);
186
+
187
+ // 原 heredoc 独有的差异行已并入共享片段,打印片段同样可见
188
+ for (const line of [
189
+ 'echo "🔍 Running harness checks..."',
190
+ 'echo "✅ All checks passed"',
191
+ ]) {
192
+ expect(hookContent).toContain(line);
193
+ expect(io2.outText()).toContain(line);
194
+ }
195
+ });
167
196
  });
168
197
 
169
198
  describe('GitHub Actions', () => {
@@ -173,15 +202,15 @@ describe('init command', () => {
173
202
  mockReaddir.mockResolvedValue(['ci.yml']);
174
203
  existingFiles.add(`${PROJECT}/.github/workflows`);
175
204
 
176
- await init({ preset: 'standard', projectPath: PROJECT });
177
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('已存在的 CI 配置'));
205
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
206
+ expect(io.outText()).toContain('已存在的 CI 配置');
178
207
  });
179
208
 
180
209
  it('落盘 harness-check.yml 使用 scoped 包名(#36)', async () => {
181
210
  mockFs.mkdir.mockResolvedValue(undefined);
182
211
  mockFs.writeFile.mockResolvedValue(undefined);
183
212
 
184
- await init({ preset: 'standard', projectPath: PROJECT });
213
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
185
214
  const writeCalls = mockFs.writeFile.mock.calls;
186
215
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-check.yml'));
187
216
  expect(workflowCall).toBeDefined();
@@ -197,7 +226,7 @@ describe('init command', () => {
197
226
  mockFs.mkdir.mockResolvedValue(undefined);
198
227
  mockFs.writeFile.mockResolvedValue(undefined);
199
228
 
200
- await init({ preset: 'standard', projectPath: PROJECT });
229
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
201
230
  const writes = mockFs.writeFile.mock.calls.map((c: unknown[]) => String(c[0]));
202
231
  expect(writes.every((w: string) => !w.includes('CAPABILITIES.md'))).toBe(true);
203
232
  });
@@ -209,8 +238,8 @@ describe('init command', () => {
209
238
  mockFs.writeFile.mockResolvedValue(undefined);
210
239
  existingFiles.add(`${PROJECT}/.harness/custom-constraints.yml`);
211
240
 
212
- await init({ preset: 'standard', projectPath: PROJECT });
213
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('custom-constraints.yml 已存在'));
241
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
242
+ expect(io.outText()).toContain('custom-constraints.yml 已存在');
214
243
  });
215
244
  });
216
245
 
@@ -219,15 +248,15 @@ describe('init command', () => {
219
248
  mockFs.mkdir.mockResolvedValue(undefined);
220
249
  mockFs.writeFile.mockResolvedValue(undefined);
221
250
 
222
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
223
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('治理级别: standard'));
251
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
252
+ expect(io.outText()).toContain('治理级别: standard');
224
253
  });
225
254
 
226
255
  it('应该创建 CHANGELOG.md', async () => {
227
256
  mockFs.mkdir.mockResolvedValue(undefined);
228
257
  mockFs.writeFile.mockResolvedValue(undefined);
229
258
 
230
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
259
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
231
260
  const writeCalls = mockFs.writeFile.mock.calls;
232
261
  const changelogCall = writeCalls.find((c: any[]) => String(c[0]).includes('CHANGELOG.md'));
233
262
  expect(changelogCall).toBeDefined();
@@ -239,8 +268,8 @@ describe('init command', () => {
239
268
  mockFs.writeFile.mockResolvedValue(undefined);
240
269
  existingFiles.add(`${PROJECT}/CHANGELOG.md`);
241
270
 
242
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
243
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('CHANGELOG.md 已存在'));
271
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
272
+ expect(io.outText()).toContain('CHANGELOG.md 已存在');
244
273
  });
245
274
 
246
275
  it('应该为 standard 治理创建 src/CONTEXT.md', async () => {
@@ -256,7 +285,7 @@ describe('init command', () => {
256
285
  return [];
257
286
  });
258
287
 
259
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
288
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
260
289
  const writeCalls = mockFs.writeFile.mock.calls;
261
290
  const contextCall = writeCalls.find((c: any[]) => String(c[0]).includes('CONTEXT.md'));
262
291
  expect(contextCall).toBeDefined();
@@ -267,7 +296,7 @@ describe('init command', () => {
267
296
  mockFs.mkdir.mockResolvedValue(undefined);
268
297
  mockFs.writeFile.mockResolvedValue(undefined);
269
298
 
270
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
299
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
271
300
  const writeCalls = mockFs.writeFile.mock.calls;
272
301
  const contextCall = writeCalls.find((c: any[]) => String(c[0]).includes('CONTEXT.md'));
273
302
  expect(contextCall).toBeUndefined();
@@ -277,7 +306,7 @@ describe('init command', () => {
277
306
  mockFs.mkdir.mockResolvedValue(undefined);
278
307
  mockFs.writeFile.mockResolvedValue(undefined);
279
308
 
280
- await init({ preset: 'standard', governance: 'minimal', projectPath: PROJECT });
309
+ await init({ preset: 'standard', governance: 'minimal', projectPath: PROJECT }, io);
281
310
  const writeCalls = mockFs.writeFile.mock.calls;
282
311
  const contextCall = writeCalls.find((c: any[]) => String(c[0]).includes('CONTEXT.md'));
283
312
  expect(contextCall).toBeUndefined();
@@ -287,7 +316,7 @@ describe('init command', () => {
287
316
  mockFs.mkdir.mockResolvedValue(undefined);
288
317
  mockFs.writeFile.mockResolvedValue(undefined);
289
318
 
290
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
319
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
291
320
  const writeCalls = mockFs.writeFile.mock.calls;
292
321
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-governance.yml'));
293
322
  expect(workflowCall).toBeDefined();
@@ -304,8 +333,8 @@ describe('init command', () => {
304
333
  mockFs.writeFile.mockResolvedValue(undefined);
305
334
  existingFiles.add(`${PROJECT}/.github/workflows/harness-governance.yml`);
306
335
 
307
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
308
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('harness-governance.yml 已存在'));
336
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
337
+ expect(io.outText()).toContain('harness-governance.yml 已存在');
309
338
  });
310
339
 
311
340
  it('应该跳过治理 CI workflow 当已有 workflow 覆盖治理命令', async () => {
@@ -320,11 +349,11 @@ describe('init command', () => {
320
349
  : Promise.reject(new Error('ENOENT'))
321
350
  );
322
351
 
323
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
352
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
324
353
  const writeCalls = mockFs.writeFile.mock.calls;
325
354
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-governance.yml'));
326
355
  expect(workflowCall).toBeUndefined();
327
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('治理检查已由 ci.yml 覆盖'));
356
+ expect(io.outText()).toContain('治理检查已由 ci.yml 覆盖');
328
357
 
329
358
  mockReaddir.mockResolvedValue([]);
330
359
  mockFs.readFile.mockRejectedValue(new Error('ENOENT'));
@@ -341,7 +370,7 @@ describe('init command', () => {
341
370
  : Promise.reject(new Error('ENOENT'))
342
371
  );
343
372
 
344
- await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT });
373
+ await init({ preset: 'standard', governance: 'standard', projectPath: PROJECT }, io);
345
374
  const writeCalls = mockFs.writeFile.mock.calls;
346
375
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-governance.yml'));
347
376
  expect(workflowCall).toBeDefined();
@@ -355,7 +384,7 @@ describe('init command', () => {
355
384
  mockFs.mkdir.mockResolvedValue(undefined);
356
385
  mockFs.writeFile.mockResolvedValue(undefined);
357
386
 
358
- await init({ preset: 'standard', governance: 'strict', projectPath: PROJECT });
387
+ await init({ preset: 'standard', governance: 'strict', projectPath: PROJECT }, io);
359
388
  const writeCalls = mockFs.writeFile.mock.calls;
360
389
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-governance.yml'));
361
390
  expect(workflowCall![1]).toContain('sync-docs');
@@ -366,7 +395,7 @@ describe('init command', () => {
366
395
  mockFs.mkdir.mockResolvedValue(undefined);
367
396
  mockFs.writeFile.mockResolvedValue(undefined);
368
397
 
369
- await init({ preset: 'standard', governance: 'minimal', projectPath: PROJECT });
398
+ await init({ preset: 'standard', governance: 'minimal', projectPath: PROJECT }, io);
370
399
  const writeCalls = mockFs.writeFile.mock.calls;
371
400
  const workflowCall = writeCalls.find((c: any[]) => String(c[0]).includes('harness-governance.yml'));
372
401
  expect(workflowCall![1]).not.toContain('sync-docs');
@@ -376,7 +405,7 @@ describe('init command', () => {
376
405
  mockFs.mkdir.mockResolvedValue(undefined);
377
406
  mockFs.writeFile.mockResolvedValue(undefined);
378
407
 
379
- await init({ preset: 'standard', projectPath: PROJECT });
408
+ await init({ preset: 'standard', projectPath: PROJECT }, io);
380
409
  const writeCalls = mockFs.writeFile.mock.calls;
381
410
  const changelogCall = writeCalls.find((c: any[]) => String(c[0]).includes('CHANGELOG.md'));
382
411
  const contextCall = writeCalls.find((c: any[]) => String(c[0]).includes('CONTEXT.md'));
@@ -0,0 +1,108 @@
1
+ /**
2
+ * 飞轮指标三处一致性(harness#81)
3
+ *
4
+ * 同一 fixture 分别走 audit D6 / knowledge stats / knowledge health 三条消费路径,
5
+ * 断言 refCoverage / avgRefs / consumptionHitRate 逐字段相等——分子只有一份实现
6
+ * (knowledge/flywheel-metrics),展示层各自做单位与字段名映射。
7
+ *
8
+ * fixture 条目全为非 archived / 非 deprecated,三处人口重合:health 的 excludeArchived
9
+ * 切片与 audit/stats 的 active 过滤在这里是同一集合(差异在人口筛选,不在计算)。
10
+ * 期望值即口径哨兵:修复前 stats/health 直接数 referencedBy.length,
11
+ * 同一 fixture 会报 refCoverage 75 / avgRefs 1.5。
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import { captureIO, lastJsonOutput, type CapturingIO } from '../../command-contract';
16
+ import * as os from 'os';
17
+ import * as path from 'path';
18
+ import { FileKnowledgeStore } from '../../../knowledge/store';
19
+ import { KnowledgeAudit } from '../../../knowledge/audit';
20
+ import { knowledgeHealth, knowledgeStats } from '../knowledge';
21
+ import type { KnowledgeEntry } from '../../../knowledge/types';
22
+
23
+ function makeEntry(id: string, maturity: KnowledgeEntry['maturity'], referencedBy: string[]): KnowledgeEntry {
24
+ return {
25
+ id,
26
+ type: 'guideline',
27
+ title: `consistency fixture ${id}`,
28
+ content: 'content long enough to stay out of the short-content rule and the audit filters',
29
+ maturity,
30
+ layer: 'project',
31
+ created: '2026-09-01T00:00:00.000Z',
32
+ lastReferenced: '2026-09-01T00:00:00.000Z',
33
+ contributors: ['human'],
34
+ projects: ['harness'],
35
+ tags: [],
36
+ applicablePhases: [],
37
+ sourceReferences: [],
38
+ referencedBy,
39
+ executionResults: [],
40
+ consumptionMode: 'reference',
41
+ origin: 'agent',
42
+ };
43
+ }
44
+
45
+ /**
46
+ * 4 条 active 条目,genuine 引用共 3 条:
47
+ * - 2 条有真实引用(K-001 / K-004)→ refCoverage 0.5
48
+ * - K-002 只有自动化引用(prompt-inject / test-agent / monitor),不给条目续命
49
+ * - dailyEvents 2 / 4 条目 → consumptionHitRate 0.5
50
+ */
51
+ const FIXTURE: KnowledgeEntry[] = [
52
+ makeEntry('K-001', 'verified', ['agent-a:2026-09-01', 'search:2026-09-01']),
53
+ makeEntry('K-002', 'verified', ['prompt-inject:2026-08-30', 'test-agent:2026-08-31', 'monitor:2026-09-01']),
54
+ makeEntry('K-003', 'draft', []),
55
+ makeEntry('K-004', 'proven', ['decision-1:2026-07-01', 'studio-web:2026-07-02']),
56
+ ];
57
+
58
+ function setupKnowledgeBase(): string {
59
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'flywheel-consistency-'));
60
+ const baseDir = path.join(projectRoot, '.harness', 'knowledge');
61
+ const store = new FileKnowledgeStore({ baseDir });
62
+ for (const entry of FIXTURE) store.save(entry);
63
+ fs.writeFileSync(
64
+ path.join(baseDir, '.consumption-stats.json'),
65
+ JSON.stringify({ date: '2026-09-02', dailyEvents: 2, searchHits: 1 }),
66
+ );
67
+ return projectRoot;
68
+ }
69
+
70
+ let io: CapturingIO;
71
+ beforeEach(() => {
72
+ io = captureIO();
73
+ });
74
+
75
+ describe('飞轮指标三处同源', () => {
76
+ it('audit D6 / stats / health 报出同一组数字', async () => {
77
+ const projectRoot = setupKnowledgeBase();
78
+ const baseDir = path.join(projectRoot, '.harness', 'knowledge');
79
+
80
+ const report = new KnowledgeAudit({ baseDir }).run();
81
+ const auditDetails = report.dimensions.flywheel.details;
82
+ await knowledgeStats({ projectPath: projectRoot, json: true }, io);
83
+ const statsFlywheel = lastJsonOutput<any>(io).flywheel;
84
+
85
+ io = captureIO();
86
+ await knowledgeHealth({ dir: baseDir, json: true }, io);
87
+ const healthSummary = lastJsonOutput<any>(io).summary;
88
+
89
+ // 逐字段一致(avgRefs 在 audit 报告层的呈现名是 avgRefCount)
90
+ expect(statsFlywheel.refCoverage).toBe(auditDetails.refCoverage);
91
+ expect(statsFlywheel.avgRefs).toBe(auditDetails.avgRefCount);
92
+ expect(healthSummary.refCoverage).toBe(auditDetails.refCoverage);
93
+ expect(healthSummary.avgRefs).toBe(auditDetails.avgRefCount);
94
+ expect(statsFlywheel.consumptionHitRate).toBe(auditDetails.consumptionHitRate);
95
+
96
+ // canonical 值(genuine 口径):人口 4、有引用 2、genuine 引用 3、dailyEvents 2
97
+ expect(auditDetails).toMatchObject({
98
+ activeEntries: 4,
99
+ entriesWithRefs: 2,
100
+ refCoverage: 50,
101
+ avgRefCount: 0.8,
102
+ dailyConsumptionEvents: 2,
103
+ consumptionHitRate: 50,
104
+ });
105
+ expect(statsFlywheel).toEqual({ refCoverage: 50, avgRefs: 0.8, consumptionHitRate: 50 });
106
+ expect(healthSummary).toMatchObject({ total: 4, refCoverage: 50, avgRefs: 0.8 });
107
+ });
108
+ });