@dommaker/harness 0.19.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 (348) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/harness.js +136 -557
  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/context/index.d.ts +1 -5
  20. package/dist/context/index.d.ts.map +1 -1
  21. package/dist/context/index.js +1 -5
  22. package/dist/context/index.js.map +1 -1
  23. package/dist/context/knowledge-injector.d.ts +3 -3
  24. package/dist/context/knowledge-injector.d.ts.map +1 -1
  25. package/dist/context/knowledge-injector.js +1 -1
  26. package/dist/context/session-manager.d.ts +1 -12
  27. package/dist/context/session-manager.d.ts.map +1 -1
  28. package/dist/context/session-manager.js +0 -63
  29. package/dist/context/session-manager.js.map +1 -1
  30. package/dist/context/types.d.ts +1 -39
  31. package/dist/context/types.d.ts.map +1 -1
  32. package/dist/context/types.js +2 -15
  33. package/dist/context/types.js.map +1 -1
  34. package/dist/core/constraints/agent-prompt-renderer.d.ts +1 -1
  35. package/dist/core/constraints/agent-prompt-renderer.js +1 -1
  36. package/dist/core/constraints/capabilities-parser.d.ts +17 -2
  37. package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
  38. package/dist/core/constraints/capabilities-parser.js +84 -2
  39. package/dist/core/constraints/capabilities-parser.js.map +1 -1
  40. package/dist/core/constraints/checker.d.ts +3 -17
  41. package/dist/core/constraints/checker.d.ts.map +1 -1
  42. package/dist/core/constraints/checker.js +7 -47
  43. package/dist/core/constraints/checker.js.map +1 -1
  44. package/dist/core/constraints/definitions.d.ts +0 -7
  45. package/dist/core/constraints/definitions.d.ts.map +1 -1
  46. package/dist/core/constraints/definitions.js +1 -8
  47. package/dist/core/constraints/definitions.js.map +1 -1
  48. package/dist/core/constraints/doc-freshness/runner.d.ts +0 -2
  49. package/dist/core/constraints/doc-freshness/runner.d.ts.map +1 -1
  50. package/dist/core/constraints/doc-freshness/runner.js.map +1 -1
  51. package/dist/core/constraints/index.d.ts +1 -1
  52. package/dist/core/constraints/index.d.ts.map +1 -1
  53. package/dist/core/constraints/index.js +2 -3
  54. package/dist/core/constraints/index.js.map +1 -1
  55. package/dist/core/constraints/interceptor.d.ts.map +1 -1
  56. package/dist/core/constraints/interceptor.js +1 -5
  57. package/dist/core/constraints/interceptor.js.map +1 -1
  58. package/dist/core/effective-constraints.d.ts.map +1 -1
  59. package/dist/core/effective-constraints.js +0 -1
  60. package/dist/core/effective-constraints.js.map +1 -1
  61. package/dist/core/project-config-loader.d.ts.map +1 -1
  62. package/dist/core/project-config-loader.js +1 -8
  63. package/dist/core/project-config-loader.js.map +1 -1
  64. package/dist/dashboard/stats.d.ts.map +1 -1
  65. package/dist/dashboard/stats.js.map +1 -1
  66. package/dist/dashboard/types.d.ts +2 -2
  67. package/dist/dashboard/types.d.ts.map +1 -1
  68. package/dist/failure/constraint-handler.d.ts.map +1 -1
  69. package/dist/failure/constraint-handler.js +0 -2
  70. package/dist/failure/constraint-handler.js.map +1 -1
  71. package/dist/gates/acceptance.d.ts +9 -1
  72. package/dist/gates/acceptance.d.ts.map +1 -1
  73. package/dist/gates/acceptance.js +21 -0
  74. package/dist/gates/acceptance.js.map +1 -1
  75. package/dist/gates/checker-gate.d.ts +23 -0
  76. package/dist/gates/checker-gate.d.ts.map +1 -0
  77. package/dist/gates/checker-gate.js +53 -0
  78. package/dist/gates/checker-gate.js.map +1 -0
  79. package/dist/gates/command.d.ts +9 -2
  80. package/dist/gates/command.d.ts.map +1 -1
  81. package/dist/gates/command.js +10 -0
  82. package/dist/gates/command.js.map +1 -1
  83. package/dist/gates/contract.d.ts +8 -2
  84. package/dist/gates/contract.d.ts.map +1 -1
  85. package/dist/gates/contract.js +9 -0
  86. package/dist/gates/contract.js.map +1 -1
  87. package/dist/gates/decision.d.ts +17 -0
  88. package/dist/gates/decision.d.ts.map +1 -0
  89. package/dist/gates/decision.js +25 -0
  90. package/dist/gates/decision.js.map +1 -0
  91. package/dist/gates/definitions.d.ts +63 -0
  92. package/dist/gates/definitions.d.ts.map +1 -0
  93. package/dist/gates/definitions.js +160 -0
  94. package/dist/gates/definitions.js.map +1 -0
  95. package/dist/gates/effective-gates.d.ts +30 -0
  96. package/dist/gates/effective-gates.d.ts.map +1 -0
  97. package/dist/gates/effective-gates.js +85 -0
  98. package/dist/gates/effective-gates.js.map +1 -0
  99. package/dist/gates/index.d.ts +25 -1
  100. package/dist/gates/index.d.ts.map +1 -1
  101. package/dist/gates/index.js +37 -1
  102. package/dist/gates/index.js.map +1 -1
  103. package/dist/gates/performance.d.ts +8 -2
  104. package/dist/gates/performance.d.ts.map +1 -1
  105. package/dist/gates/performance.js +9 -0
  106. package/dist/gates/performance.js.map +1 -1
  107. package/dist/gates/registry.d.ts +32 -0
  108. package/dist/gates/registry.d.ts.map +1 -0
  109. package/dist/gates/registry.js +90 -0
  110. package/dist/gates/registry.js.map +1 -0
  111. package/dist/gates/review.d.ts +8 -2
  112. package/dist/gates/review.d.ts.map +1 -1
  113. package/dist/gates/review.js +9 -0
  114. package/dist/gates/review.js.map +1 -1
  115. package/dist/gates/runner.d.ts +30 -0
  116. package/dist/gates/runner.d.ts.map +1 -0
  117. package/dist/gates/runner.js +38 -0
  118. package/dist/gates/runner.js.map +1 -0
  119. package/dist/gates/security.d.ts +8 -2
  120. package/dist/gates/security.d.ts.map +1 -1
  121. package/dist/gates/security.js +9 -0
  122. package/dist/gates/security.js.map +1 -1
  123. package/dist/gates/types.d.ts +41 -1
  124. package/dist/gates/types.d.ts.map +1 -1
  125. package/dist/hooks/bootstrap.d.ts.map +1 -1
  126. package/dist/hooks/bootstrap.js +0 -2
  127. package/dist/hooks/bootstrap.js.map +1 -1
  128. package/dist/hooks/config.d.ts +26 -0
  129. package/dist/hooks/config.d.ts.map +1 -0
  130. package/dist/hooks/config.js +30 -0
  131. package/dist/hooks/config.js.map +1 -0
  132. package/dist/hooks/index.d.ts +2 -1
  133. package/dist/hooks/index.d.ts.map +1 -1
  134. package/dist/hooks/index.js +4 -1
  135. package/dist/hooks/index.js.map +1 -1
  136. package/dist/hooks/registry.d.ts +18 -1
  137. package/dist/hooks/registry.d.ts.map +1 -1
  138. package/dist/hooks/registry.js +45 -0
  139. package/dist/hooks/registry.js.map +1 -1
  140. package/dist/hooks/types.d.ts +20 -0
  141. package/dist/hooks/types.d.ts.map +1 -1
  142. package/dist/index.d.ts +4 -8
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +5 -20
  145. package/dist/index.js.map +1 -1
  146. package/dist/knowledge/import.d.ts +2 -2
  147. package/dist/knowledge/import.d.ts.map +1 -1
  148. package/dist/knowledge/ingest.js.map +1 -1
  149. package/dist/knowledge/lifecycle-hooks.d.ts +2 -2
  150. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  151. package/dist/knowledge/types.d.ts +5 -7
  152. package/dist/knowledge/types.d.ts.map +1 -1
  153. package/dist/knowledge/types.js +1 -1
  154. package/dist/knowledge/types.js.map +1 -1
  155. package/dist/monitoring/constraint-doctor.d.ts +4 -22
  156. package/dist/monitoring/constraint-doctor.d.ts.map +1 -1
  157. package/dist/monitoring/constraint-doctor.js +7 -94
  158. package/dist/monitoring/constraint-doctor.js.map +1 -1
  159. package/dist/presets/standard.d.ts +1 -1
  160. package/dist/presets/standard.d.ts.map +1 -1
  161. package/dist/presets/standard.js +1 -2
  162. package/dist/presets/standard.js.map +1 -1
  163. package/dist/spec/annotation-checker.d.ts.map +1 -1
  164. package/dist/spec/annotation-checker.js +6 -26
  165. package/dist/spec/annotation-checker.js.map +1 -1
  166. package/dist/tools/index.d.ts +0 -4
  167. package/dist/tools/index.d.ts.map +1 -1
  168. package/dist/tools/index.js +0 -4
  169. package/dist/tools/index.js.map +1 -1
  170. package/dist/types/constraint.d.ts +1 -12
  171. package/dist/types/constraint.d.ts.map +1 -1
  172. package/dist/types/constraint.js +0 -3
  173. package/dist/types/constraint.js.map +1 -1
  174. package/dist/types/project-config.d.ts +0 -2
  175. package/dist/types/project-config.d.ts.map +1 -1
  176. package/dist/utils/file-walk.d.ts +2 -2
  177. package/dist/utils/file-walk.js +2 -2
  178. package/package.json +6 -1
  179. package/src/CONTEXT.md +14 -0
  180. package/src/__tests__/checker-extra.test.ts +2 -59
  181. package/src/__tests__/constraint-doctor.test.ts +1 -140
  182. package/src/__tests__/constraint-handler.test.ts +0 -4
  183. package/src/__tests__/project-config-loader-extra.test.ts +0 -20
  184. package/src/__tests__/project-config-loader.test.ts +0 -1
  185. package/src/cli/commands/CONTEXT.md +15 -7
  186. package/src/cli/commands/__tests__/check-drift.test.ts +0 -3
  187. package/src/cli/commands/__tests__/check-skip-output.test.ts +0 -5
  188. package/src/cli/commands/__tests__/check.test.ts +7 -75
  189. package/src/cli/commands/__tests__/registry.test.ts +201 -0
  190. package/src/cli/commands/check.ts +2 -14
  191. package/src/cli/commands/definitions.ts +593 -0
  192. package/src/cli/commands/knowledge.ts +3 -3
  193. package/src/cli/commands/sync-docs/capabilities-syncer.ts +4 -110
  194. package/src/cli/commands/sync-docs/index.ts +8 -5
  195. package/src/context/CONTEXT.md +2 -7
  196. package/src/context/__tests__/session-manager.test.ts +0 -91
  197. package/src/context/index.ts +1 -5
  198. package/src/context/knowledge-injector.ts +3 -3
  199. package/src/context/session-manager.ts +0 -74
  200. package/src/context/types.ts +2 -63
  201. package/src/core/constraints/__tests__/interceptor.test.ts +0 -2
  202. package/src/core/constraints/agent-prompt-renderer.ts +1 -1
  203. package/src/core/constraints/capabilities-parser.ts +95 -2
  204. package/src/core/constraints/checker.ts +8 -53
  205. package/src/core/constraints/definitions.ts +0 -8
  206. package/src/core/constraints/doc-freshness/runner.ts +1 -2
  207. package/src/core/constraints/index.ts +1 -2
  208. package/src/core/constraints/interceptor.ts +1 -6
  209. package/src/core/effective-constraints.ts +0 -1
  210. package/src/core/project-config-loader.ts +1 -7
  211. package/src/dashboard/stats.ts +2 -2
  212. package/src/dashboard/types.ts +2 -2
  213. package/src/failure/constraint-handler.ts +0 -2
  214. package/src/gates/CONTEXT.md +16 -13
  215. package/src/gates/__tests__/checker-gate.test.ts +45 -0
  216. package/src/gates/__tests__/contract.test.ts +73 -0
  217. package/src/gates/__tests__/effective-gates.test.ts +82 -0
  218. package/src/gates/__tests__/gate-interface.test.ts +202 -0
  219. package/src/gates/__tests__/registry.test.ts +87 -0
  220. package/src/gates/__tests__/runner.test.ts +81 -0
  221. package/src/gates/acceptance.ts +24 -1
  222. package/src/gates/checker-gate.ts +54 -0
  223. package/src/gates/command.ts +13 -2
  224. package/src/gates/contract.ts +12 -2
  225. package/src/gates/decision.ts +27 -0
  226. package/src/gates/definitions.ts +207 -0
  227. package/src/gates/effective-gates.ts +106 -0
  228. package/src/gates/index.ts +38 -0
  229. package/src/gates/performance.ts +12 -2
  230. package/src/gates/registry.ts +107 -0
  231. package/src/gates/review.ts +12 -2
  232. package/src/gates/runner.ts +57 -0
  233. package/src/gates/security.ts +12 -2
  234. package/src/gates/types.ts +48 -1
  235. package/src/hooks/CONTEXT.md +12 -2
  236. package/src/hooks/__tests__/bootstrap.test.ts +0 -1
  237. package/src/hooks/__tests__/config.test.ts +19 -0
  238. package/src/hooks/__tests__/pipeline.test.ts +79 -0
  239. package/src/hooks/__tests__/registry.test.ts +67 -0
  240. package/src/hooks/bootstrap.ts +0 -2
  241. package/src/hooks/config.ts +29 -0
  242. package/src/hooks/index.ts +2 -1
  243. package/src/hooks/registry.ts +60 -1
  244. package/src/hooks/types.ts +21 -0
  245. package/src/index.ts +5 -22
  246. package/src/knowledge/import.ts +3 -3
  247. package/src/knowledge/ingest.ts +4 -4
  248. package/src/knowledge/lifecycle-hooks.ts +3 -3
  249. package/src/knowledge/types.ts +5 -8
  250. package/src/monitoring/constraint-doctor.ts +7 -106
  251. package/src/presets/__tests__/standard.test.ts +0 -2
  252. package/src/presets/standard.ts +1 -2
  253. package/src/spec/__tests__/annotation-checker.test.ts +9 -3
  254. package/src/spec/annotation-checker.ts +7 -29
  255. package/src/tools/CONTEXT.md +7 -14
  256. package/src/tools/index.ts +0 -4
  257. package/src/types/constraint.ts +1 -14
  258. package/src/types/project-config.ts +0 -3
  259. package/src/utils/file-walk.ts +2 -2
  260. package/dist/architecture/constraint-engine.d.ts +0 -67
  261. package/dist/architecture/constraint-engine.d.ts.map +0 -1
  262. package/dist/architecture/constraint-engine.js +0 -295
  263. package/dist/architecture/constraint-engine.js.map +0 -1
  264. package/dist/architecture/cross-project-checker.d.ts +0 -54
  265. package/dist/architecture/cross-project-checker.d.ts.map +0 -1
  266. package/dist/architecture/cross-project-checker.js +0 -467
  267. package/dist/architecture/cross-project-checker.js.map +0 -1
  268. package/dist/cli/commands/index.d.ts +0 -30
  269. package/dist/cli/commands/index.d.ts.map +0 -1
  270. package/dist/cli/commands/index.js +0 -91
  271. package/dist/cli/commands/index.js.map +0 -1
  272. package/dist/context/file-budget.d.ts +0 -40
  273. package/dist/context/file-budget.d.ts.map +0 -1
  274. package/dist/context/file-budget.js +0 -147
  275. package/dist/context/file-budget.js.map +0 -1
  276. package/dist/context/progressive-loader.d.ts +0 -109
  277. package/dist/context/progressive-loader.d.ts.map +0 -1
  278. package/dist/context/progressive-loader.js +0 -166
  279. package/dist/context/progressive-loader.js.map +0 -1
  280. package/dist/context/token-pipeline.d.ts +0 -55
  281. package/dist/context/token-pipeline.d.ts.map +0 -1
  282. package/dist/context/token-pipeline.js +0 -220
  283. package/dist/context/token-pipeline.js.map +0 -1
  284. package/dist/context/tool-output-budget.d.ts +0 -59
  285. package/dist/context/tool-output-budget.d.ts.map +0 -1
  286. package/dist/context/tool-output-budget.js +0 -177
  287. package/dist/context/tool-output-budget.js.map +0 -1
  288. package/dist/core/constraints/default-executors.d.ts +0 -10
  289. package/dist/core/constraints/default-executors.d.ts.map +0 -1
  290. package/dist/core/constraints/default-executors.js +0 -120
  291. package/dist/core/constraints/default-executors.js.map +0 -1
  292. package/dist/llm/adapter.d.ts +0 -36
  293. package/dist/llm/adapter.d.ts.map +0 -1
  294. package/dist/llm/adapter.js +0 -118
  295. package/dist/llm/adapter.js.map +0 -1
  296. package/dist/llm/index.d.ts +0 -3
  297. package/dist/llm/index.d.ts.map +0 -1
  298. package/dist/llm/index.js +0 -19
  299. package/dist/llm/index.js.map +0 -1
  300. package/dist/llm/types.d.ts +0 -62
  301. package/dist/llm/types.d.ts.map +0 -1
  302. package/dist/llm/types.js +0 -8
  303. package/dist/llm/types.js.map +0 -1
  304. package/dist/tools/core/index.d.ts +0 -13
  305. package/dist/tools/core/index.d.ts.map +0 -1
  306. package/dist/tools/core/index.js +0 -198
  307. package/dist/tools/core/index.js.map +0 -1
  308. package/dist/tools/loader.d.ts +0 -21
  309. package/dist/tools/loader.d.ts.map +0 -1
  310. package/dist/tools/loader.js +0 -199
  311. package/dist/tools/loader.js.map +0 -1
  312. package/dist/tools/registry.d.ts +0 -59
  313. package/dist/tools/registry.d.ts.map +0 -1
  314. package/dist/tools/registry.js +0 -115
  315. package/dist/tools/registry.js.map +0 -1
  316. package/dist/tools/types.d.ts +0 -38
  317. package/dist/tools/types.d.ts.map +0 -1
  318. package/dist/tools/types.js +0 -6
  319. package/dist/tools/types.js.map +0 -1
  320. package/src/__tests__/architecture-engine.test.ts +0 -619
  321. package/src/__tests__/context/progressive-loader.test.ts +0 -131
  322. package/src/__tests__/cross-project-checker.test.ts +0 -318
  323. package/src/architecture/CONTEXT.md +0 -22
  324. package/src/architecture/constraint-engine.ts +0 -351
  325. package/src/architecture/cross-project-checker.ts +0 -545
  326. package/src/cli/commands/index.ts +0 -30
  327. package/src/context/__tests__/file-budget.test.ts +0 -142
  328. package/src/context/__tests__/progressive-loader.test.ts +0 -264
  329. package/src/context/__tests__/token-pipeline.test.ts +0 -178
  330. package/src/context/__tests__/tool-output-budget.test.ts +0 -95
  331. package/src/context/file-budget.ts +0 -142
  332. package/src/context/progressive-loader.ts +0 -260
  333. package/src/context/token-pipeline.ts +0 -265
  334. package/src/context/tool-output-budget.ts +0 -178
  335. package/src/core/constraints/__tests__/default-executors.test.ts +0 -57
  336. package/src/core/constraints/default-executors.ts +0 -92
  337. package/src/llm/CONTEXT.md +0 -21
  338. package/src/llm/__tests__/adapter.test.ts +0 -172
  339. package/src/llm/adapter.ts +0 -149
  340. package/src/llm/index.ts +0 -2
  341. package/src/llm/types.ts +0 -81
  342. package/src/tools/__tests__/loader.test.ts +0 -252
  343. package/src/tools/__tests__/registry.test.ts +0 -157
  344. package/src/tools/core/__tests__/core.test.ts +0 -122
  345. package/src/tools/core/index.ts +0 -201
  346. package/src/tools/loader.ts +0 -216
  347. package/src/tools/registry.ts +0 -128
  348. package/src/tools/types.ts +0 -46
