@hassangameryt/freeflow 0.2.0 → 0.4.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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +4 -4
- package/{plugins/freeflow/.claude-plugin → .claude-plugin}/plugin.json +5 -2
- package/{plugins/freeflow/.codex-plugin → .codex-plugin}/plugin.json +8 -9
- package/README.md +194 -127
- package/assets/feedback-loop.png +0 -0
- package/assets/freeflow-plugin-icon.png +0 -0
- package/assets/freeflow_workflow_map.png +0 -0
- package/assets/workflow.png +0 -0
- package/command-surface.json +129 -0
- package/hooks/freeflow-runtime-context.mjs +419 -0
- package/hooks/tests/check-runtime-context-hook.sh +223 -0
- package/package.json +41 -22
- package/pi-extension/dist/index.js +269 -0
- package/pi-extension/dist/integrations/mcp-config.js +29 -0
- package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
- package/pi-extension/dist/output-router/native-safety-net.js +216 -0
- package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
- package/pi-extension/dist/output-router/renderers.js +1023 -0
- package/pi-extension/dist/output-router/router-tools.js +554 -0
- package/pi-extension/dist/output-router/schemas.js +383 -0
- package/pi-extension/dist/output-router/status.js +744 -0
- package/pi-extension/dist/output-router/utils.js +502 -0
- package/pi-extension/dist/runtime/runtime-context.js +798 -0
- package/pi-extension/dist/settings/settings-tui.js +314 -0
- package/pi-extension/dist/settings/settings-ui.js +1487 -0
- package/pi-extension/freeflow/index.js +1 -0
- package/pi-extension/src/index.ts +295 -0
- package/pi-extension/src/integrations/mcp-config.ts +32 -0
- package/pi-extension/src/output-router/host-producer-identification.ts +256 -0
- package/pi-extension/src/output-router/native-safety-net.ts +248 -0
- package/pi-extension/src/output-router/observed-tool-routing.ts +134 -0
- package/pi-extension/src/output-router/renderers.ts +1108 -0
- package/pi-extension/src/output-router/router-tools.ts +579 -0
- package/pi-extension/src/output-router/schemas.ts +387 -0
- package/pi-extension/src/output-router/status.ts +791 -0
- package/pi-extension/src/output-router/utils.ts +554 -0
- package/pi-extension/src/runtime/runtime-context.ts +895 -0
- package/pi-extension/src/settings/settings-tui.ts +408 -0
- package/pi-extension/src/settings/settings-ui.ts +1730 -0
- package/pi-extension/tests/integration/pi-extension.test.js +4018 -0
- package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
- package/pi-extension/tests/output-router/search.test.js +722 -0
- package/pi-extension/tests/settings/settings-tui.test.js +243 -0
- package/pi-extension/tsconfig.json +16 -0
- package/{plugins/freeflow/router/dist → router/dist/benchmarks}/benchmark-harness.d.ts +16 -13
- package/router/dist/benchmarks/benchmark-harness.js +92 -0
- package/router/dist/benchmarks/benchmarks.d.ts +109 -0
- package/router/dist/benchmarks/benchmarks.js +979 -0
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +95 -0
- package/router/dist/benchmarks/codex-qa-benchmarks.js +500 -0
- package/router/dist/benchmarks/command-benchmarks.d.ts +143 -0
- package/router/dist/benchmarks/command-benchmarks.js +698 -0
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +82 -0
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +773 -0
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +132 -0
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1380 -0
- package/router/dist/benchmarks/context-saving-baseline.d.ts +60 -0
- package/router/dist/benchmarks/context-saving-baseline.js +470 -0
- package/router/dist/benchmarks/index-benchmarks.d.ts +103 -0
- package/router/dist/benchmarks/index-benchmarks.js +862 -0
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +101 -0
- package/router/dist/benchmarks/storage-policy-benchmarks.js +618 -0
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +56 -0
- package/router/dist/benchmarks/vault-index-benchmarks.js +322 -0
- package/router/dist/config/config.d.ts +103 -0
- package/router/dist/config/config.js +632 -0
- package/{plugins/freeflow/router/dist → router/dist/config}/router-contract.d.ts +2 -2
- package/router/dist/config/router-contract.js +54 -0
- package/{plugins/freeflow/router/dist → router/dist/config}/schema.d.ts +19 -10
- package/router/dist/config/schema.js +648 -0
- package/router/dist/config/types.d.ts +573 -0
- package/router/dist/config/types.js +41 -0
- package/router/dist/evidence/bounded-evidence.d.ts +40 -0
- package/router/dist/evidence/bounded-evidence.js +323 -0
- package/router/dist/evidence/evidence-range-selector.d.ts +24 -0
- package/router/dist/evidence/evidence-range-selector.js +232 -0
- package/router/dist/evidence/evidence-search.d.ts +27 -0
- package/router/dist/evidence/evidence-search.js +490 -0
- package/{plugins/freeflow/router/dist → router/dist/evidence}/evidence.d.ts +18 -18
- package/router/dist/evidence/evidence.js +200 -0
- package/router/dist/evidence/failure-contracts.d.ts +37 -0
- package/router/dist/evidence/failure-contracts.js +166 -0
- package/router/dist/evidence/line-ranges.d.ts +24 -0
- package/router/dist/evidence/line-ranges.js +22 -0
- package/router/dist/experiments/local-index.d.ts +64 -0
- package/router/dist/experiments/local-index.js +581 -0
- package/router/dist/index.d.ts +30 -0
- package/router/dist/index.js +32 -0
- package/router/dist/local/local-traversal.d.ts +18 -0
- package/router/dist/local/local-traversal.js +334 -0
- package/router/dist/processing/engine.d.ts +132 -0
- package/router/dist/processing/engine.js +533 -0
- package/router/dist/processing/reducers.d.ts +280 -0
- package/router/dist/processing/reducers.js +1673 -0
- package/router/dist/processing/renderers.d.ts +31 -0
- package/router/dist/processing/renderers.js +195 -0
- package/router/dist/processing/scripts.d.ts +91 -0
- package/router/dist/processing/scripts.js +386 -0
- package/{plugins/freeflow/router/dist → router/dist/repo}/repo-traversal.d.ts +9 -9
- package/router/dist/repo/repo-traversal.js +245 -0
- package/router/dist/routing/observed-reducers.d.ts +41 -0
- package/router/dist/routing/observed-reducers.js +661 -0
- package/router/dist/routing/observed-routing.d.ts +26 -0
- package/router/dist/routing/observed-routing.js +378 -0
- package/router/dist/routing/parsers.d.ts +16 -0
- package/router/dist/routing/parsers.js +521 -0
- package/router/dist/routing/run-filters.d.ts +60 -0
- package/router/dist/routing/run-filters.js +238 -0
- package/router/dist/routing/run-reducers.d.ts +49 -0
- package/router/dist/routing/run-reducers.js +122 -0
- package/router/dist/sandbox/adapter-roots.d.ts +46 -0
- package/router/dist/sandbox/adapter-roots.js +66 -0
- package/router/dist/sandbox/eryx-python-adapter.d.ts +13 -0
- package/router/dist/sandbox/eryx-python-adapter.js +868 -0
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +9 -0
- package/router/dist/sandbox/jq-wasm-adapter.js +429 -0
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +13 -0
- package/router/dist/sandbox/quickjs-wasi-adapter.js +384 -0
- package/router/dist/sandbox/script-sandbox.d.ts +134 -0
- package/router/dist/sandbox/script-sandbox.js +337 -0
- package/router/dist/setup/script-transform-adapters.d.ts +32 -0
- package/router/dist/setup/script-transform-adapters.js +278 -0
- package/router/dist/tools/batch.d.ts +33 -0
- package/router/dist/tools/batch.js +586 -0
- package/router/dist/tools/run.d.ts +62 -0
- package/router/dist/tools/run.js +1304 -0
- package/router/dist/tools/search.d.ts +66 -0
- package/router/dist/tools/search.js +1364 -0
- package/router/dist/transform/engine.d.ts +168 -0
- package/router/dist/transform/engine.js +2072 -0
- package/router/dist/vault/vault-index.d.ts +125 -0
- package/router/dist/vault/vault-index.js +416 -0
- package/router/dist/vault/vault.d.ts +138 -0
- package/router/dist/vault/vault.js +630 -0
- package/runtime/interaction-contract.md +16 -0
- package/skills/bypass/SKILL.md +82 -0
- package/skills/commit-work/SKILL.md +106 -0
- package/skills/commit-work/references/staging-decisions.md +73 -0
- package/skills/decision-gate/SKILL.md +48 -0
- package/skills/design-for-depth/SKILL.md +106 -0
- package/skills/design-for-depth/references/design-pressure-signals.md +314 -0
- package/skills/design-for-depth/references/interface-design-loop.md +156 -0
- package/skills/design-for-depth/references/software-design-philosophy.md +248 -0
- package/skills/diagnose-failure/SKILL.md +105 -0
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
- package/skills/diagnose-failure/references/flaky-and-performance.md +43 -0
- package/skills/discuss/SKILL.md +121 -0
- package/skills/discuss/references/checkpoints.md +66 -0
- package/skills/evaluate-skill/SKILL.md +105 -0
- package/skills/evaluate-skill/references/definition-schema.md +345 -0
- package/skills/evaluate-skill/references/evaluation-design.md +86 -0
- package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
- package/skills/evaluate-skill/references/review-and-revision.md +51 -0
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
- package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
- package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
- package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
- package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
- package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
- package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
- package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
- package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
- package/skills/execute-work/SKILL.md +99 -0
- package/skills/execute-work/references/code-practices.md +78 -0
- package/skills/execute-work/references/execution-loop.md +77 -0
- package/skills/finish-branch/SKILL.md +117 -0
- package/skills/finish-branch/references/integration-options.md +80 -0
- package/skills/handoff/SKILL.md +92 -0
- package/skills/handoff/references/templates.md +141 -0
- package/skills/launch-work/SKILL.md +116 -0
- package/skills/launch-work/references/launch-readiness.md +85 -0
- package/skills/migration-work/SKILL.md +115 -0
- package/skills/migration-work/references/migration-lifecycle.md +82 -0
- package/skills/mode-contract/SKILL.md +82 -0
- package/skills/output-router/SKILL.md +206 -0
- package/skills/output-router/references/safety-policy.md +40 -0
- package/skills/release-work/SKILL.md +118 -0
- package/skills/release-work/references/release-evidence.md +78 -0
- package/skills/review-artifact/SKILL.md +126 -0
- package/skills/review-artifact/references/reviewer-prompt.md +148 -0
- package/skills/review-work/SKILL.md +126 -0
- package/skills/review-work/references/reviewer-prompt.md +134 -0
- package/skills/review-work/references/security-risk-lens.md +67 -0
- package/skills/setup-freeflow/SKILL.md +98 -0
- package/skills/setup-freeflow/references/activation-contract.md +120 -0
- package/skills/setup-freeflow/references/host-setup.md +72 -0
- package/skills/setup-freeflow/references/output-router-setup.md +110 -0
- package/skills/simplify-code/SKILL.md +80 -0
- package/skills/simplify-code/references/simplification-patterns.md +70 -0
- package/skills/tdd/SKILL.md +96 -0
- package/skills/tdd/references/test-design.md +96 -0
- package/skills/track-work/SKILL.md +84 -0
- package/skills/track-work/references/working-record-schema.md +378 -0
- package/skills/verify-work/SKILL.md +85 -0
- package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
- package/skills/verify-work/references/integration-evidence.md +51 -0
- package/skills/verify-work/references/performance-evidence.md +57 -0
- package/skills/workflow/SKILL.md +91 -0
- package/skills/workflow/references/domain-skill-composition.md +57 -0
- package/skills/workflow/references/workflow-loop.md +92 -0
- package/skills/write-plan/SKILL.md +123 -0
- package/skills/write-plan/references/plan-shapes.md +156 -0
- package/skills/write-skill/SKILL.md +119 -0
- package/skills/write-skill/references/activation-boundaries.md +62 -0
- package/skills/write-skill/references/agent-first-instructions.md +87 -0
- package/skills/write-skill/references/progressive-disclosure.md +63 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
- package/skills/write-skill/scripts/skill-author.mjs +104 -0
- package/skills/write-spec/SKILL.md +104 -0
- package/skills/write-spec/references/artifact-standards.md +86 -0
- package/skills/write-spec/references/decision-records.md +77 -0
- package/skills/write-spec/references/spec-shapes.md +128 -0
- package/plugins/freeflow/command-surface.json +0 -77
- package/plugins/freeflow/docs/README.md +0 -40
- package/plugins/freeflow/docs/adr/0001-three-modes.md +0 -27
- package/plugins/freeflow/docs/adr/0002-evals-before-hooks.md +0 -17
- package/plugins/freeflow/docs/adr/0003-release-boundary.md +0 -21
- package/plugins/freeflow/docs/adr/0004-research-replaces-shallow-discovery-skills.md +0 -26
- package/plugins/freeflow/docs/adr/README.md +0 -8
- package/plugins/freeflow/docs/architecture.md +0 -86
- package/plugins/freeflow/docs/output-router.md +0 -212
- package/plugins/freeflow/docs/release-evidence.md +0 -69
- package/plugins/freeflow/docs/skills.md +0 -43
- package/plugins/freeflow/docs/workflow.md +0 -57
- package/plugins/freeflow/hooks/freeflow-runtime-context.mjs +0 -257
- package/plugins/freeflow/pi-extension/index.js +0 -1176
- package/plugins/freeflow/router/dist/benchmark-harness.js +0 -92
- package/plugins/freeflow/router/dist/benchmarks.d.ts +0 -102
- package/plugins/freeflow/router/dist/benchmarks.js +0 -880
- package/plugins/freeflow/router/dist/bounded-evidence.d.ts +0 -40
- package/plugins/freeflow/router/dist/bounded-evidence.js +0 -310
- package/plugins/freeflow/router/dist/codex-qa-benchmarks.d.ts +0 -91
- package/plugins/freeflow/router/dist/codex-qa-benchmarks.js +0 -461
- package/plugins/freeflow/router/dist/command-benchmarks.d.ts +0 -128
- package/plugins/freeflow/router/dist/command-benchmarks.js +0 -652
- package/plugins/freeflow/router/dist/config.d.ts +0 -22
- package/plugins/freeflow/router/dist/config.js +0 -114
- package/plugins/freeflow/router/dist/evidence-range-selector.d.ts +0 -22
- package/plugins/freeflow/router/dist/evidence-range-selector.js +0 -180
- package/plugins/freeflow/router/dist/evidence-search.d.ts +0 -25
- package/plugins/freeflow/router/dist/evidence-search.js +0 -450
- package/plugins/freeflow/router/dist/evidence.js +0 -196
- package/plugins/freeflow/router/dist/experimental-local-index.d.ts +0 -1
- package/plugins/freeflow/router/dist/experimental-local-index.js +0 -1
- package/plugins/freeflow/router/dist/experiments/local-index.d.ts +0 -58
- package/plugins/freeflow/router/dist/experiments/local-index.js +0 -562
- package/plugins/freeflow/router/dist/index-benchmarks.d.ts +0 -84
- package/plugins/freeflow/router/dist/index-benchmarks.js +0 -458
- package/plugins/freeflow/router/dist/index.d.ts +0 -12
- package/plugins/freeflow/router/dist/index.js +0 -12
- package/plugins/freeflow/router/dist/line-ranges.d.ts +0 -22
- package/plugins/freeflow/router/dist/line-ranges.js +0 -22
- package/plugins/freeflow/router/dist/parsers.d.ts +0 -16
- package/plugins/freeflow/router/dist/parsers.js +0 -473
- package/plugins/freeflow/router/dist/repo-traversal.js +0 -242
- package/plugins/freeflow/router/dist/retrieve.d.ts +0 -33
- package/plugins/freeflow/router/dist/retrieve.js +0 -855
- package/plugins/freeflow/router/dist/router-contract.js +0 -54
- package/plugins/freeflow/router/dist/run.d.ts +0 -26
- package/plugins/freeflow/router/dist/run.js +0 -356
- package/plugins/freeflow/router/dist/schema.js +0 -354
- package/plugins/freeflow/router/dist/types.d.ts +0 -234
- package/plugins/freeflow/router/dist/types.js +0 -9
- package/plugins/freeflow/router/dist/vault.d.ts +0 -80
- package/plugins/freeflow/router/dist/vault.js +0 -484
- package/plugins/freeflow/skills/bypass/SKILL.md +0 -68
- package/plugins/freeflow/skills/commit-work/SKILL.md +0 -98
- package/plugins/freeflow/skills/commit-work/references/staging-decisions.md +0 -51
- package/plugins/freeflow/skills/diagnose-failure/SKILL.md +0 -95
- package/plugins/freeflow/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
- package/plugins/freeflow/skills/diagnose-failure/references/flaky-and-performance.md +0 -31
- package/plugins/freeflow/skills/evaluate-skill/SKILL.md +0 -96
- package/plugins/freeflow/skills/evaluate-skill/references/eval-patterns.md +0 -41
- package/plugins/freeflow/skills/evaluate-skill/references/grading-priority.md +0 -33
- package/plugins/freeflow/skills/execute-plan/SKILL.md +0 -132
- package/plugins/freeflow/skills/execute-plan/references/execution-map.md +0 -122
- package/plugins/freeflow/skills/handoff/SKILL.md +0 -66
- package/plugins/freeflow/skills/handoff/references/templates.md +0 -64
- package/plugins/freeflow/skills/interview-gate/SKILL.md +0 -90
- package/plugins/freeflow/skills/mode-contract/SKILL.md +0 -79
- package/plugins/freeflow/skills/output-router/SKILL.md +0 -69
- package/plugins/freeflow/skills/output-router/references/safety-policy.md +0 -33
- package/plugins/freeflow/skills/research/ARTIFACT-DESTINATIONS.md +0 -58
- package/plugins/freeflow/skills/research/CHECKPOINTS.md +0 -74
- package/plugins/freeflow/skills/research/LANGUAGE.md +0 -61
- package/plugins/freeflow/skills/research/SKILL.md +0 -128
- package/plugins/freeflow/skills/review-artifact/SKILL.md +0 -115
- package/plugins/freeflow/skills/review-artifact/references/reviewer-prompt.md +0 -56
- package/plugins/freeflow/skills/review-work/SKILL.md +0 -96
- package/plugins/freeflow/skills/review-work/references/reviewer-prompt.md +0 -97
- package/plugins/freeflow/skills/setup-freeflow/SKILL.md +0 -94
- package/plugins/freeflow/skills/setup-freeflow/references/activation-contract.md +0 -99
- package/plugins/freeflow/skills/setup-freeflow/references/host-setup.md +0 -35
- package/plugins/freeflow/skills/setup-freeflow/references/output-router-setup.md +0 -52
- package/plugins/freeflow/skills/verify-work/SKILL.md +0 -62
- package/plugins/freeflow/skills/workflow/SKILL.md +0 -95
- package/plugins/freeflow/skills/workflow/references/workflow-map.md +0 -102
- package/plugins/freeflow/skills/write-plan/SKILL.md +0 -88
- package/plugins/freeflow/skills/write-plan/references/plan-shapes.md +0 -70
- package/plugins/freeflow/skills/write-skill/SKILL.md +0 -88
- package/plugins/freeflow/skills/write-spec/SKILL.md +0 -90
- package/plugins/freeflow/skills/write-spec/references/artifact-standards.md +0 -67
- package/plugins/freeflow/skills/write-spec/references/spec-shapes.md +0 -69
- /package/{plugins/freeflow/assets → assets}/freeflow-plugin-icon-transparent.png +0 -0
- /package/{plugins/freeflow/hooks → hooks}/hooks.json +0 -0
|
@@ -0,0 +1,791 @@
|
|
|
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
|
+
}
|