principles-disciple 1.8.0 → 1.8.2

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 (460) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +6 -1
  10. package/package.json +13 -15
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +185 -63
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +166 -139
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +263 -36
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +603 -0
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/src/service/subagent-workflow/types.ts +378 -0
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -127
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -99
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -12
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -51
  403. package/dist/hooks/progressive-trust-gate.js +0 -89
  404. package/dist/hooks/prompt.d.ts +0 -47
  405. package/dist/hooks/prompt.js +0 -884
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -567
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -52
  423. package/dist/service/empathy-observer-manager.js +0 -229
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -974
  428. package/dist/service/nocturnal-runtime.d.ts +0 -183
  429. package/dist/service/nocturnal-service.d.ts +0 -163
  430. package/dist/service/nocturnal-service.js +0 -787
  431. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  432. package/dist/service/nocturnal-target-selector.js +0 -315
  433. package/dist/service/phase3-input-filter.d.ts +0 -73
  434. package/dist/service/phase3-input-filter.js +0 -172
  435. package/dist/service/runtime-summary-service.d.ts +0 -122
  436. package/dist/service/runtime-summary-service.js +0 -485
  437. package/dist/service/trajectory-service.d.ts +0 -2
  438. package/dist/service/trajectory-service.js +0 -15
  439. package/dist/tools/critique-prompt.d.ts +0 -14
  440. package/dist/tools/deep-reflect.d.ts +0 -39
  441. package/dist/tools/deep-reflect.js +0 -350
  442. package/dist/tools/model-index.d.ts +0 -9
  443. package/dist/types/event-types.d.ts +0 -306
  444. package/dist/types/event-types.js +0 -106
  445. package/dist/types/hygiene-types.d.ts +0 -20
  446. package/dist/types/hygiene-types.js +0 -12
  447. package/dist/types/runtime-summary.d.ts +0 -47
  448. package/dist/types/runtime-summary.js +0 -1
  449. package/dist/types.d.ts +0 -50
  450. package/dist/types.js +0 -22
  451. package/dist/utils/file-lock.d.ts +0 -71
  452. package/dist/utils/file-lock.js +0 -309
  453. package/dist/utils/glob-match.d.ts +0 -28
  454. package/dist/utils/hashing.d.ts +0 -9
  455. package/dist/utils/io.d.ts +0 -6
  456. package/dist/utils/io.js +0 -106
  457. package/dist/utils/nlp.d.ts +0 -9
  458. package/dist/utils/plugin-logger.d.ts +0 -39
  459. package/dist/utils/subagent-probe.d.ts +0 -34
  460. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,669 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { handleBeforeToolCall } from '../../src/hooks/gate.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { WorkspaceContext } from '../../src/core/workspace-context.js';
