@dommaker/harness 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +0 -2
  3. package/bin/harness.js +86 -32
  4. package/dist/cli/command-contract.d.ts +83 -0
  5. package/dist/cli/command-contract.d.ts.map +1 -0
  6. package/dist/cli/command-contract.js +66 -0
  7. package/dist/cli/command-contract.js.map +1 -0
  8. package/dist/cli/commands/acceptance.d.ts +3 -2
  9. package/dist/cli/commands/acceptance.d.ts.map +1 -1
  10. package/dist/cli/commands/acceptance.js +28 -24
  11. package/dist/cli/commands/acceptance.js.map +1 -1
  12. package/dist/cli/commands/analyze-sessions.d.ts +2 -1
  13. package/dist/cli/commands/analyze-sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/analyze-sessions.js +25 -24
  15. package/dist/cli/commands/analyze-sessions.js.map +1 -1
  16. package/dist/cli/commands/check.d.ts +14 -2
  17. package/dist/cli/commands/check.d.ts.map +1 -1
  18. package/dist/cli/commands/check.js +104 -61
  19. package/dist/cli/commands/check.js.map +1 -1
  20. package/dist/cli/commands/command.d.ts +5 -1
  21. package/dist/cli/commands/command.d.ts.map +1 -1
  22. package/dist/cli/commands/command.js +26 -17
  23. package/dist/cli/commands/command.js.map +1 -1
  24. package/dist/cli/commands/constraints-report.d.ts +2 -1
  25. package/dist/cli/commands/constraints-report.d.ts.map +1 -1
  26. package/dist/cli/commands/constraints-report.js +37 -33
  27. package/dist/cli/commands/constraints-report.js.map +1 -1
  28. package/dist/cli/commands/constraints-retire.d.ts +6 -5
  29. package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
  30. package/dist/cli/commands/constraints-retire.js +50 -52
  31. package/dist/cli/commands/constraints-retire.js.map +1 -1
  32. package/dist/cli/commands/constraints.d.ts +2 -1
  33. package/dist/cli/commands/constraints.d.ts.map +1 -1
  34. package/dist/cli/commands/constraints.js +10 -27
  35. package/dist/cli/commands/constraints.js.map +1 -1
  36. package/dist/cli/commands/contract.d.ts +3 -2
  37. package/dist/cli/commands/contract.d.ts.map +1 -1
  38. package/dist/cli/commands/contract.js +45 -42
  39. package/dist/cli/commands/contract.js.map +1 -1
  40. package/dist/cli/commands/definitions.d.ts +11 -11
  41. package/dist/cli/commands/definitions.d.ts.map +1 -1
  42. package/dist/cli/commands/definitions.js +27 -219
  43. package/dist/cli/commands/definitions.js.map +1 -1
  44. package/dist/cli/commands/failure.d.ts +5 -4
  45. package/dist/cli/commands/failure.d.ts.map +1 -1
  46. package/dist/cli/commands/failure.js +31 -25
  47. package/dist/cli/commands/failure.js.map +1 -1
  48. package/dist/cli/commands/init.d.ts +7 -5
  49. package/dist/cli/commands/init.d.ts.map +1 -1
  50. package/dist/cli/commands/init.js +147 -211
  51. package/dist/cli/commands/init.js.map +1 -1
  52. package/dist/cli/commands/knowledge.d.ts +17 -30
  53. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  54. package/dist/cli/commands/knowledge.js +183 -273
  55. package/dist/cli/commands/knowledge.js.map +1 -1
  56. package/dist/cli/commands/passes-gate.d.ts +8 -2
  57. package/dist/cli/commands/passes-gate.d.ts.map +1 -1
  58. package/dist/cli/commands/passes-gate.js +44 -66
  59. package/dist/cli/commands/passes-gate.js.map +1 -1
  60. package/dist/cli/commands/performance.d.ts +2 -1
  61. package/dist/cli/commands/performance.d.ts.map +1 -1
  62. package/dist/cli/commands/performance.js +20 -18
  63. package/dist/cli/commands/performance.js.map +1 -1
  64. package/dist/cli/commands/posteval-plan.d.ts +5 -1
  65. package/dist/cli/commands/posteval-plan.d.ts.map +1 -1
  66. package/dist/cli/commands/posteval-plan.js +26 -15
  67. package/dist/cli/commands/posteval-plan.js.map +1 -1
  68. package/dist/cli/commands/release.d.ts +6 -1
  69. package/dist/cli/commands/release.d.ts.map +1 -1
  70. package/dist/cli/commands/release.js +69 -59
  71. package/dist/cli/commands/release.js.map +1 -1
  72. package/dist/cli/commands/report.d.ts +2 -1
  73. package/dist/cli/commands/report.d.ts.map +1 -1
  74. package/dist/cli/commands/report.js +9 -5
  75. package/dist/cli/commands/report.js.map +1 -1
  76. package/dist/cli/commands/review.d.ts +3 -2
  77. package/dist/cli/commands/review.d.ts.map +1 -1
  78. package/dist/cli/commands/review.js +33 -29
  79. package/dist/cli/commands/review.js.map +1 -1
  80. package/dist/cli/commands/sdd.d.ts +2 -1
  81. package/dist/cli/commands/sdd.d.ts.map +1 -1
  82. package/dist/cli/commands/sdd.js +7 -5
  83. package/dist/cli/commands/sdd.js.map +1 -1
  84. package/dist/cli/commands/security.d.ts +3 -2
  85. package/dist/cli/commands/security.d.ts.map +1 -1
  86. package/dist/cli/commands/security.js +29 -26
  87. package/dist/cli/commands/security.js.map +1 -1
  88. package/dist/cli/commands/spec-baseline-check.d.ts +2 -1
  89. package/dist/cli/commands/spec-baseline-check.d.ts.map +1 -1
  90. package/dist/cli/commands/spec-baseline-check.js +15 -11
  91. package/dist/cli/commands/spec-baseline-check.js.map +1 -1
  92. package/dist/cli/commands/spec.d.ts +7 -2
  93. package/dist/cli/commands/spec.d.ts.map +1 -1
  94. package/dist/cli/commands/spec.js +61 -51
  95. package/dist/cli/commands/spec.js.map +1 -1
  96. package/dist/cli/commands/status.d.ts +2 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +57 -53
  99. package/dist/cli/commands/status.js.map +1 -1
  100. package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
  101. package/dist/cli/commands/sync-docs/agents-syncer.js +5 -2
  102. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  103. package/dist/cli/commands/sync-docs/context-syncer.d.ts.map +1 -1
  104. package/dist/cli/commands/sync-docs/context-syncer.js +3 -2
  105. package/dist/cli/commands/sync-docs/context-syncer.js.map +1 -1
  106. package/dist/cli/commands/sync-docs/index.d.ts +2 -1
  107. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  108. package/dist/cli/commands/sync-docs/index.js +68 -88
  109. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  110. package/dist/cli/commands/sync-docs/project-reader.d.ts +6 -1
  111. package/dist/cli/commands/sync-docs/project-reader.d.ts.map +1 -1
  112. package/dist/cli/commands/sync-docs/project-reader.js +14 -16
  113. package/dist/cli/commands/sync-docs/project-reader.js.map +1 -1
  114. package/dist/cli/commands/update-user-model.d.ts +9 -3
  115. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  116. package/dist/cli/commands/update-user-model.js +56 -61
  117. package/dist/cli/commands/update-user-model.js.map +1 -1
  118. package/dist/cli/commands/validate.d.ts +4 -3
  119. package/dist/cli/commands/validate.d.ts.map +1 -1
  120. package/dist/cli/commands/validate.js +28 -26
  121. package/dist/cli/commands/validate.js.map +1 -1
  122. package/dist/cli/session-mining/transcript.d.ts +7 -1
  123. package/dist/cli/session-mining/transcript.d.ts.map +1 -1
  124. package/dist/cli/session-mining/transcript.js +21 -14
  125. package/dist/cli/session-mining/transcript.js.map +1 -1
  126. package/dist/context/session-manager.d.ts.map +1 -1
  127. package/dist/context/session-manager.js +19 -32
  128. package/dist/context/session-manager.js.map +1 -1
  129. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -1
  130. package/dist/core/constraints/agent-prompt-renderer.js +1 -4
  131. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -1
  132. package/dist/core/constraints/capabilities-parser.d.ts +24 -7
  133. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  134. package/dist/core/constraints/capabilities-parser.js +32 -28
  135. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  136. package/dist/core/constraints/capabilities-reconcile.d.ts +67 -0
  137. package/dist/core/constraints/capabilities-reconcile.d.ts.map +1 -0
  138. package/dist/core/constraints/capabilities-reconcile.js +105 -0
  139. package/dist/core/constraints/capabilities-reconcile.js.map +1 -0
  140. package/dist/core/constraints/checker.d.ts +21 -43
  141. package/dist/core/constraints/checker.d.ts.map +1 -1
  142. package/dist/core/constraints/checker.js +68 -117
  143. package/dist/core/constraints/checker.js.map +1 -1
  144. package/dist/core/constraints/checkers/capability-sync.d.ts +20 -8
  145. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  146. package/dist/core/constraints/checkers/capability-sync.js +66 -77
  147. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  148. package/dist/core/constraints/checkers/context-doc-sync.d.ts +4 -2
  149. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  150. package/dist/core/constraints/checkers/context-doc-sync.js +13 -22
  151. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  152. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  153. package/dist/core/constraints/checkers/docs-freshness.js +49 -66
  154. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  155. package/dist/core/constraints/checkers/governance-presence.d.ts +2 -2
  156. package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
  157. package/dist/core/constraints/checkers/governance-presence.js +7 -28
  158. package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
  159. package/dist/core/constraints/checkers/index.d.ts +2 -2
  160. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  161. package/dist/core/constraints/checkers/index.js +2 -1
  162. package/dist/core/constraints/checkers/index.js.map +1 -1
  163. package/dist/core/constraints/checkers/no-test-simplification.d.ts.map +1 -1
  164. package/dist/core/constraints/checkers/no-test-simplification.js +4 -3
  165. package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
  166. package/dist/core/constraints/checkers/types.d.ts +43 -4
  167. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  168. package/dist/core/constraints/checkers/types.js +33 -0
  169. package/dist/core/constraints/checkers/types.js.map +1 -1
  170. package/dist/core/constraints/context-builder.d.ts +15 -14
  171. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  172. package/dist/core/constraints/context-builder.js +32 -96
  173. package/dist/core/constraints/context-builder.js.map +1 -1
  174. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  175. package/dist/core/constraints/doc-freshness/runner.js +2 -1
  176. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  177. package/dist/core/constraints/git-evidence.d.ts +60 -0
  178. package/dist/core/constraints/git-evidence.d.ts.map +1 -0
  179. package/dist/core/constraints/git-evidence.js +88 -0
  180. package/dist/core/constraints/git-evidence.js.map +1 -0
  181. package/dist/core/constraints/injection-drift.d.ts +3 -20
  182. package/dist/core/constraints/injection-drift.d.ts.map +1 -1
  183. package/dist/core/constraints/injection-drift.js +10 -57
  184. package/dist/core/constraints/injection-drift.js.map +1 -1
  185. package/dist/core/constraints/injection-writer.d.ts +113 -0
  186. package/dist/core/constraints/injection-writer.d.ts.map +1 -0
  187. package/dist/core/constraints/injection-writer.js +228 -0
  188. package/dist/core/constraints/injection-writer.js.map +1 -0
  189. package/dist/core/constraints/usage-report.d.ts +22 -1
  190. package/dist/core/constraints/usage-report.d.ts.map +1 -1
  191. package/dist/core/constraints/usage-report.js +27 -20
  192. package/dist/core/constraints/usage-report.js.map +1 -1
  193. package/dist/core/index.d.ts +2 -2
  194. package/dist/core/index.d.ts.map +1 -1
  195. package/dist/core/index.js +4 -2
  196. package/dist/core/index.js.map +1 -1
  197. package/dist/core/project-config-loader.d.ts +32 -1
  198. package/dist/core/project-config-loader.d.ts.map +1 -1
  199. package/dist/core/project-config-loader.js +39 -9
  200. package/dist/core/project-config-loader.js.map +1 -1
  201. package/dist/core/validators/passes-gate.d.ts +15 -64
  202. package/dist/core/validators/passes-gate.d.ts.map +1 -1
  203. package/dist/core/validators/passes-gate.js +84 -222
  204. package/dist/core/validators/passes-gate.js.map +1 -1
  205. package/dist/core/validators/test-output.d.ts +58 -0
  206. package/dist/core/validators/test-output.d.ts.map +1 -0
  207. package/dist/core/validators/test-output.js +109 -0
  208. package/dist/core/validators/test-output.js.map +1 -0
  209. package/dist/failure/recorder.d.ts +6 -0
  210. package/dist/failure/recorder.d.ts.map +1 -1
  211. package/dist/failure/recorder.js +12 -8
  212. package/dist/failure/recorder.js.map +1 -1
  213. package/dist/gates/acceptance.d.ts +1 -48
  214. package/dist/gates/acceptance.d.ts.map +1 -1
  215. package/dist/gates/acceptance.js +6 -38
  216. package/dist/gates/acceptance.js.map +1 -1
  217. package/dist/gates/checker-gate.d.ts +2 -1
  218. package/dist/gates/checker-gate.d.ts.map +1 -1
  219. package/dist/gates/checker-gate.js +11 -13
  220. package/dist/gates/checker-gate.js.map +1 -1
  221. package/dist/gates/command.d.ts.map +1 -1
  222. package/dist/gates/command.js +10 -22
  223. package/dist/gates/command.js.map +1 -1
  224. package/dist/gates/contract.d.ts.map +1 -1
  225. package/dist/gates/contract.js +22 -61
  226. package/dist/gates/contract.js.map +1 -1
  227. package/dist/gates/performance.d.ts.map +1 -1
  228. package/dist/gates/performance.js +13 -31
  229. package/dist/gates/performance.js.map +1 -1
  230. package/dist/gates/review.d.ts.map +1 -1
  231. package/dist/gates/review.js +21 -53
  232. package/dist/gates/review.js.map +1 -1
  233. package/dist/gates/security.d.ts.map +1 -1
  234. package/dist/gates/security.js +14 -32
  235. package/dist/gates/security.js.map +1 -1
  236. package/dist/gates/types.d.ts +28 -5
  237. package/dist/gates/types.d.ts.map +1 -1
  238. package/dist/gates/types.js +38 -1
  239. package/dist/gates/types.js.map +1 -1
  240. package/dist/hooks/bootstrap.d.ts +2 -0
  241. package/dist/hooks/bootstrap.d.ts.map +1 -1
  242. package/dist/hooks/bootstrap.js +5 -2
  243. package/dist/hooks/bootstrap.js.map +1 -1
  244. package/dist/index.d.ts +1 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/knowledge/audit.d.ts +7 -2
  248. package/dist/knowledge/audit.d.ts.map +1 -1
  249. package/dist/knowledge/audit.js +59 -79
  250. package/dist/knowledge/audit.js.map +1 -1
  251. package/dist/knowledge/flywheel-metrics.d.ts +39 -0
  252. package/dist/knowledge/flywheel-metrics.d.ts.map +1 -0
  253. package/dist/knowledge/flywheel-metrics.js +47 -0
  254. package/dist/knowledge/flywheel-metrics.js.map +1 -0
  255. package/dist/knowledge/import.d.ts.map +1 -1
  256. package/dist/knowledge/import.js +4 -14
  257. package/dist/knowledge/import.js.map +1 -1
  258. package/dist/knowledge/index-generator.d.ts.map +1 -1
  259. package/dist/knowledge/index-generator.js +17 -14
  260. package/dist/knowledge/index-generator.js.map +1 -1
  261. package/dist/knowledge/ingest.d.ts.map +1 -1
  262. package/dist/knowledge/ingest.js +9 -4
  263. package/dist/knowledge/ingest.js.map +1 -1
  264. package/dist/knowledge/migration.d.ts.map +1 -1
  265. package/dist/knowledge/migration.js +11 -8
  266. package/dist/knowledge/migration.js.map +1 -1
  267. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  268. package/dist/knowledge/reference-tracker.js +12 -9
  269. package/dist/knowledge/reference-tracker.js.map +1 -1
  270. package/dist/knowledge/store.d.ts +15 -0
  271. package/dist/knowledge/store.d.ts.map +1 -1
  272. package/dist/knowledge/store.js +59 -10
  273. package/dist/knowledge/store.js.map +1 -1
  274. package/dist/monitoring/context-tracker.d.ts.map +1 -1
  275. package/dist/monitoring/context-tracker.js +11 -20
  276. package/dist/monitoring/context-tracker.js.map +1 -1
  277. package/dist/monitoring/index.d.ts +1 -1
  278. package/dist/monitoring/index.d.ts.map +1 -1
  279. package/dist/monitoring/index.js +1 -2
  280. package/dist/monitoring/index.js.map +1 -1
  281. package/dist/monitoring/trace-analyzer.d.ts +15 -0
  282. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  283. package/dist/monitoring/trace-analyzer.js +19 -2
  284. package/dist/monitoring/trace-analyzer.js.map +1 -1
  285. package/dist/monitoring/traces.d.ts +21 -2
  286. package/dist/monitoring/traces.d.ts.map +1 -1
  287. package/dist/monitoring/traces.js +38 -29
  288. package/dist/monitoring/traces.js.map +1 -1
  289. package/dist/release/index.d.ts +2 -2
  290. package/dist/release/index.d.ts.map +1 -1
  291. package/dist/release/index.js +1 -5
  292. package/dist/release/index.js.map +1 -1
  293. package/dist/sdd/index-generator.js +10 -11
  294. package/dist/sdd/index-generator.js.map +1 -1
  295. package/dist/test-setup/project-fixture.d.ts +48 -0
  296. package/dist/test-setup/project-fixture.d.ts.map +1 -0
  297. package/dist/test-setup/project-fixture.js +106 -0
  298. package/dist/test-setup/project-fixture.js.map +1 -0
  299. package/dist/types/constraint.d.ts +8 -18
  300. package/dist/types/constraint.d.ts.map +1 -1
  301. package/dist/types/constraint.js +6 -1
  302. package/dist/types/constraint.js.map +1 -1
  303. package/dist/types/passes-gate.d.ts +10 -25
  304. package/dist/types/passes-gate.d.ts.map +1 -1
  305. package/dist/types/project-config.d.ts +1 -1
  306. package/dist/types/session.d.ts +6 -5
  307. package/dist/types/session.d.ts.map +1 -1
  308. package/dist/types/trace.d.ts +9 -0
  309. package/dist/types/trace.d.ts.map +1 -1
  310. package/dist/utils/exec.d.ts +10 -0
  311. package/dist/utils/exec.d.ts.map +1 -1
  312. package/dist/utils/exec.js +12 -0
  313. package/dist/utils/exec.js.map +1 -1
  314. package/dist/utils/file-walk.d.ts +9 -1
  315. package/dist/utils/file-walk.d.ts.map +1 -1
  316. package/dist/utils/file-walk.js +10 -1
  317. package/dist/utils/file-walk.js.map +1 -1
  318. package/dist/utils/frontmatter.d.ts +73 -0
  319. package/dist/utils/frontmatter.d.ts.map +1 -0
  320. package/dist/utils/frontmatter.js +141 -0
  321. package/dist/utils/frontmatter.js.map +1 -0
  322. package/dist/utils/jsonl.d.ts +92 -0
  323. package/dist/utils/jsonl.d.ts.map +1 -0
  324. package/dist/utils/jsonl.js +166 -0
  325. package/dist/utils/jsonl.js.map +1 -0
  326. package/dist/utils/package-version.d.ts +16 -0
  327. package/dist/utils/package-version.d.ts.map +1 -0
  328. package/dist/utils/package-version.js +63 -0
  329. package/dist/utils/package-version.js.map +1 -0
  330. package/package.json +1 -1
  331. package/src/CONTEXT.md +8 -2
  332. package/src/__tests__/acceptance-gate.test.ts +3 -1
  333. package/src/__tests__/checker-extra.test.ts +7 -1218
  334. package/src/__tests__/context-files-resolution.test.ts +116 -0
  335. package/src/__tests__/effective-constraints.test.ts +10 -13
  336. package/src/__tests__/exec.test.ts +35 -1
  337. package/src/__tests__/layering.test.ts +152 -0
  338. package/src/__tests__/passes-gate-extra.test.ts +0 -145
  339. package/src/__tests__/passes-gate.test.ts +130 -101
  340. package/src/__tests__/skip-semantics.test.ts +5 -9
  341. package/src/cli/__tests__/command-contract.test.ts +170 -0
  342. package/src/cli/command-contract.ts +108 -0
  343. package/src/cli/commands/CONTEXT.md +23 -8
  344. package/src/cli/commands/__tests__/acceptance.test.ts +40 -33
  345. package/src/cli/commands/__tests__/analyze-sessions.test.ts +35 -29
  346. package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +107 -0
  347. package/src/cli/commands/__tests__/check-drift.test.ts +27 -34
  348. package/src/cli/commands/__tests__/check-skip-output.test.ts +21 -24
  349. package/src/cli/commands/__tests__/check.test.ts +448 -378
  350. package/src/cli/commands/__tests__/command.test.ts +85 -70
  351. package/src/cli/commands/__tests__/constraints-report.test.ts +100 -71
  352. package/src/cli/commands/__tests__/constraints-retire.test.ts +152 -95
  353. package/src/cli/commands/__tests__/contract.test.ts +27 -34
  354. package/src/cli/commands/__tests__/failure.test.ts +31 -34
  355. package/src/cli/commands/__tests__/init-injection.test.ts +58 -28
  356. package/src/cli/commands/__tests__/init.test.ts +77 -48
  357. package/src/cli/commands/__tests__/knowledge-flywheel-consistency.test.ts +108 -0
  358. package/src/cli/commands/__tests__/knowledge.test.ts +69 -43
  359. package/src/cli/commands/__tests__/passes-gate.test.ts +43 -45
  360. package/src/cli/commands/__tests__/performance.test.ts +22 -23
  361. package/src/cli/commands/__tests__/posteval-plan.test.ts +39 -38
  362. package/src/cli/commands/__tests__/project-path-anchoring.test.ts +157 -0
  363. package/src/cli/commands/__tests__/project-path-convention.test.ts +231 -0
  364. package/src/cli/commands/__tests__/registry.test.ts +75 -35
  365. package/src/cli/commands/__tests__/release.test.ts +209 -80
  366. package/src/cli/commands/__tests__/report-extra.test.ts +16 -16
  367. package/src/cli/commands/__tests__/report.test.ts +25 -25
  368. package/src/cli/commands/__tests__/review.test.ts +33 -31
  369. package/src/cli/commands/__tests__/security.test.ts +23 -27
  370. package/src/cli/commands/__tests__/spec-baseline-check.test.ts +20 -20
  371. package/src/cli/commands/__tests__/spec.test.ts +41 -29
  372. package/src/cli/commands/__tests__/status-extra.test.ts +18 -18
  373. package/src/cli/commands/__tests__/status-skipped-lines.test.ts +72 -0
  374. package/src/cli/commands/__tests__/status.test.ts +34 -34
  375. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +77 -78
  376. package/src/cli/commands/__tests__/sync-docs.test.ts +72 -74
  377. package/src/cli/commands/__tests__/update-user-model.test.ts +97 -34
  378. package/src/cli/commands/__tests__/validate.test.ts +27 -29
  379. package/src/cli/commands/acceptance.ts +35 -24
  380. package/src/cli/commands/analyze-sessions.ts +25 -24
  381. package/src/cli/commands/check.ts +127 -66
  382. package/src/cli/commands/command.ts +30 -17
  383. package/src/cli/commands/constraints-report.ts +37 -34
  384. package/src/cli/commands/constraints-retire.ts +58 -55
  385. package/src/cli/commands/constraints.ts +10 -27
  386. package/src/cli/commands/contract.ts +51 -42
  387. package/src/cli/commands/definitions.ts +34 -227
  388. package/src/cli/commands/failure.ts +33 -26
  389. package/src/cli/commands/init.ts +166 -220
  390. package/src/cli/commands/knowledge.ts +192 -289
  391. package/src/cli/commands/passes-gate.ts +58 -68
  392. package/src/cli/commands/performance.ts +23 -18
  393. package/src/cli/commands/posteval-plan.ts +29 -15
  394. package/src/cli/commands/release.ts +70 -59
  395. package/src/cli/commands/report.ts +9 -5
  396. package/src/cli/commands/review.ts +39 -29
  397. package/src/cli/commands/sdd.ts +7 -5
  398. package/src/cli/commands/security.ts +35 -26
  399. package/src/cli/commands/spec-baseline-check.ts +16 -11
  400. package/src/cli/commands/spec.ts +66 -53
  401. package/src/cli/commands/status.ts +59 -53
  402. package/src/cli/commands/sync-docs/agents-syncer.ts +5 -2
  403. package/src/cli/commands/sync-docs/context-syncer.ts +3 -2
  404. package/src/cli/commands/sync-docs/index.ts +73 -100
  405. package/src/cli/commands/sync-docs/project-reader.ts +17 -18
  406. package/src/cli/commands/update-user-model.ts +64 -61
  407. package/src/cli/commands/validate.ts +31 -25
  408. package/src/cli/session-mining/__tests__/transcript.test.ts +118 -0
  409. package/src/cli/session-mining/transcript.ts +26 -8
  410. package/src/context/session-manager.ts +19 -33
  411. package/src/core/CONTEXT.md +10 -6
  412. package/src/core/constraints/__tests__/capabilities-reconcile.test.ts +200 -0
  413. package/src/core/constraints/__tests__/capability-counts.test.ts +94 -0
  414. package/src/core/constraints/__tests__/git-evidence.test.ts +208 -0
  415. package/src/core/constraints/__tests__/injection-drift.test.ts +3 -10
  416. package/src/core/constraints/__tests__/injection-writer.test.ts +231 -0
  417. package/src/core/constraints/__tests__/trace-injection.test.ts +71 -0
  418. package/src/core/constraints/__tests__/usage-report.test.ts +112 -0
  419. package/src/core/constraints/agent-prompt-renderer.ts +2 -6
  420. package/src/core/constraints/capabilities-parser.ts +53 -35
  421. package/src/core/constraints/capabilities-reconcile.ts +166 -0
  422. package/src/core/constraints/checker.ts +82 -133
  423. package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +273 -0
  424. package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
  425. package/src/core/constraints/checkers/__tests__/context-doc-sync.test.ts +77 -0
  426. package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +126 -0
  427. package/src/{__tests__ → core/constraints/checkers/__tests__}/governance-presence.test.ts +83 -9
  428. package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +50 -0
  429. package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +76 -0
  430. package/src/{__tests__ → core/constraints/checkers/__tests__}/no-hardcoded-credentials.test.ts +4 -3
  431. package/src/core/constraints/checkers/__tests__/no-test-simplification.test.ts +58 -0
  432. package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +153 -0
  433. package/src/core/constraints/checkers/capability-sync.ts +75 -96
  434. package/src/core/constraints/checkers/context-doc-sync.ts +15 -25
  435. package/src/core/constraints/checkers/docs-freshness.ts +54 -70
  436. package/src/core/constraints/checkers/governance-presence.ts +7 -25
  437. package/src/core/constraints/checkers/index.ts +9 -2
  438. package/src/core/constraints/checkers/no-test-simplification.ts +4 -3
  439. package/src/core/constraints/checkers/types.ts +60 -4
  440. package/src/core/constraints/context-builder.ts +39 -91
  441. package/src/core/constraints/doc-freshness/runner.ts +2 -1
  442. package/src/core/constraints/git-evidence.ts +120 -0
  443. package/src/core/constraints/injection-drift.ts +13 -60
  444. package/src/core/constraints/injection-writer.ts +212 -0
  445. package/src/core/constraints/usage-report.ts +36 -17
  446. package/src/core/index.ts +4 -2
  447. package/src/core/project-config-loader.ts +49 -9
  448. package/src/core/validators/__tests__/passes-gate.test.ts +2 -56
  449. package/src/core/validators/__tests__/test-output.test.ts +372 -0
  450. package/src/core/validators/passes-gate.ts +86 -248
  451. package/src/core/validators/test-output.ts +128 -0
  452. package/src/failure/__tests__/recorder.test.ts +168 -0
  453. package/src/failure/recorder.ts +12 -10
  454. package/src/gates/CONTEXT.md +7 -4
  455. package/src/gates/__tests__/acceptance-types-single-source.test.ts +68 -0
  456. package/src/gates/__tests__/acceptance.test.ts +4 -86
  457. package/src/gates/__tests__/checker-gate.test.ts +24 -0
  458. package/src/gates/__tests__/effective-gates.test.ts +3 -8
  459. package/src/gates/__tests__/test-verdict-consistency.test.ts +140 -0
  460. package/src/gates/acceptance.ts +19 -84
  461. package/src/gates/checker-gate.ts +12 -15
  462. package/src/gates/command.ts +10 -16
  463. package/src/gates/contract.ts +22 -61
  464. package/src/gates/performance.ts +10 -22
  465. package/src/gates/review.ts +21 -41
  466. package/src/gates/security.ts +10 -22
  467. package/src/gates/types.ts +70 -5
  468. package/src/hooks/CONTEXT.md +1 -1
  469. package/src/hooks/bootstrap.ts +5 -2
  470. package/src/index.ts +0 -4
  471. package/src/knowledge/CONTEXT.md +8 -3
  472. package/src/knowledge/__tests__/audit-labels.test.ts +44 -0
  473. package/src/knowledge/__tests__/flywheel-metrics.test.ts +135 -0
  474. package/src/knowledge/__tests__/import.test.ts +44 -0
  475. package/src/knowledge/__tests__/index-generator.test.ts +53 -0
  476. package/src/knowledge/__tests__/ingest.test.ts +85 -0
  477. package/src/knowledge/__tests__/migration.test.ts +47 -0
  478. package/src/knowledge/__tests__/reference-tracker.test.ts +16 -0
  479. package/src/knowledge/__tests__/store.test.ts +194 -0
  480. package/src/knowledge/audit.ts +67 -71
  481. package/src/knowledge/flywheel-metrics.ts +71 -0
  482. package/src/knowledge/import.ts +4 -18
  483. package/src/knowledge/index-generator.ts +19 -14
  484. package/src/knowledge/ingest.ts +9 -4
  485. package/src/knowledge/migration.ts +11 -8
  486. package/src/knowledge/reference-tracker.ts +12 -8
  487. package/src/knowledge/store.ts +64 -11
  488. package/src/monitoring/CONTEXT.md +8 -3
  489. package/src/monitoring/__tests__/skipped-lines-report.test.ts +178 -0
  490. package/src/monitoring/__tests__/traces.test.ts +57 -0
  491. package/src/monitoring/context-tracker.ts +11 -23
  492. package/src/monitoring/index.ts +1 -1
  493. package/src/monitoring/trace-analyzer.ts +21 -2
  494. package/src/monitoring/traces.ts +39 -36
  495. package/src/release/CONTEXT.md +1 -0
  496. package/src/release/__tests__/barrel.test.ts +60 -0
  497. package/src/release/index.ts +2 -13
  498. package/src/sdd/__tests__/index-generator.test.ts +71 -0
  499. package/src/sdd/index-generator.ts +9 -9
  500. package/src/test-setup/__tests__/project-fixture.test.ts +178 -0
  501. package/src/test-setup/project-fixture.ts +87 -0
  502. package/src/tools/CONTEXT.md +6 -0
  503. package/src/tools/__tests__/registry.test.ts +221 -0
  504. package/src/types/__tests__/types.test.ts +4 -5
  505. package/src/types/constraint.ts +16 -27
  506. package/src/types/passes-gate.ts +10 -30
  507. package/src/types/project-config.ts +1 -1
  508. package/src/types/session.ts +5 -5
  509. package/src/types/trace.ts +10 -0
  510. package/src/utils/__tests__/frontmatter.test.ts +206 -0
  511. package/src/utils/__tests__/jsonl-skip-disposition.test.ts +175 -0
  512. package/src/utils/__tests__/jsonl.test.ts +187 -0
  513. package/src/utils/__tests__/package-version.test.ts +97 -0
  514. package/src/utils/exec.ts +15 -0
  515. package/src/utils/file-walk.ts +11 -2
  516. package/src/utils/frontmatter.ts +121 -0
  517. package/src/utils/jsonl.ts +167 -0
  518. package/src/utils/package-version.ts +27 -0
  519. package/dist/cli/commands/doc-freshness-check.d.ts +0 -53
  520. package/dist/cli/commands/doc-freshness-check.d.ts.map +0 -1
  521. package/dist/cli/commands/doc-freshness-check.js +0 -352
  522. package/dist/cli/commands/doc-freshness-check.js.map +0 -1
  523. package/dist/types/index.d.ts +0 -14
  524. package/dist/types/index.d.ts.map +0 -1
  525. package/dist/types/index.js +0 -39
  526. package/dist/types/index.js.map +0 -1
  527. package/src/__tests__/long-running.test.ts +0 -17
  528. package/src/__tests__/passes-gate-extension.test.ts +0 -151
  529. package/src/cli/commands/__tests__/doc-freshness-check.test.ts +0 -168
  530. package/src/cli/commands/doc-freshness-check.ts +0 -386
  531. package/src/spec/CONTEXT.md +0 -11
  532. package/src/types/index.ts +0 -47
