@dommaker/harness 0.18.0 → 1.0.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 (366) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/harness.js +136 -552
  3. package/dist/cli/commands/check.d.ts.map +1 -1
  4. package/dist/cli/commands/check.js +2 -13
  5. package/dist/cli/commands/check.js.map +1 -1
  6. package/dist/cli/commands/definitions.d.ts +90 -0
  7. package/dist/cli/commands/definitions.d.ts.map +1 -0
  8. package/dist/cli/commands/definitions.js +519 -0
  9. package/dist/cli/commands/definitions.js.map +1 -0
  10. package/dist/cli/commands/knowledge.js.map +1 -1
  11. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +3 -25
  12. package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
  13. package/dist/cli/commands/sync-docs/capabilities-syncer.js +3 -93
  14. package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
  15. package/dist/cli/commands/sync-docs/index.d.ts +1 -1
  16. package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
  17. package/dist/cli/commands/sync-docs/index.js +4 -4
  18. package/dist/cli/commands/sync-docs/index.js.map +1 -1
  19. package/dist/cli/commands/update-user-model.d.ts +1 -0
  20. package/dist/cli/commands/update-user-model.d.ts.map +1 -1
  21. package/dist/cli/commands/update-user-model.js +12 -5
  22. package/dist/cli/commands/update-user-model.js.map +1 -1
  23. package/dist/context/index.d.ts +1 -5
  24. package/dist/context/index.d.ts.map +1 -1
  25. package/dist/context/index.js +1 -5
  26. package/dist/context/index.js.map +1 -1
  27. package/dist/context/knowledge-injector.d.ts +3 -3
  28. package/dist/context/knowledge-injector.d.ts.map +1 -1
  29. package/dist/context/knowledge-injector.js +1 -1
  30. package/dist/context/session-manager.d.ts +1 -12
  31. package/dist/context/session-manager.d.ts.map +1 -1
  32. package/dist/context/session-manager.js +0 -63
  33. package/dist/context/session-manager.js.map +1 -1
  34. package/dist/context/types.d.ts +1 -39
  35. package/dist/context/types.d.ts.map +1 -1
  36. package/dist/context/types.js +2 -15
  37. package/dist/context/types.js.map +1 -1
  38. package/dist/core/constraints/agent-prompt-renderer.d.ts +32 -0
  39. package/dist/core/constraints/agent-prompt-renderer.d.ts.map +1 -0
  40. package/dist/core/constraints/agent-prompt-renderer.js +58 -0
  41. package/dist/core/constraints/agent-prompt-renderer.js.map +1 -0
  42. package/dist/core/constraints/capabilities-parser.d.ts +17 -2
  43. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  44. package/dist/core/constraints/capabilities-parser.js +84 -2
  45. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  46. package/dist/core/constraints/check-cache.d.ts +48 -3
  47. package/dist/core/constraints/check-cache.d.ts.map +1 -1
  48. package/dist/core/constraints/check-cache.js +53 -5
  49. package/dist/core/constraints/check-cache.js.map +1 -1
  50. package/dist/core/constraints/checker.d.ts +3 -17
  51. package/dist/core/constraints/checker.d.ts.map +1 -1
  52. package/dist/core/constraints/checker.js +7 -47
  53. package/dist/core/constraints/checker.js.map +1 -1
  54. package/dist/core/constraints/definitions.d.ts +0 -7
  55. package/dist/core/constraints/definitions.d.ts.map +1 -1
  56. package/dist/core/constraints/definitions.js +1 -8
  57. package/dist/core/constraints/definitions.js.map +1 -1
  58. package/dist/core/constraints/doc-freshness/runner.d.ts +0 -2
  59. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  60. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  61. package/dist/core/constraints/index.d.ts +5 -1
  62. package/dist/core/constraints/index.d.ts.map +1 -1
  63. package/dist/core/constraints/index.js +8 -3
  64. package/dist/core/constraints/index.js.map +1 -1
  65. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  66. package/dist/core/constraints/interceptor.js +1 -5
  67. package/dist/core/constraints/interceptor.js.map +1 -1
  68. package/dist/core/effective-constraints.d.ts.map +1 -1
  69. package/dist/core/effective-constraints.js +0 -1
  70. package/dist/core/effective-constraints.js.map +1 -1
  71. package/dist/core/project-config-loader.d.ts.map +1 -1
  72. package/dist/core/project-config-loader.js +1 -8
  73. package/dist/core/project-config-loader.js.map +1 -1
  74. package/dist/dashboard/stats.d.ts.map +1 -1
  75. package/dist/dashboard/stats.js.map +1 -1
  76. package/dist/dashboard/types.d.ts +2 -2
  77. package/dist/dashboard/types.d.ts.map +1 -1
  78. package/dist/failure/constraint-handler.d.ts.map +1 -1
  79. package/dist/failure/constraint-handler.js +0 -2
  80. package/dist/failure/constraint-handler.js.map +1 -1
  81. package/dist/gates/acceptance.d.ts +9 -1
  82. package/dist/gates/acceptance.d.ts.map +1 -1
  83. package/dist/gates/acceptance.js +21 -0
  84. package/dist/gates/acceptance.js.map +1 -1
  85. package/dist/gates/checker-gate.d.ts +23 -0
  86. package/dist/gates/checker-gate.d.ts.map +1 -0
  87. package/dist/gates/checker-gate.js +53 -0
  88. package/dist/gates/checker-gate.js.map +1 -0
  89. package/dist/gates/command.d.ts +9 -2
  90. package/dist/gates/command.d.ts.map +1 -1
  91. package/dist/gates/command.js +10 -0
  92. package/dist/gates/command.js.map +1 -1
  93. package/dist/gates/contract.d.ts +8 -2
  94. package/dist/gates/contract.d.ts.map +1 -1
  95. package/dist/gates/contract.js +9 -0
  96. package/dist/gates/contract.js.map +1 -1
  97. package/dist/gates/decision.d.ts +17 -0
  98. package/dist/gates/decision.d.ts.map +1 -0
  99. package/dist/gates/decision.js +25 -0
  100. package/dist/gates/decision.js.map +1 -0
  101. package/dist/gates/definitions.d.ts +63 -0
  102. package/dist/gates/definitions.d.ts.map +1 -0
  103. package/dist/gates/definitions.js +160 -0
  104. package/dist/gates/definitions.js.map +1 -0
  105. package/dist/gates/effective-gates.d.ts +30 -0
  106. package/dist/gates/effective-gates.d.ts.map +1 -0
  107. package/dist/gates/effective-gates.js +85 -0
  108. package/dist/gates/effective-gates.js.map +1 -0
  109. package/dist/gates/index.d.ts +25 -1
  110. package/dist/gates/index.d.ts.map +1 -1
  111. package/dist/gates/index.js +37 -1
  112. package/dist/gates/index.js.map +1 -1
  113. package/dist/gates/performance.d.ts +8 -2
  114. package/dist/gates/performance.d.ts.map +1 -1
  115. package/dist/gates/performance.js +9 -0
  116. package/dist/gates/performance.js.map +1 -1
  117. package/dist/gates/registry.d.ts +32 -0
  118. package/dist/gates/registry.d.ts.map +1 -0
  119. package/dist/gates/registry.js +90 -0
  120. package/dist/gates/registry.js.map +1 -0
  121. package/dist/gates/review.d.ts +8 -2
  122. package/dist/gates/review.d.ts.map +1 -1
  123. package/dist/gates/review.js +9 -0
  124. package/dist/gates/review.js.map +1 -1
  125. package/dist/gates/runner.d.ts +30 -0
  126. package/dist/gates/runner.d.ts.map +1 -0
  127. package/dist/gates/runner.js +38 -0
  128. package/dist/gates/runner.js.map +1 -0
  129. package/dist/gates/security.d.ts +8 -2
  130. package/dist/gates/security.d.ts.map +1 -1
  131. package/dist/gates/security.js +9 -0
  132. package/dist/gates/security.js.map +1 -1
  133. package/dist/gates/types.d.ts +41 -1
  134. package/dist/gates/types.d.ts.map +1 -1
  135. package/dist/hooks/bootstrap.d.ts.map +1 -1
  136. package/dist/hooks/bootstrap.js +0 -2
  137. package/dist/hooks/bootstrap.js.map +1 -1
  138. package/dist/hooks/config.d.ts +26 -0
  139. package/dist/hooks/config.d.ts.map +1 -0
  140. package/dist/hooks/config.js +30 -0
  141. package/dist/hooks/config.js.map +1 -0
  142. package/dist/hooks/index.d.ts +2 -1
  143. package/dist/hooks/index.d.ts.map +1 -1
  144. package/dist/hooks/index.js +4 -1
  145. package/dist/hooks/index.js.map +1 -1
  146. package/dist/hooks/registry.d.ts +18 -1
  147. package/dist/hooks/registry.d.ts.map +1 -1
  148. package/dist/hooks/registry.js +45 -0
  149. package/dist/hooks/registry.js.map +1 -1
  150. package/dist/hooks/types.d.ts +20 -0
  151. package/dist/hooks/types.d.ts.map +1 -1
  152. package/dist/index.d.ts +8 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +13 -21
  155. package/dist/index.js.map +1 -1
  156. package/dist/knowledge/import.d.ts +2 -2
  157. package/dist/knowledge/import.d.ts.map +1 -1
  158. package/dist/knowledge/ingest.js.map +1 -1
  159. package/dist/knowledge/lifecycle-hooks.d.ts +2 -2
  160. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  161. package/dist/knowledge/types.d.ts +5 -7
  162. package/dist/knowledge/types.d.ts.map +1 -1
  163. package/dist/knowledge/types.js +1 -1
  164. package/dist/knowledge/types.js.map +1 -1
  165. package/dist/monitoring/constraint-doctor.d.ts +4 -22
  166. package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
  167. package/dist/monitoring/constraint-doctor.js +7 -94
  168. package/dist/monitoring/constraint-doctor.js.map +1 -1
  169. package/dist/presets/standard.d.ts +1 -1
  170. package/dist/presets/standard.d.ts.map +1 -1
  171. package/dist/presets/standard.js +1 -2
  172. package/dist/presets/standard.js.map +1 -1
  173. package/dist/spec/annotation-checker.d.ts.map +1 -1
  174. package/dist/spec/annotation-checker.js +6 -26
  175. package/dist/spec/annotation-checker.js.map +1 -1
  176. package/dist/tools/index.d.ts +0 -4
  177. package/dist/tools/index.d.ts.map +1 -1
  178. package/dist/tools/index.js +0 -4
  179. package/dist/tools/index.js.map +1 -1
  180. package/dist/types/constraint.d.ts +1 -12
  181. package/dist/types/constraint.d.ts.map +1 -1
  182. package/dist/types/constraint.js +0 -3
  183. package/dist/types/constraint.js.map +1 -1
  184. package/dist/types/project-config.d.ts +0 -2
  185. package/dist/types/project-config.d.ts.map +1 -1
  186. package/dist/utils/file-walk.d.ts +2 -2
  187. package/dist/utils/file-walk.js +2 -2
  188. package/package.json +9 -4
  189. package/src/CONTEXT.md +14 -0
  190. package/src/__tests__/checker-extra.test.ts +2 -59
  191. package/src/__tests__/constraint-doctor.test.ts +1 -140
  192. package/src/__tests__/constraint-handler.test.ts +0 -4
  193. package/src/__tests__/project-config-loader-extra.test.ts +0 -20
  194. package/src/__tests__/project-config-loader.test.ts +0 -1
  195. package/src/cli/commands/CONTEXT.md +15 -7
  196. package/src/cli/commands/__tests__/analyze-sessions.test.ts +178 -0
  197. package/src/cli/commands/__tests__/check-drift.test.ts +0 -3
  198. package/src/cli/commands/__tests__/check-skip-output.test.ts +0 -5
  199. package/src/cli/commands/__tests__/check.test.ts +7 -75
  200. package/src/cli/commands/__tests__/registry.test.ts +201 -0
  201. package/src/cli/commands/__tests__/release.test.ts +162 -0
  202. package/src/cli/commands/__tests__/update-user-model.test.ts +205 -0
  203. package/src/cli/commands/check.ts +2 -14
  204. package/src/cli/commands/definitions.ts +593 -0
  205. package/src/cli/commands/knowledge.ts +3 -3
  206. package/src/cli/commands/sync-docs/capabilities-syncer.ts +4 -110
  207. package/src/cli/commands/sync-docs/index.ts +8 -5
  208. package/src/cli/commands/update-user-model.ts +15 -5
  209. package/src/context/CONTEXT.md +2 -7
  210. package/src/context/__tests__/session-manager.test.ts +0 -91
  211. package/src/context/index.ts +1 -5
  212. package/src/context/knowledge-injector.ts +3 -3
  213. package/src/context/session-manager.ts +0 -74
  214. package/src/context/types.ts +2 -63
  215. package/src/core/CONTEXT.md +1 -1
  216. package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +129 -0
  217. package/src/core/constraints/__tests__/check-cache.test.ts +227 -0
  218. package/src/core/constraints/__tests__/interceptor.test.ts +0 -2
  219. package/src/core/constraints/agent-prompt-renderer.ts +71 -0
  220. package/src/core/constraints/capabilities-parser.ts +95 -2
  221. package/src/core/constraints/check-cache.ts +75 -3
  222. package/src/core/constraints/checker.ts +8 -53
  223. package/src/core/constraints/definitions.ts +0 -8
  224. package/src/core/constraints/doc-freshness/runner.ts +1 -2
  225. package/src/core/constraints/index.ts +11 -2
  226. package/src/core/constraints/interceptor.ts +1 -6
  227. package/src/core/effective-constraints.ts +0 -1
  228. package/src/core/project-config-loader.ts +1 -7
  229. package/src/dashboard/stats.ts +2 -2
  230. package/src/dashboard/types.ts +2 -2
  231. package/src/failure/constraint-handler.ts +0 -2
  232. package/src/gates/CONTEXT.md +16 -13
  233. package/src/gates/__tests__/checker-gate.test.ts +45 -0
  234. package/src/gates/__tests__/contract.test.ts +73 -0
  235. package/src/gates/__tests__/effective-gates.test.ts +82 -0
  236. package/src/gates/__tests__/gate-interface.test.ts +202 -0
  237. package/src/gates/__tests__/registry.test.ts +87 -0
  238. package/src/gates/__tests__/runner.test.ts +81 -0
  239. package/src/gates/acceptance.ts +24 -1
  240. package/src/gates/checker-gate.ts +54 -0
  241. package/src/gates/command.ts +13 -2
  242. package/src/gates/contract.ts +12 -2
  243. package/src/gates/decision.ts +27 -0
  244. package/src/gates/definitions.ts +207 -0
  245. package/src/gates/effective-gates.ts +106 -0
  246. package/src/gates/index.ts +38 -0
  247. package/src/gates/performance.ts +12 -2
  248. package/src/gates/registry.ts +107 -0
  249. package/src/gates/review.ts +12 -2
  250. package/src/gates/runner.ts +57 -0
  251. package/src/gates/security.ts +12 -2
  252. package/src/gates/types.ts +48 -1
  253. package/src/hooks/CONTEXT.md +12 -2
  254. package/src/hooks/__tests__/bootstrap.test.ts +0 -1
  255. package/src/hooks/__tests__/config.test.ts +19 -0
  256. package/src/hooks/__tests__/pipeline.test.ts +79 -0
  257. package/src/hooks/__tests__/registry.test.ts +67 -0
  258. package/src/hooks/bootstrap.ts +0 -2
  259. package/src/hooks/config.ts +29 -0
  260. package/src/hooks/index.ts +2 -1
  261. package/src/hooks/registry.ts +60 -1
  262. package/src/hooks/types.ts +21 -0
  263. package/src/index.ts +13 -22
  264. package/src/knowledge/import.ts +3 -3
  265. package/src/knowledge/ingest.ts +4 -4
  266. package/src/knowledge/lifecycle-hooks.ts +3 -3
  267. package/src/knowledge/types.ts +5 -8
  268. package/src/monitoring/constraint-doctor.ts +7 -106
  269. package/src/presets/__tests__/standard.test.ts +0 -2
  270. package/src/presets/standard.ts +1 -2
  271. package/src/spec/__tests__/annotation-checker.test.ts +9 -3
  272. package/src/spec/annotation-checker.ts +7 -29
  273. package/src/tools/CONTEXT.md +7 -14
  274. package/src/tools/index.ts +0 -4
  275. package/src/types/constraint.ts +1 -14
  276. package/src/types/project-config.ts +0 -3
  277. package/src/utils/file-walk.ts +2 -2
  278. package/dist/architecture/constraint-engine.d.ts +0 -67
  279. package/dist/architecture/constraint-engine.d.ts.map +0 -1
  280. package/dist/architecture/constraint-engine.js +0 -295
  281. package/dist/architecture/constraint-engine.js.map +0 -1
  282. package/dist/architecture/cross-project-checker.d.ts +0 -54
  283. package/dist/architecture/cross-project-checker.d.ts.map +0 -1
  284. package/dist/architecture/cross-project-checker.js +0 -467
  285. package/dist/architecture/cross-project-checker.js.map +0 -1
  286. package/dist/cli/commands/index.d.ts +0 -30
  287. package/dist/cli/commands/index.d.ts.map +0 -1
  288. package/dist/cli/commands/index.js +0 -91
  289. package/dist/cli/commands/index.js.map +0 -1
  290. package/dist/context/file-budget.d.ts +0 -40
  291. package/dist/context/file-budget.d.ts.map +0 -1
  292. package/dist/context/file-budget.js +0 -147
  293. package/dist/context/file-budget.js.map +0 -1
  294. package/dist/context/progressive-loader.d.ts +0 -109
  295. package/dist/context/progressive-loader.d.ts.map +0 -1
  296. package/dist/context/progressive-loader.js +0 -166
  297. package/dist/context/progressive-loader.js.map +0 -1
  298. package/dist/context/token-pipeline.d.ts +0 -55
  299. package/dist/context/token-pipeline.d.ts.map +0 -1
  300. package/dist/context/token-pipeline.js +0 -220
  301. package/dist/context/token-pipeline.js.map +0 -1
  302. package/dist/context/tool-output-budget.d.ts +0 -59
  303. package/dist/context/tool-output-budget.d.ts.map +0 -1
  304. package/dist/context/tool-output-budget.js +0 -177
  305. package/dist/context/tool-output-budget.js.map +0 -1
  306. package/dist/core/constraints/default-executors.d.ts +0 -10
  307. package/dist/core/constraints/default-executors.d.ts.map +0 -1
  308. package/dist/core/constraints/default-executors.js +0 -120
  309. package/dist/core/constraints/default-executors.js.map +0 -1
  310. package/dist/llm/adapter.d.ts +0 -36
  311. package/dist/llm/adapter.d.ts.map +0 -1
  312. package/dist/llm/adapter.js +0 -118
  313. package/dist/llm/adapter.js.map +0 -1
  314. package/dist/llm/index.d.ts +0 -3
  315. package/dist/llm/index.d.ts.map +0 -1
  316. package/dist/llm/index.js +0 -19
  317. package/dist/llm/index.js.map +0 -1
  318. package/dist/llm/types.d.ts +0 -62
  319. package/dist/llm/types.d.ts.map +0 -1
  320. package/dist/llm/types.js +0 -8
  321. package/dist/llm/types.js.map +0 -1
  322. package/dist/tools/core/index.d.ts +0 -13
  323. package/dist/tools/core/index.d.ts.map +0 -1
  324. package/dist/tools/core/index.js +0 -198
  325. package/dist/tools/core/index.js.map +0 -1
  326. package/dist/tools/loader.d.ts +0 -21
  327. package/dist/tools/loader.d.ts.map +0 -1
  328. package/dist/tools/loader.js +0 -199
  329. package/dist/tools/loader.js.map +0 -1
  330. package/dist/tools/registry.d.ts +0 -59
  331. package/dist/tools/registry.d.ts.map +0 -1
  332. package/dist/tools/registry.js +0 -115
  333. package/dist/tools/registry.js.map +0 -1
  334. package/dist/tools/types.d.ts +0 -38
  335. package/dist/tools/types.d.ts.map +0 -1
  336. package/dist/tools/types.js +0 -6
  337. package/dist/tools/types.js.map +0 -1
  338. package/src/__tests__/architecture-engine.test.ts +0 -619
  339. package/src/__tests__/context/progressive-loader.test.ts +0 -131
  340. package/src/__tests__/cross-project-checker.test.ts +0 -318
  341. package/src/architecture/CONTEXT.md +0 -22
  342. package/src/architecture/constraint-engine.ts +0 -351
  343. package/src/architecture/cross-project-checker.ts +0 -545
  344. package/src/cli/commands/index.ts +0 -30
  345. package/src/context/__tests__/file-budget.test.ts +0 -142
  346. package/src/context/__tests__/progressive-loader.test.ts +0 -264
  347. package/src/context/__tests__/token-pipeline.test.ts +0 -178
  348. package/src/context/__tests__/tool-output-budget.test.ts +0 -95
  349. package/src/context/file-budget.ts +0 -142
  350. package/src/context/progressive-loader.ts +0 -260
  351. package/src/context/token-pipeline.ts +0 -265
  352. package/src/context/tool-output-budget.ts +0 -178
  353. package/src/core/constraints/__tests__/default-executors.test.ts +0 -57
  354. package/src/core/constraints/default-executors.ts +0 -92
  355. package/src/llm/CONTEXT.md +0 -21
  356. package/src/llm/__tests__/adapter.test.ts +0 -172
  357. package/src/llm/adapter.ts +0 -149
  358. package/src/llm/index.ts +0 -2
  359. package/src/llm/types.ts +0 -81
  360. package/src/tools/__tests__/loader.test.ts +0 -252
  361. package/src/tools/__tests__/registry.test.ts +0 -157
  362. package/src/tools/core/__tests__/core.test.ts +0 -122
  363. package/src/tools/core/index.ts +0 -201
  364. package/src/tools/loader.ts +0 -216
  365. package/src/tools/registry.ts +0 -128
  366. package/src/tools/types.ts +0 -46
