@hassangameryt/freeflow 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -1
  4. package/README.md +150 -155
  5. package/capabilities/cognitive-routing/SKILL.md +229 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +17 -13
  9. package/hooks/freeflow-runtime-context.mjs +393 -159
  10. package/hooks/hooks.json +26 -1
  11. package/hooks/tests/check-runtime-context-hook.sh +223 -54
  12. package/package.json +25 -16
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +236 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +862 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +439 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +126 -0
  18. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  19. package/pi-extension/dist/cognitive-routing/tool.js +211 -0
  20. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  21. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  22. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  23. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  24. package/pi-extension/dist/context-virtualization/state.js +57 -0
  25. package/pi-extension/dist/context-virtualization/types.js +10 -0
  26. package/pi-extension/dist/conversation-history/passages.js +66 -0
  27. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  28. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  29. package/pi-extension/dist/conversation-history/search.js +56 -0
  30. package/pi-extension/dist/conversation-history/types.js +1 -0
  31. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  32. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  33. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  34. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  35. package/pi-extension/dist/freeflow-context/types.js +12 -0
  36. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  37. package/pi-extension/dist/index.js +516 -98
  38. package/pi-extension/dist/runtime/runtime-context.js +318 -271
  39. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  40. package/pi-extension/dist/settings/settings-tui.js +78 -0
  41. package/pi-extension/dist/settings/settings-ui.js +593 -563
  42. package/pi-extension/dist/ui/text-component.js +57 -0
  43. package/pi-extension/src/cognitive-routing/commands.ts +119 -0
  44. package/pi-extension/src/cognitive-routing/config.ts +307 -0
  45. package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
  46. package/pi-extension/src/cognitive-routing/history.ts +638 -0
  47. package/pi-extension/src/cognitive-routing/host.ts +172 -0
  48. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  49. package/pi-extension/src/cognitive-routing/tool.ts +272 -0
  50. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  51. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  52. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  53. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  54. package/pi-extension/src/context-virtualization/state.ts +79 -0
  55. package/pi-extension/src/context-virtualization/types.ts +92 -0
  56. package/pi-extension/src/conversation-history/passages.ts +81 -0
  57. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  58. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  59. package/pi-extension/src/conversation-history/search.ts +79 -0
  60. package/pi-extension/src/conversation-history/types.ts +31 -0
  61. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  62. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  63. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  64. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  65. package/pi-extension/src/freeflow-context/types.ts +70 -0
  66. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  67. package/pi-extension/src/index.ts +553 -101
  68. package/pi-extension/src/runtime/runtime-context.ts +339 -281
  69. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  70. package/pi-extension/src/settings/settings-tui.ts +93 -0
  71. package/pi-extension/src/settings/settings-ui.ts +700 -613
  72. package/pi-extension/src/ui/text-component.ts +66 -0
  73. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  74. package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
  75. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
  76. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  77. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  78. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  79. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
  80. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  81. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  82. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  83. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  84. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  85. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  86. package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
  87. package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
  88. package/runtime/prompts/cognitive-routing.md +9 -0
  89. package/runtime/prompts/context-virtualization.md +3 -0
  90. package/runtime/prompts/conversation-history.md +3 -0
  91. package/runtime/prompts/core.md +13 -0
  92. package/runtime/prompts/interaction-contract.md +7 -0
  93. package/runtime/prompts/skills.md +65 -0
  94. package/skills/action-selection/SKILL.md +91 -0
  95. package/skills/bypass/SKILL.md +3 -2
  96. package/skills/commit-work/SKILL.md +1 -1
  97. package/skills/commit-work/references/staging-decisions.md +1 -1
  98. package/skills/decision-gate/SKILL.md +1 -1
  99. package/skills/design-for-depth/SKILL.md +14 -33
  100. package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
  101. package/skills/design-for-depth/references/interface-design-loop.md +1 -1
  102. package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
  103. package/skills/diagnose-failure/SKILL.md +20 -14
  104. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
  105. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
  106. package/skills/discuss/SKILL.md +34 -22
  107. package/skills/discuss/references/discussion-continuity.md +71 -0
  108. package/skills/evaluate-skill/SKILL.md +5 -5
  109. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
  110. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  111. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  112. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  113. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  114. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  115. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  116. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  117. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  118. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  119. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  120. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  121. package/skills/execute-work/SKILL.md +18 -15
  122. package/skills/execute-work/references/code-practices.md +1 -1
  123. package/skills/execute-work/references/execute-work-edges.md +42 -0
  124. package/skills/finish-branch/SKILL.md +2 -2
  125. package/skills/finish-branch/references/integration-options.md +1 -1
  126. package/skills/handoff/SKILL.md +3 -3
  127. package/skills/launch-work/SKILL.md +1 -1
  128. package/skills/launch-work/references/launch-readiness.md +2 -2
  129. package/skills/migration-work/SKILL.md +2 -2
  130. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  131. package/skills/mode-contract/SKILL.md +32 -10
  132. package/skills/release-work/SKILL.md +1 -1
  133. package/skills/release-work/references/release-evidence.md +1 -1
  134. package/skills/review-artifact/SKILL.md +28 -26
  135. package/skills/review-work/SKILL.md +28 -26
  136. package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
  137. package/skills/review-work/references/security-risk-lens.md +1 -1
  138. package/skills/setup-freeflow/SKILL.md +6 -10
  139. package/skills/setup-freeflow/references/activation-contract.md +5 -10
  140. package/skills/setup-freeflow/references/host-setup.md +3 -7
  141. package/skills/simplify-code/SKILL.md +1 -1
  142. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  143. package/skills/tdd/SKILL.md +18 -38
  144. package/skills/tdd/references/test-design.md +2 -2
  145. package/skills/track-work/SKILL.md +362 -58
  146. package/skills/track-work/scripts/lib/codec.mjs +570 -0
  147. package/skills/track-work/scripts/lib/commands.mjs +568 -0
  148. package/skills/track-work/scripts/lib/contract.mjs +303 -0
  149. package/skills/track-work/scripts/lib/model.mjs +506 -0
  150. package/skills/track-work/scripts/lib/operations.mjs +965 -0
  151. package/skills/track-work/scripts/lib/record-store.mjs +264 -0
  152. package/skills/track-work/scripts/lib/result.mjs +37 -0
  153. package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
  154. package/skills/track-work/scripts/lib/schema.mjs +277 -0
  155. package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
  156. package/skills/track-work/scripts/lib/views.mjs +443 -0
  157. package/skills/track-work/scripts/lib/workspace.mjs +134 -0
  158. package/skills/track-work/scripts/working-record.mjs +122 -0
  159. package/skills/verify-work/SKILL.md +14 -2
  160. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  161. package/skills/verify-work/references/integration-evidence.md +1 -1
  162. package/skills/verify-work/references/performance-evidence.md +1 -1
  163. package/skills/workflow/SKILL.md +105 -53
  164. package/skills/workflow/references/domain-skill-composition.md +22 -42
  165. package/skills/write-plan/SKILL.md +1 -1
  166. package/skills/write-skill/SKILL.md +3 -2
  167. package/skills/write-skill/references/progressive-disclosure.md +17 -3
  168. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  169. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  170. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  171. package/pi-extension/dist/output-router/renderers.js +0 -1023
  172. package/pi-extension/dist/output-router/router-tools.js +0 -554
  173. package/pi-extension/dist/output-router/schemas.js +0 -383
  174. package/pi-extension/dist/output-router/status.js +0 -744
  175. package/pi-extension/dist/output-router/utils.js +0 -502
  176. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  177. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  178. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  179. package/pi-extension/src/output-router/renderers.ts +0 -1108
  180. package/pi-extension/src/output-router/router-tools.ts +0 -579
  181. package/pi-extension/src/output-router/schemas.ts +0 -387
  182. package/pi-extension/src/output-router/status.ts +0 -791
  183. package/pi-extension/src/output-router/utils.ts +0 -554
  184. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  185. package/pi-extension/tests/output-router/search.test.js +0 -722
  186. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  187. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  188. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  189. package/router/dist/benchmarks/benchmarks.js +0 -979
  190. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  191. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  192. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  193. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  194. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  195. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  196. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  197. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  198. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  199. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  200. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  201. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  202. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  203. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  204. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  205. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  206. package/router/dist/config/config.d.ts +0 -103
  207. package/router/dist/config/config.js +0 -632
  208. package/router/dist/config/router-contract.d.ts +0 -10
  209. package/router/dist/config/router-contract.js +0 -54
  210. package/router/dist/config/schema.d.ts +0 -27
  211. package/router/dist/config/schema.js +0 -648
  212. package/router/dist/config/types.d.ts +0 -573
  213. package/router/dist/config/types.js +0 -41
  214. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  215. package/router/dist/evidence/bounded-evidence.js +0 -323
  216. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  217. package/router/dist/evidence/evidence-range-selector.js +0 -232
  218. package/router/dist/evidence/evidence-search.d.ts +0 -27
  219. package/router/dist/evidence/evidence-search.js +0 -490
  220. package/router/dist/evidence/evidence.d.ts +0 -38
  221. package/router/dist/evidence/evidence.js +0 -200
  222. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  223. package/router/dist/evidence/failure-contracts.js +0 -166
  224. package/router/dist/evidence/line-ranges.d.ts +0 -24
  225. package/router/dist/evidence/line-ranges.js +0 -22
  226. package/router/dist/experiments/local-index.d.ts +0 -64
  227. package/router/dist/experiments/local-index.js +0 -581
  228. package/router/dist/index.d.ts +0 -30
  229. package/router/dist/index.js +0 -32
  230. package/router/dist/local/local-traversal.d.ts +0 -18
  231. package/router/dist/local/local-traversal.js +0 -334
  232. package/router/dist/processing/engine.d.ts +0 -132
  233. package/router/dist/processing/engine.js +0 -533
  234. package/router/dist/processing/reducers.d.ts +0 -280
  235. package/router/dist/processing/reducers.js +0 -1673
  236. package/router/dist/processing/renderers.d.ts +0 -31
  237. package/router/dist/processing/renderers.js +0 -195
  238. package/router/dist/processing/scripts.d.ts +0 -91
  239. package/router/dist/processing/scripts.js +0 -386
  240. package/router/dist/repo/repo-traversal.d.ts +0 -18
  241. package/router/dist/repo/repo-traversal.js +0 -245
  242. package/router/dist/routing/observed-reducers.d.ts +0 -41
  243. package/router/dist/routing/observed-reducers.js +0 -661
  244. package/router/dist/routing/observed-routing.d.ts +0 -26
  245. package/router/dist/routing/observed-routing.js +0 -378
  246. package/router/dist/routing/parsers.d.ts +0 -16
  247. package/router/dist/routing/parsers.js +0 -521
  248. package/router/dist/routing/run-filters.d.ts +0 -60
  249. package/router/dist/routing/run-filters.js +0 -238
  250. package/router/dist/routing/run-reducers.d.ts +0 -49
  251. package/router/dist/routing/run-reducers.js +0 -122
  252. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  253. package/router/dist/sandbox/adapter-roots.js +0 -66
  254. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  255. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  256. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  257. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  258. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  259. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  260. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  261. package/router/dist/sandbox/script-sandbox.js +0 -337
  262. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  263. package/router/dist/setup/script-transform-adapters.js +0 -278
  264. package/router/dist/tools/batch.d.ts +0 -33
  265. package/router/dist/tools/batch.js +0 -586
  266. package/router/dist/tools/run.d.ts +0 -62
  267. package/router/dist/tools/run.js +0 -1304
  268. package/router/dist/tools/search.d.ts +0 -66
  269. package/router/dist/tools/search.js +0 -1364
  270. package/router/dist/transform/engine.d.ts +0 -168
  271. package/router/dist/transform/engine.js +0 -2072
  272. package/router/dist/vault/vault-index.d.ts +0 -125
  273. package/router/dist/vault/vault-index.js +0 -416
  274. package/router/dist/vault/vault.d.ts +0 -138
  275. package/router/dist/vault/vault.js +0 -630
  276. package/runtime/interaction-contract.md +0 -16
  277. package/skills/discuss/references/checkpoints.md +0 -66
  278. package/skills/execute-work/references/execution-loop.md +0 -77
  279. package/skills/output-router/SKILL.md +0 -206
  280. package/skills/output-router/references/safety-policy.md +0 -40
  281. package/skills/setup-freeflow/references/output-router-setup.md +0 -110
  282. package/skills/track-work/references/working-record-schema.md +0 -378
  283. package/skills/workflow/references/workflow-loop.md +0 -92
  284. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
  285. /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
