@hassangameryt/freeflow 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/{plugins/freeflow/.claude-plugin → .claude-plugin}/plugin.json +5 -2
  4. package/{plugins/freeflow/.codex-plugin → .codex-plugin}/plugin.json +8 -9
  5. package/README.md +194 -127
  6. package/assets/feedback-loop.png +0 -0
  7. package/assets/freeflow-plugin-icon.png +0 -0
  8. package/assets/freeflow_workflow_map.png +0 -0
  9. package/assets/workflow.png +0 -0
  10. package/command-surface.json +129 -0
  11. package/hooks/freeflow-runtime-context.mjs +419 -0
  12. package/hooks/tests/check-runtime-context-hook.sh +223 -0
  13. package/package.json +41 -22
  14. package/pi-extension/dist/index.js +269 -0
  15. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  16. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  17. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  18. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  19. package/pi-extension/dist/output-router/renderers.js +1023 -0
  20. package/pi-extension/dist/output-router/router-tools.js +554 -0
  21. package/pi-extension/dist/output-router/schemas.js +383 -0
  22. package/pi-extension/dist/output-router/status.js +744 -0
  23. package/pi-extension/dist/output-router/utils.js +502 -0
  24. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  25. package/pi-extension/dist/settings/settings-tui.js +314 -0
  26. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  27. package/pi-extension/freeflow/index.js +1 -0
  28. package/pi-extension/src/index.ts +295 -0
  29. package/pi-extension/src/integrations/mcp-config.ts +32 -0
  30. package/pi-extension/src/output-router/host-producer-identification.ts +256 -0
  31. package/pi-extension/src/output-router/native-safety-net.ts +248 -0
  32. package/pi-extension/src/output-router/observed-tool-routing.ts +134 -0
  33. package/pi-extension/src/output-router/renderers.ts +1108 -0
  34. package/pi-extension/src/output-router/router-tools.ts +579 -0
  35. package/pi-extension/src/output-router/schemas.ts +387 -0
  36. package/pi-extension/src/output-router/status.ts +791 -0
  37. package/pi-extension/src/output-router/utils.ts +554 -0
  38. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  39. package/pi-extension/src/settings/settings-tui.ts +408 -0
  40. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  41. package/pi-extension/tests/integration/pi-extension.test.js +4018 -0
  42. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  43. package/pi-extension/tests/output-router/search.test.js +722 -0
  44. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  45. package/pi-extension/tsconfig.json +16 -0
  46. package/{plugins/freeflow/router/dist → router/dist/benchmarks}/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +92 -0
  48. package/router/dist/benchmarks/benchmarks.d.ts +109 -0
  49. package/router/dist/benchmarks/benchmarks.js +979 -0
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +95 -0
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +500 -0
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +143 -0
  53. package/router/dist/benchmarks/command-benchmarks.js +698 -0
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +82 -0
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +773 -0
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +132 -0
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1380 -0
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +60 -0
  59. package/router/dist/benchmarks/context-saving-baseline.js +470 -0
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +103 -0
  61. package/router/dist/benchmarks/index-benchmarks.js +862 -0
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +101 -0
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +618 -0
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +56 -0
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +322 -0
  66. package/router/dist/config/config.d.ts +103 -0
  67. package/router/dist/config/config.js +632 -0
  68. package/{plugins/freeflow/router/dist → router/dist/config}/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +54 -0
  70. package/{plugins/freeflow/router/dist → router/dist/config}/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +648 -0
  72. package/router/dist/config/types.d.ts +573 -0
  73. package/router/dist/config/types.js +41 -0
  74. package/router/dist/evidence/bounded-evidence.d.ts +40 -0
  75. package/router/dist/evidence/bounded-evidence.js +323 -0
  76. package/router/dist/evidence/evidence-range-selector.d.ts +24 -0
  77. package/router/dist/evidence/evidence-range-selector.js +232 -0
  78. package/router/dist/evidence/evidence-search.d.ts +27 -0
  79. package/router/dist/evidence/evidence-search.js +490 -0
  80. package/{plugins/freeflow/router/dist → router/dist/evidence}/evidence.d.ts +18 -18
  81. package/router/dist/evidence/evidence.js +200 -0
  82. package/router/dist/evidence/failure-contracts.d.ts +37 -0
  83. package/router/dist/evidence/failure-contracts.js +166 -0
  84. package/router/dist/evidence/line-ranges.d.ts +24 -0
  85. package/router/dist/evidence/line-ranges.js +22 -0
  86. package/router/dist/experiments/local-index.d.ts +64 -0
  87. package/router/dist/experiments/local-index.js +581 -0
  88. package/router/dist/index.d.ts +30 -0
  89. package/router/dist/index.js +32 -0
  90. package/router/dist/local/local-traversal.d.ts +18 -0
  91. package/router/dist/local/local-traversal.js +334 -0
  92. package/router/dist/processing/engine.d.ts +132 -0
  93. package/router/dist/processing/engine.js +533 -0
  94. package/router/dist/processing/reducers.d.ts +280 -0
  95. package/router/dist/processing/reducers.js +1673 -0
  96. package/router/dist/processing/renderers.d.ts +31 -0
  97. package/router/dist/processing/renderers.js +195 -0
  98. package/router/dist/processing/scripts.d.ts +91 -0
  99. package/router/dist/processing/scripts.js +386 -0
  100. package/{plugins/freeflow/router/dist → router/dist/repo}/repo-traversal.d.ts +9 -9
  101. package/router/dist/repo/repo-traversal.js +245 -0
  102. package/router/dist/routing/observed-reducers.d.ts +41 -0
  103. package/router/dist/routing/observed-reducers.js +661 -0
  104. package/router/dist/routing/observed-routing.d.ts +26 -0
  105. package/router/dist/routing/observed-routing.js +378 -0
  106. package/router/dist/routing/parsers.d.ts +16 -0
  107. package/router/dist/routing/parsers.js +521 -0
  108. package/router/dist/routing/run-filters.d.ts +60 -0
  109. package/router/dist/routing/run-filters.js +238 -0
  110. package/router/dist/routing/run-reducers.d.ts +49 -0
  111. package/router/dist/routing/run-reducers.js +122 -0
  112. package/router/dist/sandbox/adapter-roots.d.ts +46 -0
  113. package/router/dist/sandbox/adapter-roots.js +66 -0
  114. package/router/dist/sandbox/eryx-python-adapter.d.ts +13 -0
  115. package/router/dist/sandbox/eryx-python-adapter.js +868 -0
  116. package/router/dist/sandbox/jq-wasm-adapter.d.ts +9 -0
  117. package/router/dist/sandbox/jq-wasm-adapter.js +429 -0
  118. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +13 -0
  119. package/router/dist/sandbox/quickjs-wasi-adapter.js +384 -0
  120. package/router/dist/sandbox/script-sandbox.d.ts +134 -0
  121. package/router/dist/sandbox/script-sandbox.js +337 -0
  122. package/router/dist/setup/script-transform-adapters.d.ts +32 -0
  123. package/router/dist/setup/script-transform-adapters.js +278 -0
  124. package/router/dist/tools/batch.d.ts +33 -0
  125. package/router/dist/tools/batch.js +586 -0
  126. package/router/dist/tools/run.d.ts +62 -0
  127. package/router/dist/tools/run.js +1304 -0
  128. package/router/dist/tools/search.d.ts +66 -0
  129. package/router/dist/tools/search.js +1364 -0
  130. package/router/dist/transform/engine.d.ts +168 -0
  131. package/router/dist/transform/engine.js +2072 -0
  132. package/router/dist/vault/vault-index.d.ts +125 -0
  133. package/router/dist/vault/vault-index.js +416 -0
  134. package/router/dist/vault/vault.d.ts +138 -0
  135. package/router/dist/vault/vault.js +630 -0
  136. package/runtime/interaction-contract.md +16 -0
  137. package/skills/bypass/SKILL.md +82 -0
  138. package/skills/commit-work/SKILL.md +106 -0
  139. package/skills/commit-work/references/staging-decisions.md +73 -0
  140. package/skills/decision-gate/SKILL.md +48 -0
  141. package/skills/design-for-depth/SKILL.md +106 -0
  142. package/skills/design-for-depth/references/design-pressure-signals.md +314 -0
  143. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  144. package/skills/design-for-depth/references/software-design-philosophy.md +248 -0
  145. package/skills/diagnose-failure/SKILL.md +105 -0
  146. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  147. package/skills/diagnose-failure/references/flaky-and-performance.md +43 -0
  148. package/skills/discuss/SKILL.md +121 -0
  149. package/skills/discuss/references/checkpoints.md +66 -0
  150. package/skills/evaluate-skill/SKILL.md +105 -0
  151. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  152. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  153. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  154. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  155. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  156. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  159. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  160. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  161. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  162. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  163. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  164. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  165. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  166. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  167. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  168. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  169. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  170. package/skills/execute-work/SKILL.md +99 -0
  171. package/skills/execute-work/references/code-practices.md +78 -0
  172. package/skills/execute-work/references/execution-loop.md +77 -0
  173. package/skills/finish-branch/SKILL.md +117 -0
  174. package/skills/finish-branch/references/integration-options.md +80 -0
  175. package/skills/handoff/SKILL.md +92 -0
  176. package/skills/handoff/references/templates.md +141 -0
  177. package/skills/launch-work/SKILL.md +116 -0
  178. package/skills/launch-work/references/launch-readiness.md +85 -0
  179. package/skills/migration-work/SKILL.md +115 -0
  180. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  181. package/skills/mode-contract/SKILL.md +82 -0
  182. package/skills/output-router/SKILL.md +206 -0
  183. package/skills/output-router/references/safety-policy.md +40 -0
  184. package/skills/release-work/SKILL.md +118 -0
  185. package/skills/release-work/references/release-evidence.md +78 -0
  186. package/skills/review-artifact/SKILL.md +126 -0
  187. package/skills/review-artifact/references/reviewer-prompt.md +148 -0
  188. package/skills/review-work/SKILL.md +126 -0
  189. package/skills/review-work/references/reviewer-prompt.md +134 -0
  190. package/skills/review-work/references/security-risk-lens.md +67 -0
  191. package/skills/setup-freeflow/SKILL.md +98 -0
  192. package/skills/setup-freeflow/references/activation-contract.md +120 -0
  193. package/skills/setup-freeflow/references/host-setup.md +72 -0
  194. package/skills/setup-freeflow/references/output-router-setup.md +110 -0
  195. package/skills/simplify-code/SKILL.md +80 -0
  196. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  197. package/skills/tdd/SKILL.md +96 -0
  198. package/skills/tdd/references/test-design.md +96 -0
  199. package/skills/track-work/SKILL.md +84 -0
  200. package/skills/track-work/references/working-record-schema.md +378 -0
  201. package/skills/verify-work/SKILL.md +85 -0
  202. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  203. package/skills/verify-work/references/integration-evidence.md +51 -0
  204. package/skills/verify-work/references/performance-evidence.md +57 -0
  205. package/skills/workflow/SKILL.md +91 -0
  206. package/skills/workflow/references/domain-skill-composition.md +57 -0
  207. package/skills/workflow/references/workflow-loop.md +92 -0
  208. package/skills/write-plan/SKILL.md +123 -0
  209. package/skills/write-plan/references/plan-shapes.md +156 -0
  210. package/skills/write-skill/SKILL.md +119 -0
  211. package/skills/write-skill/references/activation-boundaries.md +62 -0
  212. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  213. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  214. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  215. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  216. package/skills/write-spec/SKILL.md +104 -0
  217. package/skills/write-spec/references/artifact-standards.md +86 -0
  218. package/skills/write-spec/references/decision-records.md +77 -0
  219. package/skills/write-spec/references/spec-shapes.md +128 -0
  220. package/plugins/freeflow/command-surface.json +0 -77
  221. package/plugins/freeflow/docs/README.md +0 -40
  222. package/plugins/freeflow/docs/adr/0001-three-modes.md +0 -27
  223. package/plugins/freeflow/docs/adr/0002-evals-before-hooks.md +0 -17
  224. package/plugins/freeflow/docs/adr/0003-release-boundary.md +0 -21
  225. package/plugins/freeflow/docs/adr/0004-research-replaces-shallow-discovery-skills.md +0 -26
  226. package/plugins/freeflow/docs/adr/README.md +0 -8
  227. package/plugins/freeflow/docs/architecture.md +0 -86
  228. package/plugins/freeflow/docs/output-router.md +0 -212
  229. package/plugins/freeflow/docs/release-evidence.md +0 -69
  230. package/plugins/freeflow/docs/skills.md +0 -43
  231. package/plugins/freeflow/docs/workflow.md +0 -57
  232. package/plugins/freeflow/hooks/freeflow-runtime-context.mjs +0 -257
  233. package/plugins/freeflow/pi-extension/index.js +0 -1176
  234. package/plugins/freeflow/router/dist/benchmark-harness.js +0 -92
  235. package/plugins/freeflow/router/dist/benchmarks.d.ts +0 -102
  236. package/plugins/freeflow/router/dist/benchmarks.js +0 -880
  237. package/plugins/freeflow/router/dist/bounded-evidence.d.ts +0 -40
  238. package/plugins/freeflow/router/dist/bounded-evidence.js +0 -310
  239. package/plugins/freeflow/router/dist/codex-qa-benchmarks.d.ts +0 -91
  240. package/plugins/freeflow/router/dist/codex-qa-benchmarks.js +0 -461
  241. package/plugins/freeflow/router/dist/command-benchmarks.d.ts +0 -128
  242. package/plugins/freeflow/router/dist/command-benchmarks.js +0 -652
  243. package/plugins/freeflow/router/dist/config.d.ts +0 -22
  244. package/plugins/freeflow/router/dist/config.js +0 -114
  245. package/plugins/freeflow/router/dist/evidence-range-selector.d.ts +0 -22
  246. package/plugins/freeflow/router/dist/evidence-range-selector.js +0 -180
  247. package/plugins/freeflow/router/dist/evidence-search.d.ts +0 -25
  248. package/plugins/freeflow/router/dist/evidence-search.js +0 -450
  249. package/plugins/freeflow/router/dist/evidence.js +0 -196
  250. package/plugins/freeflow/router/dist/experimental-local-index.d.ts +0 -1
  251. package/plugins/freeflow/router/dist/experimental-local-index.js +0 -1
  252. package/plugins/freeflow/router/dist/experiments/local-index.d.ts +0 -58
  253. package/plugins/freeflow/router/dist/experiments/local-index.js +0 -562
  254. package/plugins/freeflow/router/dist/index-benchmarks.d.ts +0 -84
  255. package/plugins/freeflow/router/dist/index-benchmarks.js +0 -458
  256. package/plugins/freeflow/router/dist/index.d.ts +0 -12
  257. package/plugins/freeflow/router/dist/index.js +0 -12
  258. package/plugins/freeflow/router/dist/line-ranges.d.ts +0 -22
  259. package/plugins/freeflow/router/dist/line-ranges.js +0 -22
  260. package/plugins/freeflow/router/dist/parsers.d.ts +0 -16
  261. package/plugins/freeflow/router/dist/parsers.js +0 -473
  262. package/plugins/freeflow/router/dist/repo-traversal.js +0 -242
  263. package/plugins/freeflow/router/dist/retrieve.d.ts +0 -33
  264. package/plugins/freeflow/router/dist/retrieve.js +0 -855
  265. package/plugins/freeflow/router/dist/router-contract.js +0 -54
  266. package/plugins/freeflow/router/dist/run.d.ts +0 -26
  267. package/plugins/freeflow/router/dist/run.js +0 -356
  268. package/plugins/freeflow/router/dist/schema.js +0 -354
  269. package/plugins/freeflow/router/dist/types.d.ts +0 -234
  270. package/plugins/freeflow/router/dist/types.js +0 -9
  271. package/plugins/freeflow/router/dist/vault.d.ts +0 -80
  272. package/plugins/freeflow/router/dist/vault.js +0 -484
  273. package/plugins/freeflow/skills/bypass/SKILL.md +0 -68
  274. package/plugins/freeflow/skills/commit-work/SKILL.md +0 -98
  275. package/plugins/freeflow/skills/commit-work/references/staging-decisions.md +0 -51
  276. package/plugins/freeflow/skills/diagnose-failure/SKILL.md +0 -95
  277. package/plugins/freeflow/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  278. package/plugins/freeflow/skills/diagnose-failure/references/flaky-and-performance.md +0 -31
  279. package/plugins/freeflow/skills/evaluate-skill/SKILL.md +0 -96
  280. package/plugins/freeflow/skills/evaluate-skill/references/eval-patterns.md +0 -41
  281. package/plugins/freeflow/skills/evaluate-skill/references/grading-priority.md +0 -33
  282. package/plugins/freeflow/skills/execute-plan/SKILL.md +0 -132
  283. package/plugins/freeflow/skills/execute-plan/references/execution-map.md +0 -122
  284. package/plugins/freeflow/skills/handoff/SKILL.md +0 -66
  285. package/plugins/freeflow/skills/handoff/references/templates.md +0 -64
  286. package/plugins/freeflow/skills/interview-gate/SKILL.md +0 -90
  287. package/plugins/freeflow/skills/mode-contract/SKILL.md +0 -79
  288. package/plugins/freeflow/skills/output-router/SKILL.md +0 -69
  289. package/plugins/freeflow/skills/output-router/references/safety-policy.md +0 -33
  290. package/plugins/freeflow/skills/research/ARTIFACT-DESTINATIONS.md +0 -58
  291. package/plugins/freeflow/skills/research/CHECKPOINTS.md +0 -74
  292. package/plugins/freeflow/skills/research/LANGUAGE.md +0 -61
  293. package/plugins/freeflow/skills/research/SKILL.md +0 -128
  294. package/plugins/freeflow/skills/review-artifact/SKILL.md +0 -115
  295. package/plugins/freeflow/skills/review-artifact/references/reviewer-prompt.md +0 -56
  296. package/plugins/freeflow/skills/review-work/SKILL.md +0 -96
  297. package/plugins/freeflow/skills/review-work/references/reviewer-prompt.md +0 -97
  298. package/plugins/freeflow/skills/setup-freeflow/SKILL.md +0 -94
  299. package/plugins/freeflow/skills/setup-freeflow/references/activation-contract.md +0 -99
  300. package/plugins/freeflow/skills/setup-freeflow/references/host-setup.md +0 -35
  301. package/plugins/freeflow/skills/setup-freeflow/references/output-router-setup.md +0 -52
  302. package/plugins/freeflow/skills/verify-work/SKILL.md +0 -62
  303. package/plugins/freeflow/skills/workflow/SKILL.md +0 -95
  304. package/plugins/freeflow/skills/workflow/references/workflow-map.md +0 -102
  305. package/plugins/freeflow/skills/write-plan/SKILL.md +0 -88
  306. package/plugins/freeflow/skills/write-plan/references/plan-shapes.md +0 -70
  307. package/plugins/freeflow/skills/write-skill/SKILL.md +0 -88
  308. package/plugins/freeflow/skills/write-spec/SKILL.md +0 -90
  309. package/plugins/freeflow/skills/write-spec/references/artifact-standards.md +0 -67
  310. package/plugins/freeflow/skills/write-spec/references/spec-shapes.md +0 -69
  311. /package/{plugins/freeflow/assets → assets}/freeflow-plugin-icon-transparent.png +0 -0
  312. /package/{plugins/freeflow/hooks → hooks}/hooks.json +0 -0