6
+ import * as riskCalculator from '../../src/core/risk-calculator.js';
7
+ import * as sessionTracker from '../../src/core/session-tracker.js';
8
+ import * as evolutionEngine from '../../src/core/evolution-engine.js';
9
+ import { EvolutionTier } from '../../src/core/evolution-types.js';
10
+
11
+ vi.mock('fs');
12
+ vi.mock('../../src/core/workspace-context.js');
13
+ vi.mock('../../src/core/risk-calculator.js');
14
+ vi.mock('../../src/core/session-tracker.js');
15
+ vi.mock('../../src/core/evolution-engine.js');
16
+
17
+ describe('GFI Gate - Hard Intercept', () => {
18
+ const workspaceDir = '/mock/workspace';
19
+
20
+ const mockConfig = {
21
+ get: vi.fn().mockImplementation((key) => {
22
+ if (key === 'trust') return {
23
+ limits: { stage_2_max_lines: 10, stage_3_max_lines: 100 }
24
+ };
25
+ if (key === 'gfi_gate') return {
26
+ enabled: true,
27
+ thresholds: {
28
+ low_risk_block: 70,
29
+ high_risk_block: 40,
30
+ large_change_block: 50
31
+ },
32
+ large_change_lines: 50,
33
+ trust_stage_multipliers: {
34
+ '1': 0.5,
35
+ '2': 0.75,
36
+ '3': 1.0,
37
+ '4': 1.5
38
+ },
39
+ bash_safe_patterns: [
40
+ '^(ls|dir|pwd|which|where|echo|env|cat|type|head|tail|less|more)\\b',
41
+ '^git\\s+(status|log|diff|branch|show|remote)\\b',
42
+ '^npm\\s+(run|test|build|start)\\b',
43
+ '^make\\s*$',
44
+ '^make\\s+(-j\\d+|--jobs\\s*\\d+)$',
45
+ '^(gradle|mvn)\\s+(clean|build|test|compile)\\b'
46
+ ],
47
+ bash_dangerous_patterns: [
48
+ 'rm\\s+(-[a-z]*r[a-z]*f|-rf)',
49
+ 'git\\s+(push\\s+.*--force|reset\\s+--hard|clean\\s+-fd)',
50
+ 'npm\\s+publish',
51
+ '(curl|wget).*\\|\\s*(ba)?sh'
52
+ ]
53
+ };
54
+ return undefined;
55
+ })
56
+ };
57
+
58
+ const mockEventLog = {
59
+ recordGateBlock: vi.fn(),
60
+ recordPlanApproval: vi.fn(),
61
+ recordGfiGateBlock: vi.fn(),
62
+ };
63
+
64
+ const mockTrajectory = {
65
+ recordGateBlock: vi.fn(),
66
+ recordTaskOutcome: vi.fn(),
67
+ };
68
+
69
+ const mockWctx = {
70
+ workspaceDir,
71
+ stateDir: '/mock/state',
72
+ config: mockConfig,
73
+ eventLog: mockEventLog,
74
+ trajectory: mockTrajectory,
75
+ resolve: vi.fn().mockImplementation((key) => {
76
+ if (key === 'PROFILE') return path.join(workspaceDir, '.principles', 'PROFILE.json');
77
+ if (key === 'PLAN') return path.join(workspaceDir, 'PLAN.md');
78
+ return '';
79
+ }),
80
+ };
81
+
82
+ beforeEach(() => {
83
+ vi.clearAllMocks();
84
+ vi.useFakeTimers();
85
+ vi.mocked(WorkspaceContext.fromHookContext).mockReturnValue(mockWctx as any);
86
+ vi.mocked(riskCalculator.assessRiskLevel).mockReturnValue('LOW');
87
+ vi.mocked(riskCalculator.estimateLineChanges).mockReturnValue(1);
88
+ vi.mocked(sessionTracker.getSession).mockReturnValue({
89
+ sessionId: 'test-session',
90
+ currentGfi: 0,
91
+ toolReadsByFile: {},
92
+ llmTurns: 0,
93
+ blockedAttempts: 0,
94
+ lastActivityAt: Date.now(),
95
+ totalInputTokens: 0,
96
+ totalOutputTokens: 0,
97
+ cacheHits: 0,
98
+ stuckLoops: 0,
99
+ lastErrorHash: '',
100
+ consecutiveErrors: 0,
101
+ dailyToolCalls: 0,
102
+ dailyToolFailures: 0,
103
+ dailyPainSignals: 0,
104
+ dailyGfiPeak: 0,
105
+ lastThinkingTimestamp: 0,
106
+ } as any);
107
+ // Mock getEvolutionEngine to return a mock engine with getTier()
108
+ vi.mocked(evolutionEngine.getEvolutionEngine).mockReturnValue({
109
+ getTier: vi.fn().mockReturnValue(EvolutionTier.Sapling),
110
+ getPoints: vi.fn().mockReturnValue(200),
111
+ getAvailablePoints: vi.fn().mockReturnValue(200),
112
+ getTierDefinition: vi.fn().mockReturnValue({
113
+ tier: EvolutionTier.Sapling,
114
+ name: 'Sapling',
115
+ requiredPoints: 200,
116
+ permissions: { maxLinesPerWrite: 500, maxFilesPerTask: 10, allowRiskPath: true, allowSubagentSpawn: true }
117
+ }),
118
+ } as any);
119
+ vi.mocked(evolutionEngine.checkEvolutionGate).mockReturnValue({
120
+ allowed: true,
121
+ currentTier: EvolutionTier.Sapling,
122
+ });
123
+ });
124
+
125
+ afterEach(() => {
126
+ vi.useRealTimers();
127
+ });
128
+
129
+ // ════════════════════════════════════════════════
130
+ // TIER 0: 只读工具 - 永不拦截
131
+ // ════════════════════════════════════════════════
132
+ describe('TIER 0: Read-only tools', () => {
133
+ it('should NEVER block read_file regardless of GFI', () => {
134
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
135
+ const mockEvent = { toolName: 'read_file', params: { file_path: 'src/main.ts' } };
136
+
137
+ // 设置高 GFI
138
+ vi.mocked(sessionTracker.getSession).mockReturnValue({
139
+ ...mockWctx,
140
+ currentGfi: 95,
141
+ } as any);
142
+
143
+
144
+ vi.mocked(fs.existsSync).mockReturnValue(true);
145
+ vi.mocked(fs.readFileSync).mockImplementation(() => {
146
+ return JSON.stringify({ risk_paths: [], progressive_gate: { enabled: true } });
147
+ });
148
+
149
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
150
+
151
+ expect(result).toBeUndefined(); // 放行
152
+ });
153
+
154
+ it('should NEVER block glob regardless of GFI', () => {
155
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
156
+ const mockEvent = { toolName: 'glob', params: { pattern: '**/*.ts' } };
157
+
158
+ vi.mocked(sessionTracker.getSession).mockReturnValue({
159
+ currentGfi: 100,
160
+ } as any);
161
+
162
+
163
+ vi.mocked(fs.existsSync).mockReturnValue(true);
164
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({}));
165
+
166
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
167
+
168
+ expect(result).toBeUndefined(); // 放行
169
+ });
170
+
171
+ it('should NEVER block lsp_hover regardless of GFI', () => {
172
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
173
+ const mockEvent = { toolName: 'lsp_hover', params: { file: 'src/main.ts', line: 10, character: 5 } };
174
+
175
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 90 } as any);
176
+
177
+ vi.mocked(fs.existsSync).mockReturnValue(true);
178
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({}));
179
+
180
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
181
+
182
+ expect(result).toBeUndefined(); // 放行
183
+ });
184
+
185
+ it('should NEVER block deep_reflect regardless of GFI', () => {
186
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
187
+ const mockEvent = { toolName: 'deep_reflect', params: { question: 'Why did this fail?' } };
188
+
189
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 85 } as any);
190
+
191
+ vi.mocked(fs.existsSync).mockReturnValue(true);
192
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({}));
193
+
194
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
195
+
196
+ expect(result).toBeUndefined(); // 放行
197
+ });
198
+ });
199
+
200
+ // ════════════════════════════════════════════════
201
+ // TIER 1: 低风险修改 - GFI >= 70 时拦截
202
+ // ════════════════════════════════════════════════
203
+ describe('TIER 1: Low-risk write tools', () => {
204
+ it('should block write when GFI >= 70 (threshold)', () => {
205
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
206
+ const mockEvent = { toolName: 'write', params: { file_path: 'docs/readme.md', content: 'test' } };
207
+
208
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 75 } as any);
209
+
210
+ vi.mocked(fs.existsSync).mockReturnValue(true);
211
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
212
+
213
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
214
+
215
+ expect(result).toBeDefined();
216
+ expect(result?.block).toBe(true);
217
+ expect(result?.blockReason).toContain('GFI');
218
+ });
219
+
220
+ it('should allow write when GFI < 70 (below threshold)', () => {
221
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
222
+ const mockEvent = { toolName: 'write', params: { file_path: 'docs/readme.md', content: 'test' } };
223
+
224
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 50 } as any);
225
+
226
+ vi.mocked(fs.existsSync).mockReturnValue(true);
227
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
228
+
229
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
230
+
231
+ expect(result).toBeUndefined(); // 放行
232
+ });
233
+
234
+ it('should block edit when GFI >= 70', () => {
235
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
236
+ const mockEvent = { toolName: 'edit', params: { file_path: 'src/util.ts', oldText: 'foo', newText: 'bar' } };
237
+
238
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 80 } as any);
239
+
240
+ vi.mocked(fs.existsSync).mockReturnValue(true);
241
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
242
+
243
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
244
+
245
+ expect(result).toBeDefined();
246
+ expect(result?.block).toBe(true);
247
+ expect(result?.blockReason).toContain('GFI');
248
+ });
249
+
250
+ it('should NOT block sessions_spawn (low risk) when GFI < 70', () => {
251
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
252
+ const mockEvent = { toolName: 'sessions_spawn', params: { task: 'Analyze code' } };
253
+
254
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 50 } as any);
255
+
256
+ vi.mocked(fs.existsSync).mockReturnValue(true);
257
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
258
+
259
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
260
+
261
+ // sessions_spawn 不在 WRITE_TOOLS 中,应该直接返回(不被 gate 处理)
262
+ // 但如果它被当作 AGENT_TOOLS 处理,也应该不被 GFI gate 拦截
263
+ expect(result).toBeUndefined();
264
+ });
265
+
266
+ it('should fail closed when dangerous bash regex is invalid', () => {
267
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
268
+ const mockEvent = { toolName: 'bash', params: { command: 'echo safe' } };
269
+ const originalGet = mockConfig.get.getMockImplementation();
270
+
271
+ mockConfig.get.mockImplementation((key) => {
272
+ if (key === 'trust') return {
273
+ limits: { stage_2_max_lines: 10, stage_3_max_lines: 100 }
274
+ };
275
+ if (key === 'gfi_gate') return {
276
+ enabled: true,
277
+ thresholds: {
278
+ low_risk_block: 70,
279
+ high_risk_block: 40,
280
+ large_change_block: 50
281
+ },
282
+ large_change_lines: 50,
283
+ trust_stage_multipliers: {
284
+ '1': 0.5,
285
+ '2': 0.75,
286
+ '3': 1.0,
287
+ '4': 1.5
288
+ },
289
+ bash_safe_patterns: ['^echo\\b'],
290
+ bash_dangerous_patterns: ['(']
291
+ };
292
+ return undefined;
293
+ });
294
+
295
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 0 } as any);
296
+
297
+ vi.mocked(fs.existsSync).mockReturnValue(true);
298
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
299
+
300
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
301
+ mockConfig.get.mockImplementation(originalGet ?? ((key) => undefined));
302
+
303
+ expect(result?.block).toBe(true);
304
+ });
305
+ });
306
+
307
+ // ════════════════════════════════════════════════
308
+ // TIER 2: 高风险操作 - GFI >= 40 时拦截
309
+ // ════════════════════════════════════════════════
310
+ describe('TIER 2: High-risk tools', () => {
311
+ it('should block delete_file when GFI >= 40', () => {
312
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
313
+ const mockEvent = { toolName: 'delete_file', params: { file_path: 'temp/file.txt' } };
314
+
315
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 50 } as any);
316
+
317
+ vi.mocked(fs.existsSync).mockReturnValue(true);
318
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
319
+
320
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
321
+
322
+ expect(result).toBeDefined();
323
+ expect(result?.block).toBe(true);
324
+ expect(result?.blockReason).toContain('GFI');
325
+ });
326
+
327
+ it('should allow delete_file when GFI < 40', () => {
328
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
329
+ const mockEvent = { toolName: 'delete_file', params: { file_path: 'temp/file.txt' } };
330
+
331
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 30 } as any);
332
+
333
+ vi.mocked(fs.existsSync).mockReturnValue(true);
334
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
335
+
336
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
337
+
338
+ expect(result).toBeUndefined(); // 放行
339
+ });
340
+
341
+ it('should block move_file when GFI >= 40', () => {
342
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
343
+ const mockEvent = { toolName: 'move_file', params: { source: 'old.ts', destination: 'new.ts' } };
344
+
345
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 55 } as any);
346
+
347
+ vi.mocked(fs.existsSync).mockReturnValue(true);
348
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
349
+
350
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
351
+
352
+ expect(result).toBeDefined();
353
+ expect(result?.block).toBe(true);
354
+ expect(result?.blockReason).toContain('GFI');
355
+ });
356
+ });
357
+
358
+ // ════════════════════════════════════════════════
359
+ // TIER 3: Bash 命令 - 根据内容判断
360
+ // ════════════════════════════════════════════════
361
+ describe('TIER 3: Bash commands', () => {
362
+ describe('Safe commands (always allowed)', () => {
363
+ it('should allow "git status" regardless of GFI', () => {
364
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
365
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'git status' } };
366
+
367
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 95 } as any);
368
+
369
+ vi.mocked(fs.existsSync).mockReturnValue(true);
370
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
371
+
372
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
373
+
374
+ expect(result).toBeUndefined(); // 放行
375
+ });
376
+
377
+ it('should allow "ls -la" regardless of GFI', () => {
378
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
379
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'ls -la' } };
380
+
381
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 90 } as any);
382
+
383
+ vi.mocked(fs.existsSync).mockReturnValue(true);
384
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
385
+
386
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
387
+
388
+ expect(result).toBeUndefined(); // 放行
389
+ });
390
+
391
+ it('should allow "npm test" regardless of GFI', () => {
392
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
393
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'npm test' } };
394
+
395
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 85 } as any);
396
+
397
+ vi.mocked(fs.existsSync).mockReturnValue(true);
398
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
399
+
400
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
401
+
402
+ expect(result).toBeUndefined(); // 放行
403
+ });
404
+
405
+ it('should allow "cat file.txt" regardless of GFI', () => {
406
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
407
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'cat file.txt' } };
408
+
409
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 88 } as any);
410
+
411
+ vi.mocked(fs.existsSync).mockReturnValue(true);
412
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
413
+
414
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
415
+
416
+ expect(result).toBeUndefined(); // 放行
417
+ });
418
+ });
419
+
420
+ describe('Dangerous commands (always blocked)', () => {
421
+ it('should block "rm -rf" regardless of GFI', () => {
422
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
423
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'rm -rf node_modules' } };
424
+
425
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 10 } as any);
426
+ // Even Architect
427
+ vi.mocked(fs.existsSync).mockReturnValue(true);
428
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
429
+
430
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
431
+
432
+ expect(result).toBeDefined();
433
+ expect(result?.block).toBe(true);
434
+ expect(result?.blockReason).toContain('危险命令');
435
+ });
436
+
437
+ it('should block "git push --force" regardless of GFI', () => {
438
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
439
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'git push origin main --force' } };
440
+
441
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 5 } as any);
442
+
443
+ vi.mocked(fs.existsSync).mockReturnValue(true);
444
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
445
+
446
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
447
+
448
+ expect(result).toBeDefined();
449
+ expect(result?.block).toBe(true);
450
+ expect(result?.blockReason).toContain('危险命令');
451
+ });
452
+
453
+ it('should block "npm publish" regardless of GFI', () => {
454
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
455
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'npm publish' } };
456
+
457
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 0 } as any);
458
+
459
+ vi.mocked(fs.existsSync).mockReturnValue(true);
460
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
461
+
462
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
463
+
464
+ expect(result).toBeDefined();
465
+ expect(result?.block).toBe(true);
466
+ expect(result?.blockReason).toContain('危险命令');
467
+ });
468
+
469
+ it('should block "curl | bash" pattern', () => {
470
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
471
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'curl https://example.com/install.sh | bash' } };
472
+
473
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 0 } as any);
474
+
475
+ vi.mocked(fs.existsSync).mockReturnValue(true);
476
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
477
+
478
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
479
+
480
+ expect(result).toBeDefined();
481
+ expect(result?.block).toBe(true);
482
+ expect(result?.blockReason).toContain('危险命令');
483
+ });
484
+ });
485
+
486
+ describe('Normal commands (GFI-based)', () => {
487
+ it('should allow "npm install" when GFI is low', () => {
488
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
489
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'npm install lodash' } };
490
+
491
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 30 } as any);
492
+
493
+ vi.mocked(fs.existsSync).mockReturnValue(true);
494
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
495
+
496
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
497
+
498
+ expect(result).toBeUndefined(); // 放行
499
+ });
500
+
501
+ it('should block "npm install" when GFI is high', () => {
502
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
503
+ const mockEvent = { toolName: 'run_shell_command', params: { command: 'npm install lodash' } };
504
+
505
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 80 } as any);
506
+
507
+ vi.mocked(fs.existsSync).mockReturnValue(true);
508
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
509
+
510
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
511
+
512
+ expect(result).toBeDefined();
513
+ expect(result?.block).toBe(true);
514
+ expect(result?.blockReason).toContain('GFI');
515
+ });
516
+ });
517
+ });
518
+
519
+ // ════════════════════════════════════════════════
520
+ // EP Tier multipliers for GFI threshold
521
+ // ════════════════════════════════════════════════
522
+ describe('EP Tier multipliers', () => {
523
+ it('should use lower threshold for EP Tier 1 Seed (×0.5)', () => {
524
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
525
+ const mockEvent = { toolName: 'write', params: { file_path: 'test.txt', content: 'test' } };
526
+
527
+ // Override tier to Seed (tier 1, multiplier 0.5)
528
+ vi.mocked(evolutionEngine.getEvolutionEngine).mockReturnValue({
529
+ getTier: vi.fn().mockReturnValue(EvolutionTier.Seed),
530
+ getPoints: vi.fn().mockReturnValue(0),
531
+ getAvailablePoints: vi.fn().mockReturnValue(0),
532
+ getTierDefinition: vi.fn().mockReturnValue({
533
+ tier: EvolutionTier.Seed,
534
+ name: 'Seed',
535
+ requiredPoints: 0,
536
+ permissions: { maxLinesPerWrite: 150, maxFilesPerTask: 3, allowRiskPath: false, allowSubagentSpawn: true }
537
+ }),
538
+ } as any);
539
+
540
+ // 基础阈值 70 × 0.5 = 35
541
+ // GFI = 40 应该被拦截
542
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 40 } as any);
543
+
544
+ vi.mocked(fs.existsSync).mockReturnValue(true);
545
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
546
+
547
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
548
+
549
+ expect(result).toBeDefined();
550
+ expect(result?.block).toBe(true);
551
+ expect(result?.blockReason).toContain('GFI');
552
+ });
553
+
554
+ it('should use standard threshold for EP Tier 3 Sapling (×1.0)', () => {
555
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
556
+ const mockEvent = { toolName: 'write', params: { file_path: 'test.txt', content: 'test' } };
557
+
558
+ // 基础阈值 70 × 1.0 = 70
559
+ // GFI = 65 应该放行
560
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 65 } as any);
561
+
562
+ vi.mocked(fs.existsSync).mockReturnValue(true);
563
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
564
+
565
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
566
+
567
+ expect(result).toBeUndefined(); // 放行
568
+ });
569
+
570
+ it('should use higher threshold for EP Tier 4 Tree (×1.5)', () => {
571
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
572
+ const mockEvent = { toolName: 'write', params: { file_path: 'test.txt', content: 'test' } };
573
+
574
+ // Override tier to Tree (tier 4, multiplier 1.5)
575
+ vi.mocked(evolutionEngine.getEvolutionEngine).mockReturnValue({
576
+ getTier: vi.fn().mockReturnValue(EvolutionTier.Tree),
577
+ getPoints: vi.fn().mockReturnValue(500),
578
+ getAvailablePoints: vi.fn().mockReturnValue(500),
579
+ getTierDefinition: vi.fn().mockReturnValue({
580
+ tier: EvolutionTier.Tree,
581
+ name: 'Tree',
582
+ requiredPoints: 500,
583
+ permissions: { maxLinesPerWrite: 1000, maxFilesPerTask: 20, allowRiskPath: true, allowSubagentSpawn: true }
584
+ }),
585
+ } as any);
586
+
587
+ // 基础阈值 70 × 1.5 = 105
588
+ // GFI = 80 应该放行
589
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 80 } as any);
590
+
591
+ vi.mocked(fs.existsSync).mockReturnValue(true);
592
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
593
+
594
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
595
+
596
+ expect(result).toBeUndefined(); // 放行
597
+ });
598
+ });
599
+
600
+ // ════════════════════════════════════════════════
601
+ // 大规模修改 - 按比例降低阈值
602
+ // ════════════════════════════════════════════════
603
+ describe('Large change threshold reduction', () => {
604
+ it('should lower threshold for large modifications (100+ lines)', () => {
605
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
606
+ const mockEvent = { toolName: 'write', params: { file_path: 'large.ts', content: 'x\n'.repeat(120) } };
607
+
608
+ // 基础阈值 70 × (1 - 120/200 * 0.5) = 70 × 0.7 = 49
609
+ // GFI = 55 应该被拦截
610
+ vi.mocked(riskCalculator.estimateLineChanges).mockReturnValue(120);
611
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 55 } as any);
612
+
613
+ vi.mocked(fs.existsSync).mockReturnValue(true);
614
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
615
+
616
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
617
+
618
+ expect(result).toBeDefined();
619
+ expect(result?.block).toBe(true);
620
+ expect(result?.blockReason).toContain('GFI');
621
+ });
622
+
623
+ it('should allow small modifications at same GFI', () => {
624
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
625
+ const mockEvent = { toolName: 'write', params: { file_path: 'small.ts', content: 'test' } };
626
+
627
+ // 小修改癸紝基础阈值 70
628
+ // GFI = 55 应该放行
629
+ vi.mocked(riskCalculator.estimateLineChanges).mockReturnValue(5);
630
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 55 } as any);
631
+
632
+ vi.mocked(fs.existsSync).mockReturnValue(true);
633
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
634
+
635
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
636
+
637
+ expect(result).toBeUndefined(); // 放行
638
+ });
639
+ });
640
+
641
+ // ════════════════════════════════════════════════
642
+ // 配置禁用
643
+ // ════════════════════════════════════════════════
644
+ describe('Configuration', () => {
645
+ it('should skip GFI gate when disabled', () => {
646
+ const mockCtx = { workspaceDir, sessionId: 'test-session' };
647
+ const mockEvent = { toolName: 'write', params: { file_path: 'test.txt', content: 'test' } };
648
+
649
+ vi.mocked(sessionTracker.getSession).mockReturnValue({ currentGfi: 90 } as any);
650
+
651
+ vi.mocked(fs.existsSync).mockReturnValue(true);
652
+ vi.mocked(fs.readFileSync).mockImplementation(() => JSON.stringify({ progressive_gate: { enabled: true } }));
653
+
654
+ // 禁用 GFI gate
655
+ mockConfig.get.mockImplementation((key) => {
656
+ if (key === 'trust') return { limits: { stage_2_max_lines: 10, stage_3_max_lines: 100 } };
657
+ if (key === 'gfi_gate') return { enabled: false };
658
+ return undefined;
659
+ });
660
+
661
+ const result = handleBeforeToolCall(mockEvent as any, mockCtx as any);
662
+
663
+ // GFI gate 禁用后不应该拦截
664
+ expect(result).toBeUndefined();
665
+ });
666
+ });
667
+
668
+ });
669
+