@dommaker/harness 0.16.9 → 0.17.1

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 (337) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +33 -32
  3. package/bin/harness.js +33 -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 +5 -1
  34. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  35. package/dist/cli/commands/sync-docs/capabilities-syncer.js +2 -10
  36. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  37. package/dist/cli/commands/validate.d.ts.map +1 -1
  38. package/dist/cli/commands/validate.js +1 -5
  39. package/dist/cli/commands/validate.js.map +1 -1
  40. package/dist/core/constraints/capabilities-parser.d.ts +7 -0
  41. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  42. package/dist/core/constraints/capabilities-parser.js +13 -0
  43. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  44. package/dist/core/constraints/checker.d.ts +13 -5
  45. package/dist/core/constraints/checker.d.ts.map +1 -1
  46. package/dist/core/constraints/checker.js +62 -25
  47. package/dist/core/constraints/checker.js.map +1 -1
  48. package/dist/core/constraints/checkers/capability-sync.d.ts +9 -2
  49. package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
  50. package/dist/core/constraints/checkers/capability-sync.js +55 -22
  51. package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
  52. package/dist/core/constraints/checkers/context-doc-sync.d.ts +3 -0
  53. package/dist/core/constraints/checkers/context-doc-sync.d.ts.map +1 -1
  54. package/dist/core/constraints/checkers/context-doc-sync.js +8 -2
  55. package/dist/core/constraints/checkers/context-doc-sync.js.map +1 -1
  56. package/dist/core/constraints/checkers/docs-freshness.d.ts +1 -0
  57. package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
  58. package/dist/core/constraints/checkers/docs-freshness.js +39 -0
  59. package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
  60. package/dist/core/constraints/checkers/index.d.ts +8 -4
  61. package/dist/core/constraints/checkers/index.d.ts.map +1 -1
  62. package/dist/core/constraints/checkers/index.js +29 -46
  63. package/dist/core/constraints/checkers/index.js.map +1 -1
  64. package/dist/core/constraints/checkers/iron-flags.d.ts +6 -7
  65. package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
  66. package/dist/core/constraints/checkers/iron-flags.js +10 -17
  67. package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
  68. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts +13 -0
  69. package/dist/core/constraints/checkers/no-hardcoded-credentials.d.ts.map +1 -0
  70. package/dist/core/constraints/checkers/no-hardcoded-credentials.js +91 -0
  71. package/dist/core/constraints/checkers/no-hardcoded-credentials.js.map +1 -0
  72. package/dist/core/constraints/checkers/types.d.ts +15 -4
  73. package/dist/core/constraints/checkers/types.d.ts.map +1 -1
  74. package/dist/core/constraints/checkers/types.js +16 -4
  75. package/dist/core/constraints/checkers/types.js.map +1 -1
  76. package/dist/core/constraints/context-builder.d.ts +6 -3
  77. package/dist/core/constraints/context-builder.d.ts.map +1 -1
  78. package/dist/core/constraints/context-builder.js +29 -20
  79. package/dist/core/constraints/context-builder.js.map +1 -1
  80. package/dist/core/constraints/definitions/guidelines.d.ts +6 -2
  81. package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
  82. package/dist/core/constraints/definitions/guidelines.js +29 -572
  83. package/dist/core/constraints/definitions/guidelines.js.map +1 -1
  84. package/dist/core/constraints/definitions/iron-laws.d.ts +6 -2
  85. package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
  86. package/dist/core/constraints/definitions/iron-laws.js +48 -231
  87. package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
  88. package/dist/core/constraints/definitions/prompts.d.ts +12 -0
  89. package/dist/core/constraints/definitions/prompts.d.ts.map +1 -0
  90. package/dist/core/constraints/definitions/prompts.js +405 -0
  91. package/dist/core/constraints/definitions/prompts.js.map +1 -0
  92. package/dist/core/constraints/definitions.d.ts +15 -9
  93. package/dist/core/constraints/definitions.d.ts.map +1 -1
  94. package/dist/core/constraints/definitions.js +18 -12
  95. package/dist/core/constraints/definitions.js.map +1 -1
  96. package/dist/core/constraints/index.d.ts +3 -2
  97. package/dist/core/constraints/index.d.ts.map +1 -1
  98. package/dist/core/constraints/index.js +8 -2
  99. package/dist/core/constraints/index.js.map +1 -1
  100. package/dist/core/constraints/injection-drift.d.ts +48 -0
  101. package/dist/core/constraints/injection-drift.d.ts.map +1 -0
  102. package/dist/core/constraints/injection-drift.js +140 -0
  103. package/dist/core/constraints/injection-drift.js.map +1 -0
  104. package/dist/core/constraints/injection-renderer.d.ts +23 -0
  105. package/dist/core/constraints/injection-renderer.d.ts.map +1 -0
  106. package/dist/core/constraints/injection-renderer.js +44 -0
  107. package/dist/core/constraints/injection-renderer.js.map +1 -0
  108. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  109. package/dist/core/constraints/interceptor.js +3 -2
  110. package/dist/core/constraints/interceptor.js.map +1 -1
  111. package/dist/core/constraints/usage-report.d.ts +95 -0
  112. package/dist/core/constraints/usage-report.d.ts.map +1 -0
  113. package/dist/core/constraints/usage-report.js +206 -0
  114. package/dist/core/constraints/usage-report.js.map +1 -0
  115. package/dist/core/effective-constraints.d.ts +47 -0
  116. package/dist/core/effective-constraints.d.ts.map +1 -0
  117. package/dist/core/effective-constraints.js +60 -0
  118. package/dist/core/effective-constraints.js.map +1 -0
  119. package/dist/core/index.d.ts +1 -0
  120. package/dist/core/index.d.ts.map +1 -1
  121. package/dist/core/index.js +2 -0
  122. package/dist/core/index.js.map +1 -1
  123. package/dist/core/project-config-loader.d.ts +17 -2
  124. package/dist/core/project-config-loader.d.ts.map +1 -1
  125. package/dist/core/project-config-loader.js +55 -8
  126. package/dist/core/project-config-loader.js.map +1 -1
  127. package/dist/dashboard/data.d.ts +1 -9
  128. package/dist/dashboard/data.d.ts.map +1 -1
  129. package/dist/dashboard/data.js +7 -15
  130. package/dist/dashboard/data.js.map +1 -1
  131. package/dist/dashboard/types.d.ts +13 -1
  132. package/dist/dashboard/types.d.ts.map +1 -1
  133. package/dist/index.d.ts +0 -4
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -13
  136. package/dist/index.js.map +1 -1
  137. package/dist/monitoring/index.d.ts +0 -2
  138. package/dist/monitoring/index.d.ts.map +1 -1
  139. package/dist/monitoring/index.js +0 -2
  140. package/dist/monitoring/index.js.map +1 -1
  141. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  142. package/dist/monitoring/knowledge-evolver.js +1 -1
  143. package/dist/monitoring/trace-analyzer.d.ts +1 -1
  144. package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
  145. package/dist/monitoring/trace-analyzer.js +16 -10
  146. package/dist/monitoring/trace-analyzer.js.map +1 -1
  147. package/dist/monitoring/traces.d.ts +2 -1
  148. package/dist/monitoring/traces.d.ts.map +1 -1
  149. package/dist/monitoring/traces.js +5 -3
  150. package/dist/monitoring/traces.js.map +1 -1
  151. package/dist/presets/standard.d.ts +3 -1
  152. package/dist/presets/standard.d.ts.map +1 -1
  153. package/dist/presets/standard.js +18 -8
  154. package/dist/presets/standard.js.map +1 -1
  155. package/dist/types/constraint.d.ts +51 -11
  156. package/dist/types/constraint.d.ts.map +1 -1
  157. package/dist/types/constraint.js +8 -4
  158. package/dist/types/constraint.js.map +1 -1
  159. package/dist/types/index.d.ts +1 -1
  160. package/dist/types/index.d.ts.map +1 -1
  161. package/dist/types/index.js.map +1 -1
  162. package/dist/types/monitoring-types.d.ts +2 -63
  163. package/dist/types/monitoring-types.d.ts.map +1 -1
  164. package/dist/types/monitoring-types.js +2 -2
  165. package/dist/types/project-config.d.ts +26 -1
  166. package/dist/types/project-config.d.ts.map +1 -1
  167. package/dist/types/trace.d.ts +16 -7
  168. package/dist/types/trace.d.ts.map +1 -1
  169. package/dist/types/trace.js +8 -0
  170. package/dist/types/trace.js.map +1 -1
  171. package/package.json +1 -1
  172. package/src/CONTEXT.md +2 -2
  173. package/src/__tests__/checker-extra.test.ts +228 -333
  174. package/src/__tests__/checker.test.ts +87 -724
  175. package/src/__tests__/constraint-doctor.test.ts +5 -5
  176. package/src/__tests__/doctor-extra.test.ts +164 -0
  177. package/src/__tests__/effective-constraints.test.ts +147 -0
  178. package/src/__tests__/extend-exceptions.test.ts +3 -3
  179. package/src/__tests__/iron-laws.test.ts +115 -91
  180. package/src/__tests__/no-hardcoded-credentials.test.ts +90 -0
  181. package/src/__tests__/presets.test.ts +7 -7
  182. package/src/__tests__/project-config-loader-extra.test.ts +41 -4
  183. package/src/__tests__/skip-semantics.test.ts +322 -0
  184. package/src/cli/commands/CONTEXT.md +5 -3
  185. package/src/cli/commands/__tests__/check-drift.test.ts +127 -0
  186. package/src/cli/commands/__tests__/check-skip-output.test.ts +128 -0
  187. package/src/cli/commands/__tests__/check.test.ts +14 -25
  188. package/src/cli/commands/__tests__/constraints-report.test.ts +327 -0
  189. package/src/cli/commands/__tests__/constraints-retire.test.ts +283 -0
  190. package/src/cli/commands/__tests__/init-injection.test.ts +234 -0
  191. package/src/cli/commands/__tests__/status-extra.test.ts +11 -11
  192. package/src/cli/commands/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/check.ts +72 -38
  194. package/src/cli/commands/constraints-report.ts +234 -0
  195. package/src/cli/commands/constraints-retire.ts +443 -0
  196. package/src/cli/commands/constraints.ts +5 -5
  197. package/src/cli/commands/index.ts +2 -1
  198. package/src/cli/commands/init.ts +94 -66
  199. package/src/cli/commands/report.ts +6 -6
  200. package/src/cli/commands/status.ts +3 -4
  201. package/src/cli/commands/sync-docs/capabilities-syncer.ts +5 -7
  202. package/src/cli/commands/validate.ts +1 -5
  203. package/src/core/CONTEXT.md +7 -4
  204. package/src/core/constraints/__tests__/injection-drift.test.ts +164 -0
  205. package/src/core/constraints/__tests__/interceptor.test.ts +4 -0
  206. package/src/core/constraints/capabilities-parser.ts +13 -0
  207. package/src/core/constraints/checker.ts +73 -28
  208. package/src/core/constraints/checkers/capability-sync.ts +72 -29
  209. package/src/core/constraints/checkers/context-doc-sync.ts +9 -2
  210. package/src/core/constraints/checkers/docs-freshness.ts +43 -0
  211. package/src/core/constraints/checkers/index.ts +38 -71
  212. package/src/core/constraints/checkers/iron-flags.ts +13 -49
  213. package/src/core/constraints/checkers/no-hardcoded-credentials.ts +93 -0
  214. package/src/core/constraints/checkers/types.ts +27 -5
  215. package/src/core/constraints/context-builder.ts +28 -19
  216. package/src/core/constraints/definitions/guidelines.ts +29 -597
  217. package/src/core/constraints/definitions/iron-laws.ts +48 -239
  218. package/src/core/constraints/definitions/prompts.ts +419 -0
  219. package/src/core/constraints/definitions.ts +18 -11
  220. package/src/core/constraints/index.ts +10 -1
  221. package/src/core/constraints/injection-drift.ts +139 -0
  222. package/src/core/constraints/injection-renderer.ts +44 -0
  223. package/src/core/constraints/interceptor.ts +3 -2
  224. package/src/core/constraints/usage-report.ts +254 -0
  225. package/src/core/effective-constraints.ts +85 -0
  226. package/src/core/index.ts +4 -1
  227. package/src/core/project-config-loader.ts +59 -9
  228. package/src/dashboard/__tests__/data.test.ts +5 -12
  229. package/src/dashboard/data.ts +9 -19
  230. package/src/dashboard/types.ts +18 -1
  231. package/src/index.ts +0 -12
  232. package/src/knowledge/CONTEXT.md +2 -2
  233. package/src/monitoring/CONTEXT.md +3 -4
  234. package/src/monitoring/index.ts +0 -2
  235. package/src/monitoring/knowledge-evolver.ts +1 -1
  236. package/src/monitoring/trace-analyzer.ts +15 -10
  237. package/src/monitoring/traces.ts +10 -6
  238. package/src/presets/__tests__/standard.test.ts +11 -9
  239. package/src/presets/standard.ts +24 -15
  240. package/src/types/constraint.ts +59 -14
  241. package/src/types/index.ts +1 -3
  242. package/src/types/monitoring-types.ts +2 -84
  243. package/src/types/project-config.ts +30 -1
  244. package/src/types/trace.ts +18 -7
  245. package/src/verification/CONTEXT.md +1 -1
  246. package/dist/cli/commands/flow.d.ts +0 -22
  247. package/dist/cli/commands/flow.d.ts.map +0 -1
  248. package/dist/cli/commands/flow.js +0 -275
  249. package/dist/cli/commands/flow.js.map +0 -1
  250. package/dist/constraints/lifecycle-runner.d.ts +0 -52
  251. package/dist/constraints/lifecycle-runner.d.ts.map +0 -1
  252. package/dist/constraints/lifecycle-runner.js +0 -151
  253. package/dist/constraints/lifecycle-runner.js.map +0 -1
  254. package/dist/constraints/registry.d.ts +0 -27
  255. package/dist/constraints/registry.d.ts.map +0 -1
  256. package/dist/constraints/registry.js +0 -128
  257. package/dist/constraints/registry.js.map +0 -1
  258. package/dist/constraints/types.d.ts +0 -53
  259. package/dist/constraints/types.d.ts.map +0 -1
  260. package/dist/constraints/types.js +0 -8
  261. package/dist/constraints/types.js.map +0 -1
  262. package/dist/core/constraints/checkers/always-pass.d.ts +0 -17
  263. package/dist/core/constraints/checkers/always-pass.d.ts.map +0 -1
  264. package/dist/core/constraints/checkers/always-pass.js +0 -23
  265. package/dist/core/constraints/checkers/always-pass.js.map +0 -1
  266. package/dist/core/constraints/checkers/guideline-flags.d.ts +0 -9
  267. package/dist/core/constraints/checkers/guideline-flags.d.ts.map +0 -1
  268. package/dist/core/constraints/checkers/guideline-flags.js +0 -13
  269. package/dist/core/constraints/checkers/guideline-flags.js.map +0 -1
  270. package/dist/core/constraints/checkers/no-any-type.d.ts +0 -6
  271. package/dist/core/constraints/checkers/no-any-type.d.ts.map +0 -1
  272. package/dist/core/constraints/checkers/no-any-type.js +0 -42
  273. package/dist/core/constraints/checkers/no-any-type.js.map +0 -1
  274. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts +0 -6
  275. package/dist/core/constraints/checkers/no-excuse-patterns.d.ts.map +0 -1
  276. package/dist/core/constraints/checkers/no-excuse-patterns.js +0 -25
  277. package/dist/core/constraints/checkers/no-excuse-patterns.js.map +0 -1
  278. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts +0 -6
  279. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.d.ts.map +0 -1
  280. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js +0 -34
  281. package/dist/core/constraints/checkers/no-fuzzy-completion-claim.js.map +0 -1
  282. package/dist/core/constraints/checkers/no-performative-agreement.d.ts +0 -8
  283. package/dist/core/constraints/checkers/no-performative-agreement.d.ts.map +0 -1
  284. package/dist/core/constraints/checkers/no-performative-agreement.js +0 -23
  285. package/dist/core/constraints/checkers/no-performative-agreement.js.map +0 -1
  286. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts +0 -6
  287. package/dist/core/constraints/checkers/no-simplification-without-approval.d.ts.map +0 -1
  288. package/dist/core/constraints/checkers/no-simplification-without-approval.js +0 -30
  289. package/dist/core/constraints/checkers/no-simplification-without-approval.js.map +0 -1
  290. package/dist/core/constraints/checkers/test-coverage.d.ts +0 -6
  291. package/dist/core/constraints/checkers/test-coverage.d.ts.map +0 -1
  292. package/dist/core/constraints/checkers/test-coverage.js +0 -33
  293. package/dist/core/constraints/checkers/test-coverage.js.map +0 -1
  294. package/dist/core/constraints/checkers/yagni.d.ts +0 -8
  295. package/dist/core/constraints/checkers/yagni.d.ts.map +0 -1
  296. package/dist/core/constraints/checkers/yagni.js +0 -96
  297. package/dist/core/constraints/checkers/yagni.js.map +0 -1
  298. package/dist/core/constraints/definitions/tips.d.ts +0 -7
  299. package/dist/core/constraints/definitions/tips.d.ts.map +0 -1
  300. package/dist/core/constraints/definitions/tips.js +0 -62
  301. package/dist/core/constraints/definitions/tips.js.map +0 -1
  302. package/dist/evolution/auto-evolve.d.ts +0 -35
  303. package/dist/evolution/auto-evolve.d.ts.map +0 -1
  304. package/dist/evolution/auto-evolve.js +0 -64
  305. package/dist/evolution/auto-evolve.js.map +0 -1
  306. package/dist/evolution/index.d.ts +0 -2
  307. package/dist/evolution/index.d.ts.map +0 -1
  308. package/dist/evolution/index.js +0 -18
  309. package/dist/evolution/index.js.map +0 -1
  310. package/dist/monitoring/constraint-evolver.d.ts +0 -120
  311. package/dist/monitoring/constraint-evolver.d.ts.map +0 -1
  312. package/dist/monitoring/constraint-evolver.js +0 -410
  313. package/dist/monitoring/constraint-evolver.js.map +0 -1
  314. package/src/__tests__/constraint-evolver.test.ts +0 -405
  315. package/src/__tests__/doctor-evolver.test.ts +0 -381
  316. package/src/cli/commands/__tests__/flow.test.ts +0 -343
  317. package/src/cli/commands/flow.ts +0 -277
  318. package/src/constraints/__tests__/lifecycle-runner.test.ts +0 -246
  319. package/src/constraints/__tests__/registry.test.ts +0 -196
  320. package/src/constraints/lifecycle-runner.ts +0 -184
  321. package/src/constraints/registry.ts +0 -134
  322. package/src/constraints/types.ts +0 -59
  323. package/src/core/constraints/checkers/always-pass.ts +0 -22
  324. package/src/core/constraints/checkers/guideline-flags.ts +0 -30
  325. package/src/core/constraints/checkers/no-any-type.ts +0 -42
  326. package/src/core/constraints/checkers/no-excuse-patterns.ts +0 -24
  327. package/src/core/constraints/checkers/no-fuzzy-completion-claim.ts +0 -33
  328. package/src/core/constraints/checkers/no-performative-agreement.ts +0 -22
  329. package/src/core/constraints/checkers/no-simplification-without-approval.ts +0 -29
  330. package/src/core/constraints/checkers/test-coverage.ts +0 -35
  331. package/src/core/constraints/checkers/yagni.ts +0 -65
  332. package/src/core/constraints/definitions/tips.ts +0 -63
  333. package/src/evolution/CONTEXT.md +0 -22
  334. package/src/evolution/__tests__/auto-evolve.test.ts +0 -319
  335. package/src/evolution/auto-evolve.ts +0 -98
  336. package/src/evolution/index.ts +0 -1
  337. package/src/monitoring/constraint-evolver.ts +0 -501
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * 约束检查引擎
3
- *
4
- * 三层约束体系检查:
5
- * - Iron Laws:检查失败立即抛出异常
6
- * - Guidelines:检查失败记录警告
7
- * - Tips:检查失败记录提示
3
+ *
4
+ * kind 二元模型(ADR-0001):
5
+ * - check · Iron Laws:检查失败立即抛出异常
6
+ * - check · Guidelines:检查失败记录警告
7
+ * - prompt:不执行 checker,仅参与注入
8
8
  */