@@ -1,855 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { readFile } from "node:fs/promises";
3
- import { resolve } from "node:path";
4
- import { buildBoundedEdgeChunks, buildBoundedExcerpt } from "./bounded-evidence.js";
5
- import { findBestLineForQuery, searchRepoEvidenceCandidates, } from "./evidence-search.js";
6
- import { resolveExactLineRange } from "./line-ranges.js";
7
- import { collectRepoTextFileRefs, resolveRepoPath } from "./repo-traversal.js";
8
- import { createVault, readOutputText, readVaultRecord } from "./vault.js";
9
- const DEFAULT_CONTEXT_LINES = 2;
10
- const DEFAULT_QUERY_TOP_K = 1;
11
- const DEFAULT_LOCATE_TOP_K = 5;
12
- const MAX_TOP_K = 10;
13
- const QUERY_EXCERPT_MAX_BYTES = 8_192;
14
- const LINE_PREVIEW_MAX_BYTES = 2_048;
15
- const EXPAND_LINES_30_MAX_BYTES = 32 * 1024;
16
- const EXPAND_LINES_30_MAX_LINES = 120;
17
- const EXPAND_LINES_80_MAX_BYTES = 64 * 1024;
18
- const EXPAND_LINES_80_MAX_LINES = 240;
19
- const EXACT_LINE_RANGE_MAX_BYTES = 64_000;
20
- const EXACT_CHUNK_MAX_BYTES = 32_000;
21
- const CONCURRENT_REPO_FILE_READS = 32;
22
- const QUERY_COVERAGE_MAX_LINES = 80;
23
- const BOUNDED_EVIDENCE_CAPS = {
24
- queryExcerptMaxBytes: QUERY_EXCERPT_MAX_BYTES,
25
- linePreviewMaxBytes: LINE_PREVIEW_MAX_BYTES,
26
- expandLines30MaxBytes: EXPAND_LINES_30_MAX_BYTES,
27
- expandLines30MaxLines: EXPAND_LINES_30_MAX_LINES,
28
- expandLines80MaxBytes: EXPAND_LINES_80_MAX_BYTES,
29
- expandLines80MaxLines: EXPAND_LINES_80_MAX_LINES,
30
- exactChunkMaxBytes: EXACT_CHUNK_MAX_BYTES,
31
- };
32
- export async function freeflowRetrieve(options) {
33
- const preserve = options.preserve ?? "important";
34
- try {
35
- if (options.source.kind === "vault") {
36
- return await retrieveVault(options, preserve);
37
- }
38
- const root = resolve(options.source.root);
39
- if (options.action === "query") {
40
- return await queryRepo(root, options, preserve);
41
- }
42
- if (options.action === "locate") {
43
- return await locateRepo(root, options, preserve);
44
- }
45
- if (options.action === "expand") {
46
- return await expandRepoEvidence(root, options, preserve);
47
- }
48
- if (options.action === "retrieve") {
49
- return await retrieveRepoPath(root, options, preserve);
50
- }
51
- if (options.action === "explain") {
52
- return explainRepoDecision(options, preserve);
53
- }
54
- return errorResult(preserve, `Retrieval action ${options.action} is not implemented yet.`);
55
- }
56
- catch (error) {
57
- const message = error instanceof Error ? error.message : String(error);
58
- return errorResult(preserve, `freeflow_retrieve failed while reading ${options.source.kind} source: ${message}`);
59
- }
60
- }
61
- async function retrieveVault(options, preserve) {
62
- if (options.action === "retrieve") {
63
- return retrieveVaultLines(options, preserve);
64
- }
65
- if (options.action === "expand") {
66
- return expandVaultEvidence(options, preserve);
67
- }
68
- if (options.action === "explain") {
69
- return explainVaultOutput(options, preserve);
70
- }
71
- if (options.action !== "query") {
72
- return errorResult(preserve, `Vault retrieval action ${options.action} is not implemented yet.`);
73
- }
74
- if (!options.query?.trim()) {
75
- return errorResult(preserve, "Vault query requires a non-empty query string.");
76
- }
77
- const stream = options.source.stream ?? "combined";
78
- const vault = createVault({ root: options.source.root });
79
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
80
- const lines = splitLines(text);
81
- const candidateLine = findBestLineForQuery(lines, options.query);
82
- if (candidateLine === null) {
83
- return {
84
- toolStatus: "ok",
85
- decisionId: decisionId("vault-query", options.source.outputId, stream, options.query, "none"),
86
- preserve,
87
- source: { kind: "vault", outputId: options.source.outputId, stream },
88
- routing: {
89
- status: "routed",
90
- route: "retrieve",
91
- reason: "Deterministic lexical vault retrieval found no matching output evidence.",
92
- },
93
- evidence: [],
94
- recovery: {
95
- how: `Refine the query or use freeflow_retrieve action=retrieve with outputId=${options.source.outputId} and an exact line range.`,
96
- outputId: options.source.outputId,
97
- },
98
- };
99
- }
100
- const start = Math.max(0, candidateLine - DEFAULT_CONTEXT_LINES);
101
- const end = Math.min(lines.length - 1, candidateLine + DEFAULT_CONTEXT_LINES);
102
- const evidenceLines = `${start + 1}-${end + 1}`;
103
- const evidence = {
104
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, options.query),
105
- source: { kind: "vault", outputId: options.source.outputId, stream },
106
- path: `${options.source.outputId}:${stream}`,
107
- lines: evidenceLines,
108
- excerpt: excerptForLineRange(lines, { start: start + 1, end: end + 1 }, "small"),
109
- why: `Deterministic lexical match for query terms in vaulted ${stream} output near line ${candidateLine + 1}.`,
110
- window: "small",
111
- expandable: true,
112
- };
113
- return {
114
- toolStatus: "ok",
115
- decisionId: decisionId("vault-query", options.source.outputId, stream, options.query, evidenceLines),
116
- preserve,
117
- source: { kind: "vault", outputId: options.source.outputId, stream },
118
- routing: {
119
- status: "routed",
120
- route: "retrieve",
121
- reason: `Deterministic lexical retrieval selected vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
122
- },
123
- evidence: [evidence],
124
- recovery: {
125
- how: `Use freeflow_retrieve action=expand with evidenceId=${evidence.id}, or action=retrieve with outputId=${options.source.outputId} and stream=${stream}.`,
126
- outputId: options.source.outputId,
127
- evidenceId: evidence.id,
128
- },
129
- };
130
- }
131
- async function retrieveVaultLines(options, preserve) {
132
- const stream = options.source.stream ?? "combined";
133
- const lineRange = options.lineRange;
134
- if (!lineRange) {
135
- return errorResult(preserve, "Vault retrieve requires a valid 1-based lineRange.");
136
- }
137
- const vault = createVault({ root: options.source.root });
138
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
139
- const lines = splitLines(text);
140
- const resolvedRange = resolveExactLineRange({
141
- requested: lineRange,
142
- lineCount: lines.length,
143
- availableLabel: "available vaulted output lines",
144
- invalidReason: "Vault retrieve requires a valid 1-based lineRange.",
145
- });
146
- if (!resolvedRange.ok) {
147
- return errorResult(preserve, resolvedRange.reason);
148
- }
149
- const { start, end } = resolvedRange.range;
150
- const evidenceLines = `${start}-${end}`;
151
- const excerpt = lines.slice(start - 1, end).join("\n");
152
- if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
153
- return retrieveVaultLineRangeOverCap(options, preserve, stream, lines, { start, end }, byteLength(excerpt));
154
- }
155
- const evidence = {
156
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, "retrieve"),
157
- source: { kind: "vault", outputId: options.source.outputId, stream },
158
- path: `${options.source.outputId}:${stream}`,
159
- lines: evidenceLines,
160
- excerpt,
161
- why: `Retrieved exact vaulted ${stream} output lines ${evidenceLines}.`,
162
- window: "exact",
163
- expandable: true,
164
- };
165
- return {
166
- toolStatus: "ok",
167
- decisionId: decisionId("vault-retrieve", options.source.outputId, stream, evidenceLines),
168
- preserve,
169
- source: { kind: "vault", outputId: options.source.outputId, stream },
170
- routing: {
171
- status: "routed",
172
- route: "retrieve",
173
- reason: `Retrieved exact vaulted outputId=${options.source.outputId} stream=${stream} lines=${evidenceLines}.`,
174
- },
175
- evidence: [evidence],
176
- recovery: {
177
- how: `Use freeflow_retrieve action=expand with evidenceId=${evidence.id} for more vaulted output context.`,
178
- outputId: options.source.outputId,
179
- evidenceId: evidence.id,
180
- },
181
- };
182
- }
183
- function retrieveVaultLineRangeOverCap(options, preserve, stream, lines, requestedRange, requestedBytes) {
184
- const evidence = buildBoundedEdgeChunks({ lines, range: requestedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => {
185
- const evidenceLines = chunk.linesLabel;
186
- return {
187
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `retrieve-over-cap-${index}`),
188
- source: { kind: "vault", outputId: options.source.outputId, stream },
189
- path: `${options.source.outputId}:${stream}`,
190
- lines: evidenceLines,
191
- excerpt: chunk.excerpt,
192
- why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} output line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
193
- window: "small",
194
- expandable: true,
195
- };
196
- });
197
- return {
198
- toolStatus: "ok",
199
- decisionId: decisionId("vault-retrieve-lines-over-cap", options.source.outputId, stream, `${requestedRange.start}-${requestedRange.end}`, String(requestedBytes)),
200
- preserve,
201
- source: { kind: "vault", outputId: options.source.outputId, stream },
202
- routing: {
203
- status: "partial",
204
- route: "retrieve",
205
- 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.`,
206
- },
207
- evidence,
208
- recovery: {
209
- how: `Use narrower freeflow_retrieve action=retrieve lineRange spans for exact vaulted output recovery, or native vault file access if a full raw span is required.`,
210
- outputId: options.source.outputId,
211
- },
212
- };
213
- }
214
- function expandVaultEvidenceOverCap(options, preserve, stream, evidence, lines, expandedRange, expandedBytes) {
215
- const chunks = buildBoundedEdgeChunks({ lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => {
216
- const evidenceLines = chunk.linesLabel;
217
- return {
218
- id: evidenceIdFor(`${options.source.outputId}:${stream}`, evidenceLines, `expand-over-cap-${index}`),
219
- source: { kind: "vault", outputId: options.source.outputId, stream },
220
- path: `${options.source.outputId}:${stream}`,
221
- lines: evidenceLines,
222
- excerpt: chunk.excerpt,
223
- why: `Bounded recoverable ${chunk.edge} preview for vaulted ${stream} expansion over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
224
- window: "small",
225
- expandable: true,
226
- };
227
- });
228
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
229
- return {
230
- toolStatus: "ok",
231
- decisionId: decisionId("vault-expand-over-cap", evidence.id, expandedLines, String(expandedBytes)),
232
- preserve,
233
- source: { kind: "vault", outputId: options.source.outputId, stream },
234
- routing: {
235
- status: "partial",
236
- route: "retrieve",
237
- 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.`,
238
- },
239
- evidence: chunks,
240
- recovery: {
241
- how: `Use narrower freeflow_retrieve action=retrieve lineRange spans for exact vaulted output recovery.`,
242
- outputId: options.source.outputId,
243
- evidenceId: evidence.id,
244
- },
245
- };
246
- }
247
- async function expandVaultEvidence(options, preserve) {
248
- const evidence = options.evidence;
249
- if (!evidence?.lines || evidence.source.kind !== "vault") {
250
- return errorResult(preserve, "Vault expansion requires a previous vault evidence packet with lines.");
251
- }
252
- const stream = options.source.stream ?? evidence.source.stream ?? "combined";
253
- const originalRange = parseLineRange(evidence.lines);
254
- if (!originalRange) {
255
- return errorResult(preserve, `Cannot expand unsupported vault evidence line range ${evidence.lines}.`);
256
- }
257
- const expansion = options.expansion ?? "lines_30";
258
- const vault = createVault({ root: options.source.root });
259
- const text = await readOutputText(vault, options.source.sessionId, options.source.outputId, stream);
260
- const lines = splitLines(text);
261
- const expandedRange = expandLineRange(originalRange, lines.length, expansion);
262
- const window = windowForExpansion(expansion);
263
- const expandedExcerpt = lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
264
- if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
265
- return expandVaultEvidenceOverCap(options, preserve, stream, evidence, lines, expandedRange, byteLength(expandedExcerpt));
266
- }
267
- const bounded = buildBoundedExcerpt({ lines, range: expandedRange, window, caps: BOUNDED_EVIDENCE_CAPS });
268
- const expandedLines = bounded.linesLabel;
269
- const expandedEvidence = {
270
- ...evidence,
271
- source: { kind: "vault", outputId: options.source.outputId, stream },
272
- path: `${options.source.outputId}:${stream}`,
273
- lines: expandedLines,
274
- excerpt: bounded.excerpt,
275
- why: bounded.truncatedByLineCap
276
- ? `Expanded deterministic vault evidence from ${evidence.lines} and bounded it to ${expandedLines} by the ${window} line cap.`
277
- : `Expanded deterministic vault evidence from ${evidence.lines} to ${expandedLines}.`,
278
- window,
279
- expandable: expansion !== "full",
280
- };
281
- return {
282
- toolStatus: "ok",
283
- decisionId: decisionId("vault-expand", evidence.id, expandedLines),
284
- preserve,
285
- source: { kind: "vault", outputId: options.source.outputId, stream },
286
- routing: {
287
- status: "routed",
288
- route: "retrieve",
289
- reason: `Expanded vaulted outputId=${options.source.outputId} stream=${stream} evidence ${evidence.id} to lines=${expandedLines}.`,
290
- },
291
- evidence: [expandedEvidence],
292
- recovery: {
293
- how: `Use freeflow_retrieve action=retrieve with outputId=${options.source.outputId}, stream=${stream}, and an exact line range for precise recovery.`,
294
- outputId: options.source.outputId,
295
- evidenceId: evidence.id,
296
- },
297
- };
298
- }
299
- async function explainVaultOutput(options, preserve) {
300
- const vault = createVault({ root: options.source.root });
301
- const record = await readVaultRecord(vault, options.source.sessionId, options.source.outputId);
302
- const details = record.kind === "command"
303
- ? `kind=command executionStatus=${record.executionStatus} exitCode=${record.exitCode} decisions=${record.decisionIds.join(",")}`
304
- : `kind=${record.kind} decisions=${record.decisionIds.join(",")}`;
305
- const source = { kind: "vault", outputId: options.source.outputId };
306
- if (options.source.stream !== undefined) {
307
- Object.assign(source, { stream: options.source.stream });
308
- }
309
- return {
310
- toolStatus: "ok",
311
- decisionId: decisionId("vault-explain", options.source.outputId, record.contentHashSha256),
312
- preserve,
313
- source,
314
- routing: {
315
- status: "routed",
316
- route: "retrieve",
317
- reason: `Vault outputId=${options.source.outputId} ${details}. Raw output is recoverable from the vault record without rerunning the original command.`,
318
- },
319
- evidence: [],
320
- recovery: {
321
- how: `Use freeflow_retrieve action=retrieve with outputId=${options.source.outputId}, stream=stdout|stderr|combined, and an exact lineRange to recover raw command output.`,
322
- outputId: options.source.outputId,
323
- },
324
- };
325
- }
326
- async function queryRepo(root, options, preserve) {
327
- if (!options.query?.trim()) {
328
- return errorResult(preserve, "Repo query requires a non-empty query string.");
329
- }
330
- const query = options.query.trim();
331
- const topK = parseTopK(options.topK, DEFAULT_QUERY_TOP_K);
332
- if (typeof topK === "string") {
333
- return errorResult(preserve, topK);
334
- }
335
- const files = await readRepoTextFiles(root, options.source.path, options.generatedPathGlobs);
336
- const candidates = searchRepoEvidenceCandidates({
337
- files,
338
- query,
339
- topK,
340
- defaultContextLines: DEFAULT_CONTEXT_LINES,
341
- queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
342
- });
343
- if (candidates.length === 0) {
344
- return {
345
- toolStatus: "ok",
346
- decisionId: decisionId("repo-query", query, root, "none"),
347
- preserve,
348
- source: { kind: "repo", path: options.source.path ?? "." },
349
- routing: {
350
- status: "routed",
351
- route: "retrieve",
352
- reason: "Deterministic lexical retrieval found no matching repo evidence.",
353
- },
354
- evidence: [],
355
- recovery: {
356
- how: "Refine the query, use action=locate for candidate paths, or use native read for a known whole file.",
357
- },
358
- };
359
- }
360
- const evidence = candidates.map((candidate) => evidenceFromCandidate(candidate, query));
361
- const first = evidence[0];
362
- const firstCandidate = candidates[0];
363
- if (!first || !firstCandidate) {
364
- return errorResult(preserve, "Repo query produced no usable candidate evidence.");
365
- }
366
- return {
367
- toolStatus: "ok",
368
- decisionId: decisionId("repo-query", query, String(topK), evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|")),
369
- preserve,
370
- source: { kind: "repo", path: firstCandidate.file.path },
371
- routing: {
372
- status: "routed",
373
- route: "retrieve",
374
- reason: `Deterministic repo retrieval selected ${evidence.length} candidate(s); top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
375
- },
376
- evidence,
377
- recovery: {
378
- how: `Use freeflow_retrieve 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.`,
379
- evidenceId: first.id,
380
- },
381
- };
382
- }
383
- async function locateRepo(root, options, preserve) {
384
- if (!options.query?.trim()) {
385
- return errorResult(preserve, "Repo locate requires a non-empty query string.");
386
- }
387
- const query = options.query.trim();
388
- const topK = parseTopK(options.topK, DEFAULT_LOCATE_TOP_K);
389
- if (typeof topK === "string") {
390
- return errorResult(preserve, topK);
391
- }
392
- const files = await readRepoTextFiles(root, options.source.path, options.generatedPathGlobs);
393
- const candidates = searchRepoEvidenceCandidates({
394
- files,
395
- query,
396
- topK,
397
- defaultContextLines: DEFAULT_CONTEXT_LINES,
398
- queryCoverageMaxLines: QUERY_COVERAGE_MAX_LINES,
399
- });
400
- if (candidates.length === 0) {
401
- return {
402
- toolStatus: "ok",
403
- decisionId: decisionId("repo-locate", query, root, "none"),
404
- preserve,
405
- source: { kind: "repo", path: options.source.path ?? "." },
406
- routing: {
407
- status: "routed",
408
- route: "retrieve",
409
- reason: "Deterministic lexical locate found no candidate location.",
410
- },
411
- evidence: [],
412
- recovery: {
413
- how: "Refine the query or use native read for a known whole file.",
414
- },
415
- };
416
- }
417
- const evidence = candidates.map((candidate) => {
418
- const line = candidate.lineIndex + 1;
419
- return evidenceFromRange({
420
- id: evidenceIdFor(candidate.file.path, `${line}-${line}`, query),
421
- file: candidate.file,
422
- lines: { start: line, end: line },
423
- why: `Candidate location from deterministic repo scoring.`,
424
- window: "small",
425
- expandable: true,
426
- });
427
- });
428
- const first = evidence[0];
429
- const firstCandidate = candidates[0];
430
- if (!first || !firstCandidate) {
431
- return errorResult(preserve, "Repo locate produced no usable candidate evidence.");
432
- }
433
- return {
434
- toolStatus: "ok",
435
- decisionId: decisionId("repo-locate", query, String(topK), evidence.map((packet) => `${packet.path}:${packet.lines ?? ""}`).join("|")),
436
- preserve,
437
- source: { kind: "repo", path: firstCandidate.file.path },
438
- routing: {
439
- status: "routed",
440
- route: "retrieve",
441
- reason: `Located ${evidence.length} candidate location(s) without broad evidence retrieval; top result ${firstCandidate.file.path}:${first.lines} (${firstCandidate.reason}).`,
442
- },
443
- evidence,
444
- recovery: {
445
- how: `Use freeflow_retrieve action=retrieve with path=${firstCandidate.file.path} for an explicit span, or action=expand with evidenceId=${first.id}.`,
446
- evidenceId: first.id,
447
- },
448
- };
449
- }
450
- async function expandRepoEvidence(root, options, preserve) {
451
- const evidence = options.evidence;
452
- if (!evidence?.path || !evidence.lines) {
453
- return errorResult(preserve, "Repo expansion requires a previous repo evidence packet with path and lines.");
454
- }
455
- const expansion = options.expansion ?? "lines_30";
456
- const originalRange = parseLineRange(evidence.lines);
457
- if (!originalRange) {
458
- return errorResult(preserve, `Cannot expand unsupported evidence line range ${evidence.lines}.`);
459
- }
460
- const file = await readRepoTextFile(root, evidence.path);
461
- const expandedRange = expandLineRange(originalRange, file.lines.length, expansion);
462
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
463
- const expandedExcerpt = file.lines.slice(expandedRange.start - 1, expandedRange.end).join("\n");
464
- if (expansion === "full" && byteLength(expandedExcerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
465
- return expandRepoEvidenceOverCap(file, evidence, expandedRange, preserve, byteLength(expandedExcerpt));
466
- }
467
- const expandedEvidence = evidenceFromRange({
468
- id: evidence.id,
469
- file,
470
- lines: expandedRange,
471
- why: `Expanded deterministic repo evidence from ${evidence.lines} to ${expandedLines}.`,
472
- window: windowForExpansion(expansion),
473
- expandable: expansion !== "full",
474
- });
475
- return {
476
- toolStatus: "ok",
477
- decisionId: decisionId("repo-expand", evidence.id, expandedEvidence.lines ?? expandedLines),
478
- preserve,
479
- source: { kind: "repo", path: evidence.path },
480
- routing: {
481
- status: "routed",
482
- route: "retrieve",
483
- reason: `Expanded repo evidence ${evidence.id} to ${evidence.path}:${expandedEvidence.lines ?? expandedLines}.`,
484
- },
485
- evidence: [expandedEvidence],
486
- recovery: {
487
- how: expansion === "full"
488
- ? `Use native read for direct whole-file behavior if needed for ${evidence.path}.`
489
- : `Use freeflow_retrieve action=expand with expansion=lines_80 or full for more context from ${evidence.path}.`,
490
- evidenceId: evidence.id,
491
- },
492
- };
493
- }
494
- async function retrieveRepoPath(root, options, preserve) {
495
- if (!options.source.path) {
496
- return errorResult(preserve, "Repo retrieve requires source.path.");
497
- }
498
- const file = await readRepoTextFile(root, options.source.path);
499
- const maxFullBytes = options.maxFullBytes ?? 64_000;
500
- if (options.lineRange) {
501
- return retrieveRepoLineRange(file, options.lineRange, preserve);
502
- }
503
- if (preserve === "full") {
504
- return retrieveFullFile(file, maxFullBytes);
505
- }
506
- const end = Math.min(file.lines.length, 10);
507
- const evidence = evidenceFromRange({
508
- id: evidenceIdFor(file.path, `1-${end}`, "retrieve"),
509
- file,
510
- lines: { start: 1, end },
511
- why: `Retrieved explicit repo span ${file.path}:1-${end}.`,
512
- window: "small",
513
- expandable: true,
514
- });
515
- return {
516
- toolStatus: "ok",
517
- decisionId: decisionId("repo-retrieve", file.path, evidence.lines ?? ""),
518
- preserve,
519
- source: { kind: "repo", path: file.path },
520
- routing: {
521
- status: "routed",
522
- route: "retrieve",
523
- reason: `Retrieved explicit repo span ${file.path}:${evidence.lines}.`,
524
- },
525
- evidence: [evidence],
526
- recovery: {
527
- how: `Use freeflow_retrieve action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
528
- evidenceId: evidence.id,
529
- },
530
- };
531
- }
532
- function expandRepoEvidenceOverCap(file, evidence, expandedRange, preserve, expandedBytes) {
533
- const chunks = buildBoundedEdgeChunks({ lines: file.lines, range: expandedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
534
- const expandedLines = `${expandedRange.start}-${expandedRange.end}`;
535
- return {
536
- toolStatus: "ok",
537
- decisionId: decisionId("repo-expand-over-cap", evidence.id, expandedLines, String(expandedBytes)),
538
- preserve,
539
- source: { kind: "repo", path: file.path },
540
- routing: {
541
- status: "partial",
542
- route: "retrieve",
543
- 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.`,
544
- },
545
- evidence: chunks,
546
- recovery: {
547
- how: `Use narrower freeflow_retrieve action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
548
- evidenceId: evidence.id,
549
- },
550
- };
551
- }
552
- function retrieveRepoLineRange(file, lineRange, preserve) {
553
- const resolvedRange = resolveExactLineRange({
554
- requested: lineRange,
555
- lineCount: file.lines.length,
556
- availableLabel: "available repo lines",
557
- invalidReason: "Repo retrieve requires a valid 1-based lineRange.",
558
- });
559
- if (!resolvedRange.ok) {
560
- return errorResult(preserve, resolvedRange.reason);
561
- }
562
- const { start, end } = resolvedRange.range;
563
- const evidenceLines = `${start}-${end}`;
564
- const excerpt = file.lines.slice(start - 1, end).join("\n");
565
- if (byteLength(excerpt) > EXACT_LINE_RANGE_MAX_BYTES) {
566
- return retrieveRepoLineRangeOverCap(file, { start, end }, preserve, byteLength(excerpt));
567
- }
568
- const evidence = evidenceFromRange({
569
- id: evidenceIdFor(file.path, evidenceLines, "retrieve"),
570
- file,
571
- lines: { start, end },
572
- why: `Retrieved exact repo lines ${file.path}:${evidenceLines}.`,
573
- window: "exact",
574
- expandable: true,
575
- });
576
- return {
577
- toolStatus: "ok",
578
- decisionId: decisionId("repo-retrieve-lines", file.path, evidenceLines),
579
- preserve,
580
- source: { kind: "repo", path: file.path },
581
- routing: {
582
- status: "routed",
583
- route: "retrieve",
584
- reason: `Retrieved exact repo lines ${file.path}:${evidenceLines}.`,
585
- },
586
- evidence: [evidence],
587
- recovery: {
588
- how: `Use freeflow_retrieve action=expand with evidenceId=${evidence.id} for more context from ${file.path}.`,
589
- evidenceId: evidence.id,
590
- },
591
- };
592
- }
593
- function retrieveRepoLineRangeOverCap(file, requestedRange, preserve, requestedBytes) {
594
- const evidence = buildBoundedEdgeChunks({ lines: file.lines, range: requestedRange, caps: BOUNDED_EVIDENCE_CAPS }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
595
- return {
596
- toolStatus: "ok",
597
- decisionId: decisionId("repo-retrieve-lines-over-cap", file.path, `${requestedRange.start}-${requestedRange.end}`, String(requestedBytes)),
598
- preserve,
599
- source: { kind: "repo", path: file.path },
600
- routing: {
601
- status: "partial",
602
- route: "retrieve",
603
- 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.`,
604
- },
605
- evidence,
606
- recovery: {
607
- how: `Use narrower freeflow_retrieve action=retrieve lineRange spans for exact content recovery from ${file.path}, or native read for direct whole-file host behavior.`,
608
- },
609
- };
610
- }
611
- function repoEvidenceForBoundedExactChunk(file, chunk, index) {
612
- const evidenceLines = chunk.linesLabel;
613
- return {
614
- id: evidenceIdFor(file.path, evidenceLines, `retrieve-over-cap-${index}`),
615
- source: { kind: "repo", path: file.path },
616
- path: file.path,
617
- lines: evidenceLines,
618
- excerpt: chunk.excerpt,
619
- why: `Bounded recoverable ${chunk.edge} preview for repo line range over cap ${EXACT_LINE_RANGE_MAX_BYTES}.`,
620
- window: "small",
621
- expandable: true,
622
- };
623
- }
624
- function retrieveFullFile(file, maxFullBytes) {
625
- const fullBytes = byteLength(file.text);
626
- if (fullBytes <= maxFullBytes) {
627
- const evidence = evidenceFromRange({
628
- id: evidenceIdFor(file.path, `1-${file.lines.length}`, "full"),
629
- file,
630
- lines: { start: 1, end: file.lines.length },
631
- why: `Returned full exact content because ${file.path} is under the full-context cap.`,
632
- window: "full",
633
- expandable: false,
634
- });
635
- return {
636
- toolStatus: "ok",
637
- decisionId: decisionId("repo-full", file.path, String(fullBytes)),
638
- preserve: "full",
639
- source: { kind: "repo", path: file.path },
640
- routing: {
641
- status: "routed",
642
- route: "retrieve",
643
- reason: `Returned full exact content for ${file.path}; ${fullBytes} bytes is under cap ${maxFullBytes}.`,
644
- },
645
- evidence: [evidence],
646
- recovery: {
647
- how: `Use native read for direct whole-file host behavior if needed for ${file.path}.`,
648
- evidenceId: evidence.id,
649
- },
650
- };
651
- }
652
- const evidence = buildBoundedEdgeChunks({
653
- lines: file.lines,
654
- range: { start: 1, end: file.lines.length },
655
- caps: BOUNDED_EVIDENCE_CAPS,
656
- }).map((chunk, index) => repoEvidenceForBoundedExactChunk(file, chunk, index));
657
- return {
658
- toolStatus: "ok",
659
- decisionId: decisionId("repo-full-over-cap", file.path, String(fullBytes), String(maxFullBytes)),
660
- preserve: "full",
661
- source: { kind: "repo", path: file.path },
662
- routing: {
663
- status: "partial",
664
- route: "retrieve",
665
- reason: `Full exact content for ${file.path} is ${fullBytes} bytes and exceeds cap ${maxFullBytes}; returned bounded edge previews instead of a summary.`,
666
- },
667
- evidence,
668
- recovery: {
669
- how: `Use freeflow_retrieve action=retrieve with path=${file.path} and an explicit span to recover exact content, or native read for direct whole-file output.`,
670
- },
671
- };
672
- }
673
- function explainRepoDecision(options, preserve) {
674
- const decision = options.decision;
675
- if (!decision) {
676
- return errorResult(preserve, "Repo explain requires a previous routed result decision.");
677
- }
678
- const recovery = {
679
- how: decision.recovery?.how ??
680
- "Use freeflow_retrieve action=expand with a returned evidence id, or action=retrieve for an explicit repo span.",
681
- };
682
- if (decision.recovery?.outputId !== undefined) {
683
- Object.assign(recovery, { outputId: decision.recovery.outputId });
684
- }
685
- if (decision.recovery?.evidenceId !== undefined) {
686
- Object.assign(recovery, { evidenceId: decision.recovery.evidenceId });
687
- }
688
- const result = {
689
- toolStatus: "ok",
690
- decisionId: decisionId("repo-explain", decision.decisionId),
691
- preserve: decision.preserve,
692
- routing: {
693
- status: "routed",
694
- route: "retrieve",
695
- reason: `Decision ${decision.decisionId} used route=${decision.routing.route}, routing.status=${decision.routing.status}: ${decision.routing.reason}`,
696
- },
697
- evidence: [],
698
- recovery,
699
- };
700
- if (decision.source !== undefined) {
701
- result.source = decision.source;
702
- }
703
- return result;
704
- }
705
- async function readRepoTextFiles(root, requestedPath, generatedPathGlobs = []) {
706
- const options = { root, generatedPathGlobs };
707
- if (requestedPath !== undefined) {
708
- options.requestedPath = requestedPath;
709
- }
710
- const fileRefs = await collectRepoTextFileRefs(options);
711
- return readRepoTextFileRefs(fileRefs);
712
- }
713
- async function readRepoTextFile(root, path) {
714
- const resolved = await resolveRepoPath(root, path);
715
- const text = await readFile(resolved.absolutePath, "utf8");
716
- return {
717
- path: resolved.relativePath,
718
- absolutePath: resolved.absolutePath,
719
- text,
720
- lines: splitLines(text),
721
- };
722
- }
723
- async function readRepoTextFileRefs(fileRefs) {
724
- const files = new Array(fileRefs.length);
725
- let nextIndex = 0;
726
- async function worker() {
727
- while (nextIndex < fileRefs.length) {
728
- const currentIndex = nextIndex;
729
- nextIndex += 1;
730
- const ref = fileRefs[currentIndex];
731
- if (!ref) {
732
- continue;
733
- }
734
- const text = await readFile(ref.absolutePath, "utf8");
735
- if (text.includes("\0")) {
736
- continue;
737
- }
738
- files[currentIndex] = { path: ref.path, absolutePath: ref.absolutePath, text, lines: splitLines(text) };
739
- }
740
- }
741
- const workerCount = Math.min(CONCURRENT_REPO_FILE_READS, fileRefs.length);
742
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
743
- return files.filter((file) => Boolean(file));
744
- }
745
- function parseTopK(value, defaultValue) {
746
- if (value === undefined) {
747
- return defaultValue;
748
- }
749
- if (!Number.isInteger(value) || value < 1 || value > MAX_TOP_K) {
750
- return `topK must be an integer from 1 to ${MAX_TOP_K}.`;
751
- }
752
- return value;
753
- }
754
- function evidenceFromCandidate(candidate, query) {
755
- const defaultStart = Math.max(0, candidate.lineIndex - DEFAULT_CONTEXT_LINES);
756
- const defaultEnd = Math.min(candidate.file.lines.length - 1, candidate.lineIndex + DEFAULT_CONTEXT_LINES);
757
- const lines = candidate.range ?? { start: defaultStart + 1, end: defaultEnd + 1 };
758
- return evidenceFromRange({
759
- id: evidenceIdFor(candidate.file.path, `${lines.start}-${lines.end}`, query),
760
- file: candidate.file,
761
- lines,
762
- why: `${candidate.reason} near ${candidate.file.path}:${candidate.lineIndex + 1}.`,
763
- window: "small",
764
- expandable: true,
765
- ...(candidate.exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase: candidate.exactNormalizedPhrase } : {}),
766
- });
767
- }
768
- function evidenceFromRange(options) {
769
- const bounded = buildBoundedExcerpt({
770
- lines: options.file.lines,
771
- range: options.lines,
772
- window: options.window,
773
- caps: BOUNDED_EVIDENCE_CAPS,
774
- ...(options.exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase: options.exactNormalizedPhrase } : {}),
775
- });
776
- const lines = bounded.linesLabel;
777
- const why = bounded.truncatedByLineCap
778
- ? `${options.why} Bounded to ${lines} by the ${options.window} line cap; use retrieve lineRange or a wider expansion for more exact context.`
779
- : options.why;
780
- return {
781
- id: options.id,
782
- source: { kind: "repo", path: options.file.path },
783
- path: options.file.path,
784
- lines,
785
- excerpt: bounded.excerpt,
786
- why,
787
- window: options.window,
788
- expandable: options.expandable,
789
- };
790
- }
791
- function parseLineRange(lines) {
792
- const match = /^(\d+)(?:-(\d+))?$/.exec(lines);
793
- if (!match) {
794
- return null;
795
- }
796
- const start = Number(match[1]);
797
- const end = Number(match[2] ?? match[1]);
798
- if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
799
- return null;
800
- }
801
- return { start, end };
802
- }
803
- function expandLineRange(range, fileLineCount, expansion) {
804
- if (expansion === "full") {
805
- return { start: 1, end: fileLineCount };
806
- }
807
- const context = expansion === "lines_80" ? 80 : 30;
808
- return {
809
- start: Math.max(1, range.start - context),
810
- end: Math.min(fileLineCount, range.end + context),
811
- };
812
- }
813
- function windowForExpansion(expansion) {
814
- return expansion;
815
- }
816
- function excerptForLineRange(lines, range, window, exactNormalizedPhrase) {
817
- return buildBoundedExcerpt({
818
- lines,
819
- range,
820
- window,
821
- caps: BOUNDED_EVIDENCE_CAPS,
822
- ...(exactNormalizedPhrase !== undefined ? { exactNormalizedPhrase } : {}),
823
- }).excerpt;
824
- }
825
- function byteLength(text) {
826
- return Buffer.byteLength(text, "utf8");
827
- }
828
- function splitLines(text) {
829
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
830
- }
831
- function decisionId(...parts) {
832
- return `ffdec_${hash(parts.join("\0")).slice(0, 16)}`;
833
- }
834
- function evidenceIdFor(path, lines, query) {
835
- return `ev_${hash(`${path}\0${lines}\0${query}`).slice(0, 16)}`;
836
- }
837
- function hash(value) {
838
- return createHash("sha256").update(value).digest("hex");
839
- }
840
- function errorResult(preserve, reason) {
841
- return {
842
- toolStatus: "error",
843
- decisionId: decisionId("repo-error", reason),
844
- preserve,
845
- routing: {
846
- status: "failed",
847
- route: "retrieve",
848
- reason,
849
- },
850
- evidence: [],
851
- recovery: {
852
- how: "Use native read for a known whole file, or retry freeflow_retrieve with a supported repo query.",
853
- },
854
- };
855
- }