@hassangameryt/freeflow 0.3.0 → 0.5.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 (692) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +6 -2
  3. package/.codex-plugin/plugin.json +9 -9
  4. package/README.md +190 -127
  5. package/assets/feedback-loop.png +0 -0
  6. package/assets/freeflow_workflow_map.png +0 -0
  7. package/assets/workflow.png +0 -0
  8. package/{skills → capabilities}/output-router/SKILL.md +21 -17
  9. package/{skills → capabilities}/output-router/references/safety-policy.md +1 -1
  10. package/capabilities/output-router/references/setup.md +110 -0
  11. package/command-surface.json +62 -7
  12. package/hooks/freeflow-runtime-context.mjs +572 -154
  13. package/hooks/hooks.json +26 -1
  14. package/hooks/tests/check-runtime-context-hook.sh +375 -0
  15. package/package.json +24 -24
  16. package/pi-extension/dist/index.js +264 -80
  17. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  18. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  19. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  20. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  21. package/pi-extension/dist/output-router/renderers.js +1023 -0
  22. package/pi-extension/dist/output-router/router-tools.js +554 -0
  23. package/pi-extension/dist/output-router/schemas.js +383 -0
  24. package/pi-extension/dist/output-router/status.js +744 -0
  25. package/pi-extension/dist/output-router/utils.js +502 -0
  26. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  27. package/pi-extension/dist/settings/settings-tui.js +314 -0
  28. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  29. package/pi-extension/freeflow/index.js +1 -0
  30. package/pi-extension/src/index.ts +230 -56
  31. package/pi-extension/src/{host-producer-identification.ts → output-router/host-producer-identification.ts} +37 -7
  32. package/pi-extension/src/{native-safety-net.ts → output-router/native-safety-net.ts} +8 -12
  33. package/pi-extension/src/{observed-tool-routing.ts → output-router/observed-tool-routing.ts} +19 -11
  34. package/pi-extension/src/{renderers.ts → output-router/renderers.ts} +246 -55
  35. package/pi-extension/src/{router-tools.ts → output-router/router-tools.ts} +130 -29
  36. package/pi-extension/src/{schemas.ts → output-router/schemas.ts} +171 -43
  37. package/pi-extension/src/output-router/status.ts +791 -0
  38. package/pi-extension/src/{utils.ts → output-router/utils.ts} +80 -18
  39. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  40. package/pi-extension/src/settings/settings-tui.ts +408 -0
  41. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  42. package/pi-extension/tests/integration/pi-extension.test.js +4020 -0
  43. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  44. package/pi-extension/tests/output-router/search.test.js +722 -0
  45. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  46. package/router/dist/benchmarks/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +62 -62
  48. package/router/dist/benchmarks/benchmarks.d.ts +80 -73
  49. package/router/dist/benchmarks/benchmarks.js +860 -761
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +71 -67
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +429 -390
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +107 -92
  53. package/router/dist/benchmarks/command-benchmarks.js +616 -571
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +64 -56
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +679 -613
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +103 -95
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1188 -1103
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +47 -41
  59. package/router/dist/benchmarks/context-saving-baseline.js +405 -374
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +74 -69
  61. package/router/dist/benchmarks/index-benchmarks.js +761 -661
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +80 -69
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +549 -469
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +43 -34
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +294 -229
  66. package/router/dist/config/config.d.ts +62 -55
  67. package/router/dist/config/config.js +552 -373
  68. package/router/dist/config/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +38 -38
  70. package/router/dist/config/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +586 -538
  72. package/router/dist/config/types.d.ts +413 -361
  73. package/router/dist/config/types.js +19 -8
  74. package/router/dist/evidence/bounded-evidence.d.ts +26 -26
  75. package/router/dist/evidence/bounded-evidence.js +265 -252
  76. package/router/dist/evidence/evidence-range-selector.d.ts +16 -14
  77. package/router/dist/evidence/evidence-range-selector.js +192 -191
  78. package/router/dist/evidence/evidence-search.d.ts +18 -16
  79. package/router/dist/evidence/evidence-search.js +401 -377
  80. package/router/dist/evidence/evidence.d.ts +17 -17
  81. package/router/dist/evidence/evidence.js +163 -159
  82. package/router/dist/evidence/failure-contracts.d.ts +25 -15
  83. package/router/dist/evidence/failure-contracts.js +132 -126
  84. package/router/dist/evidence/line-ranges.d.ts +17 -15
  85. package/router/dist/evidence/line-ranges.js +18 -18
  86. package/router/dist/experiments/local-index.d.ts +47 -41
  87. package/router/dist/experiments/local-index.js +477 -458
  88. package/router/dist/local/local-traversal.d.ts +18 -0
  89. package/router/dist/local/local-traversal.js +334 -0
  90. package/router/dist/processing/engine.d.ts +94 -70
  91. package/router/dist/processing/engine.js +462 -364
  92. package/router/dist/processing/reducers.d.ts +208 -194
  93. package/router/dist/processing/reducers.js +1434 -1256
  94. package/router/dist/processing/renderers.d.ts +24 -18
  95. package/router/dist/processing/renderers.js +161 -152
  96. package/router/dist/processing/scripts.d.ts +80 -67
  97. package/router/dist/processing/scripts.js +343 -290
  98. package/router/dist/repo/repo-traversal.d.ts +9 -9
  99. package/router/dist/repo/repo-traversal.js +205 -202
  100. package/router/dist/routing/observed-reducers.d.ts +25 -19
  101. package/router/dist/routing/observed-reducers.js +559 -545
  102. package/router/dist/routing/observed-routing.d.ts +23 -14
  103. package/router/dist/routing/observed-routing.js +319 -318
  104. package/router/dist/routing/parsers.d.ts +10 -10
  105. package/router/dist/routing/parsers.js +454 -406
  106. package/router/dist/routing/run-filters.d.ts +45 -41
  107. package/router/dist/routing/run-filters.js +215 -207
  108. package/router/dist/routing/run-reducers.d.ts +45 -26
  109. package/router/dist/routing/run-reducers.js +102 -90
  110. package/router/dist/sandbox/adapter-roots.d.ts +34 -26
  111. package/router/dist/sandbox/adapter-roots.js +43 -41
  112. package/router/dist/sandbox/eryx-python-adapter.d.ts +9 -5
  113. package/router/dist/sandbox/eryx-python-adapter.js +659 -588
  114. package/router/dist/sandbox/jq-wasm-adapter.d.ts +3 -3
  115. package/router/dist/sandbox/jq-wasm-adapter.js +319 -311
  116. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +9 -5
  117. package/router/dist/sandbox/quickjs-wasi-adapter.js +336 -333
  118. package/router/dist/sandbox/script-sandbox.d.ts +105 -84
  119. package/router/dist/sandbox/script-sandbox.js +294 -260
  120. package/router/dist/setup/script-transform-adapters.d.ts +23 -19
  121. package/router/dist/setup/script-transform-adapters.js +223 -204
  122. package/router/dist/tools/batch.d.ts +27 -16
  123. package/router/dist/tools/batch.js +493 -416
  124. package/router/dist/tools/run.d.ts +47 -35
  125. package/router/dist/tools/run.js +1172 -1087
  126. package/router/dist/tools/search.d.ts +53 -31
  127. package/router/dist/tools/search.js +1224 -1065
  128. package/router/dist/transform/engine.d.ts +121 -96
  129. package/router/dist/transform/engine.js +1853 -1736
  130. package/router/dist/vault/vault-index.d.ts +97 -78
  131. package/router/dist/vault/vault-index.js +347 -342
  132. package/router/dist/vault/vault.d.ts +109 -74
  133. package/router/dist/vault/vault.js +528 -533
  134. package/runtime/interaction-contract.md +16 -0
  135. package/skills/bypass/SKILL.md +52 -38
  136. package/skills/commit-work/SKILL.md +54 -46
  137. package/skills/commit-work/references/staging-decisions.md +46 -24
  138. package/skills/decision-gate/SKILL.md +48 -0
  139. package/skills/design-for-depth/SKILL.md +75 -59
  140. package/skills/design-for-depth/references/design-pressure-signals.md +56 -7
  141. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  142. package/skills/design-for-depth/references/software-design-philosophy.md +32 -3
  143. package/skills/diagnose-failure/SKILL.md +65 -60
  144. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  145. package/skills/diagnose-failure/references/flaky-and-performance.md +30 -18
  146. package/skills/discuss/SKILL.md +121 -0
  147. package/skills/discuss/references/checkpoints.md +66 -0
  148. package/skills/evaluate-skill/SKILL.md +62 -53
  149. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  150. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  151. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  152. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  153. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  154. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  155. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  156. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  159. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  160. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  161. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  162. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  163. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  164. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  165. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  166. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  167. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  168. package/skills/execute-work/SKILL.md +99 -0
  169. package/skills/execute-work/references/code-practices.md +78 -0
  170. package/skills/execute-work/references/execution-loop.md +77 -0
  171. package/skills/finish-branch/SKILL.md +117 -0
  172. package/skills/finish-branch/references/integration-options.md +80 -0
  173. package/skills/handoff/SKILL.md +65 -39
  174. package/skills/handoff/references/templates.md +104 -27
  175. package/skills/launch-work/SKILL.md +116 -0
  176. package/skills/launch-work/references/launch-readiness.md +85 -0
  177. package/skills/migration-work/SKILL.md +115 -0
  178. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  179. package/skills/mode-contract/SKILL.md +72 -52
  180. package/skills/release-work/SKILL.md +118 -0
  181. package/skills/release-work/references/release-evidence.md +78 -0
  182. package/skills/review-artifact/SKILL.md +77 -69
  183. package/skills/review-artifact/references/reviewer-prompt.md +133 -42
  184. package/skills/review-work/SKILL.md +82 -57
  185. package/skills/review-work/references/reviewer-prompt.md +97 -60
  186. package/skills/review-work/references/security-risk-lens.md +67 -0
  187. package/skills/setup-freeflow/SKILL.md +54 -68
  188. package/skills/setup-freeflow/references/activation-contract.md +84 -69
  189. package/skills/setup-freeflow/references/host-setup.md +50 -17
  190. package/skills/simplify-code/SKILL.md +80 -0
  191. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  192. package/skills/tdd/SKILL.md +96 -0
  193. package/skills/tdd/references/test-design.md +96 -0
  194. package/skills/track-work/SKILL.md +84 -0
  195. package/skills/track-work/references/working-record-schema.md +378 -0
  196. package/skills/verify-work/SKILL.md +59 -38
  197. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  198. package/skills/verify-work/references/integration-evidence.md +51 -0
  199. package/skills/verify-work/references/performance-evidence.md +57 -0
  200. package/skills/workflow/SKILL.md +53 -59
  201. package/skills/workflow/references/domain-skill-composition.md +57 -0
  202. package/skills/workflow/references/workflow-loop.md +92 -0
  203. package/skills/write-plan/SKILL.md +85 -58
  204. package/skills/write-plan/references/plan-shapes.md +132 -46
  205. package/skills/write-skill/SKILL.md +82 -51
  206. package/skills/write-skill/references/activation-boundaries.md +62 -0
  207. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  208. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  209. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  210. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  211. package/skills/write-spec/SKILL.md +65 -54
  212. package/skills/write-spec/references/artifact-standards.md +60 -41
  213. package/skills/write-spec/references/decision-records.md +77 -0
  214. package/skills/write-spec/references/spec-shapes.md +119 -60
  215. package/evals/README.md +0 -32
  216. package/evals/fixtures/copy-conventions.md +0 -31
  217. package/evals/fixtures/output-router/README.md +0 -12
  218. package/evals/fixtures/output-router/failed-command-output.txt +0 -9
  219. package/evals/fixtures/output-router/large-router-manual.md +0 -63
  220. package/evals/fixtures/output-router/native-large-output.txt +0 -40
  221. package/evals/fixtures/output-router/noisy-test-output.txt +0 -30
  222. package/evals/fixtures/output-router/verification-output.txt +0 -8
  223. package/evals/fixtures/tiny-artifact-review-app/README.md +0 -5
  224. package/evals/fixtures/tiny-artifact-review-app/docs/billing-policy.md +0 -10
  225. package/evals/fixtures/tiny-artifact-review-app/docs/specs/billing-webhook-api-draft.md +0 -21
  226. package/evals/fixtures/tiny-artifact-review-app/docs/specs/failed-payment-grace-period.md +0 -24
  227. package/evals/fixtures/tiny-artifact-review-app/docs/specs/immediate-downgrade.md +0 -17
  228. package/evals/fixtures/tiny-artifact-review-app/tests/billing.test.md +0 -8
  229. package/evals/fixtures/tiny-commit-ambiguous-app/.freeflow-eval-setup.sh +0 -15
  230. package/evals/fixtures/tiny-commit-ambiguous-app/README.md +0 -3
  231. package/evals/fixtures/tiny-commit-ambiguous-app/src/billing.ts +0 -5
  232. package/evals/fixtures/tiny-commit-ambiguous-app/src/settings-page.ts +0 -1
  233. package/evals/fixtures/tiny-commit-ambiguous-app/tests/settings.test.md +0 -5
  234. package/evals/fixtures/tiny-commit-app/.freeflow-eval-setup.sh +0 -14
  235. package/evals/fixtures/tiny-commit-app/README.md +0 -3
  236. package/evals/fixtures/tiny-commit-app/docs/specs/settings-title.md +0 -5
  237. package/evals/fixtures/tiny-commit-app/src/billing.ts +0 -5
  238. package/evals/fixtures/tiny-commit-app/src/settings-page.ts +0 -1
  239. package/evals/fixtures/tiny-commit-app/tests/settings.test.md +0 -5
  240. package/evals/fixtures/tiny-commit-everything-app/.freeflow-eval-setup.sh +0 -15
  241. package/evals/fixtures/tiny-commit-everything-app/README.md +0 -3
  242. package/evals/fixtures/tiny-commit-everything-app/docs/specs/settings-title.md +0 -5
  243. package/evals/fixtures/tiny-commit-everything-app/src/billing.ts +0 -5
  244. package/evals/fixtures/tiny-commit-everything-app/src/settings-page.ts +0 -1
  245. package/evals/fixtures/tiny-commit-everything-app/tests/settings.test.md +0 -5
  246. package/evals/fixtures/tiny-commit-staged-app/.freeflow-eval-setup.sh +0 -15
  247. package/evals/fixtures/tiny-commit-staged-app/README.md +0 -3
  248. package/evals/fixtures/tiny-commit-staged-app/docs/specs/settings-title.md +0 -5
  249. package/evals/fixtures/tiny-commit-staged-app/src/billing.ts +0 -5
  250. package/evals/fixtures/tiny-commit-staged-app/src/settings-page.ts +0 -1
  251. package/evals/fixtures/tiny-commit-staged-app/tests/settings.test.md +0 -5
  252. package/evals/fixtures/tiny-commit-staged-mixed-app/.freeflow-eval-setup.sh +0 -15
  253. package/evals/fixtures/tiny-commit-staged-mixed-app/README.md +0 -3
  254. package/evals/fixtures/tiny-commit-staged-mixed-app/docs/billing-policy.md +0 -3
  255. package/evals/fixtures/tiny-commit-staged-mixed-app/docs/release-notes.md +0 -3
  256. package/evals/fixtures/tiny-commit-staged-mixed-app/src/billing.ts +0 -5
  257. package/evals/fixtures/tiny-dashboard-stale-app/README.md +0 -11
  258. package/evals/fixtures/tiny-dashboard-stale-app/src/api-client.ts +0 -13
  259. package/evals/fixtures/tiny-dashboard-stale-app/src/dashboard-data.ts +0 -19
  260. package/evals/fixtures/tiny-dashboard-stale-app/tests/dashboard-data.test.md +0 -9
  261. package/evals/fixtures/tiny-decision-destination-app/README.md +0 -6
  262. package/evals/fixtures/tiny-decision-destination-app/src/risk.ts +0 -8
  263. package/evals/fixtures/tiny-decision-destination-app/tests/risk.test.md +0 -4
  264. package/evals/fixtures/tiny-design-depth-app/README.md +0 -15
  265. package/evals/fixtures/tiny-design-depth-app/src/routes.ts +0 -25
  266. package/evals/fixtures/tiny-design-depth-app/src/sendEmail.ts +0 -3
  267. package/evals/fixtures/tiny-design-depth-app/src/sendSms.ts +0 -3
  268. package/evals/fixtures/tiny-design-depth-app/src/telemetry.ts +0 -3
  269. package/evals/fixtures/tiny-design-depth-app/tests/routes.test.md +0 -12
  270. package/evals/fixtures/tiny-eval-method-app/README.md +0 -8
  271. package/evals/fixtures/tiny-eval-method-app/evals/reports/review-pr-eval-result.md +0 -5
  272. package/evals/fixtures/tiny-eval-method-app/evals/reports/triage-question-failure.md +0 -17
  273. package/evals/fixtures/tiny-eval-method-app/evals/runs/review-pr-with-skill-output.diff +0 -11
  274. package/evals/fixtures/tiny-eval-method-app/evals/runs/review-pr-with-skill-output.md +0 -3
  275. package/evals/fixtures/tiny-eval-method-app/skills/review-pr/SKILL.md +0 -10
  276. package/evals/fixtures/tiny-eval-method-app/skills/triage-question/SKILL.md +0 -10
  277. package/evals/fixtures/tiny-execute-plan-app/README.md +0 -5
  278. package/evals/fixtures/tiny-execute-plan-app/checks/settings-and-billing-check.sh +0 -5
  279. package/evals/fixtures/tiny-execute-plan-app/docs/billing-policy.md +0 -10
  280. package/evals/fixtures/tiny-execute-plan-app/docs/plans/immediate-downgrade-plan.md +0 -11
  281. package/evals/fixtures/tiny-execute-plan-app/docs/plans/settings-title-plan.md +0 -12
  282. package/evals/fixtures/tiny-execute-plan-app/docs/plans/settings-title-with-bad-check.md +0 -15
  283. package/evals/fixtures/tiny-execute-plan-app/docs/plans/unverified-billing-plan.md +0 -9
  284. package/evals/fixtures/tiny-execute-plan-app/docs/specs/failed-payment-grace-period.md +0 -9
  285. package/evals/fixtures/tiny-execute-plan-app/docs/specs/settings-title.md +0 -9
  286. package/evals/fixtures/tiny-execute-plan-app/src/billing.ts +0 -7
  287. package/evals/fixtures/tiny-execute-plan-app/src/settings-page.ts +0 -1
  288. package/evals/fixtures/tiny-execute-plan-app/tests/billing.test.md +0 -7
  289. package/evals/fixtures/tiny-execute-plan-app/tests/settings.test.md +0 -5
  290. package/evals/fixtures/tiny-execute-review-loop-app/README.md +0 -5
  291. package/evals/fixtures/tiny-execute-review-loop-app/docs/billing-policy.md +0 -9
  292. package/evals/fixtures/tiny-execute-review-loop-app/docs/plans/settings-title-plan.md +0 -12
  293. package/evals/fixtures/tiny-execute-review-loop-app/docs/review/previous-agent-summary.md +0 -15
  294. package/evals/fixtures/tiny-execute-review-loop-app/docs/review/review-pass-1-non-pass.md +0 -11
  295. package/evals/fixtures/tiny-execute-review-loop-app/docs/specs/settings-title.md +0 -9
  296. package/evals/fixtures/tiny-execute-review-loop-app/src/billing.ts +0 -7
  297. package/evals/fixtures/tiny-execute-review-loop-app/src/settings-page.ts +0 -1
  298. package/evals/fixtures/tiny-execute-review-loop-app/tests/billing.test.md +0 -7
  299. package/evals/fixtures/tiny-execute-review-loop-app/tests/settings.test.md +0 -5
  300. package/evals/fixtures/tiny-flaky-session-app/README.md +0 -10
  301. package/evals/fixtures/tiny-flaky-session-app/src/session-timeout.ts +0 -13
  302. package/evals/fixtures/tiny-flaky-session-app/tests/session-timeout.test.md +0 -8
  303. package/evals/fixtures/tiny-handoff-app/README.md +0 -5
  304. package/evals/fixtures/tiny-handoff-app/docs/billing-policy.md +0 -10
  305. package/evals/fixtures/tiny-handoff-app/docs/handoffs/old-agent-handoff.md +0 -8
  306. package/evals/fixtures/tiny-handoff-app/docs/plans/billing-retry-plan.md +0 -11
  307. package/evals/fixtures/tiny-handoff-app/src/billing.ts +0 -7
  308. package/evals/fixtures/tiny-handoff-report-app/README.md +0 -3
  309. package/evals/fixtures/tiny-handoff-report-app/evals/runs/handoff-3/hof-003/with-skill-output.diff +0 -1
  310. package/evals/fixtures/tiny-handoff-report-app/evals/runs/handoff-3/hof-003/with-skill-output.md +0 -1
  311. package/evals/fixtures/tiny-handoff-report-app/evals/scores/hof-003-score.txt +0 -4
  312. package/evals/fixtures/tiny-plan-app/README.md +0 -5
  313. package/evals/fixtures/tiny-plan-app/docs/billing-policy.md +0 -10
  314. package/evals/fixtures/tiny-plan-app/docs/specs/failed-payment-grace-period.md +0 -25
  315. package/evals/fixtures/tiny-plan-app/src/billing.ts +0 -17
  316. package/evals/fixtures/tiny-plan-app/src/settings-page.ts +0 -1
  317. package/evals/fixtures/tiny-plan-app/tests/billing.test.md +0 -8
  318. package/evals/fixtures/tiny-plan-app/tests/settings.test.md +0 -5
  319. package/evals/fixtures/tiny-plan-strict-api-app/README.md +0 -3
  320. package/evals/fixtures/tiny-plan-strict-api-app/docs/billing-policy.md +0 -5
  321. package/evals/fixtures/tiny-plan-strict-api-app/docs/specs/billing-webhook-api.md +0 -25
  322. package/evals/fixtures/tiny-plan-strict-api-app/src/billing.ts +0 -5
  323. package/evals/fixtures/tiny-plan-strict-api-app/src/webhooks.ts +0 -3
  324. package/evals/fixtures/tiny-plan-strict-api-app/tests/billing-webhook.test.md +0 -8
  325. package/evals/fixtures/tiny-post-setup-invalid-config-app/.freeflow/config.json +0 -3
  326. package/evals/fixtures/tiny-post-setup-invalid-config-app/AGENTS.md +0 -15
  327. package/evals/fixtures/tiny-post-setup-invalid-config-app/README.md +0 -16
  328. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/auth-review.md +0 -15
  329. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/billing-policy.md +0 -10
  330. package/evals/fixtures/tiny-post-setup-invalid-config-app/docs/copy-conventions.md +0 -15
  331. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/auth-middleware.ts +0 -12
  332. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/billing.ts +0 -20
  333. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/login-copy.ts +0 -1
  334. package/evals/fixtures/tiny-post-setup-invalid-config-app/src/settings-page.ts +0 -1
  335. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/auth-middleware.test.md +0 -8
  336. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/billing.test.md +0 -8
  337. package/evals/fixtures/tiny-post-setup-invalid-config-app/tests/copy.test.md +0 -7
  338. package/evals/fixtures/tiny-post-setup-missing-config-app/AGENTS.md +0 -15
  339. package/evals/fixtures/tiny-post-setup-missing-config-app/README.md +0 -16
  340. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/auth-review.md +0 -15
  341. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/billing-policy.md +0 -10
  342. package/evals/fixtures/tiny-post-setup-missing-config-app/docs/copy-conventions.md +0 -15
  343. package/evals/fixtures/tiny-post-setup-missing-config-app/src/auth-middleware.ts +0 -12
  344. package/evals/fixtures/tiny-post-setup-missing-config-app/src/billing.ts +0 -20
  345. package/evals/fixtures/tiny-post-setup-missing-config-app/src/login-copy.ts +0 -1
  346. package/evals/fixtures/tiny-post-setup-missing-config-app/src/settings-page.ts +0 -1
  347. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/auth-middleware.test.md +0 -8
  348. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/billing.test.md +0 -8
  349. package/evals/fixtures/tiny-post-setup-missing-config-app/tests/copy.test.md +0 -7
  350. package/evals/fixtures/tiny-post-setup-source-conflict-app/.freeflow/config.json +0 -3
  351. package/evals/fixtures/tiny-post-setup-source-conflict-app/AGENTS.md +0 -15
  352. package/evals/fixtures/tiny-post-setup-source-conflict-app/README.md +0 -16
  353. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/auth-review.md +0 -15
  354. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/billing-policy.md +0 -10
  355. package/evals/fixtures/tiny-post-setup-source-conflict-app/docs/copy-conventions.md +0 -15
  356. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/auth-middleware.ts +0 -12
  357. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/billing.ts +0 -20
  358. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/login-copy.ts +0 -1
  359. package/evals/fixtures/tiny-post-setup-source-conflict-app/src/settings-page.ts +0 -1
  360. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/auth-middleware.test.md +0 -8
  361. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/billing.test.md +0 -8
  362. package/evals/fixtures/tiny-post-setup-source-conflict-app/tests/copy.test.md +0 -7
  363. package/evals/fixtures/tiny-report-performance-app/README.md +0 -11
  364. package/evals/fixtures/tiny-report-performance-app/docs/performance.md +0 -10
  365. package/evals/fixtures/tiny-report-performance-app/src/report-summary.ts +0 -29
  366. package/evals/fixtures/tiny-report-performance-app/tests/report-performance.test.md +0 -8
  367. package/evals/fixtures/tiny-review-request-app/README.md +0 -3
  368. package/evals/fixtures/tiny-review-request-app/docs/billing-policy.md +0 -5
  369. package/evals/fixtures/tiny-review-request-app/docs/plans/billing-webhook-api.md +0 -19
  370. package/evals/fixtures/tiny-review-request-app/docs/review/previous-agent-summary.md +0 -20
  371. package/evals/fixtures/tiny-review-request-app/docs/specs/billing-webhook-api.md +0 -25
  372. package/evals/fixtures/tiny-review-request-app/src/billing.ts +0 -11
  373. package/evals/fixtures/tiny-review-request-app/src/webhooks.ts +0 -19
  374. package/evals/fixtures/tiny-review-request-app/tests/billing-webhook.test.md +0 -8
  375. package/evals/fixtures/tiny-review-work-app/README.md +0 -5
  376. package/evals/fixtures/tiny-review-work-app/docs/billing-policy.md +0 -9
  377. package/evals/fixtures/tiny-review-work-app/docs/plans/settings-title-plan.md +0 -12
  378. package/evals/fixtures/tiny-review-work-app/docs/review/previous-agent-summary.md +0 -15
  379. package/evals/fixtures/tiny-review-work-app/docs/specs/settings-title.md +0 -9
  380. package/evals/fixtures/tiny-review-work-app/src/billing.ts +0 -7
  381. package/evals/fixtures/tiny-review-work-app/src/settings-page.ts +0 -1
  382. package/evals/fixtures/tiny-review-work-app/tests/billing.test.md +0 -7
  383. package/evals/fixtures/tiny-review-work-app/tests/settings.test.md +0 -5
  384. package/evals/fixtures/tiny-saas-app/README.md +0 -16
  385. package/evals/fixtures/tiny-saas-app/docs/auth-review.md +0 -15
  386. package/evals/fixtures/tiny-saas-app/docs/billing-policy.md +0 -10
  387. package/evals/fixtures/tiny-saas-app/docs/copy-conventions.md +0 -15
  388. package/evals/fixtures/tiny-saas-app/docs/specs/settings-title.md +0 -13
  389. package/evals/fixtures/tiny-saas-app/src/auth-middleware.ts +0 -12
  390. package/evals/fixtures/tiny-saas-app/src/billing.ts +0 -20
  391. package/evals/fixtures/tiny-saas-app/src/login-copy.ts +0 -1
  392. package/evals/fixtures/tiny-saas-app/src/settings-page.ts +0 -1
  393. package/evals/fixtures/tiny-saas-app/tests/auth-middleware.test.md +0 -8
  394. package/evals/fixtures/tiny-saas-app/tests/billing.test.md +0 -8
  395. package/evals/fixtures/tiny-saas-app/tests/copy.test.md +0 -7
  396. package/evals/fixtures/tiny-setup-agents-app/AGENTS.md +0 -6
  397. package/evals/fixtures/tiny-setup-both-app/AGENTS.md +0 -5
  398. package/evals/fixtures/tiny-setup-both-app/CLAUDE.md +0 -5
  399. package/evals/fixtures/tiny-setup-claude-app/CLAUDE.md +0 -6
  400. package/evals/fixtures/tiny-setup-codex-rules-app/.codex/rules/shell.rules +0 -5
  401. package/evals/fixtures/tiny-setup-codex-rules-app/AGENTS.md +0 -6
  402. package/evals/fixtures/tiny-setup-conflict-app/AGENTS.md +0 -6
  403. package/evals/fixtures/tiny-setup-existing-block-app/AGENTS.md +0 -14
  404. package/evals/fixtures/tiny-setup-four-rules-app/CLAUDE.md +0 -6
  405. package/evals/fixtures/tiny-skill-dev-app/README.md +0 -10
  406. package/evals/fixtures/tiny-skill-dev-app/evals/prompts/review-pr-001.txt +0 -8
  407. package/evals/fixtures/tiny-skill-dev-app/evals/reports/review-pr-failure.md +0 -17
  408. package/evals/fixtures/tiny-skill-dev-app/skills/review-pr/SKILL.md +0 -13
  409. package/evals/fixtures/tiny-team-app/README.md +0 -5
  410. package/evals/fixtures/tiny-team-app/docs/product.md +0 -6
  411. package/evals/fixtures/tiny-team-app/src/teams.ts +0 -9
  412. package/evals/fixtures/tiny-team-app/src/users.ts +0 -5
  413. package/evals/fixtures/tiny-team-app/tests/teams.test.md +0 -7
  414. package/evals/fixtures/tiny-verify-app/README.md +0 -5
  415. package/evals/fixtures/tiny-verify-app/src/settings-nav.ts +0 -1
  416. package/evals/fixtures/tiny-verify-app/src/settings-page.ts +0 -1
  417. package/evals/fixtures/tiny-verify-app/tests/settings.test.md +0 -7
  418. package/evals/prompts/aon-001.txt +0 -2
  419. package/evals/prompts/byp-001.txt +0 -2
  420. package/evals/prompts/byp-002.txt +0 -2
  421. package/evals/prompts/cap-001.txt +0 -3
  422. package/evals/prompts/cap-002.txt +0 -3
  423. package/evals/prompts/cmd-001.txt +0 -3
  424. package/evals/prompts/cmd-002.txt +0 -3
  425. package/evals/prompts/cmd-003.txt +0 -6
  426. package/evals/prompts/cmd-004.txt +0 -5
  427. package/evals/prompts/cmd-005.txt +0 -5
  428. package/evals/prompts/cmd-006.txt +0 -5
  429. package/evals/prompts/cmd-007.txt +0 -5
  430. package/evals/prompts/cmd-008.txt +0 -5
  431. package/evals/prompts/cmd-009.txt +0 -8
  432. package/evals/prompts/cmd-010.txt +0 -5
  433. package/evals/prompts/cmd-011.txt +0 -5
  434. package/evals/prompts/cmd-012.txt +0 -5
  435. package/evals/prompts/cmd-013.txt +0 -5
  436. package/evals/prompts/cmd-014.txt +0 -7
  437. package/evals/prompts/cmd-015.txt +0 -7
  438. package/evals/prompts/cmd-016.txt +0 -5
  439. package/evals/prompts/cmt-001.txt +0 -1
  440. package/evals/prompts/cmt-002.txt +0 -1
  441. package/evals/prompts/cmt-003.txt +0 -1
  442. package/evals/prompts/cmt-004.txt +0 -1
  443. package/evals/prompts/cmt-005.txt +0 -1
  444. package/evals/prompts/dfd-001.txt +0 -3
  445. package/evals/prompts/dia-001.txt +0 -3
  446. package/evals/prompts/dia-002.txt +0 -3
  447. package/evals/prompts/dia-003.txt +0 -3
  448. package/evals/prompts/dis-001.txt +0 -5
  449. package/evals/prompts/dis-002.txt +0 -5
  450. package/evals/prompts/esk-001.txt +0 -5
  451. package/evals/prompts/esk-002.txt +0 -5
  452. package/evals/prompts/esk-003.txt +0 -5
  453. package/evals/prompts/grc-001.txt +0 -3
  454. package/evals/prompts/hof-001.txt +0 -3
  455. package/evals/prompts/hof-002.txt +0 -1
  456. package/evals/prompts/hof-003.txt +0 -1
  457. package/evals/prompts/hof-004.txt +0 -1
  458. package/evals/prompts/hof-005.txt +0 -1
  459. package/evals/prompts/ivg-001.txt +0 -1
  460. package/evals/prompts/ivg-002.txt +0 -11
  461. package/evals/prompts/ivg-003.txt +0 -1
  462. package/evals/prompts/ivg-004.txt +0 -1
  463. package/evals/prompts/mode-001.txt +0 -3
  464. package/evals/prompts/mode-002.txt +0 -1
  465. package/evals/prompts/mode-003.txt +0 -3
  466. package/evals/prompts/mode-004.txt +0 -3
  467. package/evals/prompts/mode-005.txt +0 -3
  468. package/evals/prompts/mode-006.txt +0 -3
  469. package/evals/prompts/mode-007.txt +0 -3
  470. package/evals/prompts/otr-001.txt +0 -3
  471. package/evals/prompts/otr-002.txt +0 -3
  472. package/evals/prompts/rar-001.txt +0 -5
  473. package/evals/prompts/rar-002.txt +0 -3
  474. package/evals/prompts/rar-003.txt +0 -6
  475. package/evals/prompts/rar-004.txt +0 -15
  476. package/evals/prompts/rar-005.txt +0 -10
  477. package/evals/prompts/rbr-001.txt +0 -3
  478. package/evals/prompts/rbr-002.txt +0 -3
  479. package/evals/prompts/rbr-003.txt +0 -7
  480. package/evals/prompts/rev-001.txt +0 -3
  481. package/evals/prompts/rev-002.txt +0 -5
  482. package/evals/prompts/rev-003.txt +0 -6
  483. package/evals/prompts/rev-004.txt +0 -1
  484. package/evals/prompts/rev-005.txt +0 -15
  485. package/evals/prompts/rev-006.txt +0 -5
  486. package/evals/prompts/stp-001.txt +0 -1
  487. package/evals/prompts/stp-002.txt +0 -1
  488. package/evals/prompts/stp-003.txt +0 -1
  489. package/evals/prompts/stp-004.txt +0 -1
  490. package/evals/prompts/stp-005.txt +0 -1
  491. package/evals/prompts/stp-006.txt +0 -1
  492. package/evals/prompts/stp-007.txt +0 -1
  493. package/evals/prompts/stp-008.txt +0 -1
  494. package/evals/prompts/stp-009.txt +0 -1
  495. package/evals/prompts/stp-010.txt +0 -1
  496. package/evals/prompts/stp-011.txt +0 -1
  497. package/evals/prompts/vfy-003.txt +0 -1
  498. package/evals/prompts/wpl-001.txt +0 -3
  499. package/evals/prompts/wpl-002.txt +0 -5
  500. package/evals/prompts/wpl-003.txt +0 -3
  501. package/evals/prompts/wpl-004.txt +0 -5
  502. package/evals/prompts/wpl-005.txt +0 -1
  503. package/evals/prompts/wrc-001.txt +0 -10
  504. package/evals/prompts/wrc-002.txt +0 -3
  505. package/evals/prompts/wsk-001.txt +0 -6
  506. package/evals/prompts/wsp-001.txt +0 -5
  507. package/evals/prompts/wsp-002.txt +0 -15
  508. package/evals/prompts/wsp-003.txt +0 -3
  509. package/evals/prompts/wsp-004.txt +0 -15
  510. package/evals/prompts/wsp-005.txt +0 -5
  511. package/evals/prompts/wsp-006.txt +0 -12
  512. package/evals/prompts/wsp-007.txt +0 -9
  513. package/evals/prompts/wsp-008.txt +0 -13
  514. package/evals/prompts/xpl-001.txt +0 -1
  515. package/evals/prompts/xpl-002.txt +0 -3
  516. package/evals/prompts/xpl-003.txt +0 -3
  517. package/evals/prompts/xpl-004.txt +0 -3
  518. package/evals/prompts/xpl-005.txt +0 -5
  519. package/evals/prompts/xpl-006.txt +0 -3
  520. package/evals/registries/adversarial-evals.json +0 -39
  521. package/evals/registries/fixture-evals.json +0 -2513
  522. package/evals/registries/handoff-evals.json +0 -74
  523. package/evals/registries/legacy-evals.json +0 -147
  524. package/evals/registries/skill-evidence.json +0 -193
  525. package/evals/registries/verify-work-evals.json +0 -46
  526. package/evals/reports/acceptance/v0.1-acceptance-report.md +0 -130
  527. package/evals/reports/by-command-surface/command-surface-1-report.md +0 -45
  528. package/evals/reports/by-command-surface/command-surface-10-report.md +0 -76
  529. package/evals/reports/by-command-surface/command-surface-11-report.md +0 -77
  530. package/evals/reports/by-command-surface/command-surface-12-report.md +0 -91
  531. package/evals/reports/by-command-surface/command-surface-13-report.md +0 -64
  532. package/evals/reports/by-command-surface/command-surface-14-report.md +0 -66
  533. package/evals/reports/by-command-surface/command-surface-15-report.md +0 -124
  534. package/evals/reports/by-command-surface/command-surface-16-report.md +0 -39
  535. package/evals/reports/by-command-surface/command-surface-2-report.md +0 -80
  536. package/evals/reports/by-command-surface/command-surface-3-report.md +0 -86
  537. package/evals/reports/by-command-surface/command-surface-4-report.md +0 -76
  538. package/evals/reports/by-command-surface/command-surface-5-report.md +0 -80
  539. package/evals/reports/by-command-surface/command-surface-6-report.md +0 -74
  540. package/evals/reports/by-command-surface/command-surface-7-report.md +0 -84
  541. package/evals/reports/by-command-surface/command-surface-8-report.md +0 -79
  542. package/evals/reports/by-command-surface/command-surface-9-report.md +0 -79
  543. package/evals/reports/by-command-surface/command-surface-matrix.md +0 -91
  544. package/evals/reports/by-skill/bypass-1-report.md +0 -124
  545. package/evals/reports/by-skill/capture-decisions-1-report.md +0 -99
  546. package/evals/reports/by-skill/capture-decisions-2-report.md +0 -102
  547. package/evals/reports/by-skill/commit-work-1-report.md +0 -165
  548. package/evals/reports/by-skill/commit-work-2-report.md +0 -109
  549. package/evals/reports/by-skill/design-for-depth-1-report.md +0 -95
  550. package/evals/reports/by-skill/diagnose-failure-1-report.md +0 -91
  551. package/evals/reports/by-skill/diagnose-failure-2-report.md +0 -94
  552. package/evals/reports/by-skill/discover-1-report.md +0 -118
  553. package/evals/reports/by-skill/evaluate-skill-2-report.md +0 -71
  554. package/evals/reports/by-skill/execute-plan-1-report.md +0 -103
  555. package/evals/reports/by-skill/execute-plan-2-report.md +0 -112
  556. package/evals/reports/by-skill/execute-plan-3-report.md +0 -87
  557. package/evals/reports/by-skill/grill-context-1-report.md +0 -103
  558. package/evals/reports/by-skill/handoff-1-report.md +0 -123
  559. package/evals/reports/by-skill/handoff-2-report.md +0 -105
  560. package/evals/reports/by-skill/handoff-3-report.md +0 -100
  561. package/evals/reports/by-skill/handoff-7-report.md +0 -77
  562. package/evals/reports/by-skill/interview-gate-1-report.md +0 -63
  563. package/evals/reports/by-skill/interview-gate-2-report.md +0 -105
  564. package/evals/reports/by-skill/mode-contract-1-report.md +0 -74
  565. package/evals/reports/by-skill/mode-contract-2-report.md +0 -87
  566. package/evals/reports/by-skill/mode-contract-3-report.md +0 -65
  567. package/evals/reports/by-skill/mode-contract-4-report.md +0 -71
  568. package/evals/reports/by-skill/mode-contract-5-report.md +0 -70
  569. package/evals/reports/by-skill/output-router-1-report.md +0 -76
  570. package/evals/reports/by-skill/output-router-2-report.md +0 -65
  571. package/evals/reports/by-skill/research-1-report.md +0 -144
  572. package/evals/reports/by-skill/research-brief-1-report.md +0 -94
  573. package/evals/reports/by-skill/research-brief-2-report.md +0 -75
  574. package/evals/reports/by-skill/review-artifact-1-report.md +0 -91
  575. package/evals/reports/by-skill/review-artifact-2-report.md +0 -79
  576. package/evals/reports/by-skill/review-artifact-3-report.md +0 -120
  577. package/evals/reports/by-skill/review-artifact-4-report.md +0 -89
  578. package/evals/reports/by-skill/review-artifact-5-report.md +0 -66
  579. package/evals/reports/by-skill/review-work-1-report.md +0 -85
  580. package/evals/reports/by-skill/review-work-2-report.md +0 -87
  581. package/evals/reports/by-skill/review-work-3-report.md +0 -104
  582. package/evals/reports/by-skill/review-work-4-report.md +0 -108
  583. package/evals/reports/by-skill/review-work-5-report.md +0 -73
  584. package/evals/reports/by-skill/review-work-6-report.md +0 -66
  585. package/evals/reports/by-skill/setup-freeflow-1-report.md +0 -199
  586. package/evals/reports/by-skill/setup-freeflow-2-report.md +0 -105
  587. package/evals/reports/by-skill/setup-freeflow-3-report.md +0 -105
  588. package/evals/reports/by-skill/setup-freeflow-4-report.md +0 -88
  589. package/evals/reports/by-skill/setup-freeflow-5-report.md +0 -115
  590. package/evals/reports/by-skill/skill-authoring-1-report.md +0 -81
  591. package/evals/reports/by-skill/verify-work-1-report.md +0 -111
  592. package/evals/reports/by-skill/verify-work-2-report.md +0 -120
  593. package/evals/reports/by-skill/workflow-1-report.md +0 -65
  594. package/evals/reports/by-skill/write-plan-1-report.md +0 -123
  595. package/evals/reports/by-skill/write-plan-2-report.md +0 -98
  596. package/evals/reports/by-skill/write-plan-3-report.md +0 -110
  597. package/evals/reports/by-skill/write-spec-1-report.md +0 -88
  598. package/evals/reports/by-skill/write-spec-2-report.md +0 -89
  599. package/evals/reports/by-skill/write-spec-3-report.md +0 -90
  600. package/evals/reports/by-skill/write-spec-4-report.md +0 -167
  601. package/evals/reports/harness/claude-harness-1-report.md +0 -103
  602. package/evals/reports/harness/eval-harness-1-report.md +0 -91
  603. package/evals/reports/iterations/iteration-1-report.md +0 -71
  604. package/evals/reports/iterations/iteration-2-report.md +0 -78
  605. package/evals/reports/iterations/iteration-3-report.md +0 -118
  606. package/evals/reports/iterations/iteration-4-report.md +0 -131
  607. package/evals/reports/iterations/iteration-5-report.md +0 -149
  608. package/evals/reports/iterations/iteration-6-report.md +0 -136
  609. package/evals/reports/runtime/always-on-runtime-1-report.md +0 -107
  610. package/evals/reports/runtime/context-mode-normalized-benchmark-1-report.md +0 -52
  611. package/evals/reports/runtime/context-mode-real-deep-baseline-1-report.md +0 -410
  612. package/evals/reports/runtime/context-mode-real-deep-final-slice-11-report.md +0 -203
  613. package/evals/reports/runtime/context-mode-real-deep-final-slice-11-review.md +0 -51
  614. package/evals/reports/runtime/context-saving-native-tools-baseline-1-report.md +0 -99
  615. package/evals/reports/runtime/eryx-python-compatibility-reprobe-1-report.md +0 -166
  616. package/evals/reports/runtime/eryx-python-proof-spike-1-report.md +0 -96
  617. package/evals/reports/runtime/eryx-python-proof-spike-2-report.md +0 -151
  618. package/evals/reports/runtime/eryx-python-script-derive-execution-1-report.md +0 -233
  619. package/evals/reports/runtime/jq-script-derive-execution-1-report.md +0 -215
  620. package/evals/reports/runtime/jq-wasm-proof-spike-1-report.md +0 -67
  621. package/evals/reports/runtime/output-router-benchmark-1-report.md +0 -81
  622. package/evals/reports/runtime/output-router-capture-derive-provider-eval-1-report.md +0 -35
  623. package/evals/reports/runtime/output-router-codex-large-index-sqlite-adhoc-report.md +0 -150
  624. package/evals/reports/runtime/output-router-codex-qa-benchmark-1-report.md +0 -46
  625. package/evals/reports/runtime/output-router-command-benchmark-1-report.md +0 -62
  626. package/evals/reports/runtime/output-router-index-benchmark-1-report.md +0 -64
  627. package/evals/reports/runtime/output-router-regression-1-report.md +0 -81
  628. package/evals/reports/runtime/output-router-transform-eval-1-report.md +0 -28
  629. package/evals/reports/runtime/pi-observed-routing-eval-1-report.md +0 -44
  630. package/evals/reports/runtime/quickjs-script-derive-execution-1-report.md +0 -159
  631. package/evals/reports/runtime/quickjs-wasi-proof-spike-1-report.md +0 -55
  632. package/evals/reports/runtime/render-format-row-protocol-1-report.md +0 -58
  633. package/evals/reports/runtime/script-sandbox-adapter-selection-review-1-report.md +0 -84
  634. package/evals/reports/runtime/script-sandbox-probe-resource-hardening-1-report.md +0 -51
  635. package/evals/reports/runtime/storage-policy-benchmark-1-report.md +0 -176
  636. package/evals/reports/runtime/vault-index-storage-spike-1-report.md +0 -39
  637. package/evals/reports/runtime/workflow-context-hook-1-report.md +0 -82
  638. package/evals/runbooks/adversarial-eval-runbook.md +0 -28
  639. package/evals/runbooks/fixture-eval-runbook.md +0 -137
  640. package/evals/runbooks/handoff-eval-runbook.md +0 -22
  641. package/evals/runbooks/verify-work-eval-runbook.md +0 -27
  642. package/evals/scripts/audit-command-surface.sh +0 -160
  643. package/evals/scripts/check-activation-contract.sh +0 -121
  644. package/evals/scripts/check-runtime-context-hook.sh +0 -183
  645. package/evals/scripts/grade-fixture-eval.sh +0 -446
  646. package/evals/scripts/run-claude-fixture-eval.sh +0 -26
  647. package/evals/scripts/run-codex-fixture-eval.sh +0 -26
  648. package/evals/scripts/run-eryx-python-proof-spike.js +0 -559
  649. package/evals/scripts/run-fixture-eval-by-id.sh +0 -23
  650. package/evals/scripts/run-fixture-eval.sh +0 -464
  651. package/evals/scripts/run-jq-wasm-proof-spike.js +0 -299
  652. package/evals/scripts/run-output-router-transform-eval.js +0 -115
  653. package/evals/scripts/run-pi-observed-routing-eval.js +0 -478
  654. package/evals/scripts/run-quickjs-wasi-proof-spike.js +0 -339
  655. package/evals/scripts/skill-evidence.sh +0 -316
  656. package/evals/scripts/validate-release-metadata.sh +0 -486
  657. package/evals/suites/v0.1-acceptance-suite.md +0 -131
  658. package/pi-extension/dist/host-producer-identification.js +0 -186
  659. package/pi-extension/dist/mcp-config.js +0 -30
  660. package/pi-extension/dist/native-safety-net.js +0 -209
  661. package/pi-extension/dist/observed-tool-routing.js +0 -114
  662. package/pi-extension/dist/renderers.js +0 -835
  663. package/pi-extension/dist/router-tools.js +0 -438
  664. package/pi-extension/dist/runtime-context.js +0 -288
  665. package/pi-extension/dist/schemas.js +0 -254
  666. package/pi-extension/dist/status.js +0 -449
  667. package/pi-extension/dist/utils.js +0 -467
  668. package/pi-extension/src/runtime-context.ts +0 -347
  669. package/pi-extension/src/status.ts +0 -505
  670. package/plugin-docs/README.md +0 -40
  671. package/plugin-docs/adr/0001-three-modes.md +0 -27
  672. package/plugin-docs/adr/0002-evals-before-hooks.md +0 -17
  673. package/plugin-docs/adr/0003-release-boundary.md +0 -21
  674. package/plugin-docs/adr/0004-discover-replaces-shallow-discovery-skills.md +0 -26
  675. package/plugin-docs/adr/README.md +0 -10
  676. package/plugin-docs/architecture.md +0 -89
  677. package/plugin-docs/output-router.md +0 -396
  678. package/plugin-docs/release-evidence.md +0 -77
  679. package/plugin-docs/skills.md +0 -44
  680. package/plugin-docs/workflow.md +0 -57
  681. package/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  682. package/skills/discover/ARTIFACT-DESTINATIONS.md +0 -58
  683. package/skills/discover/CHECKPOINTS.md +0 -74
  684. package/skills/discover/SKILL.md +0 -173
  685. package/skills/evaluate-skill/references/eval-patterns.md +0 -41
  686. package/skills/evaluate-skill/references/grading-priority.md +0 -33
  687. package/skills/execute-plan/SKILL.md +0 -133
  688. package/skills/execute-plan/references/execution-map.md +0 -122
  689. package/skills/interview-gate/SKILL.md +0 -90
  690. package/skills/setup-freeflow/references/output-router-setup.md +0 -102
  691. package/skills/workflow/references/workflow-map.md +0 -104
  692. /package/pi-extension/src/{mcp-config.ts → integrations/mcp-config.ts} +0 -0