9
9
 
10
10
  import {
@@ -17,14 +17,14 @@ import {
17
17
  ConstraintViolationError,
18
18
  } from '../../types/constraint';
19
19
  import type { ExecutionTrace } from '../../types/trace';
20
- import { IRON_LAWS, GUIDELINES, TIPS } from './definitions';
20
+ import { IRON_LAWS, GUIDELINES, TIPS, PROMPTS } from './definitions';
21
21
  import type { MergedConstraintsConfig } from '../../types/project-config';
22
22
  import { normalizeTriggers } from '../../utils/exec';
23
23
  import { runCommand } from '../../utils/exec';
24
24
  import { join, relative } from 'path';
25
25
  import { CheckCache } from './check-cache';
26
26
  import { findTsSourceFiles } from '../../utils/file-walk';
27
- import { getConstraintCheck, type CheckEnv } from './checkers';
27
+ import { getConstraintCheck, type CheckEnv, type CheckOutcome } from './checkers';
28
28
 
29
29
  /**
30
30
  * 例外名称 → ConstraintContext 中布尔字段的映射
@@ -158,11 +158,12 @@ export class ConstraintChecker {
158
158
  ironLaws: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
159
159
  guidelines: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
160
160
  tips: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
161
+ prompts: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
161
162
  } {
162
163
  const config = customConfig ?? this.customConfig;
163
164
  const source = config
164
- ? { ironLaws: config.ironLaws, guidelines: config.guidelines, tips: config.tips }
165
- : { ironLaws: IRON_LAWS, guidelines: GUIDELINES, tips: TIPS };
165
+ ? { ironLaws: config.ironLaws, guidelines: config.guidelines, tips: config.tips, prompts: config.prompts ?? PROMPTS }
166
+ : { ironLaws: IRON_LAWS, guidelines: GUIDELINES, tips: TIPS, prompts: PROMPTS };
166
167
 
167
168
  // Wire unified check() method on every constraint
168
169
  const wire = (constraints: Record<string, Constraint>) => {
@@ -171,7 +172,12 @@ export class ConstraintChecker {
171
172
  }
172
173
  return constraints as Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
173
174
  };
174
- return { ironLaws: wire(source.ironLaws), guidelines: wire(source.guidelines), tips: wire(source.tips) };
175
+ return {
176
+ ironLaws: wire(source.ironLaws),
177
+ guidelines: wire(source.guidelines),
178
+ tips: wire(source.tips),
179
+ prompts: wire(source.prompts),
180
+ };
175
181
  }
176
182
 
177
183
  /**
@@ -181,6 +187,17 @@ export class ConstraintChecker {
181
187
  constraint: Constraint,
182
188
  context: ConstraintContext
183
189
  ): Promise<ConstraintResult> {
190
+ // prompt 类约束不参与 checker 执行(ADR-0001:仅参与注入)
191
+ if (constraint.kind === 'prompt') {
192
+ return {
193
+ id: constraint.id,
194
+ level: constraint.level,
195
+ satisfied: true,
196
+ constraint,
197
+ checkedAt: new Date(),
198
+ };
199
+ }
200
+
184
201
  // 检查例外条件(仅 Guidelines 有效)
185
202
  if (constraint.level === 'guideline' && constraint.exceptions) {
186
203
  if (this.checkException(constraint, context)) {
@@ -195,8 +212,22 @@ export class ConstraintChecker {
195
212
  }
196
213
  }
197
214
 
198
- // 检查前置条件
199
- const satisfied = await this.checkPrecondition(constraint, context);
215
+ // 检查前置条件('skip' = 约定未采用/证据未接线:satisfied 置 true 但不计 pass/fail)
216
+ const outcome = await this.checkPrecondition(constraint, context);
217
+
218
+ if (outcome === 'skip') {
219
+ return {
220
+ id: constraint.id,
221
+ level: constraint.level,
222
+ satisfied: true,
223
+ skipped: true,
224
+ constraint,
225
+ message: `约束 ${constraint.id} 跳过评估(约定未采用或证据未接线)`,
226
+ checkedAt: new Date(),
227
+ };
228
+ }
229
+
230
+ const satisfied = outcome;
200
231
 
201
232
  return {
202
233
  id: constraint.id,
@@ -228,6 +259,7 @@ export class ConstraintChecker {
228
259
  return 'error';
229
260
  case 'guideline':
230
261
  return 'warning';
262
+ case 'prompt':
231
263
  case 'tip':
232
264
  return 'info';
233
265
  default:
@@ -237,10 +269,14 @@ export class ConstraintChecker {
237
269
 
238
270
  /**
239
271
  * 判断约束是否匹配当前触发条件
272
+ *
273
+ * context.operation 为主触发条件,context.extraTriggers 为次级推断
274
+ * (ADR-0001:pre-commit 代码变更附加 code_implementation),任一命中即匹配。
240
275
  */
241
- private matchesTrigger(constraint: Constraint, operation: ConstraintTrigger): boolean {
276
+ private matchesTrigger(constraint: Constraint, context: ConstraintContext): boolean {
242
277
  const triggers = normalizeTriggers<ConstraintTrigger>(constraint.trigger);
243
- return triggers.includes(operation);
278
+ const operations = [context.operation, ...(context.extraTriggers ?? [])];
279
+ return operations.some(op => triggers.includes(op));
244
280
  }
245
281
 
246
282
  /**
@@ -256,7 +292,7 @@ export class ConstraintChecker {
256
292
  constraintId: constraint.id,
257
293
  level: constraint.level,
258
294
  timestamp: Date.now(),
259
- result: checkResult.satisfied ? 'pass' : 'fail',
295
+ result: checkResult.skipped ? 'skip' : checkResult.satisfied ? 'pass' : 'fail',
260
296
  operation: context.operation,
261
297
  severity: this.getSeverity(constraint.level),
262
298
  exceptionApplied: checkResult.message?.includes('豁免')
@@ -270,14 +306,20 @@ export class ConstraintChecker {
270
306
  /**
271
307
  * 检查约束前置条件(工单 21:分发至 checkers/ 注册表)
272
308
  *
273
- * 未注册的约束默认通过(保持历史 default 分支语义)。
309
+ * 注册表闭环(ADR-0001):kind='check' 未注册 checker 直接抛错,
310
+ * 不再有"未注册默认通过"路径;kind='prompt' 在 check() 入口已短路。
274
311
  */
275
312
  private async checkPrecondition(
276
313
  constraint: Constraint,
277
314
  context: ConstraintContext
278
- ): Promise<boolean> {
315
+ ): Promise<CheckOutcome> {
279
316
  const impl = getConstraintCheck(constraint.id);
280
- if (!impl) return true;
317
+ if (!impl) {
318
+ throw new Error(
319
+ `[harness] 约束 "${constraint.id}" (kind='check') 未注册 checker,拒绝静默通过。` +
320
+ `请在 checkers/ 注册实现,或将约束改为 kind='prompt'。`
321
+ );
322
+ }
281
323
 
282
324
  const projectPath = context.projectPath || process.cwd();
283
325
  const env: CheckEnv = {
@@ -309,8 +351,9 @@ export class ConstraintChecker {
309
351
  ironLaws: Constraint[];
310
352
  guidelines: Constraint[];
311
353
  tips: Constraint[];
354
+ prompts: Constraint[];
312
355
  } {
313
- const trigger = context.operation;
356
+ const operations = [context.operation, ...(context.extraTriggers ?? [])];
314
357
  const constraints = this.getConstraints(customConfig);
315
358
 
316
359
  const filterByTrigger = (constraintSet: Record<string, Constraint>): Constraint[] => {
@@ -318,7 +361,7 @@ export class ConstraintChecker {
318
361
  const triggers = Array.isArray(constraint.trigger)
319
362
  ? constraint.trigger
320
363
  : [constraint.trigger];
321
- return triggers.includes(trigger);
364
+ return operations.some(op => triggers.includes(op));
322
365
  });
323
366
  };
324
367
 
@@ -326,6 +369,7 @@ export class ConstraintChecker {
326
369
  ironLaws: filterByTrigger(constraints.ironLaws),
327
370
  guidelines: filterByTrigger(constraints.guidelines),
328
371
  tips: filterByTrigger(constraints.tips),
372
+ prompts: filterByTrigger(constraints.prompts),
329
373
  };
330
374
  }
331
375
 
@@ -362,7 +406,7 @@ export class ConstraintChecker {
362
406
 
363
407
  // 1. Iron Laws: 必须全部通过
364
408
  for (const constraint of Object.values(constraints.ironLaws)) {
365
- if (!this.matchesTrigger(constraint, context.operation)) continue;
409
+ if (!this.matchesTrigger(constraint, context)) continue;
366
410
 
367
411
  const checkResult = await this.check(constraint, context);
368
412
  result.ironLaws.push(checkResult);
@@ -376,7 +420,7 @@ export class ConstraintChecker {
376
420
 
377
421
  // 2. Guidelines: 记录警告
378
422
  for (const constraint of Object.values(constraints.guidelines)) {
379
- if (!this.matchesTrigger(constraint, context.operation)) continue;
423
+ if (!this.matchesTrigger(constraint, context)) continue;
380
424
 
381
425
  const checkResult = await this.check(constraint, context);
382
426
  result.guidelines.push(checkResult);
@@ -389,7 +433,7 @@ export class ConstraintChecker {
389
433
 
390
434
  // 3. Tips: 仅记录
391
435
  for (const constraint of Object.values(constraints.tips)) {
392
- if (!this.matchesTrigger(constraint, context.operation)) continue;
436
+ if (!this.matchesTrigger(constraint, context)) continue;
393
437
 
394
438
  const checkResult = await this.check(constraint, context);
395
439
  result.tips.push(checkResult);
@@ -434,7 +478,7 @@ export class ConstraintChecker {
434
478
  const constraints = this.getConstraints(customConfig);
435
479
 
436
480
  for (const constraint of Object.values(constraints.ironLaws)) {
437
- if (!this.matchesTrigger(constraint, context.operation)) continue;
481
+ if (!this.matchesTrigger(constraint, context)) continue;
438
482
  const checkResult = await this.check(constraint, context);
439
483
  result.ironLaws.push(checkResult);
440
484
  this.recordTrace(traceCollector, constraint, checkResult, context);
@@ -442,7 +486,7 @@ export class ConstraintChecker {
442
486
  }
443
487
 
444
488
  for (const constraint of Object.values(constraints.guidelines)) {
445
- if (!this.matchesTrigger(constraint, context.operation)) continue;
489
+ if (!this.matchesTrigger(constraint, context)) continue;
446
490
  const checkResult = await this.check(constraint, context);
447
491
  result.guidelines.push(checkResult);
448
492
  this.recordTrace(traceCollector, constraint, checkResult, context);
@@ -450,7 +494,7 @@ export class ConstraintChecker {
450
494
  }
451
495
 
452
496
  for (const constraint of Object.values(constraints.tips)) {
453
- if (!this.matchesTrigger(constraint, context.operation)) continue;
497
+ if (!this.matchesTrigger(constraint, context)) continue;
454
498
  const checkResult = await this.check(constraint, context);
455
499
  result.tips.push(checkResult);
456
500
  this.recordTrace(traceCollector, constraint, checkResult, context);
@@ -475,7 +519,7 @@ export class ConstraintChecker {
475
519
  customConfig?: MergedConstraintsConfig | null
476
520
  ): Promise<void> {
477
521
  const constraints = this.getConstraints(customConfig);
478
- const operations = normalizeTriggers(context.operation);
522
+ const operations = [context.operation, ...(context.extraTriggers ?? [])];
479
523
 
480
524
  for (const constraint of Object.values(constraints.ironLaws)) {
481
525
  if (!normalizeTriggers(constraint.trigger).some((t) => operations.includes(t))) continue;
@@ -505,7 +549,8 @@ export async function checkConstraint(
505
549
  const constraint =
506
550
  constraints.ironLaws[constraintId] ||
507
551
  constraints.guidelines[constraintId] ||
508
- constraints.tips[constraintId];
552
+ constraints.tips[constraintId] ||
553
+ constraints.prompts[constraintId];
509
554
 
510
555
  if (!constraint) {
511
556
  return {
@@ -5,21 +5,54 @@
5
5
  * 1. Git diff 增量 — 当前变更的非测试文件是否在文档中有记录
6
6
  * 2. 全量扫描 — 源码根下所有源文件是否都在 CAPABILITIES.md 中(T-058)
7
7
  *
8
- * governance.capabilities.mode=module step 2 改按目录覆盖判定:
9
- * 文件条目精确匹配或目录条目前缀覆盖,未覆盖文件按源码根下第一级子目录聚合。
8
+ * 覆盖判定两步统一(isCoveredBy):目录条目(以 / 结尾)前缀匹配,
9
+ * 文件条目精确匹配或路径边界后缀匹配。module 模式下目录条目参与覆盖,
10
+ * 未覆盖文件按源码根下第一级子目录聚合。
11
+ *
12
+ * 清单格式(计数行,mode=listing 或嗅探命中)无文件表可核对,直接放行;
13
+ * 检查异常时 fail-open 但输出 console.warn 保留可观测性。
14
+ *
15
+ * ADR-0001 存在性探测:项目根无 CAPABILITIES.md(未采用该约定)时返回 'skip',
16
+ * 不计 pass/fail,避免在未采用约定的项目上全量误报。
10
17
  */
11
18
 
12
- import { existsSync } from 'fs';
19
+ import { existsSync, readFileSync } from 'fs';
13
20
  import { join } from 'path';
14
- import { aggregateToSourceSubdir, readCapabilitiesEntries } from '../capabilities-parser';
21
+ import {
22
+ aggregateToSourceSubdir,
23
+ isCapabilityListingFormat,
24
+ parseCapabilitiesEntries,
25
+ } from '../capabilities-parser';
15
26
  import { getCapabilitiesMode } from '../../project-config-loader';
16
27
  import { detectSourceRoots } from '../../../utils/detect-source-roots';
17
28
  import type { ConstraintCheck } from './types';
18
29
 
30
+ /** markdown 表格分隔行(|------|------|):区分「无表格的散文文档」与「有表格但零条目」 */
31
+ const TABLE_SEPARATOR_REGEX = /^\s*\|[\s:|-]+\|\s*$/m;
32
+
33
+ /**
34
+ * 变更文件是否被条目覆盖
35
+ *
36
+ * 目录条目(以 / 结尾)前缀匹配;文件条目精确匹配或路径边界后缀匹配
37
+ * (兼容 basename 条目,但拒绝 xfoo.ts 命中 foo.ts、docs/src/foo.tsx 命中 src/foo.ts
38
+ * 这类 endsWith/includes 模糊匹配造成的碰撞)
39
+ */
40
+ function isCoveredBy(entries: string[], changed: string): boolean {
41
+ return entries.some((listed) =>
42
+ listed.endsWith('/')
43
+ ? changed.startsWith(listed)
44
+ : changed === listed || changed.endsWith('/' + listed)
45
+ );
46
+ }
47
+
19
48
  export const capabilitySync: ConstraintCheck = {
20
49
  id: 'capability_sync',
21
50
  async evaluate(env) {
22
51
  const projectPath = env.projectPath;
52
+ // ADR-0001 存在性探测:项目未采用 CAPABILITIES.md 约定 → skip(不计 pass/fail)
53
+ if (!existsSync(join(projectPath, 'CAPABILITIES.md'))) {
54
+ return 'skip';
55
+ }
23
56
  try {
24
57
  const capabilitiesPath = join(projectPath, 'CAPABILITIES.md');
25
58
  if (!existsSync(capabilitiesPath)) {
@@ -31,45 +64,53 @@ export const capabilitySync: ConstraintCheck = {
31
64
  return changedCodeFiles.length === 0;
32
65
  }
33
66
 
34
- const listedFiles = readCapabilitiesEntries(capabilitiesPath, { secondColumnOnly: true });
67
+ const content = readFileSync(capabilitiesPath, 'utf-8');
68
+ const capabilitiesMode = getCapabilitiesMode(projectPath);
69
+
70
+ // 清单格式(计数行)没有文件表可核对,计数由 sync-docs 维护,直接放行
71
+ if (capabilitiesMode === 'listing' || isCapabilityListingFormat(content)) {
72
+ return true;
73
+ }
74
+
75
+ const listedFiles = parseCapabilitiesEntries(content, { secondColumnOnly: true });
76
+ // module 模式下目录条目同样构成覆盖依据,与 step 2 判定对齐
77
+ const entries =
78
+ capabilitiesMode === 'module'
79
+ ? parseCapabilitiesEntries(content, { includeDirs: true })
80
+ : listedFiles;
81
+ // 有表格但零条目 = 文档什么都没登记,不得直接放行;无表格的散文文档保持历史放行
82
+ const hasTable = TABLE_SEPARATOR_REGEX.test(content);
35
83
 
36
84
  // ── Step 1: Git diff 增量检查 ──
37
85
  const diff = await env.stagedDiffNames();
38
86
  const changedCodeFiles = diff.split('\n').filter(
39
87
  (f: string) => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js')
40
88
  );
89
+ const significantChanges = changedCodeFiles.filter(
90
+ (f: string) => !f.includes('__tests__') && !f.includes('.test.') && !f.includes('.spec.')
91
+ );
41
92
 
42
- if (changedCodeFiles.length > 0 && listedFiles.length > 0) {
43
- const significantChanges = changedCodeFiles.filter(
44
- (f: string) => !f.includes('__tests__') && !f.includes('.test.') && !f.includes('.spec.')
45
- );
46
-
47
- if (significantChanges.length > 0) {
48
- const covered = significantChanges.some((changed: string) =>
49
- listedFiles.some((listed: string) => changed.endsWith(listed) || changed.includes(listed))
50
- );
51
- if (!covered) return false;
52
- }
93
+ // 每个变更文件都必须被覆盖(every;some 会让一个命中掩盖其余漏网)
94
+ if (
95
+ significantChanges.length > 0 &&
96
+ (entries.length > 0 || hasTable) &&
97
+ !significantChanges.every((changed: string) => isCoveredBy(entries, changed))
98
+ ) {
99
+ return false;
53
100
  }
54
101
 
55
102
  // ── Step 2: 全量源码根扫描(T-058 修复核心)──
56
- const capabilitiesMode = getCapabilitiesMode(projectPath);
57
103
  if (capabilitiesMode === 'module') {
58
- // module 模式:文件条目精确匹配 或 目录条目前缀覆盖;
104
+ // module 模式:目录条目参与覆盖(isCoveredBy 统一判定);
59
105
  // 未覆盖文件按源码根下第一级子目录聚合,任一组非空即失败
60
- const entries = readCapabilitiesEntries(capabilitiesPath, { includeDirs: true });
61
- if (entries.length > 0) {
62
- const fileEntries = entries.filter(e => !e.endsWith('/'));
63
- const dirEntries = entries.filter(e => e.endsWith('/'));
106
+ if (entries.length > 0 || hasTable) {
64
107
  const uncoveredDirs = new Set<string>();
65
108
  const sourceRoots = detectSourceRoots(projectPath);
66
109
  for (const root of sourceRoots) {
67
110
  const srcDir = join(projectPath, root);
68
111
  if (existsSync(srcDir)) {
69
112
  for (const file of env.srcScan(root)) {
70
- const covered =
71
- fileEntries.includes(file) || dirEntries.some(d => file.startsWith(d));
72
- if (!covered) {
113
+ if (!isCoveredBy(entries, file)) {
73
114
  uncoveredDirs.add(aggregateToSourceSubdir(root, file));
74
115
  }
75
116
  }
@@ -77,13 +118,13 @@ export const capabilitySync: ConstraintCheck = {
77
118
  }
78
119
  if (uncoveredDirs.size > 0) return false;
79
120
  }
80
- } else if (listedFiles.length > 0) {
121
+ } else if (listedFiles.length > 0 || hasTable) {
81
122
  const sourceRoots = detectSourceRoots(projectPath);
82
123
  for (const root of sourceRoots) {
83
124
  const srcDir = join(projectPath, root);
84
125
  if (existsSync(srcDir)) {
85
126
  for (const file of env.srcScan(root)) {
86
- if (!listedFiles.includes(file)) {
127
+ if (!isCoveredBy(listedFiles, file)) {
87
128
  return false;
88
129
  }
89
130
  }
@@ -92,8 +133,10 @@ export const capabilitySync: ConstraintCheck = {
92
133
  }
93
134
 
94
135
  return true;
95
- } catch {
96
- return true; // 检查失败,默认通过
136
+ } catch (err) {
137
+ // fail-open 语义保留,但必须可观测:静默吞错会让解析 bug 变成「永远通过」
138
+ console.warn('[capability_sync] 检查执行异常,默认放行:', err);
139
+ return true;
97
140
  }
98
141
  },
99
142
  };
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * context_doc_sync:关键目录必须有 CONTEXT.md(工单 21)
3
+ *
4
+ * ADR-0001 存在性探测:项目未配置 governance.context_files 约定
5
+ * (未启用或无 required_dirs)时返回 'skip',不计 pass/fail。
3
6
  */
4
7
 
5
8
  import { existsSync } from 'fs';
@@ -17,10 +20,14 @@ export const contextDocSync: ConstraintCheck = {
17
20
  const contextFiles = governance?.context_files as Record<string, unknown> | undefined;
18
21
 
19
22
  if (!contextFiles?.enabled || !Array.isArray(contextFiles.required_dirs)) {
20
- return true; // 未配置,跳过
23
+ return 'skip'; // 未配置 context_files 约定,跳过评估
21
24
  }
22
25
 
23
26
  const requiredDirs = contextFiles.required_dirs as string[];
27
+ if (requiredDirs.length === 0) {
28
+ return 'skip'; // 无目标目录,约定未生效
29
+ }
30
+
24
31
  for (const dir of requiredDirs) {
25
32
  const contextPath = join(projectPath, dir, 'CONTEXT.md');
26
33
  if (!existsSync(contextPath)) {
@@ -30,7 +37,7 @@ export const contextDocSync: ConstraintCheck = {
30
37
 
31
38
  return true;
32
39
  } catch {
33
- return true; // 配置不存在或解析失败,跳过
40
+ return 'skip'; // 配置不存在或解析失败 = 无法判定约定,跳过
34
41
  }
35
42
  },
36
43
  };
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * docs_freshness:CAPABILITIES.md 文件表 + 能力清单格式 + CLAUDE.md + CHANGELOG(工单 21)
3
3
  *
4
+ * 0. 存在性探测(ADR-0001):项目无任何 freshness 配置/目标时 skip,不计 pass/fail
4
5
  * 1. 文件表格式:CAPABILITIES.md 中列出的文件必须仍存在(防过期引用)
5
6
  * 2. 能力清单格式:FreshnessRunner 配置驱动检查
6
7
  * 3. 无配置时注入内置默认配置(等价旧硬编码行为)
@@ -71,11 +72,53 @@ function checkCapabilitiesFreshness(projectPath: string): boolean {
71
72
  }
72
73
  }
73
74
 
75
+ /**
76
+ * ADR-0001 存在性探测:项目是否存在任何 freshness 配置/检查目标。
77
+ *
78
+ * 判定信号(任一命中即视为采用了文档新鲜度约定):
79
+ * - 配置了 governance.doc_freshness.checks
80
+ * - 配置了 governance.context_files(enabled + 非空 required_dirs)
81
+ * - 项目根存在 CAPABILITIES.md
82
+ * - 项目根存在 CHANGELOG.md / CHANGELOG
83
+ *
84
+ * 全部缺失 → 项目未采用该约定,调用方应 skip 而非评估内置默认配置。
85
+ */
86
+ function hasFreshnessTargets(projectPath: string): boolean {
87
+ if (existsSync(join(projectPath, 'CAPABILITIES.md'))) return true;
88
+ if (existsSync(join(projectPath, 'CHANGELOG.md')) || existsSync(join(projectPath, 'CHANGELOG'))) {
89
+ return true;
90
+ }
91
+
92
+ try {
93
+ const raw = loadRawProjectConfig(projectPath);
94
+ const governance = raw?.governance as Record<string, unknown> | undefined;
95
+
96
+ const freshnessConfig = governance?.doc_freshness as DocFreshnessConfig | undefined;
97
+ if (freshnessConfig?.checks && freshnessConfig.checks.length > 0) return true;
98
+
99
+ const contextFiles = governance?.context_files as Record<string, unknown> | undefined;
100
+ if (
101
+ contextFiles?.enabled &&
102
+ Array.isArray(contextFiles.required_dirs) &&
103
+ (contextFiles.required_dirs as string[]).length > 0
104
+ ) {
105
+ return true;
106
+ }
107
+ } catch {
108
+ // 配置不可读视为无配置
109
+ }
110
+
111
+ return false;
112
+ }
113
+
74
114
  export const docsFreshness: ConstraintCheck = {
75
115
  id: 'docs_freshness',
76
116
  async evaluate(env) {
77
117
  const projectPath = env.projectPath;
78
118
 
119
+ // ADR-0001 存在性探测:项目无任何 freshness 配置/目标 → skip(不计 pass/fail)
120
+ if (!hasFreshnessTargets(projectPath)) return 'skip';
121
+
79
122
  // Step 1: 文件表格式 — 检查列出的文件是否还存在
80
123
  if (!checkCapabilitiesFreshness(projectPath)) return false;
81
124
 
@@ -1,104 +1,71 @@
1
1
  /**
2
2
  * 约束检查器注册表(工单 21)
3
3
  *
4
- * checker.ts 编排层通过 getConstraintCheck(id) 查找实现,
5
- * 未注册的约束默认通过(保持历史 default 分支语义)。
4
+ * checker.ts 编排层通过 getConstraintCheck(id) 查找实现。
5
+ *
6
+ * 注册表闭环(ADR-0001):模块加载时校验全部 kind='check' 的内置约束
7
+ * 都有已注册 checker,且注册表中没有无对应 check 定义的孤儿实现;
8
+ * 任一不满足即抛错(加载期失败,不许静默 pass)。
6
9
  */
7
10
 
8
11
  import type { ConstraintCheck } from './types';
12
+ import { getAllConstraints } from '../definitions';
9
13
 
10
14
  import {
11
- noSelfApproval,
12
15
  noCompletionWithoutVerification,
13
16
  incrementalProgress,
14
- verifyExternalCapability,
15
- noImplementationWithoutRequirementReview,
16
17
  noImplementationWithoutRequirement,
17
- preferWorktree,
18
- noClaimWithoutEvidence,
19
- noDeleteWithoutContext,
20
- twoStageReviewRequired,
21
18
  } from './iron-flags';
22
- import {
23
- noFixWithoutRootCause,
24
- noCodeWithoutTest,
25
- simplestSolutionFirst,
26
- noCreationWithoutReuseCheck,
27
- noSkillWithoutTest,
28
- } from './guideline-flags';
29
- import {
30
- surgicalChangesOnly,
31
- noModelForDeterministic,
32
- noConflictBlending,
33
- readBeforeWrite,
34
- followConventions,
35
- firstPrinciplesFirst,
36
- fixTheProblemNotTheGate,
37
- diagnosisToFixGate,
38
- readmeRequired,
39
- docRequiredForPublicApi,
40
- } from './always-pass';
41
19
  import { noBypassCheckpoint } from './no-bypass-checkpoint';
42
20
  import { noTestSimplification } from './no-test-simplification';
43
- import { noFuzzyCompletionClaim } from './no-fuzzy-completion-claim';
44
- import { noPerformativeAgreement } from './no-performative-agreement';
45
- import { noExcusePatterns } from './no-excuse-patterns';
46
- import { noAnyType } from './no-any-type';
47
21
  import { capabilitySync } from './capability-sync';
48
- import { noSimplificationWithoutApproval } from './no-simplification-without-approval';
49
- import { testCoverageRequired } from './test-coverage';
50
22
  import { contextDocSync } from './context-doc-sync';
51
23
  import { docsFreshness } from './docs-freshness';
52
- import { yagniCheck } from './yagni';
24
+ import { noHardcodedCredentials } from './no-hardcoded-credentials';
53
25
 
54
26
  const CHECKS: ConstraintCheck[] = [
55
27
  // Iron Laws
56
- noBypassCheckpoint,
57
- noSelfApproval,
58
28
  noCompletionWithoutVerification,
59
- noTestSimplification,
60
29
  incrementalProgress,
61
- verifyExternalCapability,
62
- noImplementationWithoutRequirementReview,
63
30
  noImplementationWithoutRequirement,
64
- preferWorktree,
65
- noFuzzyCompletionClaim,
66
- noClaimWithoutEvidence,
67
- noDeleteWithoutContext,
68
- noPerformativeAgreement,
69
- twoStageReviewRequired,
31
+ noTestSimplification,
32
+ docsFreshness,
70
33
  // Guidelines
71
- noFixWithoutRootCause,
72
- noCodeWithoutTest,
73
- noAnyType,
74
- simplestSolutionFirst,
75
- noCreationWithoutReuseCheck,
34
+ noHardcodedCredentials,
35
+ noBypassCheckpoint,
76
36
  capabilitySync,
77
- noSimplificationWithoutApproval,
78
- noSkillWithoutTest,
79
- testCoverageRequired,
80
37
  contextDocSync,
81
- docsFreshness,
82
- noExcusePatterns,
83
- yagniCheck,
84
- // 行为类(promptInjection 驱动)恒通过
85
- surgicalChangesOnly,
86
- noModelForDeterministic,
87
- noConflictBlending,
88
- readBeforeWrite,
89
- followConventions,
90
- firstPrinciplesFirst,
91
- fixTheProblemNotTheGate,
92
- diagnosisToFixGate,
93
- // Tips
94
- readmeRequired,
95
- docRequiredForPublicApi,
96
38
  ];
97
39
 
98
40
  const registry = new Map<string, ConstraintCheck>(CHECKS.map(c => [c.id, c]));
99
41
 
42
+ // ========================================
43
+ // 注册表闭环校验(加载期)
44
+ // ========================================
45
+
46
+ const checkConstraints = getAllConstraints().filter(c => c.kind === 'check');
47
+
48
+ for (const c of checkConstraints) {
49
+ if (!registry.has(c.id)) {
50
+ throw new Error(
51
+ `[harness] 约束注册表闭环校验失败:kind='check' 的约束 "${c.id}" 未注册 checker。` +
52
+ `请在 checkers/ 中实现并注册,或将该约束降级为 kind='prompt'。`
53
+ );
54
+ }
55
+ }
56
+
57
+ for (const id of registry.keys()) {
58
+ if (!checkConstraints.some(c => c.id === id)) {
59
+ throw new Error(
60
+ `[harness] 约束注册表闭环校验失败:checker "${id}" 没有对应的 kind='check' 约束定义。` +
61
+ `请在 definitions/ 中补齐定义,或从注册表移除。`
62
+ );
63
+ }
64
+ }
65
+
100
66
  /**
101
- * 按约束 ID 查找检查实现;未注册返回 undefined(编排层默认通过)
67
+ * 按约束 ID 查找检查实现;未注册返回 undefined
68
+ * (编排层对 kind='check' 未注册的情况抛错,kind='prompt' 不查表)
102
69
  */
103
70
  export function getConstraintCheck(id: string): ConstraintCheck | undefined {
104
71
  return registry.get(id);
@@ -111,4 +78,4 @@ export function registeredCheckCount(): number {
111
78
  return registry.size;
112
79
  }
113
80
 
114
- export type { ConstraintCheck, CheckEnv } from './types';
81
+ export type { ConstraintCheck, CheckEnv, CheckOutcome } from './types';