@dommaker/harness 0.16.8 → 0.17.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 (344) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +34 -20
  4. package/dist/cli/commands/check.d.ts +3 -2
  5. package/dist/cli/commands/check.d.ts.map +1 -1
  6. package/dist/cli/commands/check.js +69 -36
  7. package/dist/cli/commands/check.js.map +1 -1
  8. package/dist/cli/commands/constraints-report.d.ts +42 -0
  9. package/dist/cli/commands/constraints-report.d.ts.map +1 -0
  10. package/dist/cli/commands/constraints-report.js +233 -0
  11. package/dist/cli/commands/constraints-report.js.map +1 -0
  12. package/dist/cli/commands/constraints-retire.d.ts +71 -0
  13. package/dist/cli/commands/constraints-retire.d.ts.map +1 -0
  14. package/dist/cli/commands/constraints-retire.js +384 -0
  15. package/dist/cli/commands/constraints-retire.js.map +1 -0
  16. package/dist/cli/commands/constraints.d.ts +1 -1
  17. package/dist/cli/commands/constraints.d.ts.map +1 -1
  18. package/dist/cli/commands/constraints.js +4 -4
  19. package/dist/cli/commands/constraints.js.map +1 -1
  20. package/dist/cli/commands/index.d.ts +2 -1
  21. package/dist/cli/commands/index.d.ts.map +1 -1
  22. package/dist/cli/commands/index.js +10 -4
  23. package/dist/cli/commands/index.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts +20 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +81 -59
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/report.js +4 -4
  29. package/dist/cli/commands/report.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +3 -4
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +19 -2
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +79 -19
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/sync-docs/index.d.ts +2 -0
  38. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  39. package/dist/cli/commands/sync-docs/index.js +83 -20
  40. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  41. package/dist/cli/commands/validate.d.ts.map +1 -1
  42. package/dist/cli/commands/validate.js +1 -5
  43. package/dist/cli/commands/validate.js.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.d.ts +14 -0
  45. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  46. package/dist/core/constraints/capabilities-parser.js +30 -0
  47. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  48. package/dist/core/constraints/checker.d.ts +13 -5
  49. package/dist/core/constraints/checker.d.ts.map +1 -1
  50. package/dist/core/constraints/checker.js +62 -25
  51. package/dist/core/constraints/checker.js.map +1 -1
  52. package/dist/core/constraints/checkers/capability-sync.d.ts +10 -0
  53. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/capability-sync.js +73 -11
  55. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  57. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  59. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  60. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  61. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  63. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  64. package/dist/core/constraints/checkers/index.d.ts +8 -4
  65. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/index.js +29 -46
  67. package/dist/core/constraints/checkers/index.js.map +1 -1
  68. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  69. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  70. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  71. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  72. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  73. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  74. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  75. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  76. package/dist/core/constraints/checkers/types.d.ts +15 -4
  77. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  78. package/dist/core/constraints/checkers/types.js +16 -4
  79. package/dist/core/constraints/checkers/types.js.map +1 -1
  80. package/dist/core/constraints/context-builder.d.ts +6 -3
  81. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  82. package/dist/core/constraints/context-builder.js +29 -20
  83. package/dist/core/constraints/context-builder.js.map +1 -1
  84. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  85. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/guidelines.js +33 -574
  87. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  88. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  89. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  90. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  91. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  92. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  93. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  94. package/dist/core/constraints/definitions/prompts.js +405 -0
  95. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  96. package/dist/core/constraints/definitions.d.ts +15 -9
  97. package/dist/core/constraints/definitions.d.ts.map +1 -1
  98. package/dist/core/constraints/definitions.js +18 -12
  99. package/dist/core/constraints/definitions.js.map +1 -1
  100. package/dist/core/constraints/index.d.ts +3 -2
  101. package/dist/core/constraints/index.d.ts.map +1 -1
  102. package/dist/core/constraints/index.js +8 -2
  103. package/dist/core/constraints/index.js.map +1 -1
  104. package/dist/core/constraints/injection-drift.d.ts +48 -0
  105. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-drift.js +140 -0
  107. package/dist/core/constraints/injection-drift.js.map +1 -0
  108. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  109. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  110. package/dist/core/constraints/injection-renderer.js +44 -0
  111. package/dist/core/constraints/injection-renderer.js.map +1 -0
  112. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  113. package/dist/core/constraints/interceptor.js +3 -2
  114. package/dist/core/constraints/interceptor.js.map +1 -1
  115. package/dist/core/constraints/usage-report.d.ts +95 -0
  116. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  117. package/dist/core/constraints/usage-report.js +206 -0
  118. package/dist/core/constraints/usage-report.js.map +1 -0
  119. package/dist/core/effective-constraints.d.ts +47 -0
  120. package/dist/core/effective-constraints.d.ts.map +1 -0
  121. package/dist/core/effective-constraints.js +60 -0
  122. package/dist/core/effective-constraints.js.map +1 -0
  123. package/dist/core/index.d.ts +1 -0
  124. package/dist/core/index.d.ts.map +1 -1
  125. package/dist/core/index.js +2 -0
  126. package/dist/core/index.js.map +1 -1
  127. package/dist/core/project-config-loader.d.ts +28 -3
  128. package/dist/core/project-config-loader.d.ts.map +1 -1
  129. package/dist/core/project-config-loader.js +74 -8
  130. package/dist/core/project-config-loader.js.map +1 -1
  131. package/dist/dashboard/data.d.ts +1 -9
  132. package/dist/dashboard/data.d.ts.map +1 -1
  133. package/dist/dashboard/data.js +7 -15
  134. package/dist/dashboard/data.js.map +1 -1
  135. package/dist/dashboard/types.d.ts +13 -1
  136. package/dist/dashboard/types.d.ts.map +1 -1
  137. package/dist/index.d.ts +0 -4
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +1 -13
  140. package/dist/index.js.map +1 -1
  141. package/dist/monitoring/index.d.ts +0 -2
  142. package/dist/monitoring/index.d.ts.map +1 -1
  143. package/dist/monitoring/index.js +0 -2
  144. package/dist/monitoring/index.js.map +1 -1
  145. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  146. package/dist/monitoring/knowledge-evolver.js +1 -1
  147. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  148. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  149. package/dist/monitoring/trace-analyzer.js +16 -10
  150. package/dist/monitoring/trace-analyzer.js.map +1 -1
  151. package/dist/monitoring/traces.d.ts +2 -1
  152. package/dist/monitoring/traces.d.ts.map +1 -1
  153. package/dist/monitoring/traces.js +5 -3
  154. package/dist/monitoring/traces.js.map +1 -1
  155. package/dist/presets/standard.d.ts +3 -1
  156. package/dist/presets/standard.d.ts.map +1 -1
  157. package/dist/presets/standard.js +18 -8
  158. package/dist/presets/standard.js.map +1 -1
  159. package/dist/types/constraint.d.ts +51 -11
  160. package/dist/types/constraint.d.ts.map +1 -1
  161. package/dist/types/constraint.js +8 -4
  162. package/dist/types/constraint.js.map +1 -1
  163. package/dist/types/index.d.ts +1 -1
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/types/index.js.map +1 -1
  166. package/dist/types/monitoring-types.d.ts +2 -63
  167. package/dist/types/monitoring-types.d.ts.map +1 -1
  168. package/dist/types/monitoring-types.js +2 -2
  169. package/dist/types/project-config.d.ts +32 -1
  170. package/dist/types/project-config.d.ts.map +1 -1
  171. package/dist/types/trace.d.ts +16 -7
  172. package/dist/types/trace.d.ts.map +1 -1
  173. package/dist/types/trace.js +8 -0
  174. package/dist/types/trace.js.map +1 -1
  175. package/package.json +1 -1
  176. package/src/CONTEXT.md +2 -2
  177. package/src/__tests__/checker-extra.test.ts +356 -331
  178. package/src/__tests__/checker.test.ts +87 -724
  179. package/src/__tests__/constraint-doctor.test.ts +5 -5
  180. package/src/__tests__/doctor-extra.test.ts +164 -0
  181. package/src/__tests__/effective-constraints.test.ts +147 -0
  182. package/src/__tests__/extend-exceptions.test.ts +3 -3
  183. package/src/__tests__/iron-laws.test.ts +115 -91
  184. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  185. package/src/__tests__/presets.test.ts +7 -7
  186. package/src/__tests__/project-config-loader-extra.test.ts +4 -4
  187. package/src/__tests__/project-config-loader.test.ts +40 -1
  188. package/src/__tests__/skip-semantics.test.ts +322 -0
  189. package/src/cli/commands/CONTEXT.md +5 -3
  190. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  191. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  192. package/src/cli/commands/__tests__/check.test.ts +14 -25
  193. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  194. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  195. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  196. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  197. package/src/cli/commands/__tests__/status.test.ts +1 -1
  198. package/src/cli/commands/__tests__/sync-docs.test.ts +163 -0
  199. package/src/cli/commands/check.ts +72 -38
  200. package/src/cli/commands/constraints-report.ts +234 -0
  201. package/src/cli/commands/constraints-retire.ts +443 -0
  202. package/src/cli/commands/constraints.ts +5 -5
  203. package/src/cli/commands/index.ts +2 -1
  204. package/src/cli/commands/init.ts +94 -66
  205. package/src/cli/commands/report.ts +6 -6
  206. package/src/cli/commands/status.ts +3 -4
  207. package/src/cli/commands/sync-docs/capabilities-syncer.ts +90 -15
  208. package/src/cli/commands/sync-docs/index.ts +87 -24
  209. package/src/cli/commands/validate.ts +1 -5
  210. package/src/core/CONTEXT.md +7 -4
  211. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  212. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  213. package/src/core/constraints/capabilities-parser.ts +28 -0
  214. package/src/core/constraints/checker.ts +73 -28
  215. package/src/core/constraints/checkers/capability-sync.ts +89 -18
  216. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  217. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  218. package/src/core/constraints/checkers/index.ts +38 -71
  219. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  220. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  221. package/src/core/constraints/checkers/types.ts +27 -5
  222. package/src/core/constraints/context-builder.ts +28 -19
  223. package/src/core/constraints/definitions/guidelines.ts +33 -599
  224. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  225. package/src/core/constraints/definitions/prompts.ts +419 -0
  226. package/src/core/constraints/definitions.ts +18 -11
  227. package/src/core/constraints/index.ts +10 -1
  228. package/src/core/constraints/injection-drift.ts +139 -0
  229. package/src/core/constraints/injection-renderer.ts +44 -0
  230. package/src/core/constraints/interceptor.ts +3 -2
  231. package/src/core/constraints/usage-report.ts +254 -0
  232. package/src/core/effective-constraints.ts +85 -0
  233. package/src/core/index.ts +4 -1
  234. package/src/core/project-config-loader.ts +82 -9
  235. package/src/dashboard/__tests__/data.test.ts +5 -12
  236. package/src/dashboard/data.ts +9 -19
  237. package/src/dashboard/types.ts +18 -1
  238. package/src/index.ts +0 -12
  239. package/src/knowledge/CONTEXT.md +2 -2
  240. package/src/monitoring/CONTEXT.md +3 -4
  241. package/src/monitoring/index.ts +0 -2
  242. package/src/monitoring/knowledge-evolver.ts +1 -1
  243. package/src/monitoring/trace-analyzer.ts +15 -10
  244. package/src/monitoring/traces.ts +10 -6
  245. package/src/presets/__tests__/standard.test.ts +11 -9
  246. package/src/presets/standard.ts +24 -15
  247. package/src/types/constraint.ts +59 -14
  248. package/src/types/index.ts +1 -3
  249. package/src/types/monitoring-types.ts +2 -84
  250. package/src/types/project-config.ts +37 -1
  251. package/src/types/trace.ts +18 -7
  252. package/src/verification/CONTEXT.md +1 -1
  253. package/dist/cli/commands/flow.d.ts +0 -22
  254. package/dist/cli/commands/flow.d.ts.map +0 -1
  255. package/dist/cli/commands/flow.js +0 -275
  256. package/dist/cli/commands/flow.js.map +0 -1
  257. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  258. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  259. package/dist/constraints/lifecycle-runner.js +0 -151
  260. package/dist/constraints/lifecycle-runner.js.map +0 -1
  261. package/dist/constraints/registry.d.ts +0 -27
  262. package/dist/constraints/registry.d.ts.map +0 -1
  263. package/dist/constraints/registry.js +0 -128
  264. package/dist/constraints/registry.js.map +0 -1
  265. package/dist/constraints/types.d.ts +0 -53
  266. package/dist/constraints/types.d.ts.map +0 -1
  267. package/dist/constraints/types.js +0 -8
  268. package/dist/constraints/types.js.map +0 -1
  269. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  270. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  271. package/dist/core/constraints/checkers/always-pass.js +0 -23
  272. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  273. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  274. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  275. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  276. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  277. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  278. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  279. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  280. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  281. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  282. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  283. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  284. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  285. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  286. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  287. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  288. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  289. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  290. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  291. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  292. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  293. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  294. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  295. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  296. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  297. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  298. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  299. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  300. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  301. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  302. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  303. package/dist/core/constraints/checkers/yagni.js +0 -96
  304. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  305. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  306. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  307. package/dist/core/constraints/definitions/tips.js +0 -62
  308. package/dist/core/constraints/definitions/tips.js.map +0 -1
  309. package/dist/evolution/auto-evolve.d.ts +0 -35
  310. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  311. package/dist/evolution/auto-evolve.js +0 -64
  312. package/dist/evolution/auto-evolve.js.map +0 -1
  313. package/dist/evolution/index.d.ts +0 -2
  314. package/dist/evolution/index.d.ts.map +0 -1
  315. package/dist/evolution/index.js +0 -18
  316. package/dist/evolution/index.js.map +0 -1
  317. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  318. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  319. package/dist/monitoring/constraint-evolver.js +0 -410
  320. package/dist/monitoring/constraint-evolver.js.map +0 -1
  321. package/src/__tests__/constraint-evolver.test.ts +0 -405
  322. package/src/__tests__/doctor-evolver.test.ts +0 -381
  323. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  324. package/src/cli/commands/flow.ts +0 -277
  325. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  326. package/src/constraints/__tests__/registry.test.ts +0 -196
  327. package/src/constraints/lifecycle-runner.ts +0 -184
  328. package/src/constraints/registry.ts +0 -134
  329. package/src/constraints/types.ts +0 -59
  330. package/src/core/constraints/checkers/always-pass.ts +0 -22
  331. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  332. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  333. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  334. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  335. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  336. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  337. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  338. package/src/core/constraints/checkers/yagni.ts +0 -65
  339. package/src/core/constraints/definitions/tips.ts +0 -63
  340. package/src/evolution/CONTEXT.md +0 -22
  341. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  342. package/src/evolution/auto-evolve.ts +0 -98
  343. package/src/evolution/index.ts +0 -1
  344. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * checker.ts 补充测试
