@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
@@ -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
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * 6 门禁统一接口行为测试(G1)
3
+ *
4
+ * 每个门禁的 evaluate(ctx) 收敛到统一 Gate 接口:
5
+ * 执行细节私有,报告 GateResult → 三态 GateDecision(passed → abstain,失败 → deny),
6
+ * 决策浅冻结。契约门禁(contract)的测试在 contract.test.ts。
7
+ */
8
+
9
+ import { exec } from 'child_process';
10
+ import * as fs from 'fs';
11
+ import * as os from 'os';
12
+ import * as path from 'path';
13
+ import { ReviewGate } from '../review';
14
+ import { SecurityGate } from '../security';
15
+ import { PerformanceGate } from '../performance';
16
+ import { SpecAcceptanceGate } from '../acceptance';
17
+ import { CommandGate } from '../command';
18
+
19
+ // Mock exec(ReviewGate/SecurityGate 走 execAsync)
20
+ jest.mock('child_process', () => ({
21
+ exec: jest.fn(),
22
+ }));
23
+
24
+ const mockExec = exec as unknown as jest.Mock;
25
+
26
+ function callExec(cmd: string, _opts: unknown, callback: (err: unknown, res: { stdout: string }) => void): void {
27
+ callback(null, { stdout: cmd.includes('@{u}') ? '' : 'abc123 commit msg' });
28
+ }
29
+
30
+ describe('ReviewGate 统一接口', () => {
31
+ beforeEach(() => jest.clearAllMocks());
32
+
33
+ it('本地模式 requireApproval=true → deny(三态映射 + 冻结)', async () => {
34
+ mockExec.mockImplementation(callExec);
35
+ const decision = await new ReviewGate().evaluate({
36
+ projectId: 'p',
37
+ projectPath: '/test/project',
38
+ });
39
+ expect(decision.status).toBe('deny');
40
+ expect(decision.result.gate).toBe('review');
41
+ expect(decision.result.passed).toBe(false);
42
+ expect(Object.isFrozen(decision)).toBe(true);
43
+ });
44
+
45
+ it('本地模式 requireApproval=false → abstain', async () => {
46
+ mockExec.mockImplementation(callExec);
47
+ const decision = await new ReviewGate({ requireApproval: false }).evaluate({
48
+ projectId: 'p',
49
+ projectPath: '/test/project',
50
+ });
51
+ expect(decision.status).toBe('abstain');
52
+ expect(decision.result.passed).toBe(true);
53
+ });
54
+
55
+ it('id/order 字段齐备', () => {
56
+ const gate = new ReviewGate();
57
+ expect(gate.id).toBe('review');
58
+ expect(gate.order).toBe(0);
59
+ });
60
+ });
61
+
62
+ describe('SecurityGate 统一接口', () => {
63
+ beforeEach(() => jest.clearAllMocks());
64
+
65
+ it('npm audit 零漏洞 → abstain', async () => {
66
+ mockExec.mockImplementation((_cmd, _opts, callback) =>
67
+ callback(null, { stdout: JSON.stringify({ audit: { advisories: {} } }) })
68
+ );
69
+ const decision = await new SecurityGate().evaluate({
70
+ projectId: 'p',
71
+ projectPath: '/test/project',
72
+ });
73
+ expect(decision.status).toBe('abstain');
74
+ expect(decision.result.passed).toBe(true);
75
+ });
76
+
77
+ it('发现 critical 漏洞 → deny', async () => {
78
+ mockExec.mockImplementation((_cmd, _opts, callback) =>
79
+ callback(null, {
80
+ stdout: JSON.stringify({
81
+ audit: {
82
+ advisories: {
83
+ '1': { severity: 'critical', name: 'bad-lib', via: [{ title: 'RCE' }] },
84
+ },
85
+ },
86
+ }),
87
+ })
88
+ );
89
+ const decision = await new SecurityGate().evaluate({
90
+ projectId: 'p',
91
+ projectPath: '/test/project',
92
+ });
93
+ expect(decision.status).toBe('deny');
94
+ expect(decision.result.details?.critical).toBe(1);
95
+ });
96
+
97
+ it('id/order 字段齐备', () => {
98
+ const gate = new SecurityGate();
99
+ expect(gate.id).toBe('security');
100
+ expect(gate.order).toBe(0);
101
+ });
102
+ });
103
+
104
+ describe('PerformanceGate 统一接口', () => {
105
+ it('enabled=false → abstain(禁用即放行)', async () => {
106
+ const decision = await new PerformanceGate({ enabled: false }).evaluate({
107
+ projectId: 'p',
108
+ projectPath: '/test/project',
109
+ });
110
+ expect(decision.status).toBe('abstain');
111
+ expect(decision.result.passed).toBe(true);
112
+ });
113
+
114
+ it('id/order 字段齐备', () => {
115
+ const gate = new PerformanceGate();
116
+ expect(gate.id).toBe('performance');
117
+ expect(gate.order).toBe(0);
118
+ });
119
+ });
120
+
121
+ describe('SpecAcceptanceGate 统一接口', () => {
122
+ let dir: string;
123
+
124
+ beforeEach(() => {
125
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-gate-acc-'));
126
+ });
127
+
128
+ afterEach(() => {
129
+ fs.rmSync(dir, { recursive: true, force: true });
130
+ });
131
+
132
+ it('tasks.yml 不存在 → abstain(跳过)', async () => {
133
+ const decision = await new SpecAcceptanceGate().evaluate({
134
+ projectId: 'p',
135
+ projectPath: dir,
136
+ tasksPath: path.join(dir, 'no-such-tasks.yml'),
137
+ });
138
+ expect(decision.status).toBe('abstain');
139
+ expect(decision.result.passed).toBe(true);
140
+ expect(decision.result.gate).toBe('acceptance');
141
+ });
142
+
143
+ it('已完成任务验收标准未勾选 → deny', async () => {
144
+ const tasksPath = path.join(dir, 'tasks.yml');
145
+ fs.writeFileSync(
146
+ tasksPath,
147
+ [
148
+ 'tasks:',
149
+ ' - id: TASK-001',
150
+ ' status: done',
151
+ ' acceptance_criteria:',
152
+ ' - id: AC-001',
153
+ ' description: Test',
154
+ ' type: manual',
155
+ ' required: true',
156
+ ' checked: false',
157
+ ].join('\n')
158
+ );
159
+ const decision = await new SpecAcceptanceGate().evaluate({
160
+ projectId: 'p',
161
+ projectPath: dir,
162
+ tasksPath,
163
+ });
164
+ expect(decision.status).toBe('deny');
165
+ expect(decision.result.passed).toBe(false);
166
+ expect(Object.isFrozen(decision)).toBe(true);
167
+ });
168
+
169
+ it('id/order 字段齐备', () => {
170
+ const gate = new SpecAcceptanceGate();
171
+ expect(gate.id).toBe('acceptance');
172
+ expect(gate.order).toBe(0);
173
+ });
174
+ });
175
+
176
+ describe('CommandGate 统一接口', () => {
177
+ it('黑名单命令 → deny', async () => {
178
+ const decision = await new CommandGate().evaluate({
179
+ projectId: 'p',
180
+ projectPath: '/test/project',
181
+ command: 'rm -rf /',
182
+ });
183
+ expect(decision.status).toBe('deny');
184
+ expect(decision.result.passed).toBe(false);
185
+ });
186
+
187
+ it('安全命令 → abstain', async () => {
188
+ const decision = await new CommandGate().evaluate({
189
+ projectId: 'p',
190
+ projectPath: '/test/project',
191
+ command: 'echo hello',
192
+ });
193
+ expect(decision.status).toBe('abstain');
194
+ expect(decision.result.passed).toBe(true);
195
+ });
196
+
197
+ it('id/order 字段齐备', () => {
198
+ const gate = new CommandGate();
199
+ expect(gate.id).toBe('command');
200
+ expect(gate.order).toBe(0);
201
+ });
202
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * 门禁注册表测试(G1 闭环)
3
+ */
4
+
5
+ import { GATE_DEFINITIONS, type GateDefinition } from '../definitions';
6
+ import {
7
+ getGate,
8
+ listRegisteredGates,
9
+ registeredGateCount,
10
+ assertGateRegistryClosed,
11
+ } from '../registry';
12
+ import { decisionFromResult } from '../decision';
13
+ import type { Gate } from '../types';
14
+
15
+ function fakeGate(id: string): Gate {
16
+ return {
17
+ id,
18
+ order: 0,
19
+ evaluate: async () =>
20
+ decisionFromResult({
21
+ gate: id,
22
+ passed: true,
23
+ message: 'ok',
24
+ timestamp: new Date().toISOString(),
25
+ }),
26
+ };
27
+ }
28
+
29
+ describe('gateRegistry', () => {
30
+ it('定义即注册:6 个门禁定义全部有实现', () => {
31
+ expect(registeredGateCount()).toBe(6);
32
+ for (const def of GATE_DEFINITIONS) {
33
+ const gate = getGate(def.id);
34
+ expect(gate).toBeDefined();
35
+ expect(gate.id).toBe(def.id);
36
+ }
37
+ });
38
+
39
+ it('注册实现携带定义表默认 order 与统一 evaluate', () => {
40
+ for (const def of GATE_DEFINITIONS) {
41
+ const gate = getGate(def.id);
42
+ expect(gate.order).toBe(def.order);
43
+ expect(typeof gate.evaluate).toBe('function');
44
+ }
45
+ });
46
+
47
+ it('listRegisteredGates 返回副本且按定义序', () => {
48
+ const gates = listRegisteredGates();
49
+ expect(gates.map(g => g.id)).toEqual(GATE_DEFINITIONS.map(d => d.id));
50
+ gates.pop();
51
+ expect(listRegisteredGates()).toHaveLength(6);
52
+ });
53
+
54
+ it('定义无实现 → 抛错(闭环失败方向 1)', () => {
55
+ const definitions: GateDefinition[] = [
56
+ { id: 'orphan-def', description: 'x', order: 0, cli: { command: 'x', description: 'x', options: [], action: { module: 'x', export: 'x' }, mapActionArgs: () => [] } },
57
+ ];
58
+ const implementations = [fakeGate('other')];
59
+ expect(() => assertGateRegistryClosed(definitions, implementations)).toThrow(
60
+ /门禁定义 "orphan-def" 未注册实现/
61
+ );
62
+ });
63
+
64
+ it('实现无定义 → 抛错(闭环失败方向 2)', () => {
65
+ const definitions: GateDefinition[] = [
66
+ { id: 'defined', description: 'x', order: 0, cli: { command: 'x', description: 'x', options: [], action: { module: 'x', export: 'x' }, mapActionArgs: () => [] } },
67
+ ];
68
+ const implementations = [fakeGate('defined'), fakeGate('undocumented-impl')];
69
+ expect(() => assertGateRegistryClosed(definitions, implementations)).toThrow(
70
+ /门禁实现 "undocumented-impl" 没有对应定义/
71
+ );
72
+ });
73
+
74
+ it('重复 id → 抛错', () => {
75
+ const definitions: GateDefinition[] = [
76
+ { id: 'dup', description: 'x', order: 0, cli: { command: 'x', description: 'x', options: [], action: { module: 'x', export: 'x' }, mapActionArgs: () => [] } },
77
+ ];
78
+ const implementations = [fakeGate('dup'), fakeGate('dup')];
79
+ expect(() => assertGateRegistryClosed(definitions, implementations)).toThrow(
80
+ /门禁 "dup" 重复注册/
81
+ );
82
+ });
83
+
84
+ it('引用未注册 → getGate 抛错(闭环)', () => {
85
+ expect(() => getGate('nonexistent')).toThrow(/门禁 "nonexistent" 未注册/);
86
+ });
87
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 门禁执行器测试(G1:deny 单调 + ask fail-closed + order 排序)
3
+ */
4
+
5
+ import { runGates } from '../runner';
6
+ import { decisionFromResult } from '../decision';
7
+ import type { Gate, GateContext, GateDecisionStatus } from '../types';
8
+
9
+ function fakeGate(id: string, order: number, status: GateDecisionStatus): Gate {
10
+ return {
11
+ id,
12
+ order,
13
+ evaluate: async () =>
14
+ decisionFromResult(
15
+ {
16
+ gate: id,
17
+ passed: status !== 'deny',
18
+ message: id,
19
+ timestamp: new Date().toISOString(),
20
+ },
21
+ status
22
+ ),
23
+ };
24
+ }
25
+
26
+ const ctx: GateContext = { projectId: 'p', projectPath: '/tmp/project' };
27
+
28
+ describe('runGates', () => {
29
+ it('deny 单调:先 deny 后 abstain,终态 deny(下游不得改回 allow)', async () => {
30
+ const result = await runGates(
31
+ [fakeGate('a', 0, 'deny'), fakeGate('b', 1, 'abstain')],
32
+ ctx
33
+ );
34
+ expect(result.status).toBe('deny');
35
+ expect(result.denied.map(d => d.result.gate)).toEqual(['a']);
36
+ expect(result.decisions.map(d => d.result.gate)).toEqual(['a', 'b']);
37
+ });
38
+
39
+ it('deny 单调:先 abstain 后 deny,终态 deny', async () => {
40
+ const result = await runGates(
41
+ [fakeGate('a', 0, 'abstain'), fakeGate('b', 1, 'deny')],
42
+ ctx
43
+ );
44
+ expect(result.status).toBe('deny');
45
+ expect(result.denied).toHaveLength(1);
46
+ });
47
+
48
+ it('全部 abstain → 终态 abstain', async () => {
49
+ const result = await runGates(
50
+ [fakeGate('a', 0, 'abstain'), fakeGate('b', 1, 'abstain')],
51
+ ctx
52
+ );
53
+ expect(result.status).toBe('abstain');
54
+ expect(result.denied).toHaveLength(0);
55
+ expect(result.asked).toHaveLength(0);
56
+ });
57
+
58
+ it('ask 枚举预留:无实现时 fail-closed = deny', async () => {
59
+ const result = await runGates([fakeGate('asker', 0, 'ask')], ctx);
60
+ expect(result.status).toBe('deny');
61
+ expect(result.asked).toHaveLength(1);
62
+ expect(result.asked[0].status).toBe('ask');
63
+ expect(result.denied).toHaveLength(0);
64
+ });
65
+
66
+ it('按声明式 order 升序执行(入参顺序无关)', async () => {
67
+ const result = await runGates(
68
+ [fakeGate('c', 3, 'abstain'), fakeGate('a', 1, 'abstain'), fakeGate('b', 2, 'abstain')],
69
+ ctx
70
+ );
71
+ expect(result.decisions.map(d => d.result.gate)).toEqual(['a', 'b', 'c']);
72
+ });
73
+
74
+ it('决策浅冻结:调用方/下游改写决策抛错(单调语义接口契约)', async () => {
75
+ const result = await runGates([fakeGate('a', 0, 'deny')], ctx);
76
+ expect(Object.isFrozen(result.decisions[0])).toBe(true);
77
+ expect(() => {
78
+ (result.decisions[0] as { status: GateDecisionStatus }).status = 'abstain';
79
+ }).toThrow(TypeError);
80
+ });
81
+ });
@@ -21,6 +21,8 @@ import { execAsync } from '../utils/exec';
21
21
  import * as fs from 'fs/promises';
22
22
  import * as path from 'path';
23
23
  import * as yaml from 'js-yaml';
24
+ import type { Gate, GateContext, GateDecision } from './types';
25
+ import { decisionFromResult } from './decision';
24
26
 
25
27
 
26
28
  // ==================== 类型定义 ====================
@@ -135,7 +137,9 @@ export interface E2ETestResult {
135
137
  /**
136
138
  * 验收标准门禁
137
139
  */
138
- export class SpecAcceptanceGate {
140
+ export class SpecAcceptanceGate implements Gate {
141
+ readonly id = 'acceptance';
142
+ order = 0;
139
143
  private config: SpecAcceptanceGateConfig;
140
144
 
141
145
  constructor(config?: Partial<SpecAcceptanceGateConfig>) {
@@ -148,6 +152,25 @@ export class SpecAcceptanceGate {
148
152
  };
149
153
  }
150
154
 
155
+ /**
156
+ * 统一门禁接口(G1):执行细节私有,决策三态由 check() 报告推导。
157
+ * AcceptanceGateResult 无 gate 字段,此处归一化为 GateResult 报告结构。
158
+ */
159
+ async evaluate(context: GateContext): Promise<GateDecision> {
160
+ const result = await this.check({
161
+ projectPath: context.projectPath,
162
+ taskId: context.taskId,
163
+ tasksPath: context.tasksPath,
164
+ });
165
+ return decisionFromResult({
166
+ gate: 'acceptance',
167
+ passed: result.passed,
168
+ message: result.message,
169
+ timestamp: result.timestamp,
170
+ details: result.details as Record<string, any>,
171
+ });
172
+ }
173
+
151
174
  /**
152
175
  * 检查验收标准
153
176
  */
@@ -0,0 +1,54 @@
1
+ /**
2
+ * checker-as-guard 接线点(G1 / studio #129 随动)
3
+ *
4
+ * 把已注册的 ConstraintCheck 适配为统一 Gate,供守卫链(如 studio
5
+ * runCompletionGuards)复用 runGates 的 deny 单调 / ask fail-closed 语义。
6
+ *
7
+ * 证据语义:本接线点构造的 CheckEnv 不带 git staged diff / 源码扫描证据
8
+ * (stagedDiff 空、srcScan 空)——证据 flag 未接线的 checker 按契约返回
9
+ * 'skip'(不阻断,映射为 abstain);纯上下文判断的 checker 照常判定。
10
+ * studio 侧若需 git 证据,在 #129 接线时扩展 CheckEnv 构造。
11
+ */
12
+
13
+ import type { CheckEnv, ConstraintCheck } from '../core/constraints/checkers';
14
+ import type { Gate, GateContext, GateDecision } from './types';
15
+ import { decisionFromResult } from './decision';
16
+
17
+ /**
18
+ * 将已注册 checker 适配为统一 Gate(checker-as-guard 接线点)
19
+ *
20
+ * 判定映射:false(违反)→ deny;true(满足)/ 'skip'(未评估)→ abstain。
21
+ *
22
+ * @param check 已注册的 ConstraintCheck(经 checkers 注册表取回)
23
+ * @param order 门禁顺序(供 guard 链排序)
24
+ */
25
+ export function createCheckerGate(check: ConstraintCheck, order = 0): Gate {
26
+ return {
27
+ id: check.id,
28
+ order,
29
+ async evaluate(ctx: GateContext): Promise<GateDecision> {
30
+ const startTime = Date.now();
31
+ const projectPath = ctx.projectPath || process.cwd();
32
+ const env: CheckEnv = {
33
+ context: { operation: 'manual', projectPath },
34
+ projectPath,
35
+ stagedDiff: async () => '',
36
+ stagedDiffNames: async () => '',
37
+ srcScan: () => [],
38
+ };
39
+ const outcome = await check.evaluate(env);
40
+ return decisionFromResult({
41
+ gate: check.id,
42
+ passed: outcome !== false,
43
+ message:
44
+ outcome === false
45
+ ? `checker "${check.id}" 判定违规`
46
+ : outcome === 'skip'
47
+ ? `checker "${check.id}" 跳过(证据未接线)`
48
+ : `checker "${check.id}" 通过`,
49
+ timestamp: new Date().toISOString(),
50
+ duration: Date.now() - startTime,
51
+ });
52
+ },
53
+ };
54
+ }
@@ -5,7 +5,8 @@
5
5
  * 检查命令是否安全,防止执行危险操作
6
6
  */
7
7
 
8
- import type { GateResult, CommandGateConfig, CommandBlacklistRule } from './types';
8
+ import type { GateResult, CommandGateConfig, CommandBlacklistRule, Gate, GateContext, GateDecision } from './types';
9
+ import { decisionFromResult } from './decision';
9
10
 
10
11
  /**
11
12
  * 黑名单级别
@@ -195,7 +196,9 @@ export const DEFAULT_COMMAND_BLACKLIST: CommandBlacklistRule[] = [
195
196
  /**
196
197
  * 命令门禁
197
198
  */
198
- export class CommandGate {
199
+ export class CommandGate implements Gate {
200
+ readonly id = 'command';
201
+ order = 0;
199
202
  private config: Required<CommandGateConfig>;
200
203
  private blacklist: CommandBlacklistRule[];
201
204
 
@@ -214,6 +217,14 @@ export class CommandGate {
214
217
  ];
215
218
  }
216
219
 
220
+ /**
221
+ * 统一门禁接口(G1):执行细节私有,决策三态由 check() 报告推导。
222
+ * 待检命令经 GateContext.command 传入。
223
+ */
224
+ async evaluate(context: GateContext): Promise<GateDecision> {
225
+ return decisionFromResult(await this.check(context.command ?? ''));
226
+ }
227
+
217
228
  /**
218
229
  * 检查命令
219
230
  */
@@ -9,12 +9,15 @@
9
9
 
10
10
  import * as fs from 'fs/promises';
11
11
  import * as path from 'path';
12
- import type { GateResult, GateContext, ContractGateConfig } from './types';
12
+ import type { GateResult, GateContext, ContractGateConfig, Gate, GateDecision } from './types';
13
+ import { decisionFromResult } from './decision';
13
14
 
14
15
  /**
15
16
  * 契约门禁
16
17
  */
17
- export class ContractGate {
18
+ export class ContractGate implements Gate {
19
+ readonly id = 'contract';
20
+ order = 0;
18
21
  private config: Required<ContractGateConfig>;
19
22
 
20
23
  constructor(config: Partial<ContractGateConfig> = {}) {
@@ -26,6 +29,13 @@ export class ContractGate {
26
29
  };
27
30
  }
28
31
 
32
+ /**
33
+ * 统一门禁接口(G1):执行细节私有,决策三态由 check() 报告推导
34
+ */
35
+ async evaluate(context: GateContext): Promise<GateDecision> {
36
+ return decisionFromResult(await this.check(context));
37
+ }
38
+
29
39
  /**
30
40
  * 检查契约
31
41
  */