@@ -1,791 +0,0 @@
1
- import { constants as fsConstants } from "node:fs";
2
- import { access, readFile, stat } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
4
-
5
- import {
6
- DEFAULT_OBSERVED_ROUTING_CONFIG,
7
- DEFAULT_OUTPUT_ROUTER_ENABLED,
8
- DEFAULT_OUTPUT_ROUTER_PROFILE,
9
- DEFAULT_POST_TOOL_ROUTING,
10
- DEFAULT_ROUTER_THRESHOLDS,
11
- DEFAULT_SCRIPT_TRANSFORM_CONFIG,
12
- DEFAULT_STORAGE_POLICY,
13
- OBSERVED_ROUTING_PERSISTENCE_MODES,
14
- RESERVED_OBSERVED_ROUTING_PERSISTENCE_MODES,
15
- DEFAULT_VAULT_RETENTION,
16
- DEFAULT_VAULT_ROOT,
17
- defaultScriptTransformAdaptersHome,
18
- createLocalVaultIndex,
19
- createVault,
20
- discoverEryxPythonSandboxAdaptersFromEnv,
21
- discoverJqWasmSandboxAdaptersFromEnv,
22
- discoverQuickJsWasiSandboxAdaptersFromEnv,
23
- normalizeFreeflowConfig,
24
- normalizeLocalFreeflowConfig,
25
- probeScriptSandboxAdapters,
26
- } from "../../../router/dist/index.js";
27
- import { VALID_MODES, readCapabilityState, readModeState } from "../runtime/runtime-context.js";
28
-
29
- const STATUS_ACTIONS = new Set(["status", "doctor", "migration"]);
30
- const TOP_LEVEL_CONFIG_KEYS = new Set([
31
- "enabled",
32
- "defaultMode",
33
- "interactionContract",
34
- "skills",
35
- "outputRouter",
36
- "observedRouting",
37
- "scriptTransform",
38
- ]);
39
- const OUTPUT_ROUTER_CONFIG_KEYS = new Set([
40
- "enabled",
41
- "profile",
42
- "postToolRouting",
43
- "storagePolicy",
44
- "largeOutputBytes",
45
- "largeOutputLines",
46
- "thresholds",
47
- "vaultRoot",
48
- "vaultRetentionDays",
49
- "vault",
50
- "generatedPaths",
51
- "noisyCommandHints",
52
- "hints",
53
- "observedRouting",
54
- "scriptTransform",
55
- ]);
56
- const OBSERVED_ROUTING_CONFIG_KEYS = new Set(["enabled", "onRoutingFailure", "mcp", "web", "fetch", "codeSearch"]);
57
- const OBSERVED_ROUTING_PRODUCER_KEYS = new Set(["enabled", "persistence"]);
58
- const OBSERVED_ROUTING_MCP_KEYS = new Set(["servers"]);
59
- const SCRIPT_TRANSFORM_CONFIG_KEYS = new Set([
60
- "enabled",
61
- "sandbox",
62
- "languages",
63
- "network",
64
- "limits",
65
- "rawScriptPersistence",
66
- ]);
67
-
68
- export async function buildFreeflowStatusReport(params = {}, ctx) {
69
- const action = normalizeStatusAction((params as any).action);
70
- const configFile = await readConfigFile(ctx.cwd);
71
- const localConfigFile = await readLocalConfigFile(ctx.cwd);
72
- const normalized = normalizeFreeflowConfig(configFile.parsed);
73
- const localNormalized = normalizeLocalFreeflowConfig(localConfigFile.parsed);
74
- const [modeState, runtimeState] = await Promise.all([readModeState(ctx.cwd), readCapabilityState(ctx.cwd)]);
75
- const effectiveFreeflowConfig = runtimeState.enabled
76
- ? normalized.config
77
- : {
78
- ...normalized.config,
79
- outputRouter: { ...normalized.config.outputRouter, enabled: false },
80
- observedRouting: {
81
- ...normalized.config.observedRouting,
82
- enabled: false,
83
- },
84
- scriptTransform: {
85
- ...normalized.config.scriptTransform,
86
- enabled: false,
87
- },
88
- };
89
- const vault = createVault({
90
- root: effectiveFreeflowConfig.outputRouter.vault.root,
91
- retention: effectiveFreeflowConfig.outputRouter.vault.retention,
92
- });
93
- const configWarnings = [...normalized.warnings];
94
- const localConfigWarnings = [...localNormalized.warnings];
95
-
96
- if (!runtimeState.repositoryConfigured && configFile.exists) {
97
- configWarnings.unshift(
98
- `.freeflow/config.json is invalid; Freeflow runtime is inactive. ${runtimeState.parseError ?? configFile.parseError ?? "Unknown config error."}`,
99
- );
100
- }
101
- if (
102
- isRecord(configFile.parsed) &&
103
- configFile.parsed.defaultMode !== undefined &&
104
- !VALID_MODES.has(configFile.parsed.defaultMode)
105
- ) {
106
- configWarnings.push(`Invalid defaultMode=${JSON.stringify(configFile.parsed.defaultMode)}; using workflow.`);
107
- }
108
- if (runtimeState.localConfigExists && !runtimeState.localConfigValid) {
109
- localConfigWarnings.unshift(
110
- `.freeflow/local.json is invalid; Freeflow runtime is inactive. ${runtimeState.localConfigParseError ?? localConfigFile.parseError ?? "Unknown local config error."}`,
111
- );
112
- }
113
-
114
- const scriptSandboxAdapters = [
115
- ...(await discoverQuickJsWasiSandboxAdaptersFromEnv()),
116
- ...(await discoverJqWasmSandboxAdaptersFromEnv()),
117
- ...(await discoverEryxPythonSandboxAdaptersFromEnv()),
118
- ];
119
- const [vaultWritability, vaultIndex, scriptSandbox] = await Promise.all([
120
- inspectVaultWritability(vault.root),
121
- inspectVaultIndex(vault),
122
- probeScriptSandboxAdapters({
123
- config: effectiveFreeflowConfig.scriptTransform,
124
- adapters: scriptSandboxAdapters,
125
- }),
126
- ]);
127
- const migration = migrationReport(configFile.parsed);
128
-
129
- return {
130
- toolStatus: "ok",
131
- action,
132
- generatedAt: new Date().toISOString(),
133
- configPath: configFile.path,
134
- configExists: configFile.exists,
135
- configValid: runtimeState.configValid,
136
- repositoryConfigValid: runtimeState.repositoryConfigured,
137
- localConfigPath: localConfigFile.path,
138
- localConfigExists: localConfigFile.exists,
139
- localConfigValid: runtimeState.localConfigValid,
140
- configuration: {
141
- repository: {
142
- path: configFile.path,
143
- exists: configFile.exists,
144
- valid: runtimeState.repositoryConfigured,
145
- error: runtimeState.repositoryConfigured ? null : (runtimeState.parseError ?? configFile.parseError),
146
- },
147
- personal: {
148
- path: localConfigFile.path,
149
- exists: localConfigFile.exists,
150
- valid: runtimeState.localConfigValid,
151
- error: runtimeState.localConfigParseError,
152
- },
153
- sources: runtimeState.configSources,
154
- },
155
- mode: {
156
- ...modeState,
157
- active: modeState.active,
158
- resolvedMode: modeState.resolvedMode,
159
- defaultModeSource: runtimeState.configSources.defaultMode,
160
- },
161
- effectiveConfig: {
162
- configured: runtimeState.configured,
163
- enabled: runtimeState.enabled,
164
- defaultMode: runtimeState.defaultMode,
165
- sources: runtimeState.configSources,
166
- interactionContract: runtimeState.interactionContract,
167
- skills: runtimeState.skills,
168
- outputRouter: {
169
- enabled: effectiveFreeflowConfig.outputRouter.enabled,
170
- profile: effectiveFreeflowConfig.outputRouter.profile,
171
- postToolRouting: effectiveFreeflowConfig.outputRouter.postToolRouting,
172
- storagePolicy: effectiveFreeflowConfig.outputRouter.storagePolicy,
173
- thresholds: effectiveFreeflowConfig.outputRouter.thresholds,
174
- vault: effectiveFreeflowConfig.outputRouter.vault,
175
- hints: effectiveFreeflowConfig.outputRouter.hints ?? {},
176
- observedRouting: effectiveFreeflowConfig.observedRouting,
177
- scriptTransform: effectiveFreeflowConfig.scriptTransform,
178
- },
179
- },
180
- effectiveLocalConfig: localNormalized.config,
181
- effectiveDefaults: {
182
- enabled: true,
183
- interactionContract: true,
184
- skills: { enabled: true },
185
- outputRouter: {
186
- enabled: DEFAULT_OUTPUT_ROUTER_ENABLED,
187
- profile: DEFAULT_OUTPUT_ROUTER_PROFILE,
188
- postToolRouting: DEFAULT_POST_TOOL_ROUTING,
189
- storagePolicy: DEFAULT_STORAGE_POLICY,
190
- thresholds: DEFAULT_ROUTER_THRESHOLDS,
191
- vaultRoot: DEFAULT_VAULT_ROOT,
192
- vaultRetention: DEFAULT_VAULT_RETENTION,
193
- observedRouting: DEFAULT_OBSERVED_ROUTING_CONFIG,
194
- scriptTransform: DEFAULT_SCRIPT_TRANSFORM_CONFIG,
195
- },
196
- },
197
- vault: {
198
- root: vault.root,
199
- configuredRoot: effectiveFreeflowConfig.outputRouter.vault.root,
200
- retention: effectiveFreeflowConfig.outputRouter.vault.retention,
201
- writability: vaultWritability,
202
- },
203
- vaultIndex,
204
- observedRouting: observedRoutingStatus(effectiveFreeflowConfig.observedRouting),
205
- scriptTransform: scriptTransformStatus(effectiveFreeflowConfig.scriptTransform, scriptSandbox),
206
- processing: processingStatus(localNormalized.config),
207
- recoverabilityDefaults: {
208
- freeflowRun:
209
- "hybrid-dedupe command capture: exact when exactness-sensitive or duplicate recovery points to a prior exact outputId; small non-sensitive successes may be metadata-only",
210
- observedRouting:
211
- "exact raw recovery for enabled observed producers when exact persistence is configured; metadata-only stores no raw stream",
212
- freeflowTransform:
213
- "deterministic transform stores exact transformed-output recovery with source lineage when persisted; script transform is disabled by default and requires an approved sandbox adapter",
214
- nativeSafetyNet:
215
- "off by default; optional native read/bash safety-net only when outputRouter.postToolRouting is safety-net",
216
- },
217
- configWarnings,
218
- localConfigWarnings,
219
- staleConfig: migration.recommendations,
220
- migration,
221
- };
222
- }
223
-
224
- function normalizeStatusAction(value) {
225
- if (typeof value === "string" && STATUS_ACTIONS.has(value)) {
226
- return value;
227
- }
228
- return "status";
229
- }
230
-
231
- async function readConfigFile(cwd) {
232
- const path = join(cwd, ".freeflow/config.json");
233
- try {
234
- const raw = await readFile(path, "utf8");
235
- try {
236
- const parsed = JSON.parse(raw);
237
- return {
238
- path,
239
- exists: true,
240
- raw,
241
- parsed: isRecord(parsed) ? parsed : {},
242
- parseError: null,
243
- };
244
- } catch (error) {
245
- const message = error instanceof Error ? error.message : String(error);
246
- return { path, exists: true, raw, parsed: {}, parseError: message };
247
- }
248
- } catch {
249
- return { path, exists: false, raw: null, parsed: {}, parseError: null };
250
- }
251
- }
252
-
253
- async function readLocalConfigFile(cwd) {
254
- const path = join(cwd, ".freeflow/local.json");
255
- try {
256
- const raw = await readFile(path, "utf8");
257
- try {
258
- const parsed = JSON.parse(raw);
259
- return {
260
- path,
261
- exists: true,
262
- raw,
263
- parsed: isRecord(parsed) ? parsed : {},
264
- parseError: null,
265
- };
266
- } catch (error) {
267
- const message = error instanceof Error ? error.message : String(error);
268
- return { path, exists: true, raw, parsed: {}, parseError: message };
269
- }
270
- } catch {
271
- return { path, exists: false, raw: null, parsed: {}, parseError: null };
272
- }
273
- }
274
-
275
- async function inspectVaultWritability(root) {
276
- try {
277
- const rootStats = await stat(root);
278
- if (!rootStats.isDirectory()) {
279
- return {
280
- status: "not_directory",
281
- detail: "Vault root exists but is not a directory.",
282
- };
283
- }
284
- await access(root, fsConstants.W_OK | fsConstants.X_OK);
285
- return { status: "writable", detail: "Vault root exists and is writable." };
286
- } catch (error) {
287
- const code = error && typeof error === "object" ? (error as any).code : undefined;
288
- if (code === "ENOENT") {
289
- return inspectMissingVaultRoot(root);
290
- }
291
- if (code === "EACCES" || code === "EPERM") {
292
- return {
293
- status: "not_writable",
294
- detail: `Vault root exists but is not writable (${code}).`,
295
- };
296
- }
297
- return {
298
- status: "unknown",
299
- detail: `Could not determine vault writability (${code ?? "unknown"}).`,
300
- };
301
- }
302
- }
303
-
304
- async function inspectVaultIndex(vault) {
305
- try {
306
- return await createLocalVaultIndex(vault).status();
307
- } catch (error) {
308
- const message = error instanceof Error ? error.message : String(error);
309
- return {
310
- engine: "local-json-sidecar",
311
- root: `${vault.root}/index/v1`,
312
- available: false,
313
- degraded: true,
314
- stale: true,
315
- rebuildRecommended: true,
316
- entryCount: 0,
317
- textEntryCount: 0,
318
- metadataOnlyEntryCount: 0,
319
- outputCount: 0,
320
- lastError: message,
321
- };
322
- }
323
- }
324
-
325
- async function inspectMissingVaultRoot(root) {
326
- const ancestor = await nearestExistingAncestor(dirname(root));
327
- if (!ancestor) {
328
- return {
329
- status: "missing_ancestor_unavailable",
330
- detail: "Vault root does not exist and no existing writable ancestor could be found. No directory was created.",
331
- };
332
- }
333
-
334
- if (!ancestor.stats.isDirectory()) {
335
- return {
336
- status: "missing_ancestor_unavailable",
337
- detail: `Nearest existing ancestor is not a directory: ${ancestor.path}. No directory was created.`,
338
- };
339
- }
340
-
341
- try {
342
- await access(ancestor.path, fsConstants.W_OK | fsConstants.X_OK);
343
- return {
344
- status: "missing_ancestor_writable",
345
- detail: `Vault root does not exist; nearest existing ancestor is writable: ${ancestor.path}. Recursive vault creation should be possible. No directory was created.`,
346
- };
347
- } catch (error) {
348
- const code = error && typeof error === "object" ? (error as any).code : undefined;
349
- return {
350
- status: "missing_ancestor_unavailable",
351
- detail: `Vault root does not exist and nearest existing ancestor is not writable: ${ancestor.path} (${code ?? "unknown"}). No directory was created.`,
352
- };
353
- }
354
- }
355
-
356
- async function nearestExistingAncestor(startPath) {
357
- let current = startPath;
358
- while (true) {
359
- try {
360
- return { path: current, stats: await stat(current) };
361
- } catch (error) {
362
- const code = error && typeof error === "object" ? (error as any).code : undefined;
363
- if (code !== "ENOENT") {
364
- return null;
365
- }
366
- const parent = dirname(current);
367
- if (parent === current) {
368
- return null;
369
- }
370
- current = parent;
371
- }
372
- }
373
- }
374
-
375
- function scriptTransformStatus(config, sandboxReport) {
376
- const adapterHome = defaultScriptTransformAdaptersHome();
377
- return {
378
- enabled: config.enabled,
379
- sandbox: config.sandbox,
380
- adapterAvailable: sandboxReport.adapterAvailable,
381
- adapterStatus: sandboxReport.adapterStatus,
382
- adapterContractVersion: sandboxReport.contractVersion,
383
- configuredLanguages: sandboxReport.configuredLanguages,
384
- availableLanguages: sandboxReport.availableLanguages,
385
- unavailableLanguages: sandboxReport.unavailableLanguages,
386
- registeredAdapters: sandboxReport.registeredAdapters,
387
- requiredProofs: sandboxReport.requiredProofs,
388
- candidateMechanisms: sandboxReport.candidateMechanisms,
389
- network: config.network,
390
- limits: config.limits,
391
- rawScriptPersistence: config.rawScriptPersistence,
392
- executionStatus:
393
- config.enabled && sandboxReport.adapterAvailable
394
- ? "available"
395
- : config.enabled
396
- ? "adapter_unavailable"
397
- : "disabled",
398
- adapterHome,
399
- setupCommand:
400
- "node <plugin-root>/router/dist/setup/script-transform-adapters.js install --config .freeflow/config.json",
401
- notes: [
402
- "Script transform is disabled until setup/user config opts in with outputRouter.scriptTransform.enabled=true.",
403
- "Setup can install proof-backed adapters into a user-global Freeflow cache and enable only languages that pass sandbox probes.",
404
- "No unsandboxed fallback is allowed; script code is not executed without an approved sandbox adapter.",
405
- "Raw script text is not persisted by default.",
406
- `Global adapter cache: ${adapterHome}. Custom roots may use FREEFLOW_QUICKJS_WASI_ROOT, FREEFLOW_JQ_WASM_ROOT, or FREEFLOW_ERYX_ROOT.`,
407
- ...sandboxReport.notes,
408
- ],
409
- };
410
- }
411
-
412
- function processingStatus(localConfig) {
413
- const enabled = localConfig.processing.unsafeUnsandboxed.enabled;
414
- return {
415
- unsafeUnsandboxed: {
416
- enabled,
417
- source: ".freeflow/local.json",
418
- status: enabled ? "enabled_unsafe" : "disabled",
419
- notes: enabled
420
- ? [
421
- "Unsafe unsandboxed processing is enabled only by local config for this checkout.",
422
- "Every unsafe execution result must be labeled unsafe/unsandboxed and must not claim sandbox/read-only/network-off execution.",
423
- ]
424
- : [
425
- "Unsafe unsandboxed processing is disabled. Shared .freeflow/config.json cannot enable it.",
426
- "Enable only in local-only .freeflow/local.json after accepting the risk.",
427
- ],
428
- },
429
- };
430
- }
431
-
432
- function observedRoutingStatus(config) {
433
- const servers = Object.entries((config.mcp?.servers ?? {}) as Record<string, any>).map(([id, server]) => ({
434
- id,
435
- enabled: server.enabled,
436
- persistence: server.persistence,
437
- }));
438
-
439
- return {
440
- enabled: config.enabled,
441
- onRoutingFailure: config.onRoutingFailure,
442
- host: {
443
- name: "pi",
444
- outputReplacement: "available",
445
- reason:
446
- "Pi tool_result hooks can modify tool results; observed routing remains controlled by explicit producer/server config.",
447
- },
448
- mcp: {
449
- configuredServerCount: servers.length,
450
- servers,
451
- },
452
- web: config.web,
453
- fetch: config.fetch,
454
- codeSearch: config.codeSearch,
455
- persistenceModes: [...OBSERVED_ROUTING_PERSISTENCE_MODES],
456
- unsupportedPersistenceModes: [...RESERVED_OBSERVED_ROUTING_PERSISTENCE_MODES],
457
- notes: [
458
- "Observed routing is off unless outputRouter.enabled, outputRouter.observedRouting.enabled, and the individual producer/server are enabled.",
459
- "redacted persistence is reserved for future work; unsupported redacted config falls back to metadata-only.",
460
- ],
461
- };
462
- }
463
-
464
- function migrationReport(rawConfig) {
465
- const recommendations = collectMigrationRecommendations(rawConfig);
466
- return {
467
- applied: false,
468
- requiresConfirmation: recommendations.length > 0,
469
- recommendations,
470
- note:
471
- recommendations.length > 0
472
- ? "Recommendations are informational only. Freeflow status/doctor does not rewrite .freeflow/config.json without explicit confirmation."
473
- : "No migration recommendations detected.",
474
- };
475
- }
476
-
477
- function collectMigrationRecommendations(rawConfig) {
478
- const recommendations = [];
479
- if (!isRecord(rawConfig)) {
480
- return recommendations;
481
- }
482
-
483
- for (const key of Object.keys(rawConfig)) {
484
- if (!TOP_LEVEL_CONFIG_KEYS.has(key)) {
485
- recommendations.push(
486
- recommendation(key, "review", "Unrecognized top-level Freeflow config key; review before migrating."),
487
- );
488
- }
489
- }
490
-
491
- collectOutputRouterRecommendations(rawConfig.outputRouter, recommendations);
492
- if (rawConfig.observedRouting !== undefined) {
493
- recommendations.push(
494
- recommendation("observedRouting", "move", "Move top-level observedRouting under outputRouter.observedRouting."),
495
- );
496
- collectObservedRoutingRecommendations(rawConfig.observedRouting, recommendations, "observedRouting");
497
- }
498
- if (rawConfig.scriptTransform !== undefined) {
499
- recommendations.push(
500
- recommendation("scriptTransform", "move", "Move top-level scriptTransform under outputRouter.scriptTransform."),
501
- );
502
- collectScriptTransformRecommendations(rawConfig.scriptTransform, recommendations, "scriptTransform");
503
- }
504
- return recommendations;
505
- }
506
-
507
- function collectOutputRouterRecommendations(value, recommendations) {
508
- if (value === undefined) {
509
- return;
510
- }
511
- if (!isRecord(value)) {
512
- recommendations.push(
513
- recommendation("outputRouter", "fix", "Expected object; remove or rewrite invalid outputRouter config."),
514
- );
515
- return;
516
- }
517
-
518
- for (const key of Object.keys(value)) {
519
- if (!OUTPUT_ROUTER_CONFIG_KEYS.has(key)) {
520
- recommendations.push(
521
- recommendation(`outputRouter.${key}`, "review", "Unrecognized outputRouter key; review before migrating."),
522
- );
523
- }
524
- }
525
-
526
- addDefaultRecommendation(recommendations, "outputRouter.enabled", value.enabled, DEFAULT_OUTPUT_ROUTER_ENABLED);
527
- addDefaultRecommendation(recommendations, "outputRouter.profile", value.profile, DEFAULT_OUTPUT_ROUTER_PROFILE);
528
- addDefaultRecommendation(
529
- recommendations,
530
- "outputRouter.postToolRouting",
531
- value.postToolRouting,
532
- DEFAULT_POST_TOOL_ROUTING,
533
- );
534
- addDefaultRecommendation(recommendations, "outputRouter.storagePolicy", value.storagePolicy, DEFAULT_STORAGE_POLICY);
535
- collectThresholdRecommendations(value.thresholds, recommendations);
536
- collectVaultRecommendations(value.vault, recommendations);
537
- collectHintsRecommendations(value.hints, recommendations);
538
- addLegacyMoveOrDefaultRecommendation(
539
- recommendations,
540
- "outputRouter.largeOutputBytes",
541
- value.largeOutputBytes,
542
- DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
543
- "outputRouter.thresholds.largeOutputBytes",
544
- );
545
- addLegacyMoveOrDefaultRecommendation(
546
- recommendations,
547
- "outputRouter.largeOutputLines",
548
- value.largeOutputLines,
549
- DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
550
- "outputRouter.thresholds.largeOutputLines",
551
- );
552
- addLegacyMoveOrDefaultRecommendation(
553
- recommendations,
554
- "outputRouter.vaultRoot",
555
- value.vaultRoot,
556
- DEFAULT_VAULT_ROOT,
557
- "outputRouter.vault.root",
558
- );
559
- addLegacyMoveOrDefaultRecommendation(
560
- recommendations,
561
- "outputRouter.vaultRetentionDays",
562
- value.vaultRetentionDays,
563
- DEFAULT_VAULT_RETENTION.ttlDays,
564
- "outputRouter.vault.retention.ttlDays",
565
- );
566
- collectObservedRoutingRecommendations(value.observedRouting, recommendations, "outputRouter.observedRouting");
567
- collectScriptTransformRecommendations(value.scriptTransform, recommendations, "outputRouter.scriptTransform");
568
-
569
- if (Object.keys(value).length === 0) {
570
- recommendations.push(
571
- recommendation("outputRouter", "remove", "Empty outputRouter object can be removed; built-in defaults apply."),
572
- );
573
- }
574
- }
575
-
576
- function collectThresholdRecommendations(value, recommendations) {
577
- if (value === undefined) return;
578
- if (!isRecord(value)) {
579
- recommendations.push(
580
- recommendation("outputRouter.thresholds", "fix", "Expected object with largeOutputBytes and largeOutputLines."),
581
- );
582
- return;
583
- }
584
- addDefaultRecommendation(
585
- recommendations,
586
- "outputRouter.thresholds.largeOutputBytes",
587
- value.largeOutputBytes,
588
- DEFAULT_ROUTER_THRESHOLDS.largeOutputBytes,
589
- );
590
- addDefaultRecommendation(
591
- recommendations,
592
- "outputRouter.thresholds.largeOutputLines",
593
- value.largeOutputLines,
594
- DEFAULT_ROUTER_THRESHOLDS.largeOutputLines,
595
- );
596
- }
597
-
598
- function collectVaultRecommendations(value, recommendations) {
599
- if (value === undefined) return;
600
- if (!isRecord(value)) {
601
- recommendations.push(recommendation("outputRouter.vault", "fix", "Expected object with root and retention."));
602
- return;
603
- }
604
- addDefaultRecommendation(recommendations, "outputRouter.vault.root", value.root, DEFAULT_VAULT_ROOT);
605
- const retention = value.retention;
606
- if (isRecord(retention)) {
607
- addDefaultRecommendation(
608
- recommendations,
609
- "outputRouter.vault.retention.strategy",
610
- retention.strategy,
611
- DEFAULT_VAULT_RETENTION.strategy,
612
- );
613
- addDefaultRecommendation(
614
- recommendations,
615
- "outputRouter.vault.retention.ttlDays",
616
- retention.ttlDays,
617
- DEFAULT_VAULT_RETENTION.ttlDays,
618
- );
619
- } else if (retention !== undefined) {
620
- recommendations.push(recommendation("outputRouter.vault.retention", "fix", "Expected retention object."));
621
- }
622
- }
623
-
624
- function collectHintsRecommendations(value, recommendations) {
625
- if (value === undefined) return;
626
- if (!isRecord(value)) {
627
- recommendations.push(
628
- recommendation(
629
- "outputRouter.hints",
630
- "fix",
631
- "Expected object with generatedPathGlobs/noisyCommandPatterns arrays.",
632
- ),
633
- );
634
- }
635
- }
636
-
637
- function collectScriptTransformRecommendations(value, recommendations, path) {
638
- if (value === undefined) {
639
- return;
640
- }
641
- if (!isRecord(value)) {
642
- recommendations.push(recommendation(path, "fix", `Expected object; remove or rewrite invalid ${path} config.`));
643
- return;
644
- }
645
-
646
- for (const key of Object.keys(value)) {
647
- if (!SCRIPT_TRANSFORM_CONFIG_KEYS.has(key)) {
648
- recommendations.push(
649
- recommendation(`${path}.${key}`, "review", "Unrecognized scriptTransform key; review before migrating."),
650
- );
651
- }
652
- }
653
-
654
- addDefaultRecommendation(recommendations, `${path}.enabled`, value.enabled, DEFAULT_SCRIPT_TRANSFORM_CONFIG.enabled);
655
- addDefaultRecommendation(recommendations, `${path}.sandbox`, value.sandbox, DEFAULT_SCRIPT_TRANSFORM_CONFIG.sandbox);
656
- addDefaultRecommendation(recommendations, `${path}.network`, value.network, DEFAULT_SCRIPT_TRANSFORM_CONFIG.network);
657
- addDefaultRecommendation(
658
- recommendations,
659
- `${path}.rawScriptPersistence`,
660
- value.rawScriptPersistence,
661
- DEFAULT_SCRIPT_TRANSFORM_CONFIG.rawScriptPersistence,
662
- );
663
-
664
- if (Object.keys(value).length === 0) {
665
- recommendations.push(
666
- recommendation(
667
- path,
668
- "remove",
669
- "Empty scriptTransform object can be removed; script transform is disabled by default.",
670
- ),
671
- );
672
- }
673
- }
674
-
675
- function collectObservedRoutingRecommendations(value, recommendations, path) {
676
- if (value === undefined) {
677
- return;
678
- }
679
- if (!isRecord(value)) {
680
- recommendations.push(recommendation(path, "fix", `Expected object; remove or rewrite invalid ${path} config.`));
681
- return;
682
- }
683
-
684
- for (const key of Object.keys(value)) {
685
- if (!OBSERVED_ROUTING_CONFIG_KEYS.has(key)) {
686
- recommendations.push(
687
- recommendation(`${path}.${key}`, "review", "Unrecognized observedRouting key; review before migrating."),
688
- );
689
- }
690
- }
691
-
692
- addDefaultRecommendation(recommendations, `${path}.enabled`, value.enabled, DEFAULT_OBSERVED_ROUTING_CONFIG.enabled);
693
- addDefaultRecommendation(
694
- recommendations,
695
- `${path}.onRoutingFailure`,
696
- value.onRoutingFailure,
697
- DEFAULT_OBSERVED_ROUTING_CONFIG.onRoutingFailure,
698
- );
699
- collectObservedMcpRecommendations(value.mcp, recommendations, `${path}.mcp`);
700
- collectObservedProducerRecommendations(value.web, `${path}.web`, recommendations);
701
- collectObservedProducerRecommendations(value.fetch, `${path}.fetch`, recommendations);
702
- collectObservedProducerRecommendations(value.codeSearch, `${path}.codeSearch`, recommendations);
703
-
704
- if (Object.keys(value).length === 0) {
705
- recommendations.push(
706
- recommendation(
707
- path,
708
- "remove",
709
- "Empty observedRouting object can be removed; observed routing is off by default.",
710
- ),
711
- );
712
- }
713
- }
714
-
715
- function collectObservedMcpRecommendations(value, recommendations, path) {
716
- if (value === undefined) {
717
- return;
718
- }
719
- if (!isRecord(value)) {
720
- recommendations.push(recommendation(path, "fix", "Expected object with explicit servers."));
721
- return;
722
- }
723
-
724
- for (const key of Object.keys(value)) {
725
- if (!OBSERVED_ROUTING_MCP_KEYS.has(key)) {
726
- recommendations.push(
727
- recommendation(`${path}.${key}`, "review", "Unrecognized observedRouting.mcp key; review before migrating."),
728
- );
729
- }
730
- }
731
-
732
- if (value.servers !== undefined && !isRecord(value.servers)) {
733
- recommendations.push(recommendation(`${path}.servers`, "fix", "Expected object keyed by MCP server id."));
734
- }
735
- }
736
-
737
- function collectObservedProducerRecommendations(value, path, recommendations) {
738
- if (value === undefined) {
739
- return;
740
- }
741
- if (!isRecord(value)) {
742
- recommendations.push(recommendation(path, "fix", "Expected object with enabled and persistence."));
743
- return;
744
- }
745
-
746
- for (const key of Object.keys(value)) {
747
- if (!OBSERVED_ROUTING_PRODUCER_KEYS.has(key)) {
748
- recommendations.push(
749
- recommendation(
750
- `${path}.${key}`,
751
- "review",
752
- "Unrecognized observed routing producer key; review before migrating.",
753
- ),
754
- );
755
- }
756
- }
757
- }
758
-
759
- function addDefaultRecommendation(recommendations, path, value, defaultValue) {
760
- if (value === undefined) {
761
- return;
762
- }
763
- if (JSON.stringify(value) === JSON.stringify(defaultValue)) {
764
- recommendations.push(
765
- recommendation(
766
- path,
767
- "remove",
768
- `Explicit default value ${JSON.stringify(defaultValue)} can be removed unless it records an intentional override.`,
769
- ),
770
- );
771
- }
772
- }
773
-
774
- function addLegacyMoveOrDefaultRecommendation(recommendations, path, value, defaultValue, targetPath) {
775
- if (value === undefined) {
776
- return;
777
- }
778
- if (JSON.stringify(value) === JSON.stringify(defaultValue)) {
779
- addDefaultRecommendation(recommendations, path, value, defaultValue);
780
- return;
781
- }
782
- recommendations.push(recommendation(path, "move", `Move legacy ${path} to ${targetPath}.`));
783
- }
784
-
785
- function recommendation(path, action, message) {
786
- return { path, action, message };
787
- }
788
-
789
- function isRecord(value) {
790
- return typeof value === "object" && value !== null && !Array.isArray(value);
791
- }