3
3
  *
4
- * 目标:覆盖 checkTestCoverage、checkNoSimplificationWithoutApproval、
5
- * checkCapabilitySync 完整流程、beforeExecution、findApplicableConstraints
4
+ * 目标:覆盖 checkCapabilitySync 完整流程、beforeExecution、findApplicableConstraints
5
+ * (ADR-0001:已退役 checker 的用例随约束数据模型 v2 移除)
6
6
  */
7
7
 
8
- import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
8
+ import { describe, it, expect, beforeAll, afterAll, jest } from '@jest/globals';
9
9
  import {
10
10
  ConstraintChecker,
11
11
  checkConstraints,
12
12
  checkBeforeExecution,
13
13
  } from '../core/constraints/checker';
14
+ import { getConstraintCheck, type CheckEnv } from '../core/constraints/checkers';
14
15
  import type { ConstraintContext } from '../types/constraint';
15
16
  import * as fs from 'fs';
16
17
  import * as path from 'path';
@@ -42,8 +43,11 @@ describe('ConstraintChecker - 补充覆盖', () => {
42
43
  }
43
44
  });
44
45
 
45
- describe('checkTestCoverage', () => {
46
- it('无 coverage 报告应该通过', async () => {
46
+ describe('checkCapabilitySync', () => {
47
+ it('无代码变更应该通过', async () => {
48
+ // 清理 staged changes
49
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
50
+
47
51
  const context: ConstraintContext = {
48
52
  operation: 'commit',
49
53
  projectPath: tempDir,
@@ -51,9 +55,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
51
55
 
52
56
  const result = await checker.check(
53
57
  {
54
- id: 'test_coverage_required',
58
+ id: 'capability_sync',
59
+ kind: 'check',
55
60
  level: 'guideline',
56
- rule: 'COVERAGE',
61
+ rule: 'CAPABILITY SYNC',
57
62
  message: 'test',
58
63
  trigger: 'commit',
59
64
  enforcement: 'test',
@@ -64,23 +69,15 @@ describe('ConstraintChecker - 补充覆盖', () => {
64
69
  expect(result.satisfied).toBe(true);
65
70
  });
66
71
 
67
- it('有 coverage-summary.json 且达标应该通过', async () => {
68
- // 创建 coverage 目录和报告
69
- const coverageDir = path.join(tempDir, 'coverage');
70
- fs.mkdirSync(coverageDir, { recursive: true });
71
-
72
- const summaryPath = path.join(coverageDir, 'coverage-summary.json');
73
- fs.writeFileSync(
74
- summaryPath,
75
- JSON.stringify({
76
- total: {
77
- lines: { pct: 80 },
78
- statements: { pct: 80 },
79
- branches: { pct: 75 },
80
- functions: { pct: 85 },
81
- },
82
- })
83
- );
72
+ it('有代码变更且有 CAPABILITIES.md 应该通过', async () => {
73
+ // 创建 CAPABILITIES.md(如果不存在)
74
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
75
+ fs.writeFileSync(capFile, '# Capabilities\n\n- Feature: test');
76
+
77
+ // 创建代码文件并 stage
78
+ const codeFile = path.join(tempDir, 'feature.ts');
79
+ fs.writeFileSync(codeFile, 'export function feature() {}');
80
+ execSync('git add .', { cwd: tempDir });
84
81
 
85
82
  const context: ConstraintContext = {
86
83
  operation: 'commit',
@@ -89,9 +86,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
89
86
 
90
87
  const result = await checker.check(
91
88
  {
92
- id: 'test_coverage_required',
89
+ id: 'capability_sync',
90
+ kind: 'check',
93
91
  level: 'guideline',
94
- rule: 'COVERAGE',
92
+ rule: 'CAPABILITY SYNC',
95
93
  message: 'test',
96
94
  trigger: 'commit',
97
95
  enforcement: 'test',
@@ -101,23 +99,19 @@ describe('ConstraintChecker - 补充覆盖', () => {
101
99
 
102
100
  expect(result.satisfied).toBe(true);
103
101
 
104
- // 清理
105
- fs.rmSync(coverageDir, { recursive: true, force: true });
102
+ // Cleanup
103
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
106
104
  });
107
105
 
108
- it('覆盖率低于 50% 应该失败', async () => {
109
- const coverageDir = path.join(tempDir, 'coverage');
110
- fs.mkdirSync(coverageDir, { recursive: true });
106
+ it('有代码变更且 CAPABILITIES.md 有表格但缺少新模块应该失败', async () => {
107
+ // 创建 CAPABILITIES.md,含表格但不包含新文件
108
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
109
+ fs.writeFileSync(capFile, '# Capabilities\n\n| 模块 | 文件 | 功能 |\n|------|------|------|\n| 旧模块 | old/module.ts | 旧功能 |');
111
110
 
112
- const summaryPath = path.join(coverageDir, 'coverage-summary.json');
113
- fs.writeFileSync(
114
- summaryPath,
115
- JSON.stringify({
116
- total: {
117
- lines: { pct: 30 },
118
- },
119
- })
120
- );
111
+ // 创建新的代码文件并 stage
112
+ const codeFile = path.join(tempDir, 'new-module.ts');
113
+ fs.writeFileSync(codeFile, 'export function newModule() {}');
114
+ execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
121
115
 
122
116
  const context: ConstraintContext = {
123
117
  operation: 'commit',
@@ -126,9 +120,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
126
120
 
127
121
  const result = await checker.check(
128
122
  {
129
- id: 'test_coverage_required',
123
+ id: 'capability_sync',
124
+ kind: 'check',
130
125
  level: 'guideline',
131
- rule: 'COVERAGE',
126
+ rule: 'CAPABILITY SYNC',
132
127
  message: 'test',
133
128
  trigger: 'commit',
134
129
  enforcement: 'test',
@@ -136,17 +131,24 @@ describe('ConstraintChecker - 补充覆盖', () => {
136
131
  context
137
132
  );
138
133
 
134
+ // CAPABILITIES.md 有表格但新文件未被覆盖,应该失败
139
135
  expect(result.satisfied).toBe(false);
140
136
 
141
- fs.rmSync(coverageDir, { recursive: true, force: true });
137
+ // Cleanup
138
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
142
139
  });
143
140
 
144
- it('coverage-summary.json 解析失败应该通过', async () => {
145
- const coverageDir = path.join(tempDir, 'coverage');
146
- fs.mkdirSync(coverageDir, { recursive: true });
141
+ it('有代码变更但无 CAPABILITIES.md 应该跳过(ADR-0001 存在性探测)', async () => {
142
+ // 确保 CAPABILITIES.md 不存在
143
+ const capFile = path.join(tempDir, 'CAPABILITIES.md');
144
+ if (fs.existsSync(capFile)) {
145
+ fs.rmSync(capFile, { force: true });
146
+ }
147
147
 
148
- const summaryPath = path.join(coverageDir, 'coverage-summary.json');
149
- fs.writeFileSync(summaryPath, 'invalid json {');
148
+ // 创建新的代码文件并 stage
149
+ const newCodeFile = path.join(tempDir, 'newfeature.ts');
150
+ fs.writeFileSync(newCodeFile, 'export function newfeature() {}');
151
+ execSync('git add ' + newCodeFile, { cwd: tempDir });
150
152
 
151
153
  const context: ConstraintContext = {
152
154
  operation: 'commit',
@@ -155,9 +157,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
155
157
 
156
158
  const result = await checker.check(
157
159
  {
158
- id: 'test_coverage_required',
160
+ id: 'capability_sync',
161
+ kind: 'check',
159
162
  level: 'guideline',
160
- rule: 'COVERAGE',
163
+ rule: 'CAPABILITY SYNC',
161
164
  message: 'test',
162
165
  trigger: 'commit',
163
166
  enforcement: 'test',
@@ -165,216 +168,318 @@ describe('ConstraintChecker - 补充覆盖', () => {
165
168
  context
166
169
  );
167
170
 
168
- // 解析失败默认通过
171
+ // 未采用 CAPABILITIES.md 约定 → skip(不计 pass/fail,不阻断)
172
+ expect(result.skipped).toBe(true);
169
173
  expect(result.satisfied).toBe(true);
170
174
 
171
- fs.rmSync(coverageDir, { recursive: true, force: true });
175
+ // Cleanup
176
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
172
177
  });
173
178
  });
174
179
 
175
- describe('checkNoSimplificationWithoutApproval', () => {
176
- it('正常 diff 应该通过', async () => {
177
- // clean state
178
- execSync('git checkout .', { cwd: tempDir, stdio: 'pipe' });
179
-
180
- const normalFile = path.join(tempDir, 'normal.ts');
181
- fs.writeFileSync(normalFile, 'export const x = 1;');
182
-
183
- const context: ConstraintContext = {
184
- operation: 'commit',
185
- projectPath: tempDir,
186
- };
180
+ describe('checkCapabilitySync module 模式(governance.capabilities.mode)', () => {
181
+ /** 建一个带 .harness/config.yml + CAPABILITIES.md + 源文件的临时项目 */
182
+ const setupCapProject = (
183
+ name: string,
184
+ mode: 'file' | 'module' | undefined,
185
+ capContent: string,
186
+ files: string[]
187
+ ): string => {
188
+ const dir = path.join(tempDir, name);
189
+ fs.mkdirSync(path.join(dir, '.harness'), { recursive: true });
190
+ const yaml = require('js-yaml');
191
+ const config = mode ? { governance: { capabilities: { mode } } } : {};
192
+ fs.writeFileSync(path.join(dir, '.harness', 'config.yml'), yaml.dump(config));
193
+ fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), capContent);
194
+ for (const f of files) {
195
+ const fp = path.join(dir, f);
196
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
197
+ fs.writeFileSync(fp, 'export const x = 1;');
198
+ }
199
+ return dir;
200
+ };
187
201
 
188
- const result = await checker.check(
202
+ const runCapCheck = (projectPath: string) =>
203
+ checker.check(
189
204
  {
190
- id: 'no_simplification_without_approval',
205
+ id: 'capability_sync',
206
+ kind: 'check',
191
207
  level: 'guideline',
192
- rule: 'NO SIMPLIFICATION',
208
+ rule: 'CAPABILITY SYNC',
193
209
  message: 'test',
194
210
  trigger: 'commit',
195
211
  enforcement: 'test',
196
212
  },
197
- context
213
+ { operation: 'commit', projectPath }
214
+ );
215
+
216
+ beforeAll(() => {
217
+ // 清理 staged 状态,隔离 step 1(git diff 增量)对本组用例的干扰
218
+ try {
219
+ execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
220
+ } catch {
221
+ // ignore
222
+ }
223
+ });
224
+
225
+ it('module 模式:目录条目覆盖源文件应该通过', async () => {
226
+ const dir = setupCapProject(
227
+ 'capmod-covered',
228
+ 'module',
229
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| 核心 | src/core/ | 核心模块 |',
230
+ ['src/core/foo.ts', 'src/core/bar.ts']
198
231
  );
199
232
 
233
+ const result = await runCapCheck(dir);
200
234
  expect(result.satisfied).toBe(true);
235
+
236
+ fs.rmSync(dir, { recursive: true, force: true });
201
237
  });
202
238
 
203
- it('包含简化关键词应该失败', async () => {
204
- // 先完成之前的提交
205
- execSync('git add .', { cwd: tempDir });
206
- execSync('git commit -m "cleanup" --allow-empty', { cwd: tempDir });
207
-
208
- // 创建包含简化关键词的文件并 stage
209
- const simplifyFile = path.join(tempDir, 'simplify-msg.ts');
210
- fs.writeFileSync(simplifyFile, '// removed test for simplicity\nexport const y = 2;');
211
- execSync('git add .', { cwd: tempDir });
239
+ it('module 模式:文件条目精确匹配也算覆盖', async () => {
240
+ const dir = setupCapProject(
241
+ 'capmod-file-entry',
242
+ 'module',
243
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| foo | src/core/foo.ts | foo |',
244
+ ['src/core/foo.ts']
245
+ );
212
246
 
213
- const context: ConstraintContext = {
214
- operation: 'commit',
215
- projectPath: tempDir,
216
- };
247
+ const result = await runCapCheck(dir);
248
+ expect(result.satisfied).toBe(true);
217
249
 
218
- const result = await checker.check(
219
- {
220
- id: 'no_simplification_without_approval',
221
- level: 'guideline',
222
- rule: 'NO SIMPLIFICATION',
223
- message: 'test',
224
- trigger: 'commit',
225
- enforcement: 'test',
226
- },
227
- context
250
+ fs.rmSync(dir, { recursive: true, force: true });
251
+ });
252
+
253
+ it('module 模式:新目录未登记应该失败', async () => {
254
+ const dir = setupCapProject(
255
+ 'capmod-uncovered',
256
+ 'module',
257
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| 核心 | src/core/ | 核心模块 |',
258
+ ['src/core/foo.ts', 'src/newdir/bar.ts']
228
259
  );
229
260
 
261
+ const result = await runCapCheck(dir);
230
262
  expect(result.satisfied).toBe(false);
231
-
232
- // cleanup - unstage and remove
233
- execSync('git reset HEAD -- ' + simplifyFile, { cwd: tempDir, stdio: 'pipe' });
234
- fs.rmSync(simplifyFile, { force: true });
235
- });
236
- });
237
263
 
238
- describe('checkCapabilitySync', () => {
239
- it('无代码变更应该通过', async () => {
240
- // 清理 staged changes
241
- execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
242
-
243
- const context: ConstraintContext = {
244
- operation: 'commit',
245
- projectPath: tempDir,
246
- };
264
+ fs.rmSync(dir, { recursive: true, force: true });
265
+ });
247
266
 
248
- const result = await checker.check(
249
- {
250
- id: 'capability_sync',
251
- level: 'guideline',
252
- rule: 'CAPABILITY SYNC',
253
- message: 'test',
254
- trigger: 'commit',
255
- enforcement: 'test',
256
- },
257
- context
267
+ it('file 模式原行为不变:目录条目不参与覆盖判定', async () => {
268
+ // file 模式下 step2 要求逐文件精确匹配,src/core/ 目录条目不能 rescue bar.ts
269
+ const dir = setupCapProject(
270
+ 'capfile-dir-no-rescue',
271
+ 'file',
272
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| foo | src/core/foo.ts | foo |\n| 目录 | src/core/ | 目录条目 |',
273
+ ['src/core/foo.ts', 'src/core/bar.ts']
258
274
  );
259
275
 
260
- expect(result.satisfied).toBe(true);
276
+ const result = await runCapCheck(dir);
277
+ expect(result.satisfied).toBe(false);
278
+
279
+ fs.rmSync(dir, { recursive: true, force: true });
261
280
  });
262
281
 
263
- it('有代码变更且有 CAPABILITIES.md 应该通过', async () => {
264
- // 创建 CAPABILITIES.md(如果不存在)
265
- const capFile = path.join(tempDir, 'CAPABILITIES.md');
266
- fs.writeFileSync(capFile, '# Capabilities\n\n- Feature: test');
267
-
268
- // 创建代码文件并 stage
269
- const codeFile = path.join(tempDir, 'feature.ts');
270
- fs.writeFileSync(codeFile, 'export function feature() {}');
271
- execSync('git add .', { cwd: tempDir });
282
+ it('step2 全量扫描(file 模式):全部源文件已登记应该通过', async () => {
283
+ const dir = setupCapProject(
284
+ 'capfile-step2-ok',
285
+ undefined,
286
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| foo | src/foo.ts | foo |',
287
+ ['src/foo.ts']
288
+ );
272
289
 
273
- const context: ConstraintContext = {
274
- operation: 'commit',
275
- projectPath: tempDir,
276
- };
290
+ const result = await runCapCheck(dir);
291
+ expect(result.satisfied).toBe(true);
277
292
 
278
- const result = await checker.check(
279
- {
280
- id: 'capability_sync',
281
- level: 'guideline',
282
- rule: 'CAPABILITY SYNC',
283
- message: 'test',
284
- trigger: 'commit',
285
- enforcement: 'test',
286
- },
287
- context
293
+ fs.rmSync(dir, { recursive: true, force: true });
294
+ });
295
+
296
+ it('step2 全量扫描(file 模式):有未登记源文件应该失败', async () => {
297
+ const dir = setupCapProject(
298
+ 'capfile-step2-missing',
299
+ undefined,
300
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n| foo | src/foo.ts | foo |',
301
+ ['src/foo.ts', 'src/unlisted.ts']
288
302
  );
289
303
 
290
- expect(result.satisfied).toBe(true);
304
+ const result = await runCapCheck(dir);
305
+ expect(result.satisfied).toBe(false);
291
306
 
292
- // Cleanup
293
- execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
307
+ fs.rmSync(dir, { recursive: true, force: true });
294
308
  });
309
+ });
295
310
 
296
- it('有代码变更且 CAPABILITIES.md 有表格但缺少新模块应该失败', async () => {
297
- // 创建 CAPABILITIES.md,含表格但不包含新文件
298
- const capFile = path.join(tempDir, 'CAPABILITIES.md');
299
- fs.writeFileSync(capFile, '# Capabilities\n\n| 模块 | 文件 | 功能 |\n|------|------|------|\n| 旧模块 | old/module.ts | 旧功能 |');
311
+ describe('checkCapabilitySync 遗留问题回归(2026-08-08 评估稿第四节)', () => {
312
+ const capCheck = getConstraintCheck('capability_sync')!;
313
+
314
+ const setupDir = (name: string, capContent: string, files: string[] = []): string => {
315
+ const dir = path.join(tempDir, name);
316
+ fs.mkdirSync(dir, { recursive: true });
317
+ fs.writeFileSync(path.join(dir, 'CAPABILITIES.md'), capContent);
318
+ for (const f of files) {
319
+ const fp = path.join(dir, f);
320
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
321
+ fs.writeFileSync(fp, 'export const x = 1;');
322
+ }
323
+ return dir;
324
+ };
325
+
326
+ /** 直接构造 CheckEnv:stub stagedDiffNames / srcScan,不走真实 git */
327
+ const makeEnv = (
328
+ projectPath: string,
329
+ staged: string[],
330
+ scan: Record<string, string[]> = {}
331
+ ): CheckEnv => ({
332
+ context: { operation: 'commit', projectPath },
333
+ projectPath,
334
+ stagedDiff: async () => '',
335
+ stagedDiffNames: async () => staged.join('\n'),
336
+ srcScan: (root: string) => scan[root] ?? [],
337
+ });
300
338
 
301
- // 创建新的代码文件并 stage
302
- const codeFile = path.join(tempDir, 'new-module.ts');
303
- fs.writeFileSync(codeFile, 'export function newModule() {}');
304
- execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
339
+ const tableWith = (row: string): string =>
340
+ '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n' + row + '\n';
305
341
 
306
- const context: ConstraintContext = {
307
- operation: 'commit',
308
- projectPath: tempDir,
309
- };
342
+ const EMPTY_TABLE = '# Capabilities\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n';
310
343
 
311
- const result = await checker.check(
312
- {
313
- id: 'capability_sync',
314
- level: 'guideline',
315
- rule: 'CAPABILITY SYNC',
316
- message: 'test',
317
- trigger: 'commit',
318
- enforcement: 'test',
319
- },
320
- context
321
- );
344
+ it('问题1:step1 应要求每个变更文件都被覆盖(every 而非 some)', async () => {
345
+ // 文档登记 src/foo.ts;同时 staged 已登记的 src/foo.ts 与未登记的 scripts/bar.ts
346
+ // some() 下只要有 foo.ts 命中即整体通过,bar.ts 漏网
347
+ const dir = setupDir('legacy-step1-some', tableWith('| foo | src/foo.ts | foo |'));
348
+ try {
349
+ expect(await capCheck.evaluate(makeEnv(dir, ['src/foo.ts', 'scripts/bar.ts']))).toBe(false);
350
+ } finally {
351
+ fs.rmSync(dir, { recursive: true, force: true });
352
+ }
353
+ });
322
354
 
323
- // CAPABILITIES.md 有表格但新文件未被覆盖,应该失败
324
- expect(result.satisfied).toBe(false);
355
+ it('问题3a:step1 不得因 endsWith 造成后缀碰撞(xfoo.ts 不应被 foo.ts 覆盖)', async () => {
356
+ const dir = setupDir('legacy-step1-endswith', tableWith('| foo | foo.ts | foo |'));
357
+ try {
358
+ expect(await capCheck.evaluate(makeEnv(dir, ['web/xfoo.ts']))).toBe(false);
359
+ } finally {
360
+ fs.rmSync(dir, { recursive: true, force: true });
361
+ }
362
+ });
325
363
 
326
- // Cleanup
327
- execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
364
+ it('问题3b:step1 不得因 includes 造成子串误配(docs/src/foo.tsx 不应被 src/foo.ts 覆盖)', async () => {
365
+ const dir = setupDir('legacy-step1-includes', tableWith('| foo | src/foo.ts | foo |'));
366
+ try {
367
+ expect(await capCheck.evaluate(makeEnv(dir, ['docs/src/foo.tsx']))).toBe(false);
368
+ } finally {
369
+ fs.rmSync(dir, { recursive: true, force: true });
370
+ }
328
371
  });
329
372
 
330
- it('有代码变更但无 CAPABILITIES.md 应该失败', async () => {
331
- // 确保 CAPABILITIES.md 不存在
332
- const capFile = path.join(tempDir, 'CAPABILITIES.md');
333
- if (fs.existsSync(capFile)) {
334
- fs.rmSync(capFile, { force: true });
373
+ it('兼容:basename 条目仍可按路径边界后缀匹配(src/foo.ts 被 foo.ts 覆盖)', async () => {
374
+ const dir = setupDir('legacy-step1-suffix-ok', tableWith('| foo | foo.ts | foo |'));
375
+ try {
376
+ expect(await capCheck.evaluate(makeEnv(dir, ['src/foo.ts']))).toBe(true);
377
+ } finally {
378
+ fs.rmSync(dir, { recursive: true, force: true });
335
379
  }
336
-
337
- // 创建新的代码文件并 stage
338
- const newCodeFile = path.join(tempDir, 'newfeature.ts');
339
- fs.writeFileSync(newCodeFile, 'export function newfeature() {}');
340
- execSync('git add ' + newCodeFile, { cwd: tempDir });
380
+ });
341
381
 
342
- const context: ConstraintContext = {
343
- operation: 'commit',
344
- projectPath: tempDir,
345
- };
382
+ it('问题2:空表格 + 有 staged 变更时不得直接放行', async () => {
383
+ const dir = setupDir('legacy-empty-table-diff', EMPTY_TABLE);
384
+ try {
385
+ expect(await capCheck.evaluate(makeEnv(dir, ['src/foo.ts']))).toBe(false);
386
+ } finally {
387
+ fs.rmSync(dir, { recursive: true, force: true });
388
+ }
389
+ });
346
390
 
347
- const result = await checker.check(
348
- {
349
- id: 'capability_sync',
350
- level: 'guideline',
351
- rule: 'CAPABILITY SYNC',
352
- message: 'test',
353
- trigger: 'commit',
354
- enforcement: 'test',
355
- },
356
- context
391
+ it('问题2:空表格 + 源码根有文件时 step2 不得直接放行', async () => {
392
+ const dir = setupDir('legacy-empty-table-scan', EMPTY_TABLE, ['src/foo.ts']);
393
+ try {
394
+ expect(await capCheck.evaluate(makeEnv(dir, [], { src: ['src/foo.ts'] }))).toBe(false);
395
+ } finally {
396
+ fs.rmSync(dir, { recursive: true, force: true });
397
+ }
398
+ });
399
+
400
+ it('问题2 兼容:清单格式(计数行)无文件表,仍直接放行', async () => {
401
+ const dir = setupDir(
402
+ 'legacy-listing-format',
403
+ '# Harness Capabilities\n\n## CLI Commands (25)\ncheck, validate\n'
357
404
  );
405
+ try {
406
+ expect(await capCheck.evaluate(makeEnv(dir, ['src/foo.ts']))).toBe(true);
407
+ } finally {
408
+ fs.rmSync(dir, { recursive: true, force: true });
409
+ }
410
+ });
358
411
 
359
- // 没有 CAPABILITIES.md,应该失败
360
- expect(result.satisfied).toBe(false);
412
+ it('问题4:检查异常默认放行时应输出 warn 而非静默吞错', async () => {
413
+ const dir = setupDir('legacy-failopen', tableWith('| foo | src/foo.ts | foo |'));
414
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
415
+ try {
416
+ const env: CheckEnv = {
417
+ ...makeEnv(dir, []),
418
+ stagedDiffNames: async () => {
419
+ throw new Error('git boom');
420
+ },
421
+ };
422
+ // fail-open 语义保留:异常时仍放行
423
+ expect(await capCheck.evaluate(env)).toBe(true);
424
+ expect(warnSpy).toHaveBeenCalled();
425
+ } finally {
426
+ warnSpy.mockRestore();
427
+ fs.rmSync(dir, { recursive: true, force: true });
428
+ }
429
+ });
361
430
 
362
- // Cleanup
363
- execSync('git reset HEAD', { cwd: tempDir, stdio: 'pipe' });
431
+ it('step2(file 模式)与 step1 语义一致:basename 条目可按路径边界后缀覆盖', async () => {
432
+ // 文档第二列写 basename foo.ts;step1 认可它(边界后缀),
433
+ // step2 裸精确匹配却永远不命中 srcScan 返回的 src/foo.ts → 永远红
434
+ const dir = setupDir('legacy-step2-basename', tableWith('| foo | foo.ts | foo |'), [
435
+ 'src/foo.ts',
436
+ ]);
437
+ try {
438
+ expect(await capCheck.evaluate(makeEnv(dir, [], { src: ['src/foo.ts'] }))).toBe(true);
439
+ } finally {
440
+ fs.rmSync(dir, { recursive: true, force: true });
441
+ }
442
+ });
443
+
444
+ it('step2(file 模式):不同路径的同后缀文件不得被误覆盖(lib/foo.ts 不覆盖 src/foo.ts)', async () => {
445
+ const dir = setupDir('legacy-step2-boundary', tableWith('| foo | lib/foo.ts | foo |'), [
446
+ 'src/foo.ts',
447
+ ]);
448
+ try {
449
+ expect(await capCheck.evaluate(makeEnv(dir, [], { src: ['src/foo.ts'] }))).toBe(false);
450
+ } finally {
451
+ fs.rmSync(dir, { recursive: true, force: true });
452
+ }
453
+ });
454
+
455
+ it('step2(module 模式)文件条目同样按路径边界后缀覆盖', async () => {
456
+ const dir = setupDir('legacy-step2-module-suffix', tableWith('| foo | foo.ts | foo |'), [
457
+ 'src/foo.ts',
458
+ ]);
459
+ fs.mkdirSync(path.join(dir, '.harness'), { recursive: true });
460
+ fs.writeFileSync(
461
+ path.join(dir, '.harness', 'config.yml'),
462
+ 'governance:\n capabilities:\n mode: module\n'
463
+ );
464
+ try {
465
+ expect(await capCheck.evaluate(makeEnv(dir, [], { src: ['src/foo.ts'] }))).toBe(true);
466
+ } finally {
467
+ fs.rmSync(dir, { recursive: true, force: true });
468
+ }
364
469
  });
365
470
  });
366
471
 
367
472
  describe('findApplicableConstraints', () => {
368
473
  it('应该过滤出匹配 trigger 的约束', () => {
369
474
  const context: ConstraintContext = {
370
- operation: 'task_completion_claim',
475
+ operation: 'code_implementation',
371
476
  };
372
477
 
373
478
  const result = checker.findApplicableConstraints(context);
374
479
 
375
480
  expect(result.ironLaws.length).toBeGreaterThan(0);
376
481
  expect(
377
- result.ironLaws.some((c: any) => c.id === 'no_self_approval')
482
+ result.ironLaws.some((c: any) => c.id === 'no_completion_without_verification')
378
483
  ).toBe(true);
379
484
  });
380
485
 
@@ -474,6 +579,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
474
579
  const result = await checker.check(
475
580
  {
476
581
  id: 'test_severity_iron',
582
+ kind: 'prompt',
477
583
  level: 'iron_law',
478
584
  rule: 'TEST',
479
585
  message: 'test',
@@ -494,6 +600,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
494
600
  const result = await checker.check(
495
601
  {
496
602
  id: 'test_severity_guideline',
603
+ kind: 'prompt',
497
604
  level: 'guideline',
498
605
  rule: 'TEST',
499
606
  message: 'test',
@@ -514,6 +621,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
514
621
  const result = await checker.check(
515
622
  {
516
623
  id: 'test_severity_tip',
624
+ kind: 'prompt',
517
625
  level: 'tip',
518
626
  rule: 'TEST',
519
627
  message: 'test',
@@ -536,9 +644,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
536
644
 
537
645
  const result = await checker.check(
538
646
  {
539
- id: 'no_fix_without_root_cause',
647
+ id: 'incremental_progress',
648
+ kind: 'check',
540
649
  level: 'guideline',
541
- rule: 'NO FIXES WITHOUT ROOT CAUSE',
650
+ rule: 'ONE TASK PER SESSION',
542
651
  message: 'test',
543
652
  trigger: 'code_implementation',
544
653
  enforcement: 'test',
@@ -560,9 +669,10 @@ describe('ConstraintChecker - 补充覆盖', () => {
560
669
 
561
670
  const result = await checker.check(
562
671
  {
563
- id: 'no_fix_without_root_cause',
672
+ id: 'incremental_progress',
673
+ kind: 'check',
564
674
  level: 'guideline',
565
- rule: 'NO FIXES WITHOUT ROOT CAUSE',
675
+ rule: 'ONE TASK PER SESSION',
566
676
  message: 'test',
567
677
  trigger: 'code_implementation',
568
678
  enforcement: 'test',
@@ -571,20 +681,23 @@ describe('ConstraintChecker - 补充覆盖', () => {
571
681
  context
572
682
  );
573
683
 
574
- expect(result.satisfied).toBe(false);
684
+ // hasSingleTask 未接线(undefined)→ skip(ADR-0001:flag 未接线不评估,不误报违规)
685
+ expect(result.skipped).toBe(true);
686
+ expect(result.satisfied).toBe(true);
575
687
  });
576
688
 
577
689
  it('iron_law 不应该检查例外条件', async () => {
578
690
  const context: ConstraintContext = {
579
691
  operation: 'code_implementation',
580
- hasTest: false,
692
+ isSimpleTypo: true,
581
693
  };
582
694
 
583
695
  const result = await checker.check(
584
696
  {
585
- id: 'no_self_approval',
697
+ id: 'incremental_progress',
698
+ kind: 'check',
586
699
  level: 'iron_law',
587
- rule: 'NO SELF APPROVAL',
700
+ rule: 'ONE TASK PER SESSION',
588
701
  message: 'test',
589
702
  trigger: 'code_implementation',
590
703
  enforcement: 'test',
@@ -593,20 +706,23 @@ describe('ConstraintChecker - 补充覆盖', () => {
593
706
  context
594
707
  );
595
708
 
596
- expect(result.satisfied).toBe(false);
709
+ // iron_law 不适用例外;hasSingleTask 未接线(undefined)→ skip(ADR-0001)
710
+ expect(result.skipped).toBe(true);
711
+ expect(result.satisfied).toBe(true);
597
712
  });
598
713
 
599
714
  it('无 exceptions 字段应该正常检查', async () => {
600
715
  const context: ConstraintContext = {
601
716
  operation: 'code_implementation',
602
- hasRootCauseInvestigation: true,
717
+ hasVerificationEvidence: true,
603
718
  };
604
719
 
605
720
  const result = await checker.check(
606
721
  {
607
- id: 'no_fix_without_root_cause',
608
- level: 'guideline',
609
- rule: 'NO FIXES WITHOUT ROOT CAUSE',
722
+ id: 'no_completion_without_verification',
723
+ kind: 'check',
724
+ level: 'iron_law',
725
+ rule: 'NO COMPLETION WITHOUT VERIFICATION',
610
726
  message: 'test',
611
727
  trigger: 'code_implementation',
612
728
  enforcement: 'test',
@@ -633,6 +749,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
633
749
  const result = await checker.check(
634
750
  {
635
751
  id: 'no_bypass_checkpoint',
752
+ kind: 'check',
636
753
  level: 'iron_law',
637
754
  rule: 'NO BYPASS',
638
755
  message: 'test',
@@ -662,6 +779,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
662
779
  const result = await checker.check(
663
780
  {
664
781
  id: 'no_bypass_checkpoint',
782
+ kind: 'check',
665
783
  level: 'iron_law',
666
784
  rule: 'NO BYPASS',
667
785
  message: 'test',
@@ -678,120 +796,6 @@ describe('ConstraintChecker - 补充覆盖', () => {
678
796
  });
679
797
  });
680
798
 
681
- describe('checkNoAnyType 文件检查', () => {
682
- it('.ts 文件包含 : any 应该失败', async () => {
683
- const anyFile = path.join(tempDir, 'any-test.ts');
684
- fs.writeFileSync(anyFile, 'const x: any = {};');
685
-
686
- const context: ConstraintContext = {
687
- operation: 'code_implementation',
688
- projectPath: tempDir,
689
- changedFiles: [anyFile],
690
- };
691
-
692
- const result = await checker.check(
693
- {
694
- id: 'no_any_type',
695
- level: 'guideline',
696
- rule: 'NO ANY',
697
- message: 'test',
698
- trigger: 'code_implementation',
699
- enforcement: 'test',
700
- },
701
- context
702
- );
703
-
704
- expect(result.satisfied).toBe(false);
705
-
706
- fs.unlinkSync(anyFile);
707
- });
708
-
709
- it('.ts 文件注释中的 : any 应该被忽略', async () => {
710
- const commentFile = path.join(tempDir, 'comment-any.ts');
711
- fs.writeFileSync(commentFile, '// const x: any = {};\nconst y: string = "ok";');
712
-
713
- const context: ConstraintContext = {
714
- operation: 'code_implementation',
715
- projectPath: tempDir,
716
- changedFiles: [commentFile],
717
- };
718
-
719
- const result = await checker.check(
720
- {
721
- id: 'no_any_type',
722
- level: 'guideline',
723
- rule: 'NO ANY',
724
- message: 'test',
725
- trigger: 'code_implementation',
726
- enforcement: 'test',
727
- },
728
- context
729
- );
730
-
731
- expect(result.satisfied).toBe(true);
732
-
733
- fs.unlinkSync(commentFile);
734
- });
735
-
736
- it('非 .ts 文件应该跳过检查', async () => {
737
- const jsFile = path.join(tempDir, 'any-test.js');
738
- fs.writeFileSync(jsFile, 'const x = {};');
739
-
740
- const context: ConstraintContext = {
741
- operation: 'code_implementation',
742
- projectPath: tempDir,
743
- changedFiles: [jsFile],
744
- };
745
-
746
- const result = await checker.check(
747
- {
748
- id: 'no_any_type',
749
- level: 'guideline',
750
- rule: 'NO ANY',
751
- message: 'test',
752
- trigger: 'code_implementation',
753
- enforcement: 'test',
754
- },
755
- context
756
- );
757
-
758
- expect(result.satisfied).toBe(true);
759
-
760
- fs.unlinkSync(jsFile);
761
- });
762
- });
763
-
764
- describe('checkTestCoverage coverage-final.json', () => {
765
- it('只有 coverage-final.json 应该通过', async () => {
766
- const coverageDir = path.join(tempDir, 'coverage');
767
- fs.mkdirSync(coverageDir, { recursive: true });
768
-
769
- const finalPath = path.join(coverageDir, 'coverage-final.json');
770
- fs.writeFileSync(finalPath, JSON.stringify({}));
771
-
772
- const context: ConstraintContext = {
773
- operation: 'commit',
774
- projectPath: tempDir,
775
- };
776
-
777
- const result = await checker.check(
778
- {
779
- id: 'test_coverage_required',
780
- level: 'guideline',
781
- rule: 'COVERAGE',
782
- message: 'test',
783
- trigger: 'commit',
784
- enforcement: 'test',
785
- },
786
- context
787
- );
788
-
789
- expect(result.satisfied).toBe(true);
790
-
791
- fs.rmSync(coverageDir, { recursive: true, force: true });
792
- });
793
- });
794
-
795
799
  describe('checkConstraint 快捷函数', () => {
796
800
  it('未知约束应该返回不满足', async () => {
797
801
  const { checkConstraint } = await import('../core/constraints/checker');
@@ -810,11 +814,11 @@ describe('ConstraintChecker - 补充覆盖', () => {
810
814
 
811
815
  const context: ConstraintContext = {
812
816
  operation: 'code_implementation',
813
- hasTest: true,
817
+ hasVerificationEvidence: true,
814
818
  };
815
819
 
816
- const result = await checkConstraint('no_self_approval', context);
817
- expect(result.id).toBe('no_self_approval');
820
+ const result = await checkConstraint('no_completion_without_verification', context);
821
+ expect(result.id).toBe('no_completion_without_verification');
818
822
  expect(result.satisfied).toBe(true);
819
823
  });
820
824
  });
@@ -845,6 +849,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
845
849
  const result = await checker.check(
846
850
  {
847
851
  id: 'no_test_simplification',
852
+ kind: 'check',
848
853
  level: 'iron_law',
849
854
  rule: 'NO SIMPLIFY',
850
855
  message: 'test',
@@ -874,6 +879,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
874
879
  const result = await checker.check(
875
880
  {
876
881
  id: 'context_doc_sync',
882
+ kind: 'check',
877
883
  level: 'guideline',
878
884
  rule: 'CONTEXT DOC SYNC',
879
885
  message: 'test',
@@ -918,6 +924,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
918
924
  const result = await checker.check(
919
925
  {
920
926
  id: 'context_doc_sync',
927
+ kind: 'check',
921
928
  level: 'guideline',
922
929
  rule: 'CONTEXT DOC SYNC',
923
930
  message: 'test',
@@ -959,6 +966,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
959
966
  const result = await checker.check(
960
967
  {
961
968
  id: 'context_doc_sync',
969
+ kind: 'check',
962
970
  level: 'guideline',
963
971
  rule: 'CONTEXT DOC SYNC',
964
972
  message: 'test',
@@ -997,6 +1005,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
997
1005
  const result = await checker.check(
998
1006
  {
999
1007
  id: 'context_doc_sync',
1008
+ kind: 'check',
1000
1009
  level: 'guideline',
1001
1010
  rule: 'CONTEXT DOC SYNC',
1002
1011
  message: 'test',
@@ -1025,6 +1034,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1025
1034
  const result = await checker.check(
1026
1035
  {
1027
1036
  id: 'docs_freshness',
1037
+ kind: 'check',
1028
1038
  level: 'guideline',
1029
1039
  rule: 'DOCS FRESHNESS',
1030
1040
  message: 'test',
@@ -1052,6 +1062,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1052
1062
  const result = await checker.check(
1053
1063
  {
1054
1064
  id: 'docs_freshness',
1065
+ kind: 'check',
1055
1066
  level: 'guideline',
1056
1067
  rule: 'DOCS FRESHNESS',
1057
1068
  message: 'test',
@@ -1088,6 +1099,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1088
1099
  const result = await checker.check(
1089
1100
  {
1090
1101
  id: 'docs_freshness',
1102
+ kind: 'check',
1091
1103
  level: 'guideline',
1092
1104
  rule: 'DOCS FRESHNESS',
1093
1105
  message: 'test',
@@ -1125,6 +1137,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1125
1137
  const result = await checker.check(
1126
1138
  {
1127
1139
  id: 'docs_freshness',
1140
+ kind: 'check',
1128
1141
  level: 'guideline',
1129
1142
  rule: 'DOCS FRESHNESS',
1130
1143
  message: 'test',
@@ -1161,6 +1174,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1161
1174
  const result = await checker.check(
1162
1175
  {
1163
1176
  id: 'docs_freshness',
1177
+ kind: 'check',
1164
1178
  level: 'guideline',
1165
1179
  rule: 'DOCS FRESHNESS',
1166
1180
  message: 'test',
@@ -1194,6 +1208,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1194
1208
  const result = await checker.check(
1195
1209
  {
1196
1210
  id: 'docs_freshness',
1211
+ kind: 'check',
1197
1212
  level: 'guideline',
1198
1213
  rule: 'DOCS FRESHNESS',
1199
1214
  message: 'test',
@@ -1226,6 +1241,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1226
1241
  const result = await checker.check(
1227
1242
  {
1228
1243
  id: 'docs_freshness',
1244
+ kind: 'check',
1229
1245
  level: 'guideline',
1230
1246
  rule: 'DOCS FRESHNESS',
1231
1247
  message: 'test',
@@ -1261,6 +1277,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1261
1277
  const result = await checker.check(
1262
1278
  {
1263
1279
  id: 'docs_freshness',
1280
+ kind: 'check',
1264
1281
  level: 'guideline',
1265
1282
  rule: 'DOCS FRESHNESS',
1266
1283
  message: 'test',
@@ -1293,6 +1310,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1293
1310
  const result = await checker.check(
1294
1311
  {
1295
1312
  id: 'docs_freshness',
1313
+ kind: 'check',
1296
1314
  level: 'guideline',
1297
1315
  rule: 'DOCS FRESHNESS',
1298
1316
  message: 'test',
@@ -1325,6 +1343,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1325
1343
  const result = await checker.check(
1326
1344
  {
1327
1345
  id: 'docs_freshness',
1346
+ kind: 'check',
1328
1347
  level: 'guideline',
1329
1348
  rule: 'DOCS FRESHNESS',
1330
1349
  message: 'test',
@@ -1351,6 +1370,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1351
1370
  const result = await checker.check(
1352
1371
  {
1353
1372
  id: 'docs_freshness',
1373
+ kind: 'check',
1354
1374
  level: 'guideline',
1355
1375
  rule: 'DOCS FRESHNESS',
1356
1376
  message: 'test',
@@ -1419,6 +1439,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1419
1439
  ironLaws: {
1420
1440
  test_only: {
1421
1441
  id: 'test_only',
1442
+ kind: 'prompt' as const,
1422
1443
  level: 'iron_law' as const,
1423
1444
  rule: 'TEST',
1424
1445
  message: 'test',
@@ -1452,6 +1473,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1452
1473
  ironLaws: {
1453
1474
  rule_a: {
1454
1475
  id: 'rule_a',
1476
+ kind: 'prompt' as const,
1455
1477
  level: 'iron_law' as const,
1456
1478
  rule: 'A',
1457
1479
  message: 'project A',
@@ -1469,6 +1491,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1469
1491
  ironLaws: {
1470
1492
  rule_b: {
1471
1493
  id: 'rule_b',
1494
+ kind: 'prompt' as const,
1472
1495
  level: 'iron_law' as const,
1473
1496
  rule: 'B',
1474
1497
  message: 'project B',
@@ -1518,7 +1541,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1518
1541
  const defaultKeys = Object.keys(defaultConstraints.ironLaws);
1519
1542
 
1520
1543
  const customConfig = {
1521
- ironLaws: { compat_test: { id: 'compat_test', level: 'iron_law' as const, rule: 'C', message: 'compat', trigger: 'file_modification', enforcement: 'test' } },
1544
+ ironLaws: { compat_test: { id: 'compat_test', kind: 'prompt' as const, level: 'iron_law' as const, rule: 'C', message: 'compat', trigger: 'file_modification', enforcement: 'test' } },
1522
1545
  guidelines: {},
1523
1546
  tips: {},
1524
1547
  disabled: [] as string[],
@@ -1542,6 +1565,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1542
1565
  ironLaws: {
1543
1566
  custom_rule: {
1544
1567
  id: 'custom_rule',
1568
+ kind: 'prompt' as const,
1545
1569
  level: 'iron_law' as const,
1546
1570
  rule: 'CUSTOM',
1547
1571
  message: 'custom',
@@ -1580,6 +1604,7 @@ describe('ConstraintChecker - 补充覆盖', () => {
1580
1604
  const result = await checker.check(
1581
1605
  {
1582
1606
  id: 'docs_freshness',
1607
+ kind: 'check',
1583
1608
  level: 'guideline',
1584
1609
  rule: 'DOCS FRESHNESS',
1585
1610
  message: 'test',