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,617 @@
1
+ /**
2
+ * Local Worker Routing Policy — Task Classification and Routing Decisions
3
+ * ======================================================================
4
+ *
5
+ * PURPOSE: Provide an explainable, testable policy that decides whether a given
6
+ * task can be delegated to a local-worker profile (local-reader or local-editor)
7
+ * or must stay on the main agent.
8
+ *
9
+ * ARCHITECTURE:
10
+ * - This module is POLICY ONLY — it makes routing decisions but does NOT execute them
11
+ * - The main agent (or a delegation hook in a future phase) is responsible for
12
+ * actually routing the task based on the RoutingDecision returned here
13
+ * - All decisions are deterministic and based on structured input fields
14
+ * - No model inference, no learning, no dynamic adaptation
15
+ *
16
+ * TASK CLASSIFICATION TAXONOMY:
17
+ * reader_eligible — clearly suitable for local-reader
18
+ * editor_eligible — clearly suitable for local-editor
19
+ * high_entropy_disallowed — high-complexity tasks that must stay on main agent
20
+ * ambiguous_scope — tasks that are unclear and need main-agent judgment
21
+ * deployment_unavailable — no enabled deployment exists for the target profile
22
+ *
23
+ * NOTE: risk_disallowed has been removed. Risk signals are now advisory only —
24
+ * the main agent decides whether to delegate based on full context.
25
+ *
26
+ * FAIL-CLOSED PRINCIPLE:
27
+ * - When in doubt → stay_main
28
+ * - Unclear intent → stay_main
29
+ * - High complexity → stay_main
30
+ * - No enabled deployment → stay_main
31
+ *
32
+ * DESIGN CONSTRAINTS:
33
+ * - No actual task execution
34
+ * - No automatic learning or route optimization
35
+ * - No Trinity or adaptive threshold logic
36
+ * - Routing decisions are fully explainable (return `reason` + `blockers[]`)
37
+ */
38
+
39
+ import type { WorkerProfile } from './model-deployment-registry.js';
40
+ import {
41
+ isRoutingEnabledForProfile,
42
+ getDeployment,
43
+ } from './model-deployment-registry.js';
44
+ import { isCheckpointDeployable } from './model-training-registry.js';
45
+ import { getPromotionState } from './promotion-gate.js';
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Routing Input Contract
49
+ // ---------------------------------------------------------------------------
50
+
51
+ /**
52
+ * The input contract for a routing decision.
53
+ * All fields are optional — the classifier handles missing data gracefully
54
+ * by treating it as ambiguous (stay_main).
55
+ */
56
+ export interface RoutingInput {
57
+ /**
58
+ * A short label or name for the task intent.
59
+ * E.g., "read_file", "edit_config", "debug_memory_leak", "design_system"
60
+ */
61
+ taskIntent?: string;
62
+
63
+ /**
64
+ * Natural-language description of the task.
65
+ * The classifier examines this for keywords indicating complexity/risk.
66
+ */
67
+ taskDescription?: string;
68
+
69
+ /**
70
+ * Specific tools requested or implied by the task.
71
+ * These are examined for risk signals (e.g., bash, rm, git push).
72
+ */
73
+ requestedTools?: string[];
74
+
75
+ /**
76
+ * Specific files involved or targeted.
77
+ * Examined for risk-path indicators (e.g., .git/, node_modules, production configs).
78
+ */
79
+ requestedFiles?: string[];
80
+
81
+ /**
82
+ * Shape of expected output.
83
+ * E.g., "json", "markdown", "one_line", "full_report"
84
+ */
85
+ expectedOutputShape?: string;
86
+
87
+ /**
88
+ * Complexity hints for the task.
89
+ * E.g., ["multi_step", "cross_file", "ambiguous", "requires_planning"]
90
+ */
91
+ complexityHints?: string[];
92
+
93
+ /**
94
+ * Target worker profile for routing consideration.
95
+ * If omitted, both profiles are evaluated and the best match is returned.
96
+ */
97
+ targetProfile?: WorkerProfile;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Routing Decision Contract
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * The result of a routing classification decision.
106
+ * Always includes a `reason` and a `blockers` list for full explainability.
107
+ */
108
+ export interface RoutingDecision {
109
+ /**
110
+ * The routing verdict.
111
+ * - `route_local` — the task may be delegated to `targetProfile`
112
+ * - `stay_main` — the task must remain on the main agent
113
+ */
114
+ decision: 'route_local' | 'stay_main';
115
+
116
+ /**
117
+ * Which profile the task should be routed to (if decision === 'route_local').
118
+ * Null if decision === 'stay_main'.
119
+ */
120
+ targetProfile: WorkerProfile | null;
121
+
122
+ /**
123
+ * The task classification category that led to this decision.
124
+ */
125
+ classification:
126
+ | 'reader_eligible'
127
+ | 'editor_eligible'
128
+ | 'high_entropy_disallowed'
129
+ | 'ambiguous_scope'
130
+ | 'profile_mismatch'
131
+ | 'deployment_unavailable';
132
+
133
+ /**
134
+ * Human-readable explanation of the routing decision.
135
+ * Must be specific enough that a developer can understand why a task was accepted/rejected.
136
+ */
137
+ reason: string;
138
+
139
+ /**
140
+ * List of specific reasons that blocked routing (if decision === 'stay_main').
141
+ * Empty if decision === 'route_local'.
142
+ */
143
+ blockers: string[];
144
+
145
+ /**
146
+ * Whether a deployment check was performed and whether it passed.
147
+ * Useful for diagnostics when deployment_unavailable is the classification.
148
+ */
149
+ deploymentCheck: {
150
+ performed: boolean;
151
+ profileAvailable: boolean;
152
+ routingEnabled: boolean;
153
+ /** Whether the active checkpoint is currently marked as deployable in the training registry. */
154
+ checkpointDeployable: boolean;
155
+ };
156
+
157
+ /**
158
+ * The active checkpoint ID that would be used for routing (if decision === 'route_local').
159
+ * This is the checkpoint from the deployment registry.
160
+ * Null if decision === 'stay_main' or if no checkpoint is active.
161
+ *
162
+ * USE FOR SHADOW OBSERVATIONS:
163
+ * When routing in shadow mode (checkpoint is in shadow_ready state),
164
+ * the caller should record a shadow observation using this checkpoint ID.
165
+ */
166
+ activeCheckpointId: string | null;
167
+
168
+ /**
169
+ * The promotion state of the active checkpoint.
170
+ * Indicates whether this is a regular deployment or a shadow rollout.
171
+ * Useful for determining whether to record shadow observations.
172
+ */
173
+ activeCheckpointState?: 'promotable' | 'shadow_ready' | 'candidate_only';
174
+
175
+ /**
176
+ * Deprecated: runtime shadow observations are now recorded from real
177
+ * subagent lifecycle hooks instead of from classifyTask().
178
+ */
179
+ shadowObservationId?: string;
180
+
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Keyword Classifiers
185
+ // ---------------------------------------------------------------------------
186
+
187
+ /**
188
+ * Keywords that indicate a task is suitable for `local-reader`.
189
+ * Matched against taskIntent + taskDescription.
190
+ */
191
+ const READER_KEYWORDS = [
192
+ 'read', 'view', 'show', 'get', 'find', 'search', 'grep', 'look',
193
+ 'inspect', 'examine', 'list', 'cat', 'head', 'tail', 'diff',
194
+ 'summary', 'summarize', 'extract', 'parse', 'review',
195
+ 'check', 'verify', 'status', 'describe', 'explain_what',
196
+ 'browse', 'fetch', 'show_content', 'file_content', 'code_read',
197
+ ];
198
+
199
+ /**
200
+ * Keywords that indicate a task is suitable for `local-editor`.
201
+ * Matched against taskIntent + taskDescription.
202
+ */
203
+ const EDITOR_KEYWORDS = [
204
+ 'edit', 'update', 'modify', 'change', 'fix', 'patch', 'replace',
205
+ 'add', 'remove', 'delete', 'insert', 'rewrite', 'refactor',
206
+ 'apply', 'execute', 'run', 'transform', 'convert', 'migrate',
207
+ 'write', 'create_file', 'append', 'touch', 'rename',
208
+ ];
209
+
210
+ /**
211
+ * Keywords that indicate HIGH ENTROPY — tasks that must stay on main agent.
212
+ * These indicate open-ended, multi-step, or ambiguous tasks.
213
+ */
214
+ const HIGH_ENTROPY_KEYWORDS = [
215
+ 'design', 'architect', 'plan', 'strategy', 'roadmap', 'propose',
216
+ 'research', 'investigate', 'explore', 'evaluate', 'compare',
217
+ 'decide', 'choose', 'recommend', 'suggest', 'analyze_tradeoffs',
218
+ 'unclear', 'vague', 'ambiguous', 'open_ended', 'multiple_options',
219
+ 'architecture', 'system_design', 'high_level', 'blueprint',
220
+ 'thinking', 'reasoning', '思考', '分析', '设计',
221
+ ];
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Classification Helpers
225
+ // ---------------------------------------------------------------------------
226
+
227
+ /**
228
+ * Simple case-insensitive keyword match.
229
+ */
230
+ function containsKeyword(text: string | undefined, keywords: string[]): boolean {
231
+ if (!text) return false;
232
+ const lower = text.toLowerCase();
233
+ return keywords.some((kw) => lower.includes(kw));
234
+ }
235
+
236
+ /**
237
+ * Compute a combined text from all input fields for keyword scanning.
238
+ */
239
+ function computeCombinedText(input: RoutingInput): string {
240
+ const parts: string[] = [];
241
+ if (input.taskIntent) parts.push(input.taskIntent);
242
+ if (input.taskDescription) parts.push(input.taskDescription);
243
+ if (input.expectedOutputShape) parts.push(input.expectedOutputShape);
244
+ if (input.complexityHints) parts.push(input.complexityHints.join(' '));
245
+ return parts.join(' ').toLowerCase();
246
+ }
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // Core Classification Logic
250
+ // ---------------------------------------------------------------------------
251
+
252
+ /**
253
+ * Classify the task based on its input fields.
254
+ * Returns a raw classification category (before deployment check).
255
+ */
256
+ function classifyTaskKind(input: RoutingInput): RoutingDecision['classification'] {
257
+ const text = computeCombinedText(input);
258
+ const { taskIntent, taskDescription, requestedFiles, complexityHints } = input;
259
+
260
+ // --- Step 1: High-entropy keyword detection ---
261
+ if (complexityHints?.some((h) =>
262
+ ['multi_step', 'cross_file', 'ambiguous', 'requires_planning', 'open_ended', 'unclear'].includes(h)
263
+ )) {
264
+ return 'high_entropy_disallowed';
265
+ }
266
+
267
+ if (containsKeyword(text, HIGH_ENTROPY_KEYWORDS)) {
268
+ return 'high_entropy_disallowed';
269
+ }
270
+
271
+ if (containsKeyword(taskIntent, ['design', 'architect', 'plan', 'propose']) ||
272
+ containsKeyword(taskDescription, ['design', 'architect', 'plan', 'propose'])) {
273
+ return 'high_entropy_disallowed';
274
+ }
275
+
276
+ // --- Step 2: Reader eligibility ---
277
+ const intentIsReader = containsKeyword(taskIntent, READER_KEYWORDS);
278
+ const descIsReader = containsKeyword(taskDescription, READER_KEYWORDS);
279
+
280
+ if (intentIsReader && (descIsReader || !taskDescription)) {
281
+ return 'reader_eligible';
282
+ }
283
+
284
+ // --- Step 3: Editor eligibility ---
285
+ const uniqueFiles = requestedFiles
286
+ ? [...new Set(requestedFiles.filter((f) => f.trim().length > 0))]
287
+ : [];
288
+ const intentIsEditor = containsKeyword(taskIntent, EDITOR_KEYWORDS);
289
+ const descIsEditor = containsKeyword(taskDescription, EDITOR_KEYWORDS);
290
+
291
+ if (intentIsEditor && (descIsEditor || !taskDescription)) {
292
+ if (uniqueFiles.length >= 4) {
293
+ return 'high_entropy_disallowed';
294
+ }
295
+ return 'editor_eligible';
296
+ }
297
+
298
+ // --- Step 4: Ambiguous scope ---
299
+ if (taskDescription && taskDescription.trim().length > 0) {
300
+ const trimmed = taskDescription.trim();
301
+ if (trimmed.length < 20 || ['todo', 'fix', 'improve', 'change', 'update', 'something'].includes(trimmed.toLowerCase())) {
302
+ return 'ambiguous_scope';
303
+ }
304
+ if (/\b(why|how|should|could|would|what if|should we|whether to)\b/i.test(trimmed)) {
305
+ return 'ambiguous_scope';
306
+ }
307
+ }
308
+
309
+ if (!taskIntent && !taskDescription) {
310
+ return 'ambiguous_scope';
311
+ }
312
+
313
+ return 'ambiguous_scope';
314
+ }
315
+
316
+ /**
317
+ * Build the reason string for a given classification.
318
+ */
319
+ function buildReason(
320
+ classification: RoutingDecision['classification'],
321
+ input: RoutingInput
322
+ ): string {
323
+ const { taskIntent, taskDescription } = input;
324
+
325
+ switch (classification) {
326
+ case 'reader_eligible':
327
+ return `Task "${taskIntent || taskDescription || '(unnamed)'}" is classified as reader_eligible. ` +
328
+ `Keywords indicate focused reading, inspection, or information retrieval. ` +
329
+ `No high-entropy or risk signals detected.`;
330
+
331
+ case 'editor_eligible':
332
+ return `Task "${taskIntent || taskDescription || '(unnamed)'}" is classified as editor_eligible. ` +
333
+ `Keywords indicate bounded editing, modification, or repair. ` +
334
+ `No high-entropy or risk signals detected.`;
335
+
336
+ case 'high_entropy_disallowed': {
337
+ const uniqueFiles = input.requestedFiles
338
+ ? [...new Set(input.requestedFiles.filter((f) => f.trim().length > 0))]
339
+ : [];
340
+ const isLargeScaleEdit = uniqueFiles.length >= 4;
341
+ if (isLargeScaleEdit) {
342
+ return `Task "${taskIntent || taskDescription || '(unnamed)'}" is blocked as high_entropy_disallowed. ` +
343
+ `Editing ${uniqueFiles.length} files simultaneously exceeds the bounded-scope limit for local-editor. ` +
344
+ `Large-scale multi-file edits require the main agent's coordination and risk judgment.`;
345
+ }
346
+ return `Task "${taskIntent || taskDescription || '(unnamed)'}" is blocked as high_entropy_disallowed. ` +
347
+ `Keywords indicate open-ended planning, architecture design, or ambiguous multi-step work. ` +
348
+ `These tasks require the main agent's full reasoning capability.`;
349
+ }
350
+
351
+ case 'ambiguous_scope':
352
+ return `Task "${taskIntent || taskDescription || '(unnamed)'}" is blocked as ambiguous_scope. ` +
353
+ `The task description is too vague, too short, or contains open-ended question words. ` +
354
+ `Main agent must clarify scope before delegation.`;
355
+
356
+ case 'profile_mismatch':
357
+ return `Task profile does not match the requested target profile. ` +
358
+ `The task's natural classification is incompatible with the specified worker profile. ` +
359
+ `Main agent must re-route or choose a compatible profile.`;
360
+
361
+ case 'deployment_unavailable':
362
+ return `No enabled deployment available for routing. ` +
363
+ `Either no checkpoint is bound to the profile, or routing has been disabled. ` +
364
+ `Main agent must handle this task.`;
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Build the blockers list for a given classification.
370
+ */
371
+ function buildBlockers(
372
+ classification: RoutingDecision['classification'],
373
+ input: RoutingInput
374
+ ): string[] {
375
+ switch (classification) {
376
+ case 'reader_eligible':
377
+ return [];
378
+ case 'editor_eligible':
379
+ return [];
380
+ case 'high_entropy_disallowed': {
381
+ const uniqueFiles = input.requestedFiles
382
+ ? [...new Set(input.requestedFiles.filter((f) => f.trim().length > 0))]
383
+ : [];
384
+ const isLargeScaleEdit = uniqueFiles.length >= 4;
385
+ return [
386
+ isLargeScaleEdit
387
+ ? `large-scale multi-file edit detected (${uniqueFiles.length} files): scope too broad for local-editor`
388
+ : 'task contains high-entropy keywords (design/plan/architect/investigate)',
389
+ 'complexity hint indicates multi-step or open-ended work',
390
+ 'main agent required for full reasoning and judgment',
391
+ ];
392
+ }
393
+ case 'ambiguous_scope':
394
+ return [
395
+ 'task description too vague or generic',
396
+ 'task intent not provided or unclear',
397
+ 'open-ended question words detected',
398
+ 'main agent must clarify scope before delegation',
399
+ ];
400
+ case 'profile_mismatch':
401
+ return [
402
+ 'task natural profile incompatible with requested target profile',
403
+ 'main agent must re-route or select a compatible profile',
404
+ ];
405
+
406
+ case 'deployment_unavailable':
407
+ return [
408
+ 'no enabled deployment found for target profile',
409
+ 'routing may be disabled in deployment registry',
410
+ 'main agent must handle task directly',
411
+ ];
412
+ }
413
+ }
414
+
415
+ // ---------------------------------------------------------------------------
416
+ // Public API
417
+ // ---------------------------------------------------------------------------
418
+
419
+ /**
420
+ * Classify a task and produce a routing decision.
421
+ *
422
+ * This is the main entry point for routing policy evaluation.
423
+ * It:
424
+ * 1. Classifies the task kind based on keywords and heuristics
425
+ * 2. Checks deployment availability for the target profile
426
+ * 3. Returns a fully explainable RoutingDecision
427
+ *
428
+ * @param input - The routing input describing the task
429
+ * @param stateDir - Workspace state directory (for deployment registry lookup)
430
+ * @returns RoutingDecision with classification, reason, blockers, and routing verdict
431
+ */
432
+ export function classifyTask(
433
+ input: RoutingInput,
434
+ stateDir: string
435
+ ): RoutingDecision {
436
+ // --- Determine the raw task classification ---
437
+ const classification = classifyTaskKind(input);
438
+
439
+ // --- Determine the target profile ---
440
+ // If input specifies a target, use it. Otherwise, pick based on classification.
441
+ // NOTE: When explicitly specified, we must validate profile-task compatibility below.
442
+ const targetProfile: WorkerProfile | null =
443
+ input.targetProfile ??
444
+ (classification === 'reader_eligible'
445
+ ? 'local-reader'
446
+ : classification === 'editor_eligible'
447
+ ? 'local-editor'
448
+ : null);
449
+
450
+ // --- Profile-task compatibility check ---
451
+ // Only applies when input.targetProfile is EXPLICITLY set.
452
+ // When auto-derived (input.targetProfile is null), compatibility is already
453
+ // guaranteed by the auto-derivation logic above (reader_eligible → local-reader).
454
+ // This check prevents routing a reader task to an editor profile (or vice versa)
455
+ // when the caller explicitly requests the wrong profile.
456
+ const isProfileCompatible =
457
+ input.targetProfile === undefined
458
+ ? true // Auto-derived profile is always compatible by construction
459
+ : targetProfile === 'local-reader'
460
+ ? classification === 'reader_eligible'
461
+ : targetProfile === 'local-editor'
462
+ ? classification === 'editor_eligible'
463
+ : false;
464
+
465
+ // --- Deployment availability check ---
466
+ let deploymentCheck: RoutingDecision['deploymentCheck'] = {
467
+ performed: false,
468
+ profileAvailable: false,
469
+ routingEnabled: false,
470
+ checkpointDeployable: false,
471
+ };
472
+
473
+ if (targetProfile) {
474
+ const deployment = getDeployment(stateDir, targetProfile);
475
+ const activeCheckpointId = deployment?.activeCheckpointId ?? null;
476
+ // Re-check deployability on every routing decision — a checkpoint may have been revoked
477
+ const checkpointDeployable = activeCheckpointId
478
+ ? isCheckpointDeployable(stateDir, activeCheckpointId)
479
+ : false;
480
+ deploymentCheck = {
481
+ performed: true,
482
+ profileAvailable: deployment !== null,
483
+ routingEnabled: isRoutingEnabledForProfile(stateDir, targetProfile),
484
+ checkpointDeployable,
485
+ };
486
+ }
487
+
488
+ // --- Build the decision ---
489
+ const blockers = buildBlockers(classification, input);
490
+ const reason = buildReason(classification, input);
491
+
492
+ // FAIL-CLOSED: route_local only if:
493
+ // 1. Classification is eligible (reader_eligible or editor_eligible)
494
+ // 2. A target profile was identified
495
+ // 3. The task's natural profile is compatible with the target profile
496
+ // 4. Deployment is available and routing is enabled
497
+ const isEligibleForRouting =
498
+ (classification === 'reader_eligible' || classification === 'editor_eligible') &&
499
+ targetProfile !== null &&
500
+ isProfileCompatible &&
501
+ deploymentCheck.routingEnabled;
502
+
503
+ const decision: RoutingDecision['decision'] = isEligibleForRouting
504
+ ? 'route_local'
505
+ : 'stay_main';
506
+
507
+ // Derive the final classification — preserves the root cause of stay_main:
508
+ // - profile_mismatch: task would be eligible but wrong profile requested
509
+ // - deployment_unavailable: eligible and compatible but no routing enabled
510
+ // - raw classification: blocked by high_entropy / risk / ambiguous
511
+ const isEligible = classification === 'reader_eligible' || classification === 'editor_eligible';
512
+ const finalClassification: RoutingDecision['classification'] =
513
+ isEligibleForRouting
514
+ ? classification
515
+ : isEligible && targetProfile !== null && !isProfileCompatible
516
+ ? 'profile_mismatch'
517
+ : isEligible
518
+ ? 'deployment_unavailable'
519
+ : classification;
520
+
521
+ // Build explainability fields specific to the stay_main reason
522
+ let finalReason = reason;
523
+ let finalBlockers = blockers;
524
+
525
+ if (decision === 'stay_main') {
526
+ if (finalClassification === 'profile_mismatch') {
527
+ const wanted = classification === 'reader_eligible' ? 'local-reader' : 'local-editor';
528
+ finalReason = `Task is ${classification} but was explicitly targeted at ${targetProfile}. ` +
529
+ `Routing requires "${wanted}" profile. Ensure the task intent matches the requested profile.`;
530
+ finalBlockers = [
531
+ `profile mismatch: task is ${classification} but targetProfile is ${targetProfile}`,
532
+ `required profile: ${wanted}`,
533
+ ];
534
+ } else if (finalClassification === 'deployment_unavailable') {
535
+ if (!deploymentCheck.performed) {
536
+ finalReason = reason;
537
+ } else if (!deploymentCheck.profileAvailable) {
538
+ finalReason = `Task is ${classification} but no deployment exists for ${targetProfile}. ` +
539
+ `Bind a checkpoint via bindCheckpointToWorkerProfile() and enable routing.`;
540
+ finalBlockers = [`no deployment found for profile: ${targetProfile}`];
541
+ } else if (!deploymentCheck.checkpointDeployable) {
542
+ finalReason = `Task is ${classification} but the active checkpoint has been revoked (no longer deployable). ` +
543
+ `Re-bind a passing checkpoint or re-evaluate the current one.`;
544
+ finalBlockers = [
545
+ `active checkpoint is no longer deployable: ${targetProfile}`,
546
+ 'revoked checkpoints must not be used for routing',
547
+ ];
548
+ } else if (!deploymentCheck.routingEnabled) {
549
+ finalReason = `Task is ${classification} and deployment exists for ${targetProfile} but routing is not enabled. ` +
550
+ `Enable routing via enableRoutingForProfile() in the deployment registry.`;
551
+ finalBlockers = [`routing is disabled for profile: ${targetProfile}`];
552
+ }
553
+ }
554
+ }
555
+
556
+ // --- Get active checkpoint ID and state for shadow observation integration ---
557
+ let activeCheckpointId: string | null = null;
558
+ let activeCheckpointState: 'promotable' | 'shadow_ready' | 'candidate_only' | null = null;
559
+
560
+ if (targetProfile && deploymentCheck.performed) {
561
+ const deployment = getDeployment(stateDir, targetProfile);
562
+ activeCheckpointId = deployment?.activeCheckpointId ?? null;
563
+ if (activeCheckpointId) {
564
+ const promotionState = getPromotionState(stateDir, activeCheckpointId);
565
+ if (promotionState === 'shadow_ready' || promotionState === 'promotable' || promotionState === 'candidate_only') {
566
+ activeCheckpointState = promotionState;
567
+ }
568
+ }
569
+ }
570
+
571
+ return {
572
+ decision,
573
+ targetProfile: decision === 'route_local' ? targetProfile : null,
574
+ classification: finalClassification,
575
+ reason: finalReason,
576
+ blockers: decision === 'stay_main' ? finalBlockers : [],
577
+ deploymentCheck,
578
+ activeCheckpointId,
579
+ activeCheckpointState: activeCheckpointState ?? undefined,
580
+ shadowObservationId: undefined,
581
+ };
582
+ }
583
+
584
+ /**
585
+ * Convenience: check if a specific profile can handle a task.
586
+ * Equivalent to calling classifyTask with targetProfile set.
587
+ */
588
+ export function canRouteToProfile(
589
+ input: RoutingInput,
590
+ stateDir: string,
591
+ profile: WorkerProfile
592
+ ): boolean {
593
+ const decision = classifyTask({ ...input, targetProfile: profile }, stateDir);
594
+ return decision.decision === 'route_local';
595
+ }
596
+
597
+ // ---------------------------------------------------------------------------
598
+ // Read-Only Query Helpers
599
+ // ---------------------------------------------------------------------------
600
+
601
+ /**
602
+ * Check if any local worker routing is currently enabled for any profile.
603
+ */
604
+ export function isAnyLocalRoutingEnabled(stateDir: string): boolean {
605
+ return isRoutingEnabledForProfile(stateDir, 'local-reader') ||
606
+ isRoutingEnabledForProfile(stateDir, 'local-editor');
607
+ }
608
+
609
+ /**
610
+ * List all profiles that currently have routing enabled.
611
+ */
612
+ export function listEnabledProfiles(stateDir: string): WorkerProfile[] {
613
+ const enabled: WorkerProfile[] = [];
614
+ if (isRoutingEnabledForProfile(stateDir, 'local-reader')) enabled.push('local-reader');
615
+ if (isRoutingEnabledForProfile(stateDir, 'local-editor')) enabled.push('local-editor');
616
+ return enabled;
617
+ }