@@ -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,
@@ -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
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ContractGate 测试(门禁规则:每个门禁必须有测试文件)
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import { ContractGate } from '../contract';
9
+
10
+ const VALID_SPEC = [
11
+ 'openapi: 3.0.0',
12
+ 'info:',
13
+ ' title: Test API',
14
+ ' version: 1.0.0',
15
+ 'paths:',
16
+ ' /users:',
17
+ ' get: {}',
18
+ ].join('\n');
19
+
20
+ const INVALID_SPEC = ['info:', ' title: Test API', 'paths:', ' /users:', ' get: {}'].join('\n');
21
+
22
+ describe('ContractGate', () => {
23
+ let dir: string;
24
+
25
+ beforeEach(() => {
26
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-gate-contract-'));
27
+ });
28
+
29
+ afterEach(() => {
30
+ fs.rmSync(dir, { recursive: true, force: true });
31
+ });
32
+
33
+ it('有效 OpenAPI → check 通过', async () => {
34
+ fs.writeFileSync(path.join(dir, 'openapi.yaml'), VALID_SPEC);
35
+ const result = await new ContractGate().check({ projectId: 'p', projectPath: dir });
36
+ expect(result.passed).toBe(true);
37
+ });
38
+
39
+ it('缺 openapi 版本 → check 失败', async () => {
40
+ fs.writeFileSync(path.join(dir, 'openapi.yaml'), INVALID_SPEC);
41
+ const result = await new ContractGate().check({ projectId: 'p', projectPath: dir });
42
+ expect(result.passed).toBe(false);
43
+ });
44
+
45
+ it('契约文件缺失 → 跳过(passed)', async () => {
46
+ const result = await new ContractGate().check({ projectId: 'p', projectPath: dir });
47
+ expect(result.passed).toBe(true);
48
+ expect(result.message).toContain('未找到契约文件');
49
+ });
50
+
51
+ describe('统一接口 evaluate', () => {
52
+ it('有效契约 → abstain(冻结决策)', async () => {
53
+ fs.writeFileSync(path.join(dir, 'openapi.yaml'), VALID_SPEC);
54
+ const decision = await new ContractGate().evaluate({ projectId: 'p', projectPath: dir });
55
+ expect(decision.status).toBe('abstain');
56
+ expect(decision.result.gate).toBe('contract');
57
+ expect(Object.isFrozen(decision)).toBe(true);
58
+ });
59
+
60
+ it('无效契约 → deny', async () => {
61
+ fs.writeFileSync(path.join(dir, 'openapi.yaml'), INVALID_SPEC);
62
+ const decision = await new ContractGate().evaluate({ projectId: 'p', projectPath: dir });
63
+ expect(decision.status).toBe('deny');
64
+ expect(decision.result.passed).toBe(false);
65
+ });
66
+
67
+ it('id/order 字段齐备', () => {
68
+ const gate = new ContractGate();
69
+ expect(gate.id).toBe('contract');
70
+ expect(gate.order).toBe(0);
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 生效门禁集测试(G1:config.yml order/enabled 裁剪,对齐 getEffectiveConstraints 模式)
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import { getEffectiveGates } from '../effective-gates';
9
+
10
+ const DEFAULT_ORDER = ['acceptance', 'command', 'contract', 'performance', 'review', 'security'];
11
+
12
+ describe('getEffectiveGates', () => {
13
+ let dir: string;
14
+
15
+ beforeEach(() => {
16
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-gates-'));
17
+ });
18
+
19
+ afterEach(() => {
20
+ fs.rmSync(dir, { recursive: true, force: true });
21
+ });
22
+
23
+ function writeConfig(yamlText: string): void {
24
+ const harnessDir = path.join(dir, '.harness');
25
+ fs.mkdirSync(harnessDir, { recursive: true });
26
+ fs.writeFileSync(path.join(harnessDir, 'config.yml'), yamlText);
27
+ }
28
+
29
+ it('无 config.yml → 6 门禁按默认 order', () => {
30
+ expect(getEffectiveGates(dir).map(g => g.id)).toEqual(DEFAULT_ORDER);
31
+ });
32
+
33
+ it('gates.order 声明式重排:列出的按列表序,未列出的按默认序排后', () => {
34
+ writeConfig('gates:\n order:\n - security\n - review\n');
35
+ expect(getEffectiveGates(dir).map(g => g.id)).toEqual([
36
+ 'security',
37
+ 'review',
38
+ 'acceptance',
39
+ 'command',
40
+ 'contract',
41
+ 'performance',
42
+ ]);
43
+ });
44
+
45
+ it('gates.<id>.enabled:false 裁剪(与 getEffectiveConstraints 同款删除语义)', () => {
46
+ writeConfig('gates:\n review:\n enabled: false\n');
47
+ const ids = getEffectiveGates(dir).map(g => g.id);
48
+ expect(ids).toHaveLength(5);
49
+ expect(ids).not.toContain('review');
50
+ });
51
+
52
+ it('order 与 enabled 组合:禁用胜出,其余按 order 重排', () => {
53
+ writeConfig('gates:\n order:\n - security\n - review\n review:\n enabled: false\n');
54
+ expect(getEffectiveGates(dir).map(g => g.id)).toEqual([
55
+ 'security',
56
+ 'acceptance',
57
+ 'command',
58
+ 'contract',
59
+ 'performance',
60
+ ]);
61
+ });
62
+
63
+ it('order 引用未注册门禁 → 抛错(闭环)', () => {
64
+ writeConfig('gates:\n order:\n - nope\n');
65
+ expect(() => getEffectiveGates(dir)).toThrow(/gates.order 引用了未注册的门禁 "nope"/);
66
+ });
67
+
68
+ it('enabled 段引用未注册门禁 → 抛错(闭环)', () => {
69
+ writeConfig('gates:\n nope:\n enabled: false\n');
70
+ expect(() => getEffectiveGates(dir)).toThrow(/gates.nope 引用了未注册的门禁/);
71
+ });
72
+
73
+ it('order 重复 id → 抛错', () => {
74
+ writeConfig('gates:\n order:\n - review\n - review\n');
75
+ expect(() => getEffectiveGates(dir)).toThrow(/门禁 "review" 重复/);
76
+ });
77
+
78
+ it('enabled:true 不裁剪', () => {
79
+ writeConfig('gates:\n review:\n enabled: true\n');
80
+ expect(getEffectiveGates(dir).map(g => g.id)).toEqual(DEFAULT_ORDER);
81
+ });
82
+ });