@@ -8,6 +8,7 @@
8
8
 
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
+ import { readJsonl, appendJsonl } from '../utils/jsonl';
11
12
  import { ContextTracker } from '../monitoring/context-tracker';
12
13
  import type {
13
14
  SessionEvent,
@@ -65,20 +66,12 @@ export class SessionManager {
65
66
  // 从磁盘恢复
66
67
  const eventsPath = path.join(this.getSessionDir(id), 'events.jsonl');
67
68
  try {
68
- if (!fs.existsSync(eventsPath)) return undefined;
69
-
70
- const content = fs.readFileSync(eventsPath, 'utf-8');
71
- const lines = content.trim().split('\n').filter(Boolean);
72
- if (lines.length === 0) return undefined;
73
-
74
- const events: SessionEvent[] = [];
75
- for (const line of lines) {
76
- try {
77
- events.push(JSON.parse(line) as SessionEvent);
78
- } catch {
79
- // 跳过损坏行
80
- }
81
- }
69
+ // 坏行策略:skip(原逐行 catch 跳过语义不变,harness#82);
70
+ // 空文件判定保持原始非空行数口径(合法 + 坏行 = 0 才算空)
71
+ // 计数去向:并进空事件判定的原始行数口径(harness#100 记名豁免:会话恢复面只区分
72
+ // "有没有事件",单列坏行数不改变任何输出;会话事件写链在本地,损坏=半写入截断)
73
+ const { records: events, skippedLines } = readJsonl<SessionEvent>(eventsPath, 'skip');
74
+ if (events.length + skippedLines === 0) return undefined;
82
75
 
83
76
  const stat = fs.statSync(eventsPath);
84
77
  const handle: SessionHandle = {
@@ -178,22 +171,16 @@ export class SessionManager {
178
171
  // 从 events.jsonl 恢复事件
179
172
  const eventsPath = path.join(sessionsDir, sessionId, 'events.jsonl');
180
173
  if (fs.existsSync(eventsPath)) {
181
- const content = fs.readFileSync(eventsPath, 'utf-8');
182
- const lines = content.trim().split('\n').filter(Boolean);
183
-
184
- // 只恢复 checkpoint 之前的事件
185
- const events = lines
186
- .slice(0, checkpointData.eventCount)
187
- .map(line => {
188
- try {
189
- return JSON.parse(line) as SessionEvent;
190
- } catch {
191
- return null;
192
- }
193
- })
194
- .filter((e): e is SessionEvent => e !== null);
195
-
196
- handle.events = events;
174
+ // 坏行策略:skip(原逐行 null-filter 语义不变,harness#82);
175
+ // 只恢复 checkpoint 之前的原始行(head 截断在 parse 之前);
176
+ // !== null 沿用原过滤口径
177
+ // 计数去向:豁免(harness#100)——恢复路径没有用户可见输出面(失败即整体抛
178
+ // `Checkpoint ${id} 不存在`),坏行只会让恢复出的事件少于 checkpoint 声明数;
179
+ // 告知需要改 loadCheckpoint 的抛错形状,属行为变更不在本票
180
+ const { records } = readJsonl<SessionEvent>(eventsPath, 'skip', {
181
+ head: checkpointData.eventCount,
182
+ });
183
+ handle.events = records.filter((e): e is SessionEvent => e !== null);
197
184
  }
198
185
 
199
186
  return handle;
@@ -237,9 +224,8 @@ export class SessionManager {
237
224
  */
238
225
  private appendEvent(sessionId: string, event: SessionEvent): void {
239
226
  try {
240
- const eventsPath = path.join(this.getSessionDir(sessionId), 'events.jsonl');
241
- const line = JSON.stringify(event) + '\n';
242
- fs.appendFileSync(eventsPath, line, 'utf-8');
227
+ // 写链收口:ensureDir + append(harness#82)
228
+ appendJsonl(path.join(this.getSessionDir(sessionId), 'events.jsonl'), event);
243
229
  } catch {
244
230
  // 持久化失败,静默处理
245
231
  }
@@ -4,27 +4,31 @@
4
4
  约束引擎核心:check/prompt 二元约束系统(ADR-0001)、生效集合并(effective-constraints)、检查点验证器(CSO/passes-gate)、会话管理、Spec 验证器、项目配置加载。
5
5
 
6
6
  ## 核心导出
7
- - `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 注入渲染(injection-renderer) + Agent prompt 渲染(agent-prompt-renderer:trigger 参数化分组渲染,role 路由留 studio,H6/G6)/漂移校验(injection-drift:含注入段落点路由 resolveInjectionTarget,CLAUDE.md 有标记优先、否则 AGENTS.md PRESERVE:governance,studio #307)/使用统计(usage-report);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none')`(checkers/types.ts:providers = 证据接线,'none' = 显式不接 → evidence flag checker 按契约 skip
7
+ - `constraints/` — 约束定义(IRON_LAWS/GUIDELINES/PROMPTS) + 检查引擎(ConstraintChecker,拦截统一由 checkBeforeExecution 承担,ADR-0004) + 缓存(CheckCache:TTL 缓存 + 计数采样,H6/G5 起公开导出) + 注入渲染(injection-renderer) + 落点写入器(injection-writer:「声明→对照→幂等替换」中 marker-range 替换的唯一 writer——replaceStandaloneRange/replaceEnclosedRange/cutMarkerBlock + 半标记守护;落点路由读写两侧同住此模块 resolveInjectionTarget(CLAUDE.md 有标记段优先、否则 AGENTS.md PRESERVE:governance,studio #307)/resolveGovernanceLanding(init 写侧,含旧版无标记 Governance Rules 块豁免),ADR-0011) + Agent prompt 渲染(agent-prompt-renderer:trigger 参数化分组渲染,role 路由留 studio,H6/G6)/漂移校验(injection-drift:纯检测只读,路由消费 injection-writer)/使用统计(usage-report:constraints report·retire 的只读数据层,读 trace 两个入口——`readProjectTracesReport()` 带 `skippedLines`、`readProjectTraces()` 兼容包装丢计数;报告模型 `ConstraintsUsageReport` 的降级位 = `traceFileExists` + `skippedLines`,harness#100)/清单对照(capabilities-reconcile:CAPABILITIES.md 与代码实况的覆盖/幽灵判定唯一实现,capability_sync·docs_freshness·sync-docs 共消费,ADR-0009);CheckEnv 生产侧唯一构造点 `buildCheckEnv(context, providers|'none')`(checkers/types.ts:providers = 证据接线,git 两项绑 GitEvidence 实例,'none' = 显式不接 → evidence flag checker 按契约 skip);判定接缝 `CheckOutcome = boolean | 'skip' | CheckDetail{pass, evidence}`(harness#119/ADR-0016)——编排层与 `gates/checker-gate` 一律经唯一归一点 `normalizeCheckOutcome()` 取三态 + 证据,不各自 `=== false`(对新形状会误判);证据行由 checker 自行措辞(自描述、不带缩进),组装与截断策略单点在 `formatEvidence()`/`MAX_EVIDENCE_ITEMS`(同住 checkers/types.ts);证据四出口 = `ConstraintResult.evidence` → CLI 结论块/提示块、`ExecutionTrace.evidence`、铁律 `ConstraintViolationError.message`(铁律违规直接 throw,异常文案是其证据唯一外溢面)
8
+ - `git-evidence.ts` — 全仓唯一 git 取证点(#87):`createGitEvidence(projectPath, run?)` 返回 `GitEvidence{stagedDiff, changedFileNames(staged), headDirs}`,seam 上两个真 adapter(缺省 `realGitCommandRunner` 走真 git / 注入 runner 供计数与替身)。命令级 memo 落在此实例(含失败结果),实例生命周期 = 一次 check run,故 context-builder 与 ConstraintChecker 传同一实例即共用证据;判定语义与迁移前一致(diff 取证失败 → 空串,`git ls-tree` 失败 → null = 所有目录视为新);`parseHeadDirs`/`splitFileNames` 是配套纯函数
9
+ - `constraints/context-builder.ts` — 只装配不取证:`buildConstraintContext(options.evidence?)` 与 `detectTrigger(options.evidence?)` 经 GitEvidence 取 git 事实(不传 = 自建真 adapter),本模块无 child_process import;其余证据标志(traces/文档标记)仍为本地探测
8
10
  - `effective-constraints.ts` — `getEffectiveConstraints(projectRoot, {preset?})`:全仓唯一生效集来源(内置 → preset → config.yml 禁用(内置与 custom 同效)→ custom 追加(禁用/已退役的不追加)→ scenes 过滤);`getMergedConstraintsConfig(projectRoot, {preset?})`:同链路的完整 MergedConstraintsConfig 形状(含 disabled/custom/unknownIds),内置工单 23 优先级规则(--preset 仅在无项目自定义配置时生效),check 命令经此入口;`lintEffectiveConfig` 配置诊断
9
11
  - `effective-set.ts` — `filterEnabledEntries(knownIds, entries, {onUnknownId})`:约束侧(collect)与门禁侧(throw)共用的 config 条目筛选器
10
- - `validators/` — checkpoints、passes-gate、CSO 验证器
12
+ - `validators/` — checkpoints、passes-gate、CSO 验证器;`test-output.ts` —「测试产物解读」唯一实现:`extractFailures` 提取函数 + `judgeTestRun` 判定入口(退出码为主 + 行首锚定的文本失败信号交叉否决;文本救不回非零退出,`allowPartialPass` 只作用于文本否决那一维),passes-gate 与 `gates/acceptance` 的结论一律从它取、各自体内不写判定 `if`(解析收口 ADR-0012,判定依据 ADR-0014);**不读覆盖率**——harness 核心判定对覆盖率不取数不执法,判覆盖率的两处(CLI `passes-gate --coverage`、`gates/performance`)各自读 json-summary,`extractCoverage` 与 `TaskTestResult.coverage` 已随 ADR-0015 删除;`PassesGate.runTests(workDir)` 的执行与证据落盘根是**必传形参**,本层不再取 cwd(harness#95,约定正本见 `src/cli/commands/CONTEXT.md`);`detectTestCommand` 是测试命令探测唯一正本(模块级导出:test:ci → test 排 echo 占位 → test:e2e → test:coverage → Python 标记 pyproject.toml/pytest.ini → go.mod,探不到返回 undefined,兜底归调用方——CLI 映射 skip、PassesGate 内部 fail-closed,不再兜底 'npm test';CLI 本地版已删,架构评审 A2);PassesGate 扩展注册表(registerExtension/unregisterExtension/getExtensionNames/runAllTests/checkAllPasses + PassesGateExtension/ExtensionTestResult 两类型与包根导出)已整段删除——零 adapter 假想 seam 且扩展结果本就不参与 allowed 判定,需要扩展测试类型时按 ADR-0014 判例在 `judgeTestRun` 层加,不恢复旧 seam(harness#104,架构评审 B1);包内模块,不进 `src/index.ts` 导出面
11
13
  - `session/` — 会话管理
12
14
  - `spec/validator` — SpecValidator
13
- - `project-config-loader` — 项目配置加载 + 约束合并(mergeConstraints 内含 preset 裁剪:presets/ 纯数据 + 未知名回落 standard,禁用到未知 id 经 effective-set 共享筛选器 collect 模式)
15
+ - `project-config-loader` — 项目配置加载 + 约束合并(mergeConstraints 内含 preset 裁剪:presets/ 纯数据 + 未知名回落 standard,禁用到未知 id 经 effective-set 共享筛选器 collect 模式);`getGovernanceConfig` 是全仓唯一允许手写钻取 `config.governance` 的点(工单 84),消费方一律经它或经 `resolveContextFiles`(三态:unconfigured / enabled-empty / enabled,dirs 元素类型在此收口)取数据,不得再自己 cast `Record<string, unknown>`;checker 对前两态一律 skip,自动探测回落只属于 init/扫描类工具流调用方
14
16
 
15
17
  ## 依赖关系
16
18
  - 被所有模块依赖(基础层,无上层依赖)
17
19
  - 依赖 `src/types/` 类型定义
18
20
  - 依赖 `src/utils/` 工具函数
19
- - 依赖 `src/monitoring/traces` TraceCollector
21
+ - `cli/`、`gates/`、`monitoring/` **零值导入**(type-only 允许):方向由 eslint.config.mjs 的 `@typescript-eslint/no-restricted-imports`(error 级)锁死,守卫测试见 `src/__tests__/layering.test.ts`(harness#88)
20
22
 
21
23
  ## 约定
22
24
  - 约束定义在 `constraints/definitions/{iron-laws,guidelines,prompts}.ts`,不在运行时代码中定义
23
25
  - check 层必须带真实 checker(注册表闭环,引用未注册 checker 构建报错)
24
26
  - Iron Law 违规必须 throw ConstraintViolationError
25
- - 检查器使用单例模式(ConstraintChecker.getInstance())
27
+ - 上行数据一律注入,不在 core 内 require 上层(harness#88):trace 记录器经 `ConstraintChecker` 构造参数注入(缺省 no-op,`getInstance()`/`constraintChecker` 即未接线的默认实例,只评估不写 trace),真实收集器由组合根接线(CLI check/report、`bootstrapHarness`);CAPABILITIES.md 能力清单计数经 `capabilities-parser` 的 `CapabilityDefinitionSource` 由 cli 侧注入定义表
28
+ - git 事实只能经 `constraints/git-evidence.ts` 的 adapter 取(#87):core/cli 内不得出现 `execSync('git …')`;一次 run 一份实例、沿调用链显式传递(CLI check → buildConstraintContext → checkConstraints),memo 既不做成模块级全局也不做成单例字段
26
29
 
27
30
  ## 注意事项
28
31
  - 零 Token 成本:所有分析纯文件操作,无 LLM 调用
29
32
  - 约束配置支持 .harness/config.yml 自定义合并(preset 真实生效)
30
- - 存在性探测约束(capability_sync/docs_freshness/context_doc_sync)在约定文件缺失时 skip,不计 pass/fail
33
+ - 存在性探测约束(capability_sync/docs_freshness/context_doc_sync)在约定文件缺失,或 context_files 约定已立但无目标(enabled-empty)时 skip,不计 pass/fail;context_doc_sync 与 docs_freshness 对三态判定同构(工单 84)
34
+ - **fail 必须可归因到被评估的对象**(harness#119/ADR-0016):`capability_sync` 的 Step 1(staged 增量未登记)判违规,Step 2(T-058 全量完备性)与本次变更无因果、只出提示(`satisfied=true` + evidence);「有表格但零条目 + 确有源文件」的文档退化门仍判违规(显式,不借 Step 2 兜)。2026-09-08 前 `capability_sync` 恒红(studio 22/22)就是这条错位造成的。注意降级并不换来执法:`sync-docs --check` 单独跑才 exit 1,CI/ship 都先跑写入(file 模式自补行)再 `--check`,harness 自有 CI 那步还是 `continue-on-error`——仓库级漏登目前无自动拦截点,缺口与修法见 ADR-0016「影响」
@@ -0,0 +1,200 @@
1
+ /**
2
+ * capabilities-reconcile 纯判定测试(ADR-0009)
3
+ *
4
+ * interface 即测试面:喂文档内容与文件清单,断言判定结果;无 fs/git fixture。
5
+ */
6
+
7
+ import {
8
+ reconcileCapabilities,
9
+ significantCodeChanges,
10
+ collectSourceFiles,
11
+ } from '../capabilities-reconcile';
12
+
13
+ const TABLE = [
14
+ '| 模块 | 文件 | 说明 |',
15
+ '|------|------|------|',
16
+ '| Core | src/core/checker.ts | 引擎 |',
17
+ '| Gates | src/gates/ | 门禁目录 |',
18
+ ].join('\n');
19
+
20
+ function withExtras(extraLines: string[]): string {
21
+ return [TABLE, ...extraLines].join('\n');
22
+ }
23
+
24
+ describe('reconcileCapabilities — 解析口径', () => {
25
+ it('收集所有单元格的文件与目录条目,不再只看第二列', () => {
26
+ const content = withExtras(['| src/utils/helper.ts | 名字在首列 | x |']);
27
+ const v = reconcileCapabilities({
28
+ content,
29
+ populationFiles: [],
30
+ });
31
+ expect(v.fileEntries).toContain('src/core/checker.ts');
32
+ expect(v.fileEntries).toContain('src/utils/helper.ts');
33
+ expect(v.dirEntries).toEqual(['src/gates/']);
34
+ });
35
+
36
+ it('hasTable 区分散文文档与有表格零条目;listingFormat 识别计数行', () => {
37
+ expect(reconcileCapabilities({ content: '随便写点什么', populationFiles: [] }).hasTable).toBe(false);
38
+ const emptyTable = '| 模块 | 文件 |\n|---|---|';
39
+ expect(reconcileCapabilities({ content: emptyTable, populationFiles: [] }).hasTable).toBe(true);
40
+ const listing = '# 能力\nCLI Commands (9)\nIron Laws (3)';
41
+ expect(reconcileCapabilities({ content: listing, populationFiles: [] }).listingFormat).toBe(true);
42
+ });
43
+ });
44
+
45
+ describe('reconcileCapabilities — 代码→文档(漏登记)', () => {
46
+ const population = ['src/core/checker.ts', 'src/gates/command.ts', 'src/knowledge/store.ts'];
47
+
48
+ it('module 模式:目录条目参与覆盖,未覆盖文件聚合为目录', () => {
49
+ const v = reconcileCapabilities({
50
+ content: TABLE,
51
+ populationFiles: population,
52
+ sourceRoots: ['src'],
53
+ });
54
+ expect(v.uncoveredFiles).toEqual(['src/knowledge/store.ts']);
55
+ expect(v.uncoveredDirs).toEqual(['src/knowledge/']);
56
+ });
57
+
58
+ it('目录条目恒参与覆盖(check/fix 同规则),mode 只是调用方取哪份输出形状', () => {
59
+ const v = reconcileCapabilities({
60
+ content: TABLE,
61
+ populationFiles: population,
62
+ sourceRoots: ['src'],
63
+ });
64
+ expect(v.coverageEntries).toContain('src/gates/');
65
+ expect(v.uncoveredFiles).toEqual(['src/knowledge/store.ts']);
66
+ // file 模式调用方读 uncoveredFiles;module 模式读 uncoveredDirs——同一判定两种投影
67
+ expect(v.uncoveredDirs).toEqual(['src/knowledge/']);
68
+ });
69
+
70
+ it('文件条目路径边界后缀匹配:兼容 basename 登记但拒绝模糊碰撞', () => {
71
+ const content = withExtras(['| 局部 | core/checker.ts | 相对路径条目 |']);
72
+ const v = reconcileCapabilities({
73
+ content,
74
+ populationFiles: ['src/core/checker.ts', 'src/otherchecker.ts', 'src/docs/src/checked.ts'],
75
+ });
76
+ expect(v.uncoveredFiles).toEqual(['src/otherchecker.ts', 'src/docs/src/checked.ts']);
77
+ });
78
+
79
+ it('增量判定独立于全量:changedFiles 未被覆盖的进入 uncoveredChanges', () => {
80
+ const v = reconcileCapabilities({
81
+ content: TABLE,
82
+ populationFiles: population,
83
+ changedFiles: ['src/core/checker.ts', 'src/knowledge/store.ts'],
84
+ sourceRoots: ['src'],
85
+ });
86
+ expect(v.uncoveredChanges).toEqual(['src/knowledge/store.ts']);
87
+ });
88
+ });
89
+
90
+ describe('reconcileCapabilities — 文档→代码(幽灵)', () => {
91
+ it('文件条目多根相对路径:项目根或任一源码根下存在即算活', () => {
92
+ const content = '| 模块 | 文件 | 说明 |\n|---|---|---|\n| API | apps/routes.ts | studio 式多根 |';
93
+ const v = reconcileCapabilities({
94
+ content,
95
+ populationFiles: [],
96
+ sourceRoots: ['src', 'apps/api'],
97
+ fileExists: (rel) => rel === 'apps/api/apps/routes.ts',
98
+ });
99
+ expect(v.deadEntries).toEqual([]);
100
+ });
101
+
102
+ it('目录条目也查幽灵(口径从严,Q6a):磁盘不存在且实况无文件才算死', () => {
103
+ const v = reconcileCapabilities({
104
+ content: TABLE,
105
+ populationFiles: ['src/core/checker.ts'],
106
+ fileExists: (rel) => !rel.startsWith('src/gates'),
107
+ });
108
+ expect(v.deadEntries).toEqual(['src/gates/']);
109
+ });
110
+
111
+ it('目录条目实况有文件即活(oracle 缺省的纯清单场景)', () => {
112
+ const v = reconcileCapabilities({
113
+ content: TABLE,
114
+ populationFiles: ['src/core/checker.ts', 'src/gates/command.ts'],
115
+ });
116
+ expect(v.deadEntries).toEqual([]);
117
+ });
118
+
119
+ it('裸文件名条目按实况兜底:无任何文件命中才算幽灵', () => {
120
+ const content = withExtras(['| 别名 | routes.ts | 裸 basename |']);
121
+ const v = reconcileCapabilities({
122
+ content,
123
+ populationFiles: [
124
+ 'src/core/checker.ts',
125
+ 'src/gates/command.ts',
126
+ 'src/agent-configs/routes.ts',
127
+ ],
128
+ });
129
+ expect(v.deadEntries).toEqual([]);
130
+ });
131
+
132
+ it(' basename 碰撞场景:全路径幽灵即使同名文件存活仍被报出(2026-08-08 事故回归)', () => {
133
+ const content = withExtras(['| 旧 | src/agent-configs/routes.ts | 已删 |']);
134
+ const v = reconcileCapabilities({
135
+ content,
136
+ populationFiles: ['src/agents/routes.ts'],
137
+ fileExists: () => false,
138
+ });
139
+ expect(v.deadEntries).toContain('src/agent-configs/routes.ts');
140
+ });
141
+ });
142
+
143
+ describe('significantCodeChanges', () => {
144
+ it('只留有意义的代码变更:扩展名过滤 + 排除测试文件', () => {
145
+ const names = [
146
+ 'src/core/checker.ts',
147
+ 'src/web/App.tsx',
148
+ 'scripts/run.js',
149
+ 'src/__tests__/checker-extra.test.ts',
150
+ 'src/core/checker.test.ts',
151
+ 'src/core/checker.spec.ts',
152
+ 'docs/readme.md',
153
+ 'package.json',
154
+ ];
155
+ expect(significantCodeChanges(names)).toEqual([
156
+ 'src/core/checker.ts',
157
+ 'src/web/App.tsx',
158
+ 'scripts/run.js',
159
+ ]);
160
+ });
161
+ });
162
+
163
+ describe('collectSourceFiles', () => {
164
+ const fs = require('fs') as typeof import('fs');
165
+ const os = require('os') as typeof import('os');
166
+ const path = require('path') as typeof import('path');
167
+
168
+ let tmpDir: string;
169
+
170
+ beforeEach(() => {
171
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'reconcile-scan-'));
172
+ fs.mkdirSync(path.join(tmpDir, 'src/core'), { recursive: true });
173
+ fs.mkdirSync(path.join(tmpDir, 'src/__tests__'), { recursive: true });
174
+ fs.writeFileSync(path.join(tmpDir, 'src/core/index.ts'), '');
175
+ fs.writeFileSync(path.join(tmpDir, 'src/core/foo.ts'), '');
176
+ fs.writeFileSync(path.join(tmpDir, 'src/core/types.d.ts'), '');
177
+ fs.writeFileSync(path.join(tmpDir, 'src/core/App.tsx'), '');
178
+ fs.writeFileSync(path.join(tmpDir, 'src/__tests__/foo.test.ts'), '');
179
+ });
180
+
181
+ it('返回项目相对路径;跳过 barrel/声明文件/测试目录;默认不含 tsx', () => {
182
+ expect(collectSourceFiles(tmpDir, ['src']).sort()).toEqual(['src/core/foo.ts']);
183
+ });
184
+
185
+ it('includeTsx 时纳入 .tsx', () => {
186
+ expect(collectSourceFiles(tmpDir, ['src'], { includeTsx: true }).sort()).toEqual([
187
+ 'src/core/App.tsx',
188
+ 'src/core/foo.ts',
189
+ ]);
190
+ });
191
+
192
+ it('多根收集并集', () => {
193
+ fs.mkdirSync(path.join(tmpDir, 'apps/api/src'), { recursive: true });
194
+ fs.writeFileSync(path.join(tmpDir, 'apps/api/src/main.ts'), '');
195
+ expect(collectSourceFiles(tmpDir, ['src', 'apps/api/src']).sort()).toEqual([
196
+ 'apps/api/src/main.ts',
197
+ 'src/core/foo.ts',
198
+ ]);
199
+ });
200
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 能力清单计数注入测试(架构评审 2026-09-02 · 候选9 / harness#88)
3
+ *
4
+ * capabilities-parser 属 core,此前**值导入** cli/commands/definitions 与
5
+ * gates/definitions 来数命令数——core 向上的运行时依赖。现在计数所需的定义
6
+ * 由调用方(cli 侧)注入,core 只消费最小形状(命令条目集合 + 门禁的 cli 字段)。
7
+ *
8
+ * 断言口径:计数由注入源推导,与宿主仓真实定义表无关。
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as os from 'os';
13
+ import * as path from 'path';
14
+ import { IRON_LAWS, GUIDELINES } from '../definitions';
15
+ import {
16
+ checkCapabilityCounts,
17
+ updateCapabilityCounts,
18
+ type CapabilityDefinitionSource,
19
+ } from '../capabilities-parser';
20
+
21
+ /** 3 条非门禁命令 + 4 条门禁(其中 2 条带 cli 元数据)→ CLI Commands = 5 */
22
+ const SOURCE: CapabilityDefinitionSource = {
23
+ commands: [{}, {}, {}],
24
+ gates: [{}, { cli: {} }, { cli: {} }, {}],
25
+ };
26
+
27
+ describe('能力清单计数(注入定义源,harness#88)', () => {
28
+ let projectDir: string;
29
+
30
+ beforeAll(() => {
31
+ projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-capability-counts-'));
32
+ });
33
+
34
+ afterAll(() => {
35
+ try {
36
+ fs.rmSync(projectDir, { recursive: true, force: true });
37
+ } catch {
38
+ // ignore
39
+ }
40
+ });
41
+
42
+ function writeCapabilities(content: string): string {
43
+ const dir = fs.mkdtempSync(path.join(projectDir, 'case-'));
44
+ fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), content, 'utf-8');
45
+ return dir;
46
+ }
47
+
48
+ describe('updateCapabilityCounts', () => {
49
+ it('CLI Commands 计数 = 注入命令数 + 带 cli 的门禁数', () => {
50
+ const out = updateCapabilityCounts('## CLI Commands (999)\n', SOURCE);
51
+ expect(out).toContain('CLI Commands (5)');
52
+ });
53
+
54
+ it('Quality Gates 计数 = 注入门禁总数(不看 cli 字段)', () => {
55
+ const out = updateCapabilityCounts('## Quality Gates (999)\n', SOURCE);
56
+ expect(out).toContain('Quality Gates (4)');
57
+ });
58
+
59
+ it('Iron Laws / Guidelines 计数仍来自 core 自身定义表(同层,不注入)', () => {
60
+ const out = updateCapabilityCounts('Iron Laws (999)\nGuidelines (999)\n', SOURCE);
61
+ expect(out).toContain(`Iron Laws (${Object.keys(IRON_LAWS).length})`);
62
+ expect(out).toContain(`Guidelines (${Object.keys(GUIDELINES).length})`);
63
+ });
64
+
65
+ it('不触碰文档中不存在的计数行', () => {
66
+ const out = updateCapabilityCounts('# Harness Capabilities\n\n无计数行\n', SOURCE);
67
+ expect(out).toBe('# Harness Capabilities\n\n无计数行\n');
68
+ });
69
+
70
+ it('同一注入源下写回幂等', () => {
71
+ const once = updateCapabilityCounts('CLI Commands (0)\nQuality Gates (0)\n', SOURCE);
72
+ expect(updateCapabilityCounts(once, SOURCE)).toBe(once);
73
+ });
74
+ });
75
+
76
+ describe('checkCapabilityCounts', () => {
77
+ it('计数与注入源一致 → match', () => {
78
+ const dir = writeCapabilities('## CLI Commands (5)\n## Quality Gates (4)\n');
79
+ expect(checkCapabilityCounts(dir, SOURCE)).toEqual({ match: true, mismatches: [] });
80
+ });
81
+
82
+ it('计数漂移 → match=false 并报出注入源的 actual', () => {
83
+ const dir = writeCapabilities('## CLI Commands (999)\n## Quality Gates (4)\n');
84
+ const { match, mismatches } = checkCapabilityCounts(dir, SOURCE);
85
+ expect(match).toBe(false);
86
+ expect(mismatches.join('\n')).toContain('CLI Commands: 文档 999 → 实际 5');
87
+ });
88
+
89
+ it('CAPABILITIES.md 不存在 → 跳过(无 mismatch)', () => {
90
+ const dir = fs.mkdtempSync(path.join(projectDir, 'empty-'));
91
+ expect(checkCapabilityCounts(dir, SOURCE)).toEqual({ match: true, mismatches: [] });
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,208 @@
1
+ /**
2
+ * git 证据适配器测试(架构评审 #87)
3
+ *
4
+ * 覆盖:
5
+ * - 真 git adapter 在三类证据(staged diff / 变更名 / HEAD 目录)上的取证正确性
6
+ * - 命令失败语义:diff → ''、ls-tree → null(全部视为新目录)
7
+ * - memo 落在实例层:同一实例内每条 git 命令至多执行一次
8
+ * - context-builder 与 checker 共用同一实例 → 一次 run 内无重复 git 命令
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as os from 'os';
13
+ import * as path from 'path';
14
+ import { execSync } from 'child_process';
15
+
16
+ import {
17
+ createGitEvidence,
18
+ parseHeadDirs,
19
+ realGitCommandRunner,
20
+ type GitCommandRunner,
21
+ type GitEvidence,
22
+ } from '../git-evidence';
23
+ import { buildConstraintContext } from '../context-builder';
24
+ import { ConstraintChecker } from '../checker';
25
+ import { GUIDELINES } from '../definitions';
26
+ import type { MergedConstraintsConfig } from '../../../types/project-config';
27
+
28
+ /** 建一个真 git fixture:committed src/existing.ts + 一个 staged 修改 */
29
+ function makeRepo(options: { stagedChange?: boolean } = {}): string {
30
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-git-evidence-'));
31
+ execSync('git init -q', { cwd: dir, stdio: 'pipe' });
32
+ execSync('git config user.email "test@example.com"', { cwd: dir, stdio: 'pipe' });
33
+ execSync('git config user.name "Test"', { cwd: dir, stdio: 'pipe' });
34
+ fs.mkdirSync(path.join(dir, 'src', 'nested'), { recursive: true });
35
+ fs.writeFileSync(path.join(dir, 'src', 'existing.ts'), 'export const a = 1;\n');
36
+ fs.writeFileSync(path.join(dir, 'src', 'nested', 'deep.ts'), 'export const b = 1;\n');
37
+ fs.writeFileSync(path.join(dir, 'README.md'), '# fixture\n');
38
+ execSync('git add .', { cwd: dir, stdio: 'pipe' });
39
+ execSync('git commit -q -m init', { cwd: dir, stdio: 'pipe' });
40
+ if (options.stagedChange !== false) {
41
+ fs.writeFileSync(path.join(dir, 'src', 'existing.ts'), 'export const a = 2;\n');
42
+ execSync('git add src/existing.ts', { cwd: dir, stdio: 'pipe' });
43
+ }
44
+ return dir;
45
+ }
46
+
47
+ /** 记录每条实际执行的 git 命令,执行本身委托真 adapter */
48
+ function countingRunner(): { runner: GitCommandRunner; commands: string[] } {
49
+ const commands: string[] = [];
50
+ return {
51
+ commands,
52
+ runner: (command, cwd) => {
53
+ commands.push(command);
54
+ return realGitCommandRunner(command, cwd);
55
+ },
56
+ };
57
+ }
58
+
59
+ /** 包住一份证据实例,记录「哪个 phase 调了哪个证据方法」(取证是否同源的可观测面) */
60
+ function recordingEvidence(
61
+ source: GitEvidence,
62
+ phase: () => string,
63
+ calls: string[]
64
+ ): GitEvidence {
65
+ return {
66
+ projectPath: source.projectPath,
67
+ stagedDiff: () => {
68
+ calls.push(`${phase()}:stagedDiff`);
69
+ return source.stagedDiff();
70
+ },
71
+ changedFileNames: (staged: boolean) => {
72
+ calls.push(`${phase()}:changedFileNames`);
73
+ return source.changedFileNames(staged);
74
+ },
75
+ headDirs: () => {
76
+ calls.push(`${phase()}:headDirs`);
77
+ return source.headDirs();
78
+ },
79
+ };
80
+ }
81
+
82
+ describe('git 证据适配器(#87)', () => {
83
+ describe('真 git adapter 取证', () => {
84
+ it('stagedDiff 返回 staged 全量 diff;changedFileNames 按 staged 选择命令', () => {
85
+ const dir = makeRepo();
86
+ const evidence = createGitEvidence(dir);
87
+
88
+ expect(evidence.stagedDiff()).toContain('+export const a = 2;');
89
+ expect(evidence.changedFileNames(true)).toBe('src/existing.ts');
90
+ // 无未暂存变更 → unstaged diff 为空
91
+ expect(evidence.changedFileNames(false)).toBe('');
92
+
93
+ fs.writeFileSync(path.join(dir, 'src', 'existing.ts'), 'export const a = 3;\n');
94
+ expect(evidence.changedFileNames(false)).toBe(''); // 实例已 memo,取同一快照
95
+ expect(createGitEvidence(dir).changedFileNames(false)).toBe('src/existing.ts');
96
+ });
97
+
98
+ it('headDirs 覆盖已提交目录,未提交目录不在集合内', () => {
99
+ const dir = makeRepo();
100
+ const dirs = createGitEvidence(dir).headDirs();
101
+
102
+ expect(dirs).not.toBeNull();
103
+ expect(dirs!.has('src')).toBe(true);
104
+ expect(dirs!.has('src/nested')).toBe(true);
105
+ fs.mkdirSync(path.join(dir, 'src', 'brand-new'), { recursive: true });
106
+ expect(createGitEvidence(dir).headDirs()!.has('src/brand-new')).toBe(false);
107
+ });
108
+
109
+ it('非 git 目录:diff 类证据为空串,headDirs 为 null(不重试、不抛错)', () => {
110
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-git-evidence-'));
111
+ const { runner, commands } = countingRunner();
112
+ const evidence = createGitEvidence(dir, runner);
113
+
114
+ expect(evidence.stagedDiff()).toBe('');
115
+ expect(evidence.changedFileNames(true)).toBe('');
116
+ expect(evidence.headDirs()).toBeNull();
117
+ // 失败结果同样 memo:重复调用不再 spawn
118
+ expect(evidence.headDirs()).toBeNull();
119
+ expect(evidence.changedFileNames(true)).toBe('');
120
+ expect(commands.length).toBe(3);
121
+ });
122
+ });
123
+
124
+ describe('parseHeadDirs', () => {
125
+ it('逐层展开文件路径的父目录', () => {
126
+ const dirs = parseHeadDirs('src/a/b.ts\nREADME.md\n');
127
+ expect([...dirs].sort()).toEqual(['src', 'src/a']);
128
+ });
129
+ });
130
+
131
+ describe('memo 落在实例层', () => {
132
+ it('同一实例内同类 git 命令至多执行一次', () => {
133
+ const dir = makeRepo();
134
+ const { runner, commands } = countingRunner();
135
+ const evidence = createGitEvidence(dir, runner);
136
+
137
+ const first = evidence.stagedDiff();
138
+ const stagedNames = evidence.changedFileNames(true);
139
+ const unstagedNames = evidence.changedFileNames(false);
140
+ const dirs = evidence.headDirs();
141
+
142
+ expect(evidence.stagedDiff()).toBe(first);
143
+ expect(evidence.changedFileNames(true)).toBe(stagedNames);
144
+ expect(evidence.changedFileNames(false)).toBe(unstagedNames);
145
+ expect(evidence.headDirs()).toEqual(dirs);
146
+
147
+ expect(commands).toEqual([
148
+ 'git diff --cached',
149
+ 'git diff --cached --name-only',
150
+ 'git diff --name-only',
151
+ 'git ls-tree -r --name-only HEAD',
152
+ ]);
153
+ expect(new Set(commands).size).toBe(commands.length);
154
+ });
155
+
156
+ it('两个实例 = 两次取证(memo 不是隐式全局)', () => {
157
+ const dir = makeRepo();
158
+ const { runner, commands } = countingRunner();
159
+
160
+ createGitEvidence(dir, runner).stagedDiff();
161
+ createGitEvidence(dir, runner).stagedDiff();
162
+
163
+ expect(commands.filter(c => c === 'git diff --cached')).toHaveLength(2);
164
+ });
165
+ });
166
+
167
+ describe('context-builder 与 checker 共用同一证据来源(#87 验收)', () => {
168
+ it('一次 check run 内同类 git 命令至多执行一次', async () => {
169
+ const dir = makeRepo();
170
+ fs.writeFileSync(
171
+ path.join(dir, 'CAPABILITIES.md'),
172
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| existing | src/existing.ts | 条目 |\n'
173
+ );
174
+ const { runner, commands } = countingRunner();
175
+ const shared = createGitEvidence(dir, runner);
176
+ // 记录器:哪个消费者(phase)调了哪个证据方法
177
+ const phase = { current: 'context' };
178
+ const calls: string[] = [];
179
+ const evidence = recordingEvidence(shared, () => phase.current, calls);
180
+
181
+ const context = await buildConstraintContext({ projectPath: dir, staged: true, evidence });
182
+ // capability_sync 经 CheckEnv.stagedDiffNames() 取同一份 git 证据
183
+ const merged: MergedConstraintsConfig = {
184
+ ironLaws: {},
185
+ guidelines: { capability_sync: GUIDELINES['capability_sync'] },
186
+ prompts: {},
187
+ custom: [],
188
+ disabled: [],
189
+ unknownIds: [],
190
+ };
191
+ const checker = new ConstraintChecker({ record: () => undefined });
192
+ phase.current = 'checker';
193
+ const runResult = await checker.checkConstraints(context, merged, evidence);
194
+
195
+ // capability_sync 确实被评估(非 skip)→ 它经 env.stagedDiffNames() 取过证据
196
+ expect(runResult.guidelines.map(r => r.id)).toEqual(['capability_sync']);
197
+ expect(runResult.guidelines[0].skipped).toBeUndefined();
198
+ // 两个消费者都从注入的这一份实例取证据(checker 没有另起炉灶)
199
+ expect(calls).toContain('context:changedFileNames');
200
+ expect(calls).toContain('checker:changedFileNames');
201
+ // 两侧各自需要同一份 staged 名单,合计只 spawn 一次
202
+ expect(commands).toEqual([
203
+ 'git diff --cached --name-only',
204
+ 'git ls-tree -r --name-only HEAD',
205
+ ]);
206
+ });
207
+ });
208
+ });