@@ -2,14 +2,15 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { buildBoundedEdgeChunks, buildBoundedExcerpt } from "../evidence/bounded-evidence.js";
5
- import { findBestLineForQuery, searchRepoEvidenceCandidates, } from "../evidence/evidence-search.js";
5
+ import { findBestLineForQuery, searchRepoEvidenceCandidates } from "../evidence/evidence-search.js";
6
6
  import { resolveExactLineRange } from "../evidence/line-ranges.js";
7
+ import { collectLocalTextFileRefs, resolveLocalPath } from "../local/local-traversal.js";
7
8
  import { collectRepoTextFileRefs, resolveRepoPath } from "../repo/repo-traversal.js";
8
9
  import { createLocalVaultIndex } from "../vault/vault-index.js";
9
10
  import { createVault, readOutputText, readVaultRecord } from "../vault/vault.js";
10
11
  export const FREEFLOW_SEARCH_ACTIONS = ["query", "locate", "get", "retrieve", "expand", "explain", "transform"];
11
12
  export function searchActionForRetrievalAction(action) {
12
- return action;
13
+ return action;
13
14
  }
14
15
  const DEFAULT_CONTEXT_LINES = 2;
15
16
  const DEFAULT_QUERY_TOP_K = 1;
@@ -26,1180 +27,1338 @@ const EXACT_CHUNK_MAX_BYTES = 32_000;
26
27
  const CONCURRENT_REPO_FILE_READS = 32;