@@ -4,10 +4,19 @@
4
4
  * 缓存 git diff 和 src/ 递归扫描结果,减少重复 I/O。
5
5
  * 同一请求内多次 checkConstraints 调用共享缓存。
6
6
  *
7
+ * 计数采样(H6/G5,收编 studio runtime/cache.ts):
8
+ * get/getSync 可传声明式采样配置,对每个 (namespace, key) 独立计数,
9
+ * 第 1 次调用必执行,此后每 N 次执行 1 次完整检查,其余 N-1 次复用缓存;
10
+ * 非采样轮缓存未命中(无缓存或已过期)时返回 defaultValueOnMiss,不执行 fn。
11
+ * invalidate 同时重置对应计数,保证失效后下一次调用恢复为完整检查。
12
+ *
7
13
  * 用法:
8
14
  * ```typescript
9
15
  * const cache = new CheckCache({ ttlMs: 5000 });
16
+ * // 普通 TTL 缓存
10
17
  * const diff = await cache.get('git_diff', projectPath, () => runCommand('git diff --cached', projectPath));
18
+ * // 计数采样:每 3 次调用执行 1 次完整检查
19
+ * const ok = await cache.get('goal_check', key, check, { sampleRate: 3, defaultValueOnMiss: true });
11
20
  * ```