27
28
  const QUERY_COVERAGE_MAX_LINES = 80;
28
29
  const BOUNDED_EVIDENCE_CAPS = {
29
- queryExcerptMaxBytes: QUERY_EXCERPT_MAX_BYTES,
30
- linePreviewMaxBytes: LINE_PREVIEW_MAX_BYTES,
31
- expandLines30MaxBytes: EXPAND_LINES_30_MAX_BYTES,
32
- expandLines30MaxLines: EXPAND_LINES_30_MAX_LINES,
33
- expandLines80MaxBytes: EXPAND_LINES_80_MAX_BYTES,
34
- expandLines80MaxLines: EXPAND_LINES_80_MAX_LINES,
35
- exactChunkMaxBytes: EXACT_CHUNK_MAX_BYTES,
30
+ queryExcerptMaxBytes: QUERY_EXCERPT_MAX_BYTES,
31
+ linePreviewMaxBytes: LINE_PREVIEW_MAX_BYTES,
32
+ expandLines30MaxBytes: EXPAND_LINES_30_MAX_BYTES,
33
+ expandLines30MaxLines: EXPAND_LINES_30_MAX_LINES,
34
+ expandLines80MaxBytes: EXPAND_LINES_80_MAX_BYTES,
35
+ expandLines80MaxLines: EXPAND_LINES_80_MAX_LINES,
36
+ exactChunkMaxBytes: EXACT_CHUNK_MAX_BYTES,
36
37
  };
37
38
  export async function freeflowSearch(options) {
38
- const preserve = options.preserve ?? "important";
39
- try {
40
- if (options.source.kind === "vault") {
41
- return await retrieveVault(options, preserve);
42
- }
43
- const root = resolve(options.source.root);
44
- if (options.action === "query") {
45
- return await queryRepo(root, options, preserve);
46
- }
47
- if (options.action === "locate") {
48
- return await locateRepo(root, options, preserve);
49
- }
50
- if (options.action === "get") {
51
- return await getRepo(root, options, preserve);
52
- }
53
- if (options.action === "expand") {
54
- return await expandRepoEvidence(root, options, preserve);
55
- }
56
- if (options.action === "retrieve") {
57
- return await retrieveRepoPath(root, options, preserve);
58
- }
59
- if (options.action === "explain") {
60
- return explainRepoDecision(options, preserve);
61
- }
62
- return errorResult(preserve, `Retrieval action ${options.action} is not implemented yet.`);
39
+ const preserve = options.preserve ?? "important";
40
+ try {
41
+ if (options.source.kind === "vault") {
42
+ return await retrieveVault(options, preserve);
43
+ }
44
+ if (options.source.kind === "local") {
45
+ if (options.action === "query") {
46
+ return await queryFileSource(options.source.root, options, preserve);
47
+ }
48
+ if (options.action === "locate") {
49
+ return await locateFileSource(options.source.root, options, preserve);
50
+ }
51
+ if (options.action === "get") {
52
+ return await getFileSource(options.source.root, options, preserve);
53
+ }
54
+ if (options.action === "expand") {
55
+ return await expandFileEvidence(options.source.root, options, preserve);
56
+ }
57
+ if (options.action === "retrieve") {
58
+ return await retrieveFilePath(options.source.root, options, preserve);
59
+ }
60
+ if (options.action === "explain") {
61
+ return explainFileDecision(options, preserve);
62
+ }
63
+ }
64
+ const root = resolve(options.source.root);
65
+ if (options.action === "query") {
66
+ return await queryFileSource(root, options, preserve);
67
+ }
68
+ if (options.action === "locate") {
69
+ return await locateFileSource(root, options, preserve);
70
+ }
71
+ if (options.action === "get") {
72
+ return await getFileSource(root, options, preserve);
63
73
  }
64
- catch (error) {
65
- const message = error instanceof Error ? error.message : String(error);
66
- return errorResult(preserve, `freeflow_search failed while reading ${options.source.kind} source: ${message}`);
74
+ if (options.action === "expand") {
75
+ return await expandFileEvidence(root, options, preserve);
67
76
  }
68
- }
69
- async function retrieveVault(options, preserve) {
70
77
  if (options.action === "retrieve") {
71
- if (!hasVaultOutputId(options)) {
72
- return errorResult(preserve, "Vault retrieve requires source.outputId.");
73
- }
74
- return retrieveVaultLines(options, preserve);
75
- }
76
- if (options.action === "expand") {
77
- if (!hasVaultOutputId(options)) {
78
- return errorResult(preserve, "Vault expansion requires source.outputId for exact raw recovery.");
79
- }
80
- return expandVaultEvidence(options, preserve);
78
+ return await retrieveFilePath(root, options, preserve);
81
79
  }
82
80
  if (options.action === "explain") {
83
- if (!hasVaultOutputId(options)) {
84
- return errorResult(preserve, "Vault explain requires source.outputId.");
85
- }
86
- return explainVaultOutput(options, preserve);
87
- }
88
- if (options.action !== "query" && options.action !== "locate" && options.action !== "get") {
89
- return errorResult(preserve, `Vault retrieval action ${options.action} is not implemented yet.`);
81
+ return explainFileDecision(options, preserve);
90
82
  }
91
- if (!options.query?.trim()) {
92
- return errorResult(preserve, `Vault ${options.action} requires a non-empty query string.`);
93
- }
94
- if (hasVaultOutputId(options) && (options.action === "query" || options.action === "get")) {
95
- return querySingleVaultOutput(options, preserve, options.action);
96
- }
97
- return queryVaultIndex(options, preserve, options.action);
83
+ return errorResult(preserve, `Retrieval action ${options.action} is not implemented yet.`);
84
+ } catch (error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ return errorResult(preserve, `freeflow_search failed while reading ${options.source.kind} source: ${message}`);
87
+ }
88
+ }
89
+ async function retrieveVault(options, preserve) {
90
+ if (options.action === "retrieve") {
91
+ if (!hasVaultOutputId(options)) {
92
+ return errorResult(preserve, "Vault retrieve requires source.outputId.");
93
+ }
94
+ return retrieveVaultLines(options, preserve);
95
+ }
96
+ if (options.action === "expand") {
97
+ if (!hasVaultOutputId(options)) {
98
+ return errorResult(preserve, "Vault expansion requires source.outputId for exact raw recovery.");
99
+ }
100
+ return expandVaultEvidence(options, preserve);
101
+ }
102
+ if (options.action === "explain") {
103
+ if (!hasVaultOutputId(options)) {
104
+ return errorResult(preserve, "Vault explain requires source.outputId.");
105
+ }
106
+ return explainVaultOutput(options, preserve);
107
+ }
108
+ if (options.action !== "query" && options.action !== "locate" && options.action !== "get") {
109
+ return errorResult(preserve, `Vault retrieval action ${options.action} is not implemented yet.`);
110
+ }
111
+ if (!options.query?.trim()) {
112
+ return errorResult(preserve, `Vault ${options.action} requires a non-empty query string.`);
113
+ }
114
+ if (hasVaultOutputId(options) && (options.action === "query" || options.action === "get")) {
115
+ return querySingleVaultOutput(options, preserve, options.action);
116
+ }
117
+ return queryVaultIndex(options, preserve, options.action);
98
118
  }
99
119
  async function querySingleVaultOutput(options, preserve, action = "query") {
100
- const query = options.query?.trim();
101
- if (!query) {
102
- return errorResult(preserve, "Vault query requires a non-empty query string.");
103
- }
104
- const vault = createVault({ root: options.source.root });
105
- const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
106
- const stream = resolveVaultStream(record, options.source.stream);
107
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
108
- const lines = splitLines(text);
109
- const candidateLine = findBestLineForQuery(lines, query);
110
- if (candidateLine === null) {
111
- return {
112
- toolStatus: "ok",
113
- decisionId: decisionId(`vault-${action}`, options.source.outputId, stream, query, "none"),
114
- preserve,
115
- source: { kind: "vault", outputId: options.source.outputId, stream },
116
- ...vaultRecordResultFields(record),
117
- routing: {
118
- status: "routed",
119
- route: "retrieve",
120
- reason: action === "get" ? "Freeflow search get found no matching vaulted output evidence." : "Deterministic lexical vault retrieval found no matching output evidence.",
121
- },
122
- evidence: [],
123
- recovery: {
124
- how: `Refine the query or use freeflow_search action=retrieve with outputId=${options.source.outputId} and an exact line range.`,
125
- outputId: options.source.outputId,
126
- },
127
- };
128
- }
129
- const start = Math.max(0, candidateLine - DEFAULT_CONTEXT_LINES);
130
- const end = Math.min(lines.length - 1, candidateLine + DEFAULT_CONTEXT_LINES);
131
- const evidenceLines = `${start + 1}-${end + 1}`;
132
- const match = matchMetadataForText(lines[candidateLine] ?? "", query);
133
- const evidence = {
134
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, query),
135
- source: { kind: "vault", outputId: options.source.outputId, stream },
136
- path: `${options.source.outputId}:${stream}`,
137
- lines: evidenceLines,
138
- excerpt: excerptForLineRange(lines, { start: start + 1, end: end + 1 }, "small"),
139
- why: action === "get"
140
- ? `Best vaulted get match near line ${candidateLine + 1}; matchType=${match.type} confidence=${match.confidence.toFixed(2)}.`
141
- : `Deterministic lexical match for query terms in vaulted ${stream} output near line ${candidateLine + 1}.`,
142
- window: "small",
143
- expandable: true,
144
- ...(action === "get" ? { match } : {}),
145
- };
120
+ const query = options.query?.trim();
121
+ if (!query) {
122
+ return errorResult(preserve, "Vault query requires a non-empty query string.");
123
+ }
124
+ const vault = createVault({ root: options.source.root });
125
+ const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
126
+ const stream = resolveVaultStream(record, options.source.stream);
127
+ const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
128
+ const lines = splitLines(text);
129
+ const candidateLine = findBestLineForQuery(lines, query);
130
+ if (candidateLine === null) {
146
131
  return {
147
- toolStatus: "ok",
148
- decisionId: decisionId(`vault-${action}`, options.source.outputId, stream, query, evidenceLines),
149
- preserve,
150
- source: { kind: "vault", outputId: options.source.outputId, stream },
151
- ...vaultRecordResultFields(record),
152
- routing: {
153
- status: "routed",
154
- route: "retrieve",
155
- reason: action === "get"
156
- ? `Freeflow search get selected best vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}; matchType=${match.type} confidence=${match.confidence.toFixed(2)}.`
157
- : `Deterministic lexical retrieval selected vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
158
- },
159
- evidence: [evidence],
160
- recovery: {
161
- how: action === "get"
162
- ? `Use freeflow_search action=retrieve with outputId=${options.source.outputId}, stream=${stream}, and lineRange=${evidenceLines} for exact recovery; use action=expand with evidenceId=${evidence.id} for more context.`
163
- : `Use freeflow_search action=expand with evidenceId=${evidence.id}, or action=retrieve with outputId=${options.source.outputId} and stream=${stream}.`,
164
- outputId: options.source.outputId,
165
- evidenceId: evidence.id,
166
- },
132
+ toolStatus: "ok",
133
+ decisionId: decisionId(`vault-${action}`, options.source.outputId, stream, query, "none"),
134
+ preserve,
135
+ source: { kind: "vault", outputId: options.source.outputId, stream },
136
+ ...vaultRecordResultFields(record),
137
+ routing: {
138
+ status: "routed",
139
+ route: "retrieve",
140
+ reason:
141
+ action === "get"
142
+ ? "Freeflow search get found no matching vaulted output evidence."
143
+ : "Deterministic lexical vault retrieval found no matching output evidence.",
144
+ },
145
+ evidence: [],
146
+ recovery: {
147
+ how: `Refine the query or use freeflow_search action=retrieve with outputId=${options.source.outputId} and an exact line range.`,
148
+ outputId: options.source.outputId,
149
+ },
167
150
  };
151
+ }
152
+ const start = Math.max(0, candidateLine - DEFAULT_CONTEXT_LINES);
153
+ const end = Math.min(lines.length - 1, candidateLine + DEFAULT_CONTEXT_LINES);
154
+ const evidenceLines = `${start + 1}-${end + 1}`;
155
+ const match = matchMetadataForText(lines[candidateLine] ?? "", query);
156
+ const evidence = {
157
+ id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, query),
158
+ source: { kind: "vault", outputId: options.source.outputId, stream },
159
+ path: `${options.source.outputId}:${stream}`,
160
+ lines: evidenceLines,
161
+ excerpt: excerptForLineRange(lines, { start: start + 1, end: end + 1 }, "small"),
162
+ why:
163
+ action === "get"
164
+ ? `Best vaulted get match near line ${candidateLine + 1}; matchType=${match.type} confidence=${match.confidence.toFixed(2)}.`
165
+ : `Deterministic lexical match for query terms in vaulted ${stream} output near line ${candidateLine + 1}.`,
166
+ window: "small",
167
+ expandable: true,
168
+ ...(action === "get" ? { match } : {}),
169
+ };
170
+ return {
171
+ toolStatus: "ok",
172
+ decisionId: decisionId(`vault-${action}`, options.source.outputId, stream, query, evidenceLines),
173
+ preserve,
174
+ source: { kind: "vault", outputId: options.source.outputId, stream },
175
+ ...vaultRecordResultFields(record),
176
+ routing: {
177
+ status: "routed",
178
+ route: "retrieve",
179
+ reason:
180
+ action === "get"
181
+ ? `Freeflow search get selected best vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}; matchType=${match.type} confidence=${match.confidence.toFixed(2)}.`
182
+ : `Deterministic lexical retrieval selected vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
183
+ },
184
+ evidence: [evidence],
185
+ recovery: {
186
+ how:
187
+ action === "get"
188
+ ? `Use freeflow_search action=retrieve with outputId=${options.source.outputId}, stream=${stream}, and lineRange=${evidenceLines} for exact recovery; use action=expand with evidenceId=${evidence.id} for more context.`
189
+ : `Use freeflow_search action=expand with evidenceId=${evidence.id}, or action=retrieve with outputId=${options.source.outputId} and stream=${stream}.`,
190
+ outputId: options.source.outputId,
191
+ evidenceId: evidence.id,
192
+ },
193
+ };
168
194
  }
169
195
  async function queryVaultIndex(options, preserve, action) {
170
- const query = options.query?.trim();
171
- if (!query) {
172
- return errorResult(preserve, `Vault ${action} requires a non-empty query string.`);
173
- }
174
- const topK = parseTopK(options.topK, action === "locate" ? DEFAULT_LOCATE_TOP_K : DEFAULT_QUERY_TOP_K);
175
- if (typeof topK === "string") {
176
- return errorResult(preserve, topK);
177
- }
178
- const vault = createVault({ root: options.source.root });
179
- const indexResult = await createLocalVaultIndex(vault).queryVault(query, vaultIndexFiltersFromSource(options.source, options.filters), {
180
- topK,
181
- maxExcerptBytes: action === "locate" ? LINE_PREVIEW_MAX_BYTES : QUERY_EXCERPT_MAX_BYTES,
182
- });
183
- if (indexResult.matches.length === 0) {
184
- return {
185
- toolStatus: "ok",
186
- decisionId: decisionId(`vault-${action}-index`, query, String(topK), "none"),
187
- preserve,
188
- routing: {
189
- status: "routed",
190
- route: "retrieve",
191
- reason: `Vault-wide ${action} found no matching indexed evidence for the current filters.`,
192
- },
193
- evidence: [],
194
- recovery: {
195
- how: "Refine the query, adjust vault filters, or use source.outputId with action=retrieve/explain when the exact output is known.",
196
- },
197
- };
198
- }
199
- const evidence = indexResult.matches.map((match) => evidenceFromVaultIndexMatch(match, query, action));
200
- const first = indexResult.matches[0];
201
- const firstEvidence = evidence[0];
202
- if (!first || !firstEvidence) {
203
- return errorResult(preserve, `Vault ${action} produced no usable indexed evidence.`);
204
- }
196
+ const query = options.query?.trim();
197
+ if (!query) {
198
+ return errorResult(preserve, `Vault ${action} requires a non-empty query string.`);
199
+ }
200
+ const topK = parseTopK(options.topK, action === "locate" ? DEFAULT_LOCATE_TOP_K : DEFAULT_QUERY_TOP_K);
201
+ if (typeof topK === "string") {
202
+ return errorResult(preserve, topK);
203
+ }
204
+ const vault = createVault({ root: options.source.root });
205
+ const indexResult = await createLocalVaultIndex(vault).queryVault(
206
+ query,
207
+ vaultIndexFiltersFromSource(options.source, options.filters),
208
+ {
209
+ topK,
210
+ maxExcerptBytes: action === "locate" ? LINE_PREVIEW_MAX_BYTES : QUERY_EXCERPT_MAX_BYTES,
211
+ },
212
+ );
213
+ if (indexResult.matches.length === 0) {
205
214
  return {
206
- toolStatus: "ok",
207
- decisionId: decisionId(`vault-${action}-index`, query, String(topK), evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|")),
208
- preserve,
209
- source: sourceRefForVaultIndexMatch(first),
210
- routing: {
211
- status: "routed",
212
- route: "retrieve",
213
- reason: action === "locate"
214
- ? `Located ${evidence.length} indexed vault candidate(s) without raw output retrieval; top result ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}.`
215
- : action === "get"
216
- ? `Freeflow search get selected best indexed vault match ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}; matchType=${firstEvidence.match?.type ?? "lexical"} confidence=${(firstEvidence.match?.confidence ?? scoreConfidence(first.score)).toFixed(2)}.`
217
- : `Vault-wide query selected ${evidence.length} indexed candidate(s); top result ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}.`,
218
- },
219
- evidence,
220
- recovery: {
221
- how: first.metadataOnly
222
- ? `Top match ${first.outputId} is metadata-only; use freeflow_search action=explain with outputId=${first.outputId} for recoverability details.`
223
- : `Use freeflow_search action=expand with source.outputId=${first.outputId} and evidenceId=${firstEvidence.id}, or action=retrieve with outputId=${first.outputId}${first.stream ? ` and stream=${first.stream}` : ""}.`,
224
- outputId: first.outputId,
225
- evidenceId: firstEvidence.id,
226
- },
215
+ toolStatus: "ok",
216
+ decisionId: decisionId(`vault-${action}-index`, query, String(topK), "none"),
217
+ preserve,
218
+ routing: {
219
+ status: "routed",
220
+ route: "retrieve",
221
+ reason: `Vault-wide ${action} found no matching indexed evidence for the current filters.`,
222
+ },
223
+ evidence: [],
224
+ recovery: {
225
+ how: "Refine the query, adjust vault filters, or use source.outputId with action=retrieve/explain when the exact output is known.",
226
+ },
227
227
  };
228
+ }
229
+ const evidence = indexResult.matches.map((match) => evidenceFromVaultIndexMatch(match, query, action));
230
+ const first = indexResult.matches[0];
231
+ const firstEvidence = evidence[0];
232
+ if (!first || !firstEvidence) {
233
+ return errorResult(preserve, `Vault ${action} produced no usable indexed evidence.`);
234
+ }
235
+ return {
236
+ toolStatus: "ok",
237
+ decisionId: decisionId(
238
+ `vault-${action}-index`,
239
+ query,
240
+ String(topK),
241
+ evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|"),
242
+ ),
243
+ preserve,
244
+ source: sourceRefForVaultIndexMatch(first),
245
+ routing: {
246
+ status: "routed",
247
+ route: "retrieve",
248
+ reason:
249
+ action === "locate"
250
+ ? `Located ${evidence.length} indexed vault candidate(s) without raw output retrieval; top result ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}.`
251
+ : action === "get"
252
+ ? `Freeflow search get selected best indexed vault match ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}; matchType=${firstEvidence.match?.type ?? "lexical"} confidence=${(firstEvidence.match?.confidence ?? scoreConfidence(first.score)).toFixed(2)}.`
253
+ : `Vault-wide query selected ${evidence.length} indexed candidate(s); top result ${first.outputId}${first.stream ? `:${first.stream}` : ""}${firstEvidence.lines ? `:${firstEvidence.lines}` : ""}.`,
254
+ },
255
+ evidence,
256
+ recovery: {
257
+ how: first.metadataOnly
258
+ ? `Top match ${first.outputId} is metadata-only; use freeflow_search action=explain with outputId=${first.outputId} for recoverability details.`
259
+ : `Use freeflow_search action=expand with source.outputId=${first.outputId} and evidenceId=${firstEvidence.id}, or action=retrieve with outputId=${first.outputId}${first.stream ? ` and stream=${first.stream}` : ""}.`,
260
+ outputId: first.outputId,
261
+ evidenceId: firstEvidence.id,
262
+ },
263
+ };
228
264
  }
229
265
  function evidenceFromVaultIndexMatch(match, query, action) {
230
- const source = sourceRefForVaultIndexMatch(match);
231
- const lines = match.lineStart !== undefined && match.lineEnd !== undefined ? `${match.lineStart}-${match.lineEnd}` : undefined;
232
- const path = `${match.outputId}${match.stream ? `:${match.stream}` : ":metadata"}`;
233
- const matchMetadata = match.metadataOnly ? { type: "metadata", confidence: scoreConfidence(match.score) } : matchMetadataForText(match.excerpt, query, match.score);
234
- const why = match.metadataOnly
235
- ? `Indexed metadata-only ${action} match for query terms; raw content is not recoverable from this entry.`
236
- : action === "get"
237
- ? `Best indexed vault get match; matchType=${matchMetadata.type} confidence=${matchMetadata.confidence.toFixed(2)} score=${match.score}.`
238
- : `Indexed vault ${action} match for query terms with score ${match.score}.`;
239
- return {
240
- id: evidenceIdFor(path, lines ?? match.chunkId, query),
241
- source,
242
- path,
243
- ...(lines !== undefined ? { lines } : {}),
244
- excerpt: match.excerpt,
245
- why,
246
- window: "small",
247
- expandable: !match.metadataOnly && lines !== undefined && match.stream !== undefined,
248
- ...(action === "get" ? { match: matchMetadata } : {}),
249
- };
266
+ const source = sourceRefForVaultIndexMatch(match);
267
+ const lines =
268
+ match.lineStart !== undefined && match.lineEnd !== undefined ? `${match.lineStart}-${match.lineEnd}` : undefined;
269
+ const path = `${match.outputId}${match.stream ? `:${match.stream}` : ":metadata"}`;
270
+ const matchMetadata = match.metadataOnly
271
+ ? { type: "metadata", confidence: scoreConfidence(match.score) }
272
+ : matchMetadataForText(match.excerpt, query, match.score);
273
+ const why = match.metadataOnly
274
+ ? `Indexed metadata-only ${action} match for query terms; raw content is not recoverable from this entry.`
275
+ : action === "get"
276
+ ? `Best indexed vault get match; matchType=${matchMetadata.type} confidence=${matchMetadata.confidence.toFixed(2)} score=${match.score}.`
277
+ : `Indexed vault ${action} match for query terms with score ${match.score}.`;
278
+ return {
279
+ id: evidenceIdFor(path, lines ?? match.chunkId, query),
280
+ source,
281
+ path,
282
+ ...(lines !== undefined ? { lines } : {}),
283
+ excerpt: match.excerpt,
284
+ why,
285
+ window: "small",
286
+ expandable: !match.metadataOnly && lines !== undefined && match.stream !== undefined,
287
+ ...(action === "get" ? { match: matchMetadata } : {}),
288
+ };
250
289
  }
251
290
  function sourceRefForVaultIndexMatch(match) {
252
- return {
253
- kind: "vault",
254
- outputId: match.outputId,
255
- ...(match.stream !== undefined ? { stream: match.stream } : {}),
256
- };
291
+ return {
292
+ kind: "vault",
293
+ outputId: match.outputId,
294
+ ...(match.stream !== undefined ? { stream: match.stream } : {}),
295
+ };
257
296
  }
258
297
  function vaultIndexFiltersFromSource(source, filters) {
259
- return {
260
- ...filters,
261
- sessionId: filters?.sessionId ?? source.sessionId,
262
- ...(source.outputId !== undefined ? { outputId: source.outputId } : {}),
263
- ...(source.producerKind !== undefined ? { producerKind: source.producerKind } : {}),
264
- ...(source.server !== undefined ? { server: source.server } : {}),
265
- ...(source.tool !== undefined ? { tool: source.tool } : {}),
266
- ...(source.hostToolName !== undefined ? { hostToolName: source.hostToolName } : {}),
267
- ...(source.stream !== undefined ? { stream: source.stream } : {}),
268
- ...(source.recordKind !== undefined ? { recordKind: source.recordKind } : {}),
269
- ...(source.recoverability !== undefined ? { recoverability: source.recoverability } : {}),
270
- };
298
+ return {
299
+ ...filters,
300
+ sessionId: filters?.sessionId ?? source.sessionId,
301
+ ...(source.outputId !== undefined ? { outputId: source.outputId } : {}),
302
+ ...(source.producerKind !== undefined ? { producerKind: source.producerKind } : {}),
303
+ ...(source.server !== undefined ? { server: source.server } : {}),
304
+ ...(source.tool !== undefined ? { tool: source.tool } : {}),
305
+ ...(source.hostToolName !== undefined ? { hostToolName: source.hostToolName } : {}),
306
+ ...(source.stream !== undefined ? { stream: source.stream } : {}),
307
+ ...(source.recordKind !== undefined ? { recordKind: source.recordKind } : {}),
308
+ ...(source.recoverability !== undefined ? { recoverability: source.recoverability } : {}),
309
+ };
271
310
  }
272
311
  function hasVaultOutputId(options) {
273
- return typeof options.source.outputId === "string" && options.source.outputId.length > 0;
312
+ return typeof options.source.outputId === "string" && options.source.outputId.length > 0;
274
313
  }
275
314
  async function retrieveVaultLines(options, preserve) {
276
- const lineRange = options.lineRange;
277
- if (!lineRange) {
278
- return errorResult(preserve, "Vault retrieve requires a valid 1-based lineRange.");
279
- }
280
- const vault = createVault({ root: options.source.root });
281
- const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
282
- const stream = resolveVaultStream(record, options.source.stream);
283
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
284
- const lines = splitLines(text);
285
- const resolvedRange = resolveExactLineRange({
286
- requested: lineRange,
287
- lineCount: lines.length,
288
- availableLabel: "available vaulted output lines",
289
- invalidReason: "Vault retrieve requires a valid 1-based lineRange.",
290
- });
291
- if (!resolvedRange.ok) {
292
- return errorResult(preserve, resolvedRange.reason);
293
- }
294
- const { start, end } = resolvedRange.range;
295
- const evidenceLines = `${start}-${end}`;
296
- const excerpt = lines.slice(start - 1, end).join("\n");
297
- if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
298
- return retrieveVaultLineRangeOverCap(options, preserve, stream, record, lines, { start, end }, byteLength(excerpt));
299
- }
300
- const evidence = {
301
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, "retrieve"),
315
+ const lineRange = options.lineRange;
316
+ if (!lineRange) {
317
+ return errorResult(preserve, "Vault retrieve requires a valid 1-based lineRange.");
318
+ }
319
+ const vault = createVault({ root: options.source.root });
320
+ const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
321
+ const stream = resolveVaultStream(record, options.source.stream);
322
+ const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
323
+ const lines = splitLines(text);
324
+ const resolvedRange = resolveExactLineRange({
325
+ requested: lineRange,
326
+ lineCount: lines.length,
327
+ availableLabel: "available vaulted output lines",
328
+ invalidReason: "Vault retrieve requires a valid 1-based lineRange.",
329
+ });
330
+ if (!resolvedRange.ok) {
331
+ return errorResult(preserve, resolvedRange.reason);
332
+ }
333
+ const { start, end } = resolvedRange.range;
334
+ const evidenceLines = `${start}-${end}`;
335
+ const excerpt = lines.slice(start - 1, end).join("\n");
336
+ if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
337
+ return retrieveVaultLineRangeOverCap(options, preserve, stream, record, lines, { start, end }, byteLength(excerpt));
338
+ }
339
+ const evidence = {
340
+ id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, "retrieve"),
341
+ source: { kind: "vault", outputId: options.source.outputId, stream },
342
+ path: `${options.source.outputId}:${stream}`,
343
+ lines: evidenceLines,
344
+ excerpt,
345
+ why: `Retrieved exact vaulted ${stream} output lines ${evidenceLines}.`,
346
+ window: "exact",
347
+ expandable: true,
348
+ };
349
+ return {
350
+ toolStatus: "ok",
351
+ decisionId: decisionId("vault-retrieve", options.source.outputId, stream, evidenceLines),
352
+ preserve,
353
+ source: { kind: "vault", outputId: options.source.outputId, stream },
354
+ ...vaultRecordResultFields(record),
355
+ routing: {
356
+ status: "routed",
357
+ route: "retrieve",
358
+ reason: `Retrieved exact vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
359
+ },
360
+ evidence: [evidence],
361
+ recovery: {
362
+ how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more vaulted output context.`,
363
+ outputId: options.source.outputId,
364
+ evidenceId: evidence.id,
365
+ },
366
+ };
367
+ }
368
+ function retrieveVaultLineRangeOverCap(options, preserve, stream, record, lines, requestedRange, requestedBytes) {
369
+ const evidence = buildBoundedEdgeChunks({ lines, range: requestedRange, caps: BOUNDED_EVIDENCE_CAPS }).map(
370
+ (chunk, index) => {
371
+ const evidenceLines = chunk.linesLabel;
372
+ return {
373
+ id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `retrieve-over-cap-${index}`),
302
374
  source: { kind: "vault", outputId: options.source.outputId, stream },
303
375
  path: `${options.source.outputId}:${stream}`,
304
376
  lines: evidenceLines,
305
- excerpt,
306
- why: `Retrieved exact vaulted ${stream} output lines ${evidenceLines}.`,
307
- window: "exact",
377
+ excerpt: chunk.excerpt,
378
+ why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} output line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
379
+ window: "small",
308
380
  expandable: true,
309
- };
310
- return {
311
- toolStatus: "ok",
312
- decisionId: decisionId("vault-retrieve", options.source.outputId, stream, evidenceLines),
313
- preserve,
314
- source: { kind: "vault", outputId: options.source.outputId, stream },
315
- ...vaultRecordResultFields(record),
316
- routing: {
317
- status: "routed",
318
- route: "retrieve",
319
- reason: `Retrieved exact vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
320
- },
321
- evidence: [evidence],
322
- recovery: {
323
- how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more vaulted output context.`,
324
- outputId: options.source.outputId,
325
- evidenceId: evidence.id,
326
- },
327
- };
328
- }
329
- function retrieveVaultLineRangeOverCap(options, preserve, stream, record, lines, requestedRange, requestedBytes) {
330
- const evidence = buildBoundedEdgeChunks({ lines, range: requestedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => {
331
- const evidenceLines = chunk.linesLabel;
332
- return {
333
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `retrieve-over-cap-${index}`),
334
- source: { kind: "vault", outputId: options.source.outputId, stream },
335
- path: `${options.source.outputId}:${stream}`,
336
- lines: evidenceLines,
337
- excerpt: chunk.excerpt,
338
- why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} output line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
339
- window: "small",
340
- expandable: true,
341
- };
342
- });
343
- return {
344
- toolStatus: "ok",
345
- decisionId: decisionId("vault-retrieve-lines-over-cap", options.source.outputId, stream, `${requestedRange.start}-${requestedRange.end}`, String(requestedBytes)),
346
- preserve,
347
- source: { kind: "vault", outputId: options.source.outputId, stream },
348
- ...vaultRecordResultFields(record),
349
- routing: {
350
- status: "partial",
351
- route: "retrieve",
352
- reason: `Requested vaulted outputId=${options.source.outputId} stream=${stream} lines=${requestedRange.start}-${requestedRange.end} is ${requestedBytes} bytes and exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge previews with recovery guidance.`,
353
- },
354
- evidence,
355
- recovery: {
356
- how: `Use narrower freeflow_search action=retrieve lineRange spans for exact vaulted output recovery, or native vault file access if a full raw span is required.`,
357
- outputId: options.source.outputId,
358
- },
359
- };
381
+ };
382
+ },
383
+ );
384
+ return {
385
+ toolStatus: "ok",
386
+ decisionId: decisionId(
387
+ "vault-retrieve-lines-over-cap",
388
+ options.source.outputId,
389
+ stream,
390
+ `${requestedRange.start}-${requestedRange.end}`,
391
+ String(requestedBytes),
392
+ ),
393
+ preserve,
394
+ source: { kind: "vault", outputId: options.source.outputId, stream },
395
+ ...vaultRecordResultFields(record),
396
+ routing: {
397
+ status: "partial",
398
+ route: "retrieve",
399
+ reason: `Requested vaulted outputId=${options.source.outputId} stream=${stream} lines=${requestedRange.start}-${requestedRange.end} is ${requestedBytes} bytes and exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge previews with recovery guidance.`,
400
+ },
401
+ evidence,
402
+ recovery: {
403
+ how: `Use narrower freeflow_search action=retrieve lineRange spans for exact vaulted output recovery, or native vault file access if a full raw span is required.`,
404
+ outputId: options.source.outputId,
405
+ },
406
+ };
360
407
  }