12
21
  */
13
22
 
@@ -16,13 +25,41 @@ interface CacheEntry {
16
25
  expiresAt: number;
17
26
  }
18
27
 
28
+ /**
29
+ * CheckCache 构造配置
30
+ */
19
31
  export interface CheckCacheConfig {
20
32
  /** 缓存 TTL(毫秒),默认 5000 */
21
33
  ttlMs: number;
22
34
  }
23
35
 
36
+ /**
37
+ * 计数采样配置(声明式)
38
+ *
39
+ * 传此配置后,对应 (namespace, key) 启用计数采样:
40
+ * - 第 1 次调用必执行 fn(采样轮),结果以正常 TTL 写入缓存;
41
+ * - 其后每 N 次调用执行 1 次(计数独立于普通缓存读写);
42
+ * - 非采样轮:缓存命中(未过期)返回缓存值;未命中或已过期
43
+ * 返回 {@link defaultValueOnMiss},不执行 fn、不刷新 TTL。
44
+ */
45
+ export interface CheckSamplingConfig<T> {
46
+ /** 采样率:每 N 次调用执行 1 次完整检查;N <= 1 视为不采样(回退为普通 TTL 缓存语义) */
47
+ sampleRate: number;
48
+ /** 非采样轮且缓存未命中(无缓存或已过期)时的默认返回值 */
49
+ defaultValueOnMiss: T;
50
+ }
51
+
52
+ /**
53
+ * 约束检查缓存:TTL 缓存 + 可选计数采样
54
+ *
55
+ * 普通 get/getSync 为 TTL 缓存;传入 sampling 配置后对应 (namespace, key)
56
+ * 启用计数采样(每 N 次执行 1 次完整检查,其余轮次复用缓存或返回
57
+ * defaultValueOnMiss)。语义详见 {@link CheckSamplingConfig}。
58
+ */
24
59
  export class CheckCache {
25
60
  private store = new Map<string, CacheEntry>();
61
+ /** 采样计数器(仅启用采样的 key 有计数) */
62
+ private sampleCounters = new Map<string, number>();
26
63
  private ttlMs: number;
27
64
 
28
65
  constructor(config?: CheckCacheConfig) {
@@ -35,19 +72,28 @@ export class CheckCache {
35
72
  * @param namespace 命名空间(如 'git_diff', 'src_scan')
36
73
  * @param key 缓存键(如 projectPath)
37
74
  * @param fn miss 时的计算函数
75
+ * @param sampling 计数采样配置(可选;不传时每次 miss 都执行 fn)
38
76
  */
39
77
  async get<T>(
40
78
  namespace: string,
41
79
  key: string,
42
80
  fn: () => Promise<T>,
81
+ sampling?: CheckSamplingConfig<T>,
43
82
  ): Promise<T> {
44
83
  const cacheKey = `${namespace}:${key}`;
45
84
  const entry = this.store.get(cacheKey);
85
+ const rate = sampling ? Math.max(1, Math.floor(sampling.sampleRate)) : 1;
86
+ // 采样轮优先于缓存命中:每 N 次调用必执行一次完整检查(第 1 次必执行)
87
+ const sampleTurn = rate > 1 && this.isSampleTurn(cacheKey, rate);
46
88
 
47
- if (entry && Date.now() < entry.expiresAt) {
89
+ if (!sampleTurn && entry && Date.now() < entry.expiresAt) {
48
90
  return entry.value as T;
49
91
  }
50
92
 
93
+ if (sampling && rate > 1 && !sampleTurn) {
94
+ return sampling.defaultValueOnMiss;
95
+ }
96
+
51
97
  const value = await fn();
52
98
  this.store.set(cacheKey, {
53
99
  value,
@@ -58,19 +104,28 @@ export class CheckCache {
58
104
 
59
105
  /**
60
106
  * 同步版本(用于 readdirSync 等同步操作)
107
+ *
108
+ * @param sampling 计数采样配置(语义同 {@link get})
61
109
  */
62
110
  getSync<T>(
63
111
  namespace: string,
64
112
  key: string,
65
113
  fn: () => T,
114
+ sampling?: CheckSamplingConfig<T>,
66
115
  ): T {
67
116
  const cacheKey = `${namespace}:${key}`;
68
117
  const entry = this.store.get(cacheKey);
118
+ const rate = sampling ? Math.max(1, Math.floor(sampling.sampleRate)) : 1;
119
+ const sampleTurn = rate > 1 && this.isSampleTurn(cacheKey, rate);
69
120
 
70
- if (entry && Date.now() < entry.expiresAt) {
121
+ if (!sampleTurn && entry && Date.now() < entry.expiresAt) {
71
122
  return entry.value as T;
72
123
  }
73
124
 
125
+ if (sampling && rate > 1 && !sampleTurn) {
126
+ return sampling.defaultValueOnMiss;
127
+ }
128
+
74
129
  const value = fn();
75
130
  this.store.set(cacheKey, {
76
131
  value,
@@ -80,7 +135,8 @@ export class CheckCache {
80
135
  }
81
136
 
82
137
  /**
83
- * 使指定命名空间缓存失效
138
+ * 使指定命名空间缓存失效(同时重置该命名空间的采样计数器,
139
+ * 保证失效后的下一次调用恢复为采样轮/完整检查)
84
140
  */
85
141
  invalidate(namespace?: string): void {
86
142
  if (namespace) {
@@ -89,8 +145,24 @@ export class CheckCache {
89
145
  this.store.delete(key);
90
146
  }
91
147
  }
148
+ for (const key of this.sampleCounters.keys()) {
149
+ if (key.startsWith(`${namespace}:`)) {
150
+ this.sampleCounters.delete(key);
151
+ }
152
+ }
92
153
  } else {
93
154
  this.store.clear();
155
+ this.sampleCounters.clear();
94
156
  }
95
157
  }
158
+
159
+ /**
160
+ * 计数采样判定:计数 +1 后返回是否采样轮(第 1 次必采样,每 N 次 1 次)。
161
+ * 仅在采样启用(rate > 1)时调用。
162
+ */
163
+ private isSampleTurn(cacheKey: string, rate: number): boolean {
164
+ const count = (this.sampleCounters.get(cacheKey) ?? 0) + 1;
165
+ this.sampleCounters.set(cacheKey, count);
166
+ return count % rate === 1;
167
+ }
96
168
  }
@@ -17,7 +17,7 @@ import {
17
17
  ConstraintViolationError,
18
18
  } from '../../types/constraint';
19
19
  import type { ExecutionTrace } from '../../types/trace';
20
- import { IRON_LAWS, GUIDELINES, TIPS, PROMPTS } from './definitions';
20
+ import { IRON_LAWS, GUIDELINES, 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';
@@ -74,9 +74,6 @@ const NOOP_TRACE_RECORDER: TraceRecorder = { record: () => undefined };
74
74
  export class ConstraintChecker {
75
75
  private static instance: ConstraintChecker;
76
76
 
77
- /** 自定义约束配置(项目级) */
78
- private customConfig: MergedConstraintsConfig | null = null;
79
-
80
77
  /** 检查结果缓存(S7:src 扫描等重复 I/O,TTL=1s 防跨测试污染) */
81
78
  private cache: CheckCache = new CheckCache({ ttlMs: 1000 });
82
79
 
@@ -139,31 +136,20 @@ export class ConstraintChecker {
139
136
  this.traceRecorder = recorder;
140
137
  }
141
138
 
142
- /**
143
- * 设置项目级约束配置
144
- *
145
- * @deprecated 修改单例状态,多项目并发时存在污染风险。
146
- * 请改用 checkConstraints() / beforeExecution() 的 customConfig 参数实现 per-request 隔离。
147
- */
148
- setCustomConfig(config: MergedConstraintsConfig): void {
149
- this.customConfig = config;
150
- }
151
-
152
139
  /**
153
140
  * 获取当前的约束集合(内置 + 自定义)
154
141
  *
155
- * @param customConfig 可选,per-request 自定义配置(优先级高于 setCustomConfig 的单例状态)
142
+ * @param customConfig 可选,per-request 自定义配置;不传则返回内置约束集
156
143
  */
157
144
  getConstraints(customConfig?: MergedConstraintsConfig | null): {
158
145
  ironLaws: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
159
146
  guidelines: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
160
- tips: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
161
147
  prompts: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
162
148
  } {
163
- const config = customConfig ?? this.customConfig;
149
+ const config = customConfig;
164
150
  const source = config
165
- ? { ironLaws: config.ironLaws, guidelines: config.guidelines, tips: config.tips, prompts: config.prompts ?? PROMPTS }
166
- : { ironLaws: IRON_LAWS, guidelines: GUIDELINES, tips: TIPS, prompts: PROMPTS };
151
+ ? { ironLaws: config.ironLaws, guidelines: config.guidelines, prompts: config.prompts ?? PROMPTS }
152
+ : { ironLaws: IRON_LAWS, guidelines: GUIDELINES, prompts: PROMPTS };
167
153
 
168
154
  // Wire unified check() method on every constraint
169
155
  const wire = (constraints: Record<string, Constraint>) => {
@@ -175,7 +161,6 @@ export class ConstraintChecker {
175
161
  return {
176
162
  ironLaws: wire(source.ironLaws),
177
163
  guidelines: wire(source.guidelines),
178
- tips: wire(source.tips),
179
164
  prompts: wire(source.prompts),
180
165
  };
181
166
  }
@@ -260,7 +245,6 @@ export class ConstraintChecker {
260
245
  case 'guideline':
261
246
  return 'warning';
262
247
  case 'prompt':
263
- case 'tip':
264
248
  return 'info';
265
249
  default:
266
250
  return 'warning';
@@ -350,7 +334,6 @@ export class ConstraintChecker {
350
334
  ): {
351
335
  ironLaws: Constraint[];
352
336
  guidelines: Constraint[];
353
- tips: Constraint[];
354
337
  prompts: Constraint[];
355
338
  } {
356
339
  const operations = [context.operation, ...(context.extraTriggers ?? [])];
@@ -368,17 +351,15 @@ export class ConstraintChecker {
368
351
  return {
369
352
  ironLaws: filterByTrigger(constraints.ironLaws),
370
353
  guidelines: filterByTrigger(constraints.guidelines),
371
- tips: filterByTrigger(constraints.tips),
372
354
  prompts: filterByTrigger(constraints.prompts),
373
355
  };
374
356
  }
375
357
 
376
358
  /**
377
- * 执行三层约束检查
359
+ * 执行约束检查
378
360
  *
379
361
  * - Iron Laws:检查失败立即抛出异常
380
362
  * - Guidelines:检查失败记录警告
381
- * - Tips:检查失败记录提示
382
363
  *
383
364
  * @param context 约束上下文
384
365
  * @param customConfig 可选,per-request 自定义配置(避免多请求间的单例状态污染)
@@ -395,10 +376,8 @@ export class ConstraintChecker {
395
376
  const result: ConstraintCheckResult = {
396
377
  ironLaws: [],
397
378
  guidelines: [],
398
- tips: [],
399
379
  passed: true,
400
380
  warningCount: 0,
401
- tipCount: 0,
402
381
  };
403
382
 
404
383
  const traceCollector = this.traceRecorder;
@@ -431,19 +410,6 @@ export class ConstraintChecker {
431
410
  }
432
411
  }
433
412
 
434
- // 3. Tips: 仅记录
435
- for (const constraint of Object.values(constraints.tips)) {
436
- if (!this.matchesTrigger(constraint, context)) continue;
437
-
438
- const checkResult = await this.check(constraint, context);
439
- result.tips.push(checkResult);
440
- this.recordTrace(traceCollector, constraint, checkResult, context);
441
-
442
- if (!checkResult.satisfied) {
443
- result.tipCount++;
444
- }
445
- }
446
-
447
413
  return result;
448
414
  } finally {
449
415
  this.runCache = null;
@@ -451,7 +417,7 @@ export class ConstraintChecker {
451
417
  }
452
418
 
453
419
  /**
454
- * S11: 安全全量检查 — 全部三层约束检查,不抛异常
420
+ * S11: 安全全量检查 — 全部约束检查,不抛异常
455
421
  *
456
422
  * 区别 checkConstraints(): Iron Law 违规收集在结果中而不是抛异常。
457
423
  * 调用方通过 result.passed + result.warningCount 判断状态。
@@ -468,10 +434,8 @@ export class ConstraintChecker {
468
434
  const result: ConstraintCheckResult = {
469
435
  ironLaws: [],
470
436
  guidelines: [],
471
- tips: [],
472
437
  passed: true,
473
438
  warningCount: 0,
474
- tipCount: 0,
475
439
  };
476
440
 
477
441
  const traceCollector = this.traceRecorder;
@@ -493,14 +457,6 @@ export class ConstraintChecker {
493
457
  if (!checkResult.satisfied) result.warningCount++;
494
458
  }
495
459
 
496
- for (const constraint of Object.values(constraints.tips)) {
497
- if (!this.matchesTrigger(constraint, context)) continue;
498
- const checkResult = await this.check(constraint, context);
499
- result.tips.push(checkResult);
500
- this.recordTrace(traceCollector, constraint, checkResult, context);
501
- if (!checkResult.satisfied) result.tipCount++;
502
- }
503
-
504
460
  return result;
505
461
  } finally {
506
462
  this.runCache = null;
@@ -549,13 +505,12 @@ export async function checkConstraint(
549
505
  const constraint =
550
506
  constraints.ironLaws[constraintId] ||
551
507
  constraints.guidelines[constraintId] ||
552
- constraints.tips[constraintId] ||
553
508
  constraints.prompts[constraintId];
554
509
 
555
510
  if (!constraint) {
556
511
  return {
557
512
  id: constraintId,
558
- level: 'tip',
513
+ level: 'guideline',
559
514
  satisfied: false,
560
515
  message: `未知的约束: ${constraintId}`,
561
516
  checkedAt: new Date(),
@@ -4,8 +4,6 @@
4
4
  * kind 二元模型(ADR-0001):
5
5
  * - IRON_LAWS / GUIDELINES:check 层,必须带真实 checker(注册表闭环)
6
6
  * - PROMPTS:prompt 层,纯文本注入,不占检查位
7
- * - TIPS:已退役,导出空表仅为在途消费者(capabilities-syncer)编译兼容,
8
- * 后续阶段随其改造一并删除
9
7
  *
10
8
  * 工单 20:字面量定义按层拆至 ./definitions/{iron-laws,guidelines,prompts}.ts;
11
9
  * 本文件保持原路径做薄聚合(studio rule-scanner 按此路径解析,P0 #8)。
@@ -16,12 +14,6 @@ import { IRON_LAWS } from './definitions/iron-laws';
16
14
  import { GUIDELINES } from './definitions/guidelines';
17
15
  import { PROMPTS } from './definitions/prompts';
18
16
 
19
- /**
20
- * @deprecated TIPS 已退役(ADR-0001),恒为空表。
21
- * 仅为在途 capabilities-syncer 编译兼容保留,后续阶段删除。
22
- */
23
- export const TIPS: Record<string, Constraint> = {};
24
-
25
17
  export { IRON_LAWS, GUIDELINES, PROMPTS };
26
18
 
27
19
  // ========================================
@@ -44,7 +44,6 @@ interface RunContext {
44
44
  constraintCounts?: {
45
45
  ironLaws?: number;
46
46
  guidelines?: number;
47
- tips?: number;
48
47
  };
49
48
  }
50
49
 
@@ -58,7 +57,7 @@ export class FreshnessRunner {
58
57
  runAll(
59
58
  config: DocFreshnessConfig,
60
59
  projectPath: string,
61
- ctx?: { requiredDirs?: string[]; constraintCounts?: { ironLaws?: number; guidelines?: number; tips?: number } }
60
+ ctx?: { requiredDirs?: string[]; constraintCounts?: { ironLaws?: number; guidelines?: number } }
62
61
  ): FreshnessCheckResult[] {
63
62
  if (config.enabled === false) return [];
64
63
  if (!config.checks || config.checks.length === 0) return [];
@@ -2,12 +2,11 @@
2
2
  * 约束模块入口
3
3
  */
4
4
 
5
- // 约束定义(kind 二元:check 层 + prompt 层;TIPS 已退役,空表仅为在途兼容保留)
5
+ // 约束定义(kind 二元:check 层 + prompt 层)
6
6
  export {
7
7
  IRON_LAWS,
8
8
  GUIDELINES,
9
9
  PROMPTS,
10
- TIPS,
11
10
  getAllConstraints,
12
11
  findConstraintsByTrigger,
13
12
  getConstraint,
@@ -29,6 +28,16 @@ export {
29
28
  renderConstraintsSection,
30
29
  } from './injection-renderer';
31
30
 
31
+ // 约束检查缓存(H6/G5:TTL 缓存 + 计数采样,公开导出)
32
+ export { CheckCache } from './check-cache';
33
+ export type { CheckCacheConfig, CheckSamplingConfig } from './check-cache';
34
+
35
+ // Agent prompt 约束段渲染(H6/G6:trigger 参数化分组渲染,role 路由留 studio)
36
+ export {
37
+ renderConstraintsByTrigger,
38
+ } from './agent-prompt-renderer';
39
+ export type { RenderConstraintsByTriggerOptions } from './agent-prompt-renderer';
40
+
32
41
  // 类型导出
33
42
  export type {
34
43
  ConstraintId,
@@ -115,14 +115,13 @@ export class ConstraintInterceptor {
115
115
  const allConstraints = [
116
116
  ...Object.values(constraints.ironLaws),
117
117
  ...Object.values(constraints.guidelines),
118
- ...Object.values(constraints.tips),
119
118
  ].filter(c => c.kind !== 'prompt');
120
119
 
121
120
  const applicableConstraints = allConstraints.filter(
122
121
  (c) => normalizeTriggers<ConstraintTrigger>(c.trigger).includes(trigger)
123
122
  );
124
123
 
125
- const order: Record<ConstraintLevel, number> = { iron_law: 0, guideline: 1, prompt: 2, tip: 3 };
124
+ const order: Record<ConstraintLevel, number> = { iron_law: 0, guideline: 1, prompt: 2 };
126
125
  const ordered = applicableConstraints.sort((a, b) => order[a.level] - order[b.level]);
127
126
 
128
127
  for (const constraint of ordered) {
@@ -200,10 +199,6 @@ export class ConstraintInterceptor {
200
199
 
201
200
  export const constraintInterceptor = ConstraintInterceptor.getInstance();
202
201
 
203
- // 注册内置 executors
204
- import { registerDefaultExecutors } from './default-executors';
205
- registerDefaultExecutors();
206
-
207
202
  export async function interceptOperation(
208
203
  trigger: ConstraintTrigger,
209
204
  context: ConstraintContext
@@ -29,7 +29,6 @@ export function getEffectiveConstraints(projectRoot: string = process.cwd()): Co
29
29
  return [
30
30
  ...Object.values(merged.ironLaws),
31
31
  ...Object.values(merged.guidelines),
32
- ...Object.values(merged.tips),
33
32
  ...Object.values(merged.prompts ?? {}),
34
33
  ];
35
34
  }
@@ -172,7 +172,6 @@ export class ProjectConfigLoader {
172
172
  const result: MergedConstraintsConfig = {
173
173
  ironLaws: presetMerged.ironLaws,
174
174
  guidelines: presetMerged.guidelines,
175
- tips: {},
176
175
  prompts: presetMerged.prompts ?? {},
177
176
  disabled: [...presetMerged.disabled],
178
177
  custom: [],
@@ -196,7 +195,6 @@ export class ProjectConfigLoader {
196
195
  // 从对应层级中移除
197
196
  delete result.ironLaws[constraintId];
198
197
  delete result.guidelines[constraintId];
199
- delete result.tips[constraintId];
200
198
  delete result.prompts![constraintId];
201
199
  }
202
200
  }
@@ -225,7 +223,6 @@ export class ProjectConfigLoader {
225
223
  ];
226
224
  if (result.ironLaws[id]) result.ironLaws[id] = constraint;
227
225
  if (result.guidelines[id]) result.guidelines[id] = constraint;
228
- if (result.tips[id]) result.tips[id] = constraint;
229
226
  if (result.prompts![id]) result.prompts![id] = constraint;
230
227
  continue;
231
228
  }
@@ -255,9 +252,6 @@ export class ProjectConfigLoader {
255
252
  case 'prompt':
256
253
  result.prompts![id] = constraint;
257
254
  break;
258
- case 'tip':
259
- result.tips[id] = constraint;
260
- break;
261
255
  }
262
256
  }
263
257
 
@@ -285,7 +279,7 @@ export class ProjectConfigLoader {
285
279
  id: string,
286
280
  merged: MergedConstraintsConfig
287
281
  ): Constraint | undefined {
288
- return merged.ironLaws[id] || merged.guidelines[id] || merged.tips[id] || merged.prompts?.[id];
282
+ return merged.ironLaws[id] || merged.guidelines[id] || merged.prompts?.[id];
289
283
  }
290
284
 
291
285
  /**
@@ -4,7 +4,7 @@
4
4
  * 纯函数,从原始数据计算统计指标
5
5
  */
6
6
 
7
- import type { KnowledgeEntry, KnowledgeType } from '../knowledge/types';
7
+ import type { KnowledgeEntry, KnowledgeSubsystem } from '../knowledge/types';
8
8
  import type { KnowledgeOverview, KnowledgeFlow } from './types';
9
9
 
10
10
  /**
@@ -36,7 +36,7 @@ export function computeKnowledgeOverview(entries: KnowledgeEntry[]): KnowledgeOv
36
36
  * 按类型分组统计
37
37
  */
38
38
  function computeByType(entries: KnowledgeEntry[]): KnowledgeOverview['byType'] {
39
- const typeMap = new Map<KnowledgeType, { total: number; proven: number; verified: number; draft: number }>();
39
+ const typeMap = new Map<KnowledgeSubsystem, { total: number; proven: number; verified: number; draft: number }>();
40
40
 
41
41
  for (const entry of entries) {
42
42
  const existing = typeMap.get(entry.type) || { total: 0, proven: 0, verified: 0, draft: 0 };
@@ -4,7 +4,7 @@
4
4
  * harness 提供数据,studio 负责渲染
5
5
  */
6
6
 
7
- import type { KnowledgeType } from '../knowledge/types';
7
+ import type { KnowledgeSubsystem } from '../knowledge/types';
8
8
 
9
9
  // ── 约束分层(原 src/constraints/types,约束分层子系统删除后归位 dashboard)──
10
10
 
@@ -28,7 +28,7 @@ export interface ConstraintStats {
28
28
 
29
29
  export interface KnowledgeOverview {
30
30
  byType: Array<{
31
- type: KnowledgeType;
31
+ type: KnowledgeSubsystem;
32
32
  total: number;
33
33
  proven: number;
34
34
  verified: number;
@@ -108,10 +108,8 @@ export class ConstraintViolationHandler {
108
108
  const empty: ConstraintCheckResult = {
109
109
  ironLaws: [],
110
110
  guidelines: [],
111
- tips: [],
112
111
  passed: false,
113
112
  warningCount: 0,
114
- tipCount: 0,
115
113
  };
116
114
  return {
117
115
  strategy: 'COLLECT',
@@ -1,27 +1,30 @@
1
1
  # gates/
2
2
 
3
3
  ## 职责
4
- 质量门禁系统:6 种门禁检查(验收/命令/契约/性能/审查/安全)。
4
+ 质量门禁系统:6 种门禁检查(验收/命令/契约/性能/审查/安全)。统一 Gate 三态协议 + 注册表闭环 + 声明式生效集(G1)。
5
5
 
6
6
  ## 核心导出
7
- - `ReviewGate` 代码审查门禁
8
- - `SecurityGate` 安全检查门禁
9
- - `PerformanceGate` 性能门禁
10
- - `ContractGate` 契约(OpenAPI)门禁
11
- - `SpecAcceptanceGate` Spec 验收门禁
12
- - `CommandGate` 命令黑名单门禁
13
- - `types.ts` GateResult / GateContext 等公共类型
14
- - 便捷工厂函数:createReviewGate / createSecurityGate / createPerformanceGate / createContractGate / createSpecAcceptanceGate
7
+ - 统一协议:`Gate{id, order, evaluate(ctx)}``GateDecision{status: deny|abstain|ask, result: GateResult}`(`types.ts`;`decisionFromResult` 报告→决策映射,浅冻结)
8
+ - 注册表:`gateRegistry`(`registry.ts`)——定义即注册 + 构建期双向闭环(定义无实现/实现无定义/重复 id → 加载期抛错);`getGate`(未注册抛错)/`listRegisteredGates`/`registeredGateCount`
9
+ - 定义表:`GATE_DEFINITIONS`(`definitions.ts`)——id/description/默认 order/CLI 元数据;bin/harness.js 注册表驱动生成 6 门禁命令(纯数据模块,禁 import 实现,保 --help 懒加载;CLI 实现引用为 module+export,per-command 懒加载,H5)
10
+ - 执行器:`runGates`(`runner.ts`)——按 order 升序;deny 单调(下游 abstain 不得改回 allow)+ ask 枚举预留 fail-closed = deny;决策浅冻结
11
+ - 生效集:`getEffectiveGates(projectRoot)`(`effective-gates.ts`)——对齐 getEffectiveConstraints 裁剪模式:config.yml `gates.order` 重排 + `gates.<id>.enabled:false` 移除;引用未注册 id 抛错
12
+ - checker-as-guard 接线点:`createCheckerGate(check)`(`checker-gate.ts`)——ConstraintCheck → Gate(studio #129 随动);false→deny,true/'skip'→abstain
13
+ - 门禁类:`ReviewGate` / `SecurityGate` / `PerformanceGate` / `ContractGate` / `SpecAcceptanceGate` / `CommandGate`(各自执行细节私有,保留 `check()`/`scan()` 报告方法)
14
+ - `types.ts` GateResult(报告结构,保留)/ GateContext / GateDecision 等公共类型
15
+ - 便捷工厂函数:createReviewGate / createSecurityGate / createPerformanceGate / createContractGate / createSpecAcceptanceGate / createCommandGate
15
16
 
16
17
  ## 依赖关系
17
- - 依赖 `src/core/` 类型(GateResult)
18
+ - 依赖 `src/core/`(effective-gates 读 project-config-loader;checker-gate 适配 checkers 注册表类型)
18
19
  - 依赖 `src/types/` 公共类型
19
20
 
20
21
  ## 约定
21
- - 所有门禁实现 GateResult 接口
22
- - 新门禁必须配 CLI 命令 + 测试文件
23
- - 工厂函数在 index.ts 中统一导出
22
+ - 新门禁必须:① 在 `definitions.ts` 补 GateDefinition(含 CLI 元数据)② 在 `registry.ts` IMPLEMENTATIONS 注册实现(缺一 → 加载期抛错)③ 实现统一 Gate 接口(evaluate 产三态决策,报告走 GateResult)④ 配 CLI 命令(命令实现文件 + CLI 元数据的 module+export 引用,bin 由定义表驱动生成,不再手写块)+ 测试文件
23
+ - deny 单调是接口契约:决策浅冻结,下游不得改写上游决策
24
+ - ask 枚举预留:暂无实现,runGates fail-closed 按 deny 计
24
25
 
25
26
  ## 注意事项
26
27
  - 门禁系统不包含业务逻辑,只提供检查能力
28
+ - 统一的是决策协议(id/order/三态),执行细节(gh pr view/正则黑名单/OpenAPI diff/benchmark)私有——不要把执行细节塞进 Gate 接口
29
+ - config.yml gates 段为新增面:引用未注册 id 直接抛错(无历史残留配置需兼容)
27
30
  - CommandGate 为命令黑名单检查(SEC-006)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * checker-as-guard 接线点测试(G1 / studio #129 随动)
3
+ */
4
+
5
+ import { createCheckerGate } from '../checker-gate';
6
+ import { contextFlag, contextEvidenceFlag } from '../../core/constraints/checkers/types';
7
+
8
+ describe('createCheckerGate', () => {
9
+ const ctx = { projectId: 'p', projectPath: '/tmp/project' };
10
+
11
+ it('checker 通过(true)→ abstain', async () => {
12
+ const gate = createCheckerGate(contextFlag('flag-pass', () => true), 7);
13
+ expect(gate.id).toBe('flag-pass');
14
+ expect(gate.order).toBe(7);
15
+
16
+ const decision = await gate.evaluate(ctx);
17
+ expect(decision.status).toBe('abstain');
18
+ expect(decision.result.gate).toBe('flag-pass');
19
+ expect(decision.result.passed).toBe(true);
20
+ expect(decision.result.message).toContain('通过');
21
+ });
22
+
23
+ it('checker 违规(false)→ deny', async () => {
24
+ const gate = createCheckerGate(contextFlag('flag-fail', () => false));
25
+ const decision = await gate.evaluate(ctx);
26
+ expect(decision.status).toBe('deny');
27
+ expect(decision.result.passed).toBe(false);
28
+ expect(decision.result.message).toContain('判定违规');
29
+ });
30
+
31
+ it('证据未接线(skip)→ abstain(不阻断)', async () => {
32
+ const gate = createCheckerGate(
33
+ contextEvidenceFlag('flag-skip', () => undefined)
34
+ );
35
+ const decision = await gate.evaluate(ctx);
36
+ expect(decision.status).toBe('abstain');
37
+ expect(decision.result.message).toContain('跳过');
38
+ });
39
+
40
+ it('决策浅冻结(单调语义契约)', async () => {
41
+ const gate = createCheckerGate(contextFlag('flag-freeze', () => true));
42
+ const decision = await gate.evaluate(ctx);
43
+ expect(Object.isFrozen(decision)).toBe(true);
44
+ });
45
+ });