361
408
  function expandVaultEvidenceOverCap(options, preserve, stream, record, evidence, lines, expandedRange, expandedBytes) {
362
- const chunks = buildBoundedEdgeChunks({ lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => {
363
- const evidenceLines = chunk.linesLabel;
364
- return {
365
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `expand-over-cap-${index}`),
366
- source: { kind: "vault", outputId: options.source.outputId, stream },
367
- path: `${options.source.outputId}:${stream}`,
368
- lines: evidenceLines,
369
- excerpt: chunk.excerpt,
370
- why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} expansion over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
371
- window: "small",
372
- expandable: true,
373
- };
374
- });
375
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
376
- return {
377
- toolStatus: "ok",
378
- decisionId: decisionId("vault-expand-over-cap", evidence.id, expandedLines, String(expandedBytes)),
379
- preserve,
409
+ const chunks = buildBoundedEdgeChunks({ lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map(
410
+ (chunk, index) => {
411
+ const evidenceLines = chunk.linesLabel;
412
+ return {
413
+ id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `expand-over-cap-${index}`),
380
414
  source: { kind: "vault", outputId: options.source.outputId, stream },
381
- ...vaultRecordResultFields(record),
382
- routing: {
383
- status: "partial",
384
- route: "retrieve",
385
- reason: `Expanded vaulted outputId=${options.source.outputId} stream=${stream} evidence ${evidence.id} to lines=${expandedLines}, but ${expandedBytes} bytes exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge chunks with recovery guidance.`,
386
- },
387
- evidence: chunks,
388
- recovery: {
389
- how: `Use narrower freeflow_search action=retrieve lineRange spans for exact vaulted output recovery.`,
390
- outputId: options.source.outputId,
391
- evidenceId: evidence.id,
392
- },
393
- };
415
+ path: `${options.source.outputId}:${stream}`,
416
+ lines: evidenceLines,
417
+ excerpt: chunk.excerpt,
418
+ why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} expansion over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
419
+ window: "small",
420
+ expandable: true,
421
+ };
422
+ },
423
+ );
424
+ const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
425
+ return {
426
+ toolStatus: "ok",
427
+ decisionId: decisionId("vault-expand-over-cap", evidence.id, expandedLines, String(expandedBytes)),
428
+ preserve,
429
+ source: { kind: "vault", outputId: options.source.outputId, stream },
430
+ ...vaultRecordResultFields(record),
431
+ routing: {
432
+ status: "partial",
433
+ route: "retrieve",
434
+ reason: `Expanded vaulted outputId=${options.source.outputId} stream=${stream} evidence ${evidence.id} to lines=${expandedLines}, but ${expandedBytes} bytes exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge chunks with recovery guidance.`,
435
+ },
436
+ evidence: chunks,
437
+ recovery: {
438
+ how: `Use narrower freeflow_search action=retrieve lineRange spans for exact vaulted output recovery.`,
439
+ outputId: options.source.outputId,
440
+ evidenceId: evidence.id,
441
+ },
442
+ };
394
443
  }
395
444
  async function expandVaultEvidence(options, preserve) {
396
- const evidence = options.evidence;
397
- if (!evidence?.lines || evidence.source.kind !== "vault") {
398
- return errorResult(preserve, "Vault expansion requires a previous vault evidence packet with lines.");
399
- }
400
- const originalRange = parseLineRange(evidence.lines);
401
- if (!originalRange) {
402
- return errorResult(preserve, `Cannot expand unsupported vault evidence line range ${evidence.lines}.`);
403
- }
404
- const expansion = options.expansion ?? "lines_30";
405
- const vault = createVault({ root: options.source.root });
406
- const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
407
- const stream = resolveVaultStream(record, options.source.stream ?? evidence.source.stream);
408
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
409
- const lines = splitLines(text);
410
- const expandedRange = expandLineRange(originalRange, lines.length, expansion);
411
- const window = windowForExpansion(expansion);
412
- const expandedExcerpt = lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
413
- if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
414
- return expandVaultEvidenceOverCap(options, preserve, stream, record, evidence, lines, expandedRange, byteLength(expandedExcerpt));
415
- }
416
- const bounded = buildBoundedExcerpt({ lines, range: expandedRange, window, caps: BOUNDED_EVIDENCE_CAPS });
417
- const expandedLines = bounded.linesLabel;
418
- const expandedEvidence = {
419
- ...evidence,
420
- source: { kind: "vault", outputId: options.source.outputId, stream },
421
- path: `${options.source.outputId}:${stream}`,
422
- lines: expandedLines,
423
- excerpt: bounded.excerpt,
424
- why: bounded.truncatedByLineCap
425
- ? `Expanded deterministic vault evidence from ${evidence.lines} and bounded it to ${expandedLines} by the ${window} line cap.`
426
- : `Expanded deterministic vault evidence from ${evidence.lines} to ${expandedLines}.`,
427
- window,
428
- expandable: expansion !== "full",
429
- };
430
- return {
431
- toolStatus: "ok",
432
- decisionId: decisionId("vault-expand", evidence.id, expandedLines),
433
- preserve,
434
- source: { kind: "vault", outputId: options.source.outputId, stream },
435
- ...vaultRecordResultFields(record),
436
- routing: {
437
- status: "routed",
438
- route: "retrieve",
439
- reason: `Expanded vaulted outputId=${options.source.outputId} stream=${stream} evidence ${evidence.id} to lines=${expandedLines}.`,
440
- },
441
- evidence: [expandedEvidence],
442
- recovery: {
443
- how: `Use freeflow_search action=retrieve with outputId=${options.source.outputId}, stream=${stream}, and an exact line range for precise recovery.`,
444
- outputId: options.source.outputId,
445
- evidenceId: evidence.id,
446
- },
447
- };
445
+ const evidence = options.evidence;
446
+ if (!evidence?.lines || evidence.source.kind !== "vault") {
447
+ return errorResult(preserve, "Vault expansion requires a previous vault evidence packet with lines.");
448
+ }
449
+ const originalRange = parseLineRange(evidence.lines);
450
+ if (!originalRange) {
451
+ return errorResult(preserve, `Cannot expand unsupported vault evidence line range ${evidence.lines}.`);
452
+ }
453
+ const expansion = options.expansion ?? "lines_30";
454
+ const vault = createVault({ root: options.source.root });
455
+ const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
456
+ const stream = resolveVaultStream(record, options.source.stream ?? evidence.source.stream);
457
+ const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
458
+ const lines = splitLines(text);
459
+ const expandedRange = expandLineRange(originalRange, lines.length, expansion);
460
+ const window = windowForExpansion(expansion);
461
+ const expandedExcerpt = lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
462
+ if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
463
+ return expandVaultEvidenceOverCap(
464
+ options,
465
+ preserve,
466
+ stream,
467
+ record,
468
+ evidence,
469
+ lines,
470
+ expandedRange,
471
+ byteLength(expandedExcerpt),
472
+ );
473
+ }
474
+ const bounded = buildBoundedExcerpt({ lines, range: expandedRange, window, caps: BOUNDED_EVIDENCE_CAPS });
475
+ const expandedLines = bounded.linesLabel;
476
+ const expandedEvidence = {
477
+ ...evidence,
478
+ source: { kind: "vault", outputId: options.source.outputId, stream },
479
+ path: `${options.source.outputId}:${stream}`,
480
+ lines: expandedLines,
481
+ excerpt: bounded.excerpt,
482
+ why: bounded.truncatedByLineCap
483
+ ? `Expanded deterministic vault evidence from ${evidence.lines} and bounded it to ${expandedLines} by the ${window} line cap.`
484
+ : `Expanded deterministic vault evidence from ${evidence.lines} to ${expandedLines}.`,
485
+ window,
486
+ expandable: expansion !== "full",
487
+ };
488
+ return {
489
+ toolStatus: "ok",
490
+ decisionId: decisionId("vault-expand", evidence.id, expandedLines),
491
+ preserve,
492
+ source: { kind: "vault", outputId: options.source.outputId, stream },
493
+ ...vaultRecordResultFields(record),
494
+ routing: {
495
+ status: "routed",
496
+ route: "retrieve",
497
+ reason: `Expanded vaulted outputId=${options.source.outputId} stream=${stream} evidence ${evidence.id} to lines=${expandedLines}.`,
498
+ },
499
+ evidence: [expandedEvidence],
500
+ recovery: {
501
+ how: `Use freeflow_search action=retrieve with outputId=${options.source.outputId}, stream=${stream}, and an exact line range for precise recovery.`,
502
+ outputId: options.source.outputId,
503
+ evidenceId: evidence.id,
504
+ },
505
+ };
448
506
  }
449
507
  async function explainVaultOutput(options, preserve) {
450
- const vault = createVault({ root: options.source.root });
451
- const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
452
- const producer = producerForVaultRecord(record);
453
- const persistence = persistenceForVaultRecord(record);
454
- const details = vaultRecordDetails(record);
455
- const recovery = vaultRecordRecovery(record, options.source.stream);
456
- const source = { kind: "vault", outputId: options.source.outputId };
457
- if (options.source.stream !== undefined) {
458
- Object.assign(source, { stream: options.source.stream });
459
- }
460
- return {
461
- toolStatus: "ok",
462
- decisionId: decisionId("vault-explain", options.source.outputId, record.contentHashSha256),
463
- preserve,
464
- source,
465
- ...vaultRecordResultFields(record),
466
- routing: {
467
- status: "routed",
468
- route: "retrieve",
469
- reason: `Vault outputId=${options.source.outputId} ${details}; producer=${producer.kind}; persistence.status=${persistence.status}; recoverability=${persistence.recoverability}. ${recovery.reason}`,
470
- },
471
- evidence: [],
472
- recovery: recovery.hint,
473
- };
508
+ const vault = createVault({ root: options.source.root });
509
+ const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
510
+ const producer = producerForVaultRecord(record);
511
+ const persistence = persistenceForVaultRecord(record);
512
+ const details = vaultRecordDetails(record);
513
+ const recovery = vaultRecordRecovery(record, options.source.stream);
514
+ const source = { kind: "vault", outputId: options.source.outputId };
515
+ if (options.source.stream !== undefined) {
516
+ Object.assign(source, { stream: options.source.stream });
517
+ }
518
+ return {
519
+ toolStatus: "ok",
520
+ decisionId: decisionId("vault-explain", options.source.outputId, record.contentHashSha256),
521
+ preserve,
522
+ source,
523
+ ...vaultRecordResultFields(record),
524
+ routing: {
525
+ status: "routed",
526
+ route: "retrieve",
527
+ reason: `Vault outputId=${options.source.outputId} ${details}; producer=${producer.kind}; persistence.status=${persistence.status}; recoverability=${persistence.recoverability}. ${recovery.reason}`,
528
+ },
529
+ evidence: [],
530
+ recovery: recovery.hint,
531
+ };
474
532
  }
475
533
  function resolveVaultStream(record, requested) {
476
- if (requested !== undefined) {
477
- return requested;
478
- }
479
- if (record.kind === "command") {
480
- return "combined";
481
- }
482
- if (record.kind === "text") {
483
- return "raw";
484
- }
485
- if (record.kind === "metadata") {
486
- throw new Error("Metadata only records store no raw stream.");
487
- }
488
- throw new Error("Repo file reference records store metadata only and have no raw stream.");
534
+ if (requested !== undefined) {
535
+ return requested;
536
+ }
537
+ if (record.kind === "command") {
538
+ return "combined";
539
+ }
540
+ if (record.kind === "text") {
541
+ return "raw";
542
+ }
543
+ if (record.kind === "metadata") {
544
+ throw new Error("Metadata only records store no raw stream.");
545
+ }
546
+ throw new Error("Repo file reference records store metadata only and have no raw stream.");
489
547
  }
490
548
  function vaultRecordResultFields(record) {
491
- const fields = {
492
- producer: producerForVaultRecord(record),
493
- persistence: persistenceForVaultRecord(record),
494
- };
495
- const recordId = record.recordId;
496
- if (typeof recordId === "string") {
497
- fields.recordId = recordId;
498
- }
499
- if (record.lineage !== undefined) {
500
- fields.lineage = record.lineage;
501
- }
502
- return fields;
549
+ const fields = {
550
+ producer: producerForVaultRecord(record),
551
+ persistence: persistenceForVaultRecord(record),
552
+ };
553
+ const recordId = record.recordId;
554
+ if (typeof recordId === "string") {
555
+ fields.recordId = recordId;
556
+ }
557
+ if (record.lineage !== undefined) {
558
+ fields.lineage = record.lineage;
559
+ }
560
+ return fields;
503
561
  }
504
562
  function producerForVaultRecord(record) {
505
- const producer = record.producer;
506
- if (producer?.kind) {
507
- return producer;
508
- }
509
- if (record.kind === "command") {
510
- return { kind: "command" };
511
- }
512
- if (record.kind === "text" || record.kind === "metadata") {
513
- if (record.sourceKind === "native") {
514
- return { kind: "native" };
515
- }
516
- if (record.sourceKind === "mcp") {
517
- return { kind: "mcp" };
518
- }
519
- if (record.sourceKind === "web") {
520
- return { kind: "web" };
521
- }
522
- if (record.sourceKind === "fetch") {
523
- return { kind: "fetch" };
524
- }
525
- if (record.sourceKind === "code_search") {
526
- return { kind: "code_search" };
527
- }
528
- return { kind: "other" };
529
- }
530
- return { kind: "repo" };
563
+ const producer = record.producer;
564
+ if (producer?.kind) {
565
+ return producer;
566
+ }
567
+ if (record.kind === "command") {
568
+ return { kind: "command" };
569
+ }
570
+ if (record.kind === "text" || record.kind === "metadata") {
571
+ if (record.sourceKind === "native") {
572
+ return { kind: "native" };
573
+ }
574
+ if (record.sourceKind === "mcp") {
575
+ return { kind: "mcp" };
576
+ }
577
+ if (record.sourceKind === "web") {
578
+ return { kind: "web" };
579
+ }
580
+ if (record.sourceKind === "fetch") {
581
+ return { kind: "fetch" };
582
+ }
583
+ if (record.sourceKind === "code_search") {
584
+ return { kind: "code_search" };
585
+ }
586
+ return { kind: "other" };
587
+ }
588
+ return { kind: "repo" };
531
589
  }
532
590
  function persistenceForVaultRecord(record) {
533
- const persistence = record.persistence;
534
- if (persistence?.status && persistence.recoverability) {
535
- return persistence;
536
- }
537
- if (record.kind === "repo-file" || record.kind === "metadata") {
538
- return { status: "metadata_only", recoverability: "metadata_only", outputId: record.outputId };
539
- }
540
- return {
541
- status: "vaulted",
542
- recoverability: "exact",
543
- recoveryOutputId: record.outputId,
544
- outputId: record.outputId,
545
- };
591
+ const persistence = record.persistence;
592
+ if (persistence?.status && persistence.recoverability) {
593
+ return persistence;
594
+ }
595
+ if (record.kind === "repo-file" || record.kind === "metadata") {
596
+ return { status: "metadata_only", recoverability: "metadata_only", outputId: record.outputId };
597
+ }
598
+ return {
599
+ status: "vaulted",
600
+ recoverability: "exact",
601
+ recoveryOutputId: record.outputId,
602
+ outputId: record.outputId,
603
+ };
546
604
  }
547
605
  function vaultRecordDetails(record) {
548
- const decisions = Array.isArray(record.decisionIds) ? record.decisionIds.join(",") : "";
549
- const recordId = record.recordId;
550
- const recordIdText = typeof recordId === "string" ? ` recordId=${recordId}` : "";
551
- if (record.kind === "command") {
552
- return `kind=command${recordIdText} executionStatus=${record.executionStatus} exitCode=${record.exitCode} decisions=${decisions}`;
553
- }
554
- if (record.kind === "text") {
555
- return `kind=text${recordIdText} sourceKind=${record.sourceKind} decisions=${decisions}`;
556
- }
557
- if (record.kind === "metadata") {
558
- return `kind=metadata${recordIdText} sourceKind=${record.sourceKind} rawLines=${record.lineCounts.raw} rawBytes=${record.byteCounts.raw} decisions=${decisions}`;
559
- }
560
- return `kind=repo-file${recordIdText} path=${record.path} decisions=${decisions}`;
606
+ const decisions = Array.isArray(record.decisionIds) ? record.decisionIds.join(",") : "";
607
+ const recordId = record.recordId;
608
+ const recordIdText = typeof recordId === "string" ? ` recordId=${recordId}` : "";
609
+ if (record.kind === "command") {
610
+ return `kind=command${recordIdText} executionStatus=${record.executionStatus} exitCode=${record.exitCode} decisions=${decisions}`;
611
+ }
612
+ if (record.kind === "text") {
613
+ return `kind=text${recordIdText} sourceKind=${record.sourceKind} decisions=${decisions}`;
614
+ }
615
+ if (record.kind === "metadata") {
616
+ return `kind=metadata${recordIdText} sourceKind=${record.sourceKind} rawLines=${record.lineCounts.raw} rawBytes=${record.byteCounts.raw} decisions=${decisions}`;
617
+ }
618
+ return `kind=repo-file${recordIdText} path=${record.path} decisions=${decisions}`;
561
619
  }
562
620
  function vaultRecordRecovery(record, requestedStream) {
563
- const persistence = persistenceForVaultRecord(record);
564
- const recoveryOutputId = persistence.recoveryOutputId ?? persistence.outputId ?? record.outputId;
565
- if (persistence.recoverability === "exact") {
566
- const stream = requestedStream ?? resolveVaultStream(record);
567
- const streamText = record.kind === "command" && requestedStream === undefined ? "stdout|stderr|combined" : stream;
568
- return {
569
- reason: `Exact content is recoverable from the vault with recoveryOutputId=${recoveryOutputId}.`,
570
- hint: {
571
- how: `Use freeflow_search action=retrieve with outputId=${recoveryOutputId}, stream=${streamText}, and an exact lineRange to recover exact vaulted content.`,
572
- outputId: recoveryOutputId,
573
- },
574
- };
575
- }
576
- if (persistence.recoverability === "redacted") {
577
- return {
578
- reason: `Only redacted content is recoverable from the vault; exact raw content is intentionally unavailable.`,
579
- hint: {
580
- how: `Use freeflow_search action=retrieve with outputId=${recoveryOutputId} to recover redacted persisted content. Exact raw recovery is unavailable.`,
581
- outputId: recoveryOutputId,
582
- },
583
- };
584
- }
585
- if (persistence.recoverability === "metadata_only") {
586
- return {
587
- reason: "Only metadata was persisted; no raw content recovery is promised.",
588
- hint: {
589
- how: "Only vault metadata is available for this record. There is no raw content stream to recover.",
590
- },
591
- };
592
- }
621
+ const persistence = persistenceForVaultRecord(record);
622
+ const recoveryOutputId = persistence.recoveryOutputId ?? persistence.outputId ?? record.outputId;
623
+ if (persistence.recoverability === "exact") {
624
+ const stream = requestedStream ?? resolveVaultStream(record);
625
+ const streamText = record.kind === "command" && requestedStream === undefined ? "stdout|stderr|combined" : stream;
593
626
  return {
594
- reason: "No content was persisted; recovery is unavailable.",
595
- hint: {
596
- how: "No persisted content is available for this record.",
597
- },
627
+ reason: `Exact content is recoverable from the vault with recoveryOutputId=${recoveryOutputId}.`,
628
+ hint: {
629
+ how: `Use freeflow_search action=retrieve with outputId=${recoveryOutputId}, stream=${streamText}, and an exact lineRange to recover exact vaulted content.`,
630
+ outputId: recoveryOutputId,
631
+ },
598
632
  };
599
- }
600
- async function queryRepo(root, options, preserve) {
601
- if (!options.query?.trim()) {
602
- return errorResult(preserve, "Repo query requires a non-empty query string.");
603
- }
604
- const query = options.query.trim();
605
- const topK = parseTopK(options.topK, DEFAULT_QUERY_TOP_K);
606
- if (typeof topK === "string") {
607
- return errorResult(preserve, topK);
608
- }
609
- const files = await readRepoTextFiles(root, options.source.path, options.generatedPathGlobs);
610
- const candidates = searchRepoEvidenceCandidates({
611
- files,
612
- query,
613
- topK,
614
- defaultContextLines: DEFAULT_CONTEXT_LINES,
615
- queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
616
- });
617
- if (candidates.length === 0) {
618
- return {
619
- toolStatus: "ok",
620
- decisionId: decisionId("repo-query", query, root, "none"),
621
- preserve,
622
- source: { kind: "repo", path: options.source.path ?? "." },
623
- routing: {
624
- status: "routed",
625
- route: "retrieve",
626
- reason: "Deterministic lexical retrieval found no matching repo evidence.",
627
- },
628
- evidence: [],
629
- recovery: {
630
- how: "Refine the query, use action=locate for candidate paths, or use native read for a known whole file.",
631
- },
632
- };
633
- }
634
- const evidence = candidates.map((candidate) => evidenceFromCandidate(candidate, query));
635
- const first = evidence[0];
636
- const firstCandidate = candidates[0];
637
- if (!first || !firstCandidate) {
638
- return errorResult(preserve, "Repo query produced no usable candidate evidence.");
639
- }
633
+ }
634
+ if (persistence.recoverability === "redacted") {
640
635
  return {
641
- toolStatus: "ok",
642
- decisionId: decisionId("repo-query", query, String(topK), evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|")),
643
- preserve,
644
- source: { kind: "repo", path: firstCandidate.file.path },
645
- routing: {
646
- status: "routed",
647
- route: "retrieve",
648
- reason: `Deterministic repo retrieval selected ${evidence.length} candidate(s); top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
649
- },
650
- evidence,
651
- recovery: {
652
- how: `Use freeflow_search action=expand with evidenceId=${first.id} for more surrounding context, action=locate for candidate paths, or action=retrieve with path=${firstCandidate.file.path} for an explicit span.`,
653
- evidenceId: first.id,
654
- },
636
+ reason: `Only redacted content is recoverable from the vault; exact raw content is intentionally unavailable.`,
637
+ hint: {
638
+ how: `Use freeflow_search action=retrieve with outputId=${recoveryOutputId} to recover redacted persisted content. Exact raw recovery is unavailable.`,
639
+ outputId: recoveryOutputId,
640
+ },
655
641
  };
656
- }
657
- async function getRepo(root, options, preserve) {
658
- if (!options.query?.trim()) {
659
- return errorResult(preserve, "Repo get requires a non-empty query string.");
660
- }
661
- const query = options.query.trim();
662
- const files = await readRepoTextFiles(root, options.source.path, options.generatedPathGlobs);
663
- const candidates = searchRepoEvidenceCandidates({
664
- files,
665
- query,
666
- topK: 1,
667
- defaultContextLines: DEFAULT_CONTEXT_LINES,
668
- queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
669
- });
670
- const candidate = candidates[0];
671
- if (!candidate) {
672
- return {
673
- toolStatus: "ok",
674
- decisionId: decisionId("repo-get", query, root, "none"),
675
- preserve,
676
- source: { kind: "repo", path: options.source.path ?? "." },
677
- routing: {
678
- status: "routed",
679
- route: "retrieve",
680
- reason: "Freeflow search get found no matching repo evidence.",
681
- },
682
- evidence: [],
683
- recovery: {
684
- how: "Refine the query, use action=locate for candidate paths, or use native read for a known whole file.",
685
- },
686
- };
687
- }
688
- const match = matchMetadataForCandidate(candidate);
689
- const evidence = evidenceFromCandidate(candidate, query);
690
- evidence.why = `Best repo get match; matchType=${match.type} confidence=${match.confidence.toFixed(2)}. ${evidence.why}`;
691
- evidence.match = match;
642
+ }
643
+ if (persistence.recoverability === "metadata_only") {
692
644
  return {
693
- toolStatus: "ok",
694
- decisionId: decisionId("repo-get", query, evidence.path ?? candidate.file.path, evidence.lines ?? ""),
695
- preserve,
696
- source: { kind: "repo", path: candidate.file.path },
697
- routing: {
698
- status: "routed",
699
- route: "retrieve",
700
- reason: `Freeflow search get selected best repo match ${candidate.file.path}:${evidence.lines}; matchType=${match.type} confidence=${match.confidence.toFixed(2)} (${candidate.reason}).`,
701
- },
702
- evidence: [evidence],
703
- recovery: {
704
- how: `Use freeflow_search action=retrieve with path=${candidate.file.path} and lineRange=${evidence.lines} for exact recovery, or action=expand with evidenceId=${evidence.id}.`,
705
- evidenceId: evidence.id,
706
- },
645
+ reason: "Only metadata was persisted; no raw content recovery is promised.",
646
+ hint: {
647
+ how: "Only vault metadata is available for this record. There is no raw content stream to recover.",
648
+ },
707
649
  };
650
+ }
651
+ return {
652
+ reason: "No content was persisted; recovery is unavailable.",
653
+ hint: {
654
+ how: "No persisted content is available for this record.",
655
+ },
656
+ };
708
657
  }
709
- async function locateRepo(root, options, preserve) {
710
- if (!options.query?.trim()) {
711
- return errorResult(preserve, "Repo locate requires a non-empty query string.");
712
- }
713
- const query = options.query.trim();
714
- const topK = parseTopK(options.topK, DEFAULT_LOCATE_TOP_K);
715
- if (typeof topK === "string") {
716
- return errorResult(preserve, topK);
717
- }
718
- const files = await readRepoTextFiles(root, options.source.path, options.generatedPathGlobs);
719
- const candidates = searchRepoEvidenceCandidates({
720
- files,
721
- query,
722
- topK,
723
- defaultContextLines: DEFAULT_CONTEXT_LINES,
724
- queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
725
- });
726
- if (candidates.length === 0) {
727
- return {
728
- toolStatus: "ok",
729
- decisionId: decisionId("repo-locate", query, root, "none"),
730
- preserve,
731
- source: { kind: "repo", path: options.source.path ?? "." },
732
- routing: {
733
- status: "routed",
734
- route: "retrieve",
735
- reason: "Deterministic lexical locate found no candidate location.",
736
- },
737
- evidence: [],
738
- recovery: {
739
- how: "Refine the query or use native read for a known whole file.",
740
- },
741
- };
742
- }
743
- const evidence = candidates.map((candidate) => {
744
- const line = candidate.lineIndex + 1;
745
- return evidenceFromRange({
746
- id: evidenceIdFor(candidate.file.path, `${line}-${line}`, query),
747
- file: candidate.file,
748
- lines: { start: line, end: line },
749
- why: `Candidate location from deterministic repo scoring.`,
750
- window: "small",
751
- expandable: true,
752
- });
753
- });
754
- const first = evidence[0];
755
- const firstCandidate = candidates[0];
756
- if (!first || !firstCandidate) {
757
- return errorResult(preserve, "Repo locate produced no usable candidate evidence.");
758
- }
658
+ async function queryFileSource(root, options, preserve) {
659
+ const sourceKind = options.source.kind;
660
+ const sourceLabel = sourceKindLabel(sourceKind);
661
+ if (!options.query?.trim()) {
662
+ return errorResult(preserve, `${sourceLabel} query requires a non-empty query string.`);
663
+ }
664
+ const query = options.query.trim();
665
+ const topK = parseTopK(options.topK, DEFAULT_QUERY_TOP_K);
666
+ if (typeof topK === "string") {
667
+ return errorResult(preserve, topK);
668
+ }
669
+ const files = await readSearchTextFiles(sourceKind, root, options.source.path, options.generatedPathGlobs);
670
+ const candidates = searchRepoEvidenceCandidates({
671
+ files,
672
+ query,
673
+ topK,
674
+ defaultContextLines: DEFAULT_CONTEXT_LINES,
675
+ queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
676
+ });
677
+ if (candidates.length === 0) {
759
678
  return {
760
- toolStatus: "ok",
761
- decisionId: decisionId("repo-locate", query, String(topK), evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|")),
762
- preserve,
763
- source: { kind: "repo", path: firstCandidate.file.path },
764
- routing: {
765
- status: "routed",
766
- route: "retrieve",
767
- reason: `Located ${evidence.length} candidate location(s) without broad evidence retrieval; top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
768
- },
769
- evidence,
770
- recovery: {
771
- how: `Use freeflow_search action=retrieve with path=${firstCandidate.file.path} for an explicit span, or action=expand with evidenceId=${first.id}.`,
772
- evidenceId: first.id,
773
- },
679
+ toolStatus: "ok",
680
+ decisionId: decisionId(`${sourceKind}-query`, query, root, "none"),
681
+ preserve,
682
+ source: sourceRefForOptions(options.source, options.source.path ?? "."),
683
+ routing: {
684
+ status: "routed",
685
+ route: "retrieve",
686
+ reason: `Deterministic lexical retrieval found no matching ${sourceKind} evidence.`,
687
+ },
688
+ evidence: [],
689
+ recovery: {
690
+ how: "Refine the query, use action=locate for candidate paths, or use native read for a known whole file.",
691
+ },
774
692
  };
693
+ }
694
+ const evidence = candidates.map((candidate) => evidenceFromCandidate(candidate, query));
695
+ const first = evidence[0];
696
+ const firstCandidate = candidates[0];
697
+ if (!first || !firstCandidate) {
698
+ return errorResult(preserve, `${sourceLabel} query produced no usable candidate evidence.`);
699
+ }
700
+ return {
701
+ toolStatus: "ok",
702
+ decisionId: decisionId(
703
+ `${sourceKind}-query`,
704
+ query,
705
+ String(topK),
706
+ evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|"),
707
+ ),
708
+ preserve,
709
+ source: fileSourceRef(firstCandidate.file),
710
+ routing: {
711
+ status: "routed",
712
+ route: "retrieve",
713
+ reason: `Deterministic ${sourceKind} retrieval selected ${evidence.length} candidate(s); top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
714
+ },
715
+ evidence,
716
+ recovery: {
717
+ how: recoveryHowForFile(
718
+ firstCandidate.file,
719
+ `Use freeflow_search action=expand with evidenceId=${first.id} for more surrounding context, action=locate for candidate paths, or action=retrieve`,
720
+ ),
721
+ evidenceId: first.id,
722
+ },
723
+ };
775
724
  }
776
- async function expandRepoEvidence(root, options, preserve) {
777
- const evidence = options.evidence;
778
- if (!evidence?.path || !evidence.lines) {
779
- return errorResult(preserve, "Repo expansion requires a previous repo evidence packet with path and lines.");
780
- }
781
- const expansion = options.expansion ?? "lines_30";
782
- const originalRange = parseLineRange(evidence.lines);
783
- if (!originalRange) {
784
- return errorResult(preserve, `Cannot expand unsupported evidence line range ${evidence.lines}.`);
785
- }
786
- const file = await readRepoTextFile(root, evidence.path);
787
- const expandedRange = expandLineRange(originalRange, file.lines.length, expansion);
788
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
789
- const expandedExcerpt = file.lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
790
- if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
791
- return expandRepoEvidenceOverCap(file, evidence, expandedRange, preserve, byteLength(expandedExcerpt));
792
- }
793
- const expandedEvidence = evidenceFromRange({
794
- id: evidence.id,
795
- file,
796
- lines: expandedRange,
797
- why: `Expanded deterministic repo evidence from ${evidence.lines} to ${expandedLines}.`,
798
- window: windowForExpansion(expansion),
799
- expandable: expansion !== "full",
800
- });
725
+ async function getFileSource(root, options, preserve) {
726
+ const sourceKind = options.source.kind;
727
+ const sourceLabel = sourceKindLabel(sourceKind);
728
+ if (!options.query?.trim()) {
729
+ return errorResult(preserve, `${sourceLabel} get requires a non-empty query string.`);
730
+ }
731
+ const query = options.query.trim();
732
+ const files = await readSearchTextFiles(sourceKind, root, options.source.path, options.generatedPathGlobs);
733
+ const candidates = searchRepoEvidenceCandidates({
734
+ files,
735
+ query,
736
+ topK: 1,
737
+ defaultContextLines: DEFAULT_CONTEXT_LINES,
738
+ queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
739
+ });
740
+ const candidate = candidates[0];
741
+ if (!candidate) {
801
742
  return {
802
- toolStatus: "ok",
803
- decisionId: decisionId("repo-expand", evidence.id, expandedEvidence.lines ?? expandedLines),
804
- preserve,
805
- source: { kind: "repo", path: evidence.path },
806
- routing: {
807
- status: "routed",
808
- route: "retrieve",
809
- reason: `Expanded repo evidence ${evidence.id} to ${evidence.path}:${expandedEvidence.lines ?? expandedLines}.`,
810
- },
811
- evidence: [expandedEvidence],
812
- recovery: {
813
- how: expansion === "full"
814
- ? `Use native read for direct whole-file behavior if needed for ${evidence.path}.`
815
- : `Use freeflow_search action=expand with expansion=lines_80 or full for more context from ${evidence.path}.`,
816
- evidenceId: evidence.id,
817
- },
743
+ toolStatus: "ok",
744
+ decisionId: decisionId(`${sourceKind}-get`, query, root, "none"),
745
+ preserve,
746
+ source: sourceRefForOptions(options.source, options.source.path ?? "."),
747
+ routing: {
748
+ status: "routed",
749
+ route: "retrieve",
750
+ reason: `Freeflow search get found no matching ${sourceKind} evidence.`,
751
+ },
752
+ evidence: [],
753
+ recovery: {
754
+ how: "Refine the query, use action=locate for candidate paths, or use native read for a known whole file.",
755
+ },
818
756
  };
757
+ }
758
+ const match = matchMetadataForCandidate(candidate);
759
+ const evidence = evidenceFromCandidate(candidate, query);
760
+ evidence.why = `Best ${sourceKind} get match; matchType=${match.type} confidence=${match.confidence.toFixed(2)}. ${evidence.why}`;
761
+ evidence.match = match;
762
+ return {
763
+ toolStatus: "ok",
764
+ decisionId: decisionId(`${sourceKind}-get`, query, evidence.path ?? candidate.file.path, evidence.lines ?? ""),
765
+ preserve,
766
+ source: fileSourceRef(candidate.file),
767
+ routing: {
768
+ status: "routed",
769
+ route: "retrieve",
770
+ reason: `Freeflow search get selected best ${sourceKind} match ${candidate.file.path}:${evidence.lines}; matchType=${match.type} confidence=${match.confidence.toFixed(2)} (${candidate.reason}).`,
771
+ },
772
+ evidence: [evidence],
773
+ recovery: {
774
+ how: recoveryHowForFile(
775
+ candidate.file,
776
+ `Use freeflow_search action=retrieve with lineRange=${evidence.lines} for exact recovery, or action=expand with evidenceId=${evidence.id}`,
777
+ ),
778
+ evidenceId: evidence.id,
779
+ },
780
+ };
819
781
  }
820
- async function retrieveRepoPath(root, options, preserve) {
821
- if (!options.source.path) {
822
- return errorResult(preserve, "Repo retrieve requires source.path.");
823
- }
824
- const file = await readRepoTextFile(root, options.source.path);
825
- const maxFullBytes = options.maxFullBytes ?? 64_000;
826
- if (options.lineRange) {
827
- return retrieveRepoLineRange(file, options.lineRange, preserve);
828
- }
829
- if (preserve === "full") {
830
- return retrieveFullFile(file, maxFullBytes);
831
- }
832
- const end = Math.min(file.lines.length, 10);
833
- const evidence = evidenceFromRange({
834
- id: evidenceIdFor(file.path, `1-${end}`, "retrieve"),
835
- file,
836
- lines: { start: 1, end },
837
- why: `Retrieved explicit repo span ${file.path}:1-${end}.`,
838
- window: "small",
839
- expandable: true,
840
- });
782
+ async function locateFileSource(root, options, preserve) {
783
+ const sourceKind = options.source.kind;
784
+ const sourceLabel = sourceKindLabel(sourceKind);
785
+ if (!options.query?.trim()) {
786
+ return errorResult(preserve, `${sourceLabel} locate requires a non-empty query string.`);
787
+ }
788
+ const query = options.query.trim();
789
+ const topK = parseTopK(options.topK, DEFAULT_LOCATE_TOP_K);
790
+ if (typeof topK === "string") {
791
+ return errorResult(preserve, topK);
792
+ }
793
+ const files = await readSearchTextFiles(sourceKind, root, options.source.path, options.generatedPathGlobs);
794
+ const candidates = searchRepoEvidenceCandidates({
795
+ files,
796
+ query,
797
+ topK,
798
+ defaultContextLines: DEFAULT_CONTEXT_LINES,
799
+ queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
800
+ });
801
+ if (candidates.length === 0) {
841
802
  return {
842
- toolStatus: "ok",
843
- decisionId: decisionId("repo-retrieve", file.path, evidence.lines ?? ""),
844
- preserve,
845
- source: { kind: "repo", path: file.path },
846
- routing: {
847
- status: "routed",
848
- route: "retrieve",
849
- reason: `Retrieved explicit repo span ${file.path}:${evidence.lines}.`,
850
- },
851
- evidence: [evidence],
852
- recovery: {
853
- how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
854
- evidenceId: evidence.id,
855
- },
803
+ toolStatus: "ok",
804
+ decisionId: decisionId(`${sourceKind}-locate`, query, root, "none"),
805
+ preserve,
806
+ source: sourceRefForOptions(options.source, options.source.path ?? "."),
807
+ routing: {
808
+ status: "routed",
809
+ route: "retrieve",
810
+ reason: "Deterministic lexical locate found no candidate location.",
811
+ },
812
+ evidence: [],
813
+ recovery: {
814
+ how: "Refine the query or use native read for a known whole file.",
815
+ },
856
816
  };
817
+ }
818
+ const evidence = candidates.map((candidate) => {
819
+ const line = candidate.lineIndex + 1;
820
+ return evidenceFromRange({
821
+ id: evidenceIdFor(fileEvidenceKey(candidate.file), `${line}-${line}`, query),
822
+ file: candidate.file,
823
+ lines: { start: line, end: line },
824
+ why: `Candidate location from deterministic ${sourceKind} scoring.`,
825
+ window: "small",
826
+ expandable: true,
827
+ });
828
+ });
829
+ const first = evidence[0];
830
+ const firstCandidate = candidates[0];
831
+ if (!first || !firstCandidate) {
832
+ return errorResult(preserve, `${sourceLabel} locate produced no usable candidate evidence.`);
833
+ }
834
+ return {
835
+ toolStatus: "ok",
836
+ decisionId: decisionId(
837
+ `${sourceKind}-locate`,
838
+ query,
839
+ String(topK),
840
+ evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|"),
841
+ ),
842
+ preserve,
843
+ source: fileSourceRef(firstCandidate.file),
844
+ routing: {
845
+ status: "routed",
846
+ route: "retrieve",
847
+ reason: `Located ${evidence.length} candidate location(s) without broad evidence retrieval; top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
848
+ },
849
+ evidence,
850
+ recovery: {
851
+ how: recoveryHowForFile(
852
+ firstCandidate.file,
853
+ `Use freeflow_search action=retrieve for an explicit span, or action=expand with evidenceId=${first.id}`,
854
+ ),
855
+ evidenceId: first.id,
856
+ },
857
+ };
857
858
  }
858
- function expandRepoEvidenceOverCap(file, evidence, expandedRange, preserve, expandedBytes) {
859
- const chunks = buildBoundedEdgeChunks({ lines: file.lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
860
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
861
- return {
862
- toolStatus: "ok",
863
- decisionId: decisionId("repo-expand-over-cap", evidence.id, expandedLines, String(expandedBytes)),
864
- preserve,
865
- source: { kind: "repo", path: file.path },
866
- routing: {
867
- status: "partial",
868
- route: "retrieve",
869
- reason: `Expanded repo evidence ${evidence.id} to ${file.path}:${expandedLines}, but ${expandedBytes} bytes exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge chunks with recovery guidance.`,
870
- },
871
- evidence: chunks,
872
- recovery: {
873
- how: `Use narrower freeflow_search action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
874
- evidenceId: evidence.id,
875
- },
876
- };
859
+ async function expandFileEvidence(root, options, preserve) {
860
+ const sourceKind = options.source.kind;
861
+ const sourceLabel = sourceKindLabel(sourceKind);
862
+ const evidence = options.evidence;
863
+ if (!evidence?.path || !evidence.lines) {
864
+ return errorResult(
865
+ preserve,
866
+ `${sourceLabel} expansion requires a previous ${sourceKind} evidence packet with path and lines.`,
867
+ );
868
+ }
869
+ const expansion = options.expansion ?? "lines_30";
870
+ const originalRange = parseLineRange(evidence.lines);
871
+ if (!originalRange) {
872
+ return errorResult(preserve, `Cannot expand unsupported evidence line range ${evidence.lines}.`);
873
+ }
874
+ const file = await readSearchTextFile(sourceKind, root, evidence.path);
875
+ const expandedRange = expandLineRange(originalRange, file.lines.length, expansion);
876
+ const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
877
+ const expandedExcerpt = file.lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
878
+ if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
879
+ return expandFileEvidenceOverCap(file, evidence, expandedRange, preserve, byteLength(expandedExcerpt));
880
+ }
881
+ const expandedEvidence = evidenceFromRange({
882
+ id: evidence.id,
883
+ file,
884
+ lines: expandedRange,
885
+ why: `Expanded deterministic ${sourceKind} evidence from ${evidence.lines} to ${expandedLines}.`,
886
+ window: windowForExpansion(expansion),
887
+ expandable: expansion !== "full",
888
+ });
889
+ return {
890
+ toolStatus: "ok",
891
+ decisionId: decisionId(`${sourceKind}-expand`, evidence.id, expandedEvidence.lines ?? expandedLines),
892
+ preserve,
893
+ source: fileSourceRef(file),
894
+ routing: {
895
+ status: "routed",
896
+ route: "retrieve",
897
+ reason: `Expanded ${sourceKind} evidence ${evidence.id} to ${evidence.path}:${expandedEvidence.lines ?? expandedLines}.`,
898
+ },
899
+ evidence: [expandedEvidence],
900
+ recovery: {
901
+ how:
902
+ expansion === "full"
903
+ ? `Use native read for direct whole-file behavior if needed for ${file.path}.`
904
+ : `Use freeflow_search action=expand with expansion=lines_80 or full for more context from ${file.path}.`,
905
+ evidenceId: evidence.id,
906
+ },
907
+ };
877
908
  }
878
- function retrieveRepoLineRange(file, lineRange, preserve) {
879
- const resolvedRange = resolveExactLineRange({
880
- requested: lineRange,
881
- lineCount: file.lines.length,
882
- availableLabel: "available repo lines",
883
- invalidReason: "Repo retrieve requires a valid 1-based lineRange.",
884
- });
885
- if (!resolvedRange.ok) {
886
- return errorResult(preserve, resolvedRange.reason);
887
- }
888
- const { start, end } = resolvedRange.range;
889
- const evidenceLines = `${start}-${end}`;
890
- const excerpt = file.lines.slice(start - 1, end).join("\n");
891
- if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
892
- return retrieveRepoLineRangeOverCap(file, { start, end }, preserve, byteLength(excerpt));
893
- }
894
- const evidence = evidenceFromRange({
895
- id: evidenceIdFor(file.path, evidenceLines, "retrieve"),
896
- file,
897
- lines: { start, end },
898
- why: `Retrieved exact repo lines ${file.path}:${evidenceLines}.`,
899
- window: "exact",
900
- expandable: true,
901
- });
902
- return {
903
- toolStatus: "ok",
904
- decisionId: decisionId("repo-retrieve-lines", file.path, evidenceLines),
905
- preserve,
906
- source: { kind: "repo", path: file.path },
907
- routing: {
908
- status: "routed",
909
- route: "retrieve",
910
- reason: `Retrieved exact repo lines ${file.path}:${evidenceLines}.`,
911
- },
912
- evidence: [evidence],
913
- recovery: {
914
- how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
915
- evidenceId: evidence.id,
916
- },
917
- };
909
+ async function retrieveFilePath(root, options, preserve) {
910
+ const sourceKind = options.source.kind;
911
+ const sourceLabel = sourceKindLabel(sourceKind);
912
+ if (!options.source.path) {
913
+ return errorResult(preserve, `${sourceLabel} retrieve requires source.path.`);
914
+ }
915
+ const file = await readSearchTextFile(sourceKind, root, options.source.path);
916
+ const maxFullBytes = options.maxFullBytes ?? 64_000;
917
+ if (options.lineRange) {
918
+ return retrieveFileLineRange(file, options.lineRange, preserve);
919
+ }
920
+ if (preserve === "full") {
921
+ return retrieveFullFile(file, maxFullBytes);
922
+ }
923
+ const end = Math.min(file.lines.length, 10);
924
+ const evidence = evidenceFromRange({
925
+ id: evidenceIdFor(fileEvidenceKey(file), `1-${end}`, "retrieve"),
926
+ file,
927
+ lines: { start: 1, end },
928
+ why: `Retrieved explicit ${file.sourceKind} span ${file.path}:1-${end}.`,
929
+ window: "small",
930
+ expandable: true,
931
+ });
932
+ return {
933
+ toolStatus: "ok",
934
+ decisionId: decisionId(`${file.sourceKind}-retrieve`, file.path, evidence.lines ?? ""),
935
+ preserve,
936
+ source: fileSourceRef(file),
937
+ routing: {
938
+ status: "routed",
939
+ route: "retrieve",
940
+ reason: `Retrieved explicit ${file.sourceKind} span ${file.path}:${evidence.lines}.`,
941
+ },
942
+ evidence: [evidence],
943
+ recovery: {
944
+ how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
945
+ evidenceId: evidence.id,
946
+ },
947
+ };
918
948
  }
919
- function retrieveRepoLineRangeOverCap(file, requestedRange, preserve, requestedBytes) {
920
- const evidence = buildBoundedEdgeChunks({ lines: file.lines, range: requestedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
921
- return {
922
- toolStatus: "ok",
923
- decisionId: decisionId("repo-retrieve-lines-over-cap", file.path, `${requestedRange.start}-${requestedRange.end}`, String(requestedBytes)),
924
- preserve,
925
- source: { kind: "repo", path: file.path },
926
- routing: {
927
- status: "partial",
928
- route: "retrieve",
929
- reason: `Requested repo lines ${file.path}:${requestedRange.start}-${requestedRange.end} are ${requestedBytes} bytes and exceed cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge previews with recovery guidance.`,
930
- },
931
- evidence,
932
- recovery: {
933
- how: `Use narrower freeflow_search action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
934
- },
935
- };
949
+ function expandFileEvidenceOverCap(file, evidence, expandedRange, preserve, expandedBytes) {
950
+ const chunks = buildBoundedEdgeChunks({ lines: file.lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map(
951
+ (chunk, index) => fileEvidenceForBoundedExactChunk(file, chunk, index),
952
+ );
953
+ const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
954
+ return {
955
+ toolStatus: "ok",
956
+ decisionId: decisionId(`${file.sourceKind}-expand-over-cap`, evidence.id, expandedLines, String(expandedBytes)),
957
+ preserve,
958
+ source: fileSourceRef(file),
959
+ routing: {
960
+ status: "partial",
961
+ route: "retrieve",
962
+ reason: `Expanded ${file.sourceKind} evidence ${evidence.id} to ${file.path}:${expandedLines}, but ${expandedBytes} bytes exceeds cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge chunks with recovery guidance.`,
963
+ },
964
+ evidence: chunks,
965
+ recovery: {
966
+ how: `Use narrower freeflow_search action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
967
+ evidenceId: evidence.id,
968
+ },
969
+ };
936
970
  }
937
- function repoEvidenceForBoundedExactChunk(file, chunk, index) {
938
- const evidenceLines = chunk.linesLabel;
939
- return {
940
- id: evidenceIdFor(file.path, evidenceLines, `retrieve-over-cap-${index}`),
941
- source: { kind: "repo", path: file.path },
942
- path: file.path,
943
- lines: evidenceLines,
944
- excerpt: chunk.excerpt,
945
- why: `Bounded recoverable ${chunk.edge} preview for repo line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
946
- window: "small",
947
- expandable: true,
948
- };
971
+ function retrieveFileLineRange(file, lineRange, preserve) {
972
+ const resolvedRange = resolveExactLineRange({
973
+ requested: lineRange,
974
+ lineCount: file.lines.length,
975
+ availableLabel: `available ${file.sourceKind} lines`,
976
+ invalidReason: `${sourceKindLabel(file.sourceKind)} retrieve requires a valid 1-based lineRange.`,
977
+ });
978
+ if (!resolvedRange.ok) {
979
+ return errorResult(preserve, resolvedRange.reason);
980
+ }
981
+ const { start, end } = resolvedRange.range;
982
+ const evidenceLines = `${start}-${end}`;
983
+ const excerpt = file.lines.slice(start - 1, end).join("\n");
984
+ if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
985
+ return retrieveFileLineRangeOverCap(file, { start, end }, preserve, byteLength(excerpt));
986
+ }
987
+ const evidence = evidenceFromRange({
988
+ id: evidenceIdFor(fileEvidenceKey(file), evidenceLines, "retrieve"),
989
+ file,
990
+ lines: { start, end },
991
+ why: `Retrieved exact ${file.sourceKind} lines ${file.path}:${evidenceLines}.`,
992
+ window: "exact",
993
+ expandable: true,
994
+ });
995
+ return {
996
+ toolStatus: "ok",
997
+ decisionId: decisionId(`${file.sourceKind}-retrieve-lines`, file.path, evidenceLines),
998
+ preserve,
999
+ source: fileSourceRef(file),
1000
+ routing: {
1001
+ status: "routed",
1002
+ route: "retrieve",
1003
+ reason: `Retrieved exact ${file.sourceKind} lines ${file.path}:${evidenceLines}.`,
1004
+ },
1005
+ evidence: [evidence],
1006
+ recovery: {
1007
+ how: `Use freeflow_search action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
1008
+ evidenceId: evidence.id,
1009
+ },
1010
+ };
1011
+ }
1012
+ function retrieveFileLineRangeOverCap(file, requestedRange, preserve, requestedBytes) {
1013
+ const evidence = buildBoundedEdgeChunks({
1014
+ lines: file.lines,
1015
+ range: requestedRange,
1016
+ caps: BOUNDED_EVIDENCE_CAPS,
1017
+ }).map((chunk, index) => fileEvidenceForBoundedExactChunk(file, chunk, index));
1018
+ return {
1019
+ toolStatus: "ok",
1020
+ decisionId: decisionId(
1021
+ `${file.sourceKind}-retrieve-lines-over-cap`,
1022
+ file.path,
1023
+ `${requestedRange.start}-${requestedRange.end}`,
1024
+ String(requestedBytes),
1025
+ ),
1026
+ preserve,
1027
+ source: fileSourceRef(file),
1028
+ routing: {
1029
+ status: "partial",
1030
+ route: "retrieve",
1031
+ reason: `Requested ${file.sourceKind} lines ${file.path}:${requestedRange.start}-${requestedRange.end} are ${requestedBytes} bytes and exceed cap ${EXACT_LINE_RANGE_MAX_BYTES}; returned bounded edge previews with recovery guidance.`,
1032
+ },
1033
+ evidence,
1034
+ recovery: {
1035
+ how: `Use narrower freeflow_search action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
1036
+ },
1037
+ };
1038
+ }
1039
+ function fileEvidenceForBoundedExactChunk(file, chunk, index) {
1040
+ const evidenceLines = chunk.linesLabel;
1041
+ return {
1042
+ id: evidenceIdFor(fileEvidenceKey(file), evidenceLines, `retrieve-over-cap-${index}`),
1043
+ source: fileSourceRef(file),
1044
+ path: file.path,
1045
+ lines: evidenceLines,
1046
+ excerpt: chunk.excerpt,
1047
+ why: `Bounded recoverable ${chunk.edge} preview for ${file.sourceKind} line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
1048
+ window: "small",
1049
+ expandable: true,
1050
+ };
949
1051
  }
950
1052
  function retrieveFullFile(file, maxFullBytes) {
951
- const fullBytes = byteLength(file.text);
952
- if (fullBytes <= maxFullBytes) {
953
- const evidence = evidenceFromRange({
954
- id: evidenceIdFor(file.path, `1-${file.lines.length}`, "full"),
955
- file,
956
- lines: { start: 1, end: file.lines.length },
957
- why: `Returned full exact content because ${file.path} is under the full-context cap.`,
958
- window: "full",
959
- expandable: false,
960
- });
961
- return {
962
- toolStatus: "ok",
963
- decisionId: decisionId("repo-full", file.path, String(fullBytes)),
964
- preserve: "full",
965
- source: { kind: "repo", path: file.path },
966
- routing: {
967
- status: "routed",
968
- route: "retrieve",
969
- reason: `Returned full exact content for ${file.path}; ${fullBytes} bytes is under cap ${maxFullBytes}.`,
970
- },
971
- evidence: [evidence],
972
- recovery: {
973
- how: `Use native read for direct whole-file host behavior if needed for ${file.path}.`,
974
- evidenceId: evidence.id,
975
- },
976
- };
977
- }
978
- const evidence = buildBoundedEdgeChunks({
979
- lines: file.lines,
980
- range: { start: 1, end: file.lines.length },
981
- caps: BOUNDED_EVIDENCE_CAPS,
982
- }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
1053
+ const fullBytes = byteLength(file.text);
1054
+ if (fullBytes <= maxFullBytes) {
1055
+ const evidence = evidenceFromRange({
1056
+ id: evidenceIdFor(fileEvidenceKey(file), `1-${file.lines.length}`, "full"),
1057
+ file,
1058
+ lines: { start: 1, end: file.lines.length },
1059
+ why: `Returned full exact content because ${file.path} is under the full-context cap.`,
1060
+ window: "full",
1061
+ expandable: false,
1062
+ });
983
1063
  return {
984
- toolStatus: "ok",
985
- decisionId: decisionId("repo-full-over-cap", file.path, String(fullBytes), String(maxFullBytes)),
986
- preserve: "full",
987
- source: { kind: "repo", path: file.path },
988
- routing: {
989
- status: "partial",
990
- route: "retrieve",
991
- reason: `Full exact content for ${file.path} is ${fullBytes} bytes and exceeds cap ${maxFullBytes}; returned bounded edge previews instead of a summary.`,
992
- },
993
- evidence,
994
- recovery: {
995
- how: `Use freeflow_search action=retrieve with path=${file.path} and an explicit span to recover exact content, or native read for direct whole-file output.`,
996
- },
1064
+ toolStatus: "ok",
1065
+ decisionId: decisionId(`${file.sourceKind}-full`, file.path, String(fullBytes)),
1066
+ preserve: "full",
1067
+ source: fileSourceRef(file),
1068
+ routing: {
1069
+ status: "routed",
1070
+ route: "retrieve",
1071
+ reason: `Returned full exact content for ${file.path}; ${fullBytes} bytes is under cap ${maxFullBytes}.`,
1072
+ },
1073
+ evidence: [evidence],
1074
+ recovery: {
1075
+ how: `Use native read for direct whole-file host behavior if needed for ${file.path}.`,
1076
+ evidenceId: evidence.id,
1077
+ },
997
1078
  };
1079
+ }
1080
+ const evidence = buildBoundedEdgeChunks({
1081
+ lines: file.lines,
1082
+ range: { start: 1, end: file.lines.length },
1083
+ caps: BOUNDED_EVIDENCE_CAPS,
1084
+ }).map((chunk, index) => fileEvidenceForBoundedExactChunk(file, chunk, index));
1085
+ return {
1086
+ toolStatus: "ok",
1087
+ decisionId: decisionId(`${file.sourceKind}-full-over-cap`, file.path, String(fullBytes), String(maxFullBytes)),
1088
+ preserve: "full",
1089
+ source: fileSourceRef(file),
1090
+ routing: {
1091
+ status: "partial",
1092
+ route: "retrieve",
1093
+ reason: `Full exact content for ${file.path} is ${fullBytes} bytes and exceeds cap ${maxFullBytes}; returned bounded edge previews instead of a summary.`,
1094
+ },
1095
+ evidence,
1096
+ recovery: {
1097
+ how: recoveryHowForFile(
1098
+ file,
1099
+ "Use freeflow_search action=retrieve with an explicit span to recover exact content, or native read for direct whole-file output",
1100
+ ),
1101
+ },
1102
+ };
998
1103
  }
999
- function explainRepoDecision(options, preserve) {
1000
- const decision = options.decision;
1001
- if (!decision) {
1002
- return errorResult(preserve, "Repo explain requires a previous routed result decision.");
1003
- }
1004
- const recovery = {
1005
- how: decision.recovery?.how ??
1006
- "Use freeflow_search action=expand with a returned evidence id, or action=retrieve for an explicit repo span.",
1007
- };
1008
- if (decision.recovery?.outputId !== undefined) {
1009
- Object.assign(recovery, { outputId: decision.recovery.outputId });
1010
- }
1011
- if (decision.recovery?.evidenceId !== undefined) {
1012
- Object.assign(recovery, { evidenceId: decision.recovery.evidenceId });
1013
- }
1014
- const result = {
1015
- toolStatus: "ok",
1016
- decisionId: decisionId("repo-explain", decision.decisionId),
1017
- preserve: decision.preserve,
1018
- routing: {
1019
- status: "routed",
1020
- route: "retrieve",
1021
- reason: `Decision ${decision.decisionId} used route=${decision.routing.route}, routing.status=${decision.routing.status}: ${decision.routing.reason}`,
1022
- },
1023
- evidence: [],
1024
- recovery,
1025
- };
1026
- if (decision.source !== undefined) {
1027
- result.source = decision.source;
1028
- }
1029
- return result;
1104
+ function explainFileDecision(options, preserve) {
1105
+ const decision = options.decision;
1106
+ if (!decision) {
1107
+ return errorResult(preserve, "File-source explain requires a previous routed result decision.");
1108
+ }
1109
+ const recovery = {
1110
+ how:
1111
+ decision.recovery?.how ??
1112
+ "Use freeflow_search action=expand with a returned evidence id, or action=retrieve for an explicit file-source span.",
1113
+ };
1114
+ if (decision.recovery?.outputId !== undefined) {
1115
+ Object.assign(recovery, { outputId: decision.recovery.outputId });
1116
+ }
1117
+ if (decision.recovery?.evidenceId !== undefined) {
1118
+ Object.assign(recovery, { evidenceId: decision.recovery.evidenceId });
1119
+ }
1120
+ const result = {
1121
+ toolStatus: "ok",
1122
+ decisionId: decisionId("file-explain", decision.decisionId),
1123
+ preserve: decision.preserve,
1124
+ routing: {
1125
+ status: "routed",
1126
+ route: "retrieve",
1127
+ reason: `Decision ${decision.decisionId} used route=${decision.routing.route}, routing.status=${decision.routing.status}: ${decision.routing.reason}`,
1128
+ },
1129
+ evidence: [],
1130
+ recovery,
1131
+ };
1132
+ if (decision.source !== undefined) {
1133
+ result.source = decision.source;
1134
+ }
1135
+ return result;
1030
1136
  }
1031
- async function readRepoTextFiles(root, requestedPath, generatedPathGlobs = []) {
1032
- const options = { root, generatedPathGlobs };
1137
+ async function readSearchTextFiles(sourceKind, root, requestedPath, generatedPathGlobs = []) {
1138
+ if (sourceKind === "local") {
1139
+ const options = {
1140
+ root,
1141
+ generatedPathGlobs,
1142
+ };
1033
1143
  if (requestedPath !== undefined) {
1034
- options.requestedPath = requestedPath;
1035
- }
1036
- const fileRefs = await collectRepoTextFileRefs(options);
1037
- return readRepoTextFileRefs(fileRefs);
1144
+ options.requestedPath = requestedPath;
1145
+ }
1146
+ const fileRefs = await collectLocalTextFileRefs(options);
1147
+ return readSearchTextFileRefs("local", fileRefs);
1148
+ }
1149
+ const options = {
1150
+ root,
1151
+ generatedPathGlobs,
1152
+ };
1153
+ if (requestedPath !== undefined) {
1154
+ options.requestedPath = requestedPath;
1155
+ }
1156
+ const fileRefs = await collectRepoTextFileRefs(options);
1157
+ return readSearchTextFileRefs("repo", fileRefs);
1038
1158
  }
1039
- async function readRepoTextFile(root, path) {
1040
- const resolved = await resolveRepoPath(root, path);
1041
- const text = await readFile(resolved.absolutePath, "utf8");
1042
- return {
1043
- path: resolved.relativePath,
1044
- absolutePath: resolved.absolutePath,
1159
+ async function readSearchTextFile(sourceKind, root, path) {
1160
+ const resolved = sourceKind === "local" ? await resolveLocalPath(root, path) : await resolveRepoPath(root, path);
1161
+ const text = await readFile(resolved.absolutePath, "utf8");
1162
+ return {
1163
+ sourceKind,
1164
+ root: resolved.root,
1165
+ path: resolved.relativePath,
1166
+ absolutePath: resolved.absolutePath,
1167
+ text,
1168
+ lines: splitLines(text),
1169
+ };
1170
+ }
1171
+ async function readSearchTextFileRefs(sourceKind, fileRefs) {
1172
+ const files = new Array(fileRefs.length);
1173
+ let nextIndex = 0;
1174
+ async function worker() {
1175
+ while (nextIndex < fileRefs.length) {
1176
+ const currentIndex = nextIndex;
1177
+ nextIndex += 1;
1178
+ const ref = fileRefs[currentIndex];
1179
+ if (!ref) {
1180
+ continue;
1181
+ }
1182
+ const text = await readFile(ref.absolutePath, "utf8");
1183
+ if (text.includes("\0")) {
1184
+ continue;
1185
+ }
1186
+ files[currentIndex] = {
1187
+ sourceKind,
1188
+ root: "root" in ref ? ref.root : "",
1189
+ path: ref.path,
1190
+ absolutePath: ref.absolutePath,
1045
1191
  text,
1046
1192
  lines: splitLines(text),
1047
- };
1048
- }
1049
- async function readRepoTextFileRefs(fileRefs) {
1050
- const files = new Array(fileRefs.length);
1051
- let nextIndex = 0;
1052
- async function worker() {
1053
- while (nextIndex < fileRefs.length) {
1054
- const currentIndex = nextIndex;
1055
- nextIndex += 1;
1056
- const ref = fileRefs[currentIndex];
1057
- if (!ref) {
1058
- continue;
1059
- }
1060
- const text = await readFile(ref.absolutePath, "utf8");
1061
- if (text.includes("\0")) {
1062
- continue;
1063
- }
1064
- files[currentIndex] = { path: ref.path, absolutePath: ref.absolutePath, text, lines: splitLines(text) };
1065
- }
1193
+ };
1066
1194
  }
1067
- const workerCount = Math.min(CONCURRENT_REPO_FILE_READS, fileRefs.length);
1068
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
1069
- return files.filter((file) => Boolean(file));
1195
+ }
1196
+ const workerCount = Math.min(CONCURRENT_REPO_FILE_READS, fileRefs.length);
1197
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1198
+ return files.filter((file) => Boolean(file));
1199
+ }
1200
+ function sourceKindLabel(sourceKind) {
1201
+ return sourceKind === "local" ? "Local" : "Repo";
1202
+ }
1203
+ function sourceRefForOptions(source, path) {
1204
+ if (source.kind === "local") {
1205
+ return { kind: "local", root: source.root, path };
1206
+ }
1207
+ return { kind: "repo", path };
1208
+ }
1209
+ function fileSourceRef(file) {
1210
+ if (file.sourceKind === "local") {
1211
+ return { kind: "local", root: file.root, path: file.path };
1212
+ }
1213
+ return { kind: "repo", path: file.path };
1214
+ }
1215
+ function fileEvidenceKey(file) {
1216
+ return file.sourceKind === "local" ? `local:${file.root}:${file.path}` : `repo:${file.path}`;
1217
+ }
1218
+ function recoveryHowForFile(file, prefix) {
1219
+ if (file.sourceKind === "local") {
1220
+ return `${prefix} with source.kind=local root=${file.root} path=${file.path}.`;
1221
+ }
1222
+ return `${prefix} with path=${file.path}.`;
1070
1223
  }
1071
1224
  function parseTopK(value, defaultValue) {
1072
- if (value === undefined) {
1073
- return defaultValue;
1074
- }
1075
- if (!Number.isInteger(value) || value < 1 || value > MAX_TOP_K) {
1076
- return `topK must be an integer from 1 to ${MAX_TOP_K}.`;
1077
- }
1078
- return value;
1225
+ if (value === undefined) {
1226
+ return defaultValue;
1227
+ }
1228
+ if (!Number.isInteger(value) || value < 1 || value > MAX_TOP_K) {
1229
+ return `topK must be an integer from 1 to ${MAX_TOP_K}.`;
1230
+ }
1231
+ return value;
1079
1232
  }
1080
1233
  function matchMetadataForCandidate(candidate) {
1081
- return {
1082
- type: candidate.exactNormalizedPhrase ? "exact_phrase" : "lexical",
1083
- confidence: candidate.exactNormalizedPhrase ? 0.95 : scoreConfidence(candidate.score),
1084
- };
1234
+ return {
1235
+ type: candidate.exactNormalizedPhrase ? "exact_phrase" : "lexical",
1236
+ confidence: candidate.exactNormalizedPhrase ? 0.95 : scoreConfidence(candidate.score),
1237
+ };
1085
1238
  }
1086
1239
  function matchMetadataForText(text, query, score) {
1087
- const normalizedText = normalizeComparableText(text);
1088
- const normalizedQuery = normalizeComparableText(query);
1089
- const exact = normalizedQuery.length > 0 && normalizedText.includes(normalizedQuery);
1090
- return {
1091
- type: exact ? "exact_phrase" : "lexical",
1092
- confidence: exact ? 0.95 : scoreConfidence(score),
1093
- };
1240
+ const normalizedText = normalizeComparableText(text);
1241
+ const normalizedQuery = normalizeComparableText(query);
1242
+ const exact = normalizedQuery.length > 0 && normalizedText.includes(normalizedQuery);
1243
+ return {
1244
+ type: exact ? "exact_phrase" : "lexical",
1245
+ confidence: exact ? 0.95 : scoreConfidence(score),
1246
+ };
1094
1247
  }
1095
1248
  function scoreConfidence(score) {
1096
- if (score === undefined || !Number.isFinite(score) || score <= 0) {
1097
- return 0.6;
1098
- }
1099
- return Math.max(0.6, Math.min(0.9, score / (score + 10)));
1249
+ if (score === undefined || !Number.isFinite(score) || score <= 0) {
1250
+ return 0.6;
1251
+ }
1252
+ return Math.max(0.6, Math.min(0.9, score / (score + 10)));
1100
1253
  }
1101
1254
  function normalizeComparableText(value) {
1102
- return value.toLowerCase().replace(/[^a-z0-9_.$/-]+/g, " ").replace(/\s+/g, " ").trim();
1255
+ return value
1256
+ .toLowerCase()
1257
+ .replace(/[^a-z0-9_.$/-]+/g, " ")
1258
+ .replace(/\s+/g, " ")
1259
+ .trim();
1103
1260
  }
1104
1261
  function evidenceFromCandidate(candidate, query) {
1105
- const defaultStart = Math.max(0, candidate.lineIndex - DEFAULT_CONTEXT_LINES);
1106
- const defaultEnd = Math.min(candidate.file.lines.length - 1, candidate.lineIndex + DEFAULT_CONTEXT_LINES);
1107
- const lines = candidate.range ?? { start: defaultStart + 1, end: defaultEnd + 1 };
1108
- return evidenceFromRange({
1109
- id: evidenceIdFor(candidate.file.path, `${lines.start}-${lines.end}`, query),
1110
- file: candidate.file,
1111
- lines,
1112
- why: `${candidate.reason} near ${candidate.file.path}:${candidate.lineIndex + 1}.`,
1113
- window: "small",
1114
- expandable: true,
1115
- ...(candidate.exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase: candidate.exactNormalizedPhrase } : {}),
1116
- });
1262
+ const defaultStart = Math.max(0, candidate.lineIndex - DEFAULT_CONTEXT_LINES);
1263
+ const defaultEnd = Math.min(candidate.file.lines.length - 1, candidate.lineIndex + DEFAULT_CONTEXT_LINES);
1264
+ const lines = candidate.range ?? { start: defaultStart + 1, end: defaultEnd + 1 };
1265
+ return evidenceFromRange({
1266
+ id: evidenceIdFor(fileEvidenceKey(candidate.file), `${lines.start}-${lines.end}`, query),
1267
+ file: candidate.file,
1268
+ lines,
1269
+ why: `${candidate.reason} near ${candidate.file.path}:${candidate.lineIndex + 1}.`,
1270
+ window: "small",
1271
+ expandable: true,
1272
+ ...(candidate.exactNormalizedPhrase !== undefined
1273
+ ? { exactNormalizedPhrase: candidate.exactNormalizedPhrase }
1274
+ : {}),
1275
+ });
1117
1276
  }
1118
1277
  function evidenceFromRange(options) {
1119
- const bounded = buildBoundedExcerpt({
1120
- lines: options.file.lines,
1121
- range: options.lines,
1122
- window: options.window,
1123
- caps: BOUNDED_EVIDENCE_CAPS,
1124
- ...(options.exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase: options.exactNormalizedPhrase } : {}),
1125
- });
1126
- const lines = bounded.linesLabel;
1127
- const why = bounded.truncatedByLineCap
1128
- ? `${options.why} Bounded to ${lines} by the ${options.window} line cap; use retrieve lineRange or a wider expansion for more exact context.`
1129
- : options.why;
1130
- return {
1131
- id: options.id,
1132
- source: { kind: "repo", path: options.file.path },
1133
- path: options.file.path,
1134
- lines,
1135
- excerpt: bounded.excerpt,
1136
- why,
1137
- window: options.window,
1138
- expandable: options.expandable,
1139
- };
1278
+ const bounded = buildBoundedExcerpt({
1279
+ lines: options.file.lines,
1280
+ range: options.lines,
1281
+ window: options.window,
1282
+ caps: BOUNDED_EVIDENCE_CAPS,
1283
+ ...(options.exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase: options.exactNormalizedPhrase } : {}),
1284
+ });
1285
+ const lines = bounded.linesLabel;
1286
+ const why = bounded.truncatedByLineCap
1287
+ ? `${options.why} Bounded to ${lines} by the ${options.window} line cap; use retrieve lineRange or a wider expansion for more exact context.`
1288
+ : options.why;
1289
+ return {
1290
+ id: options.id,
1291
+ source: fileSourceRef(options.file),
1292
+ path: options.file.path,
1293
+ lines,
1294
+ excerpt: bounded.excerpt,
1295
+ why,
1296
+ window: options.window,
1297
+ expandable: options.expandable,
1298
+ };
1140
1299
  }
1141
1300
  function parseLineRange(lines) {
1142
- const match = /^(\d+)(?:-(\d+))?$/.exec(lines);
1143
- if (!match) {
1144
- return null;
1145
- }
1146
- const start = Number(match[1]);
1147
- const end = Number(match[2] ?? match[1]);
1148
- if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
1149
- return null;
1150
- }
1151
- return { start, end };
1301
+ const match = /^(\d+)(?:-(\d+))?$/.exec(lines);
1302
+ if (!match) {
1303
+ return null;
1304
+ }
1305
+ const start = Number(match[1]);
1306
+ const end = Number(match[2] ?? match[1]);
1307
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
1308
+ return null;
1309
+ }
1310
+ return { start, end };
1152
1311
  }
1153
1312
  function expandLineRange(range, fileLineCount, expansion) {
1154
- if (expansion === "full") {
1155
- return { start: 1, end: fileLineCount };
1156
- }
1157
- const context = expansion === "lines_80" ? 80 : 30;
1158
- return {
1159
- start: Math.max(1, range.start - context),
1160
- end: Math.min(fileLineCount, range.end + context),
1161
- };
1313
+ if (expansion === "full") {
1314
+ return { start: 1, end: fileLineCount };
1315
+ }
1316
+ const context = expansion === "lines_80" ? 80 : 30;
1317
+ return {
1318
+ start: Math.max(1, range.start - context),
1319
+ end: Math.min(fileLineCount, range.end + context),
1320
+ };
1162
1321
  }
1163
1322
  function windowForExpansion(expansion) {
1164
- return expansion;
1323
+ return expansion;
1165
1324
  }
1166
1325
  function excerptForLineRange(lines, range, window, exactNormalizedPhrase) {
1167
- return buildBoundedExcerpt({
1168
- lines,
1169
- range,
1170
- window,
1171
- caps: BOUNDED_EVIDENCE_CAPS,
1172
- ...(exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase } : {}),
1173
- }).excerpt;
1326
+ return buildBoundedExcerpt({
1327
+ lines,
1328
+ range,
1329
+ window,
1330
+ caps: BOUNDED_EVIDENCE_CAPS,
1331
+ ...(exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase } : {}),
1332
+ }).excerpt;
1174
1333
  }
1175
1334
  function byteLength(text) {
1176
- return Buffer.byteLength(text, "utf8");
1335
+ return Buffer.byteLength(text, "utf8");
1177
1336
  }
1178
1337
  function splitLines(text) {
1179
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1338
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1180
1339
  }
1181
1340
  function decisionId(...parts) {
1182
- return `ffdec_${hash(parts.join("\0")).slice(0, 16)}`;
1341
+ return `ffdec_${hash(parts.join("\0")).slice(0, 16)}`;
1183
1342
  }
1184
1343
  function evidenceIdFor(path, lines, query) {
1185
- return `ev_${hash(`${path}\0${lines}\0${query}`).slice(0, 16)}`;
1344
+ return `ev_${hash(`${path}\0${lines}\0${query}`).slice(0, 16)}`;
1186
1345
  }
1187
1346
  function hash(value) {
1188
- return createHash("sha256").update(value).digest("hex");
1347
+ return createHash("sha256").update(value).digest("hex");
1189
1348
  }
1190
1349
  function errorResult(preserve, reason) {
1191
- return {
1192
- toolStatus: "error",
1193
- decisionId: decisionId("repo-error", reason),
1194
- preserve,
1195
- routing: {
1196
- status: "failed",
1197
- route: "retrieve",
1198
- reason,
1199
- },
1200
- evidence: [],
1201
- recovery: {
1202
- how: "Use native read for a known whole file, or retry freeflow_search with a supported repo query.",
1203
- },
1204
- };
1350
+ return {
1351
+ toolStatus: "error",
1352
+ decisionId: decisionId("repo-error", reason),
1353
+ preserve,
1354
+ routing: {
1355
+ status: "failed",
1356
+ route: "retrieve",
1357
+ reason,
1358
+ },
1359
+ evidence: [],
1360
+ recovery: {
1361
+ how: "Use native read for a known whole file, or retry freeflow_search with a supported repo query.",
1362
+ },
1363
+ };
1205
1364
  }