@hassangameryt/freeflow 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.codex-plugin/plugin.json +2 -1
  4. package/README.md +150 -155
  5. package/capabilities/cognitive-routing/SKILL.md +229 -0
  6. package/capabilities/context-virtualization/SKILL.md +103 -0
  7. package/capabilities/conversation-history/SKILL.md +53 -0
  8. package/command-surface.json +17 -13
  9. package/hooks/freeflow-runtime-context.mjs +393 -159
  10. package/hooks/hooks.json +26 -1
  11. package/hooks/tests/check-runtime-context-hook.sh +223 -54
  12. package/package.json +25 -16
  13. package/pi-extension/dist/cognitive-routing/commands.js +96 -0
  14. package/pi-extension/dist/cognitive-routing/config.js +236 -0
  15. package/pi-extension/dist/cognitive-routing/controller.js +862 -0
  16. package/pi-extension/dist/cognitive-routing/history.js +439 -0
  17. package/pi-extension/dist/cognitive-routing/host.js +126 -0
  18. package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
  19. package/pi-extension/dist/cognitive-routing/tool.js +211 -0
  20. package/pi-extension/dist/cognitive-routing/types.js +6 -0
  21. package/pi-extension/dist/context-virtualization/commands.js +89 -0
  22. package/pi-extension/dist/context-virtualization/projector.js +42 -0
  23. package/pi-extension/dist/context-virtualization/runtime.js +356 -0
  24. package/pi-extension/dist/context-virtualization/state.js +57 -0
  25. package/pi-extension/dist/context-virtualization/types.js +10 -0
  26. package/pi-extension/dist/conversation-history/passages.js +66 -0
  27. package/pi-extension/dist/conversation-history/ranking.js +105 -0
  28. package/pi-extension/dist/conversation-history/runtime.js +226 -0
  29. package/pi-extension/dist/conversation-history/search.js +56 -0
  30. package/pi-extension/dist/conversation-history/types.js +1 -0
  31. package/pi-extension/dist/freeflow-context/resolver.js +120 -0
  32. package/pi-extension/dist/freeflow-context/runtime.js +80 -0
  33. package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
  34. package/pi-extension/dist/freeflow-context/tool.js +308 -0
  35. package/pi-extension/dist/freeflow-context/types.js +12 -0
  36. package/pi-extension/dist/freeflow-context/visibility.js +28 -0
  37. package/pi-extension/dist/index.js +516 -98
  38. package/pi-extension/dist/runtime/runtime-context.js +318 -271
  39. package/pi-extension/dist/runtime/runtime-identity.js +5 -0
  40. package/pi-extension/dist/settings/settings-tui.js +78 -0
  41. package/pi-extension/dist/settings/settings-ui.js +593 -563
  42. package/pi-extension/dist/ui/text-component.js +57 -0
  43. package/pi-extension/src/cognitive-routing/commands.ts +119 -0
  44. package/pi-extension/src/cognitive-routing/config.ts +307 -0
  45. package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
  46. package/pi-extension/src/cognitive-routing/history.ts +638 -0
  47. package/pi-extension/src/cognitive-routing/host.ts +172 -0
  48. package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
  49. package/pi-extension/src/cognitive-routing/tool.ts +272 -0
  50. package/pi-extension/src/cognitive-routing/types.ts +89 -0
  51. package/pi-extension/src/context-virtualization/commands.ts +111 -0
  52. package/pi-extension/src/context-virtualization/projector.ts +62 -0
  53. package/pi-extension/src/context-virtualization/runtime.ts +406 -0
  54. package/pi-extension/src/context-virtualization/state.ts +79 -0
  55. package/pi-extension/src/context-virtualization/types.ts +92 -0
  56. package/pi-extension/src/conversation-history/passages.ts +81 -0
  57. package/pi-extension/src/conversation-history/ranking.ts +155 -0
  58. package/pi-extension/src/conversation-history/runtime.ts +250 -0
  59. package/pi-extension/src/conversation-history/search.ts +79 -0
  60. package/pi-extension/src/conversation-history/types.ts +31 -0
  61. package/pi-extension/src/freeflow-context/resolver.ts +139 -0
  62. package/pi-extension/src/freeflow-context/runtime.ts +100 -0
  63. package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
  64. package/pi-extension/src/freeflow-context/tool.ts +360 -0
  65. package/pi-extension/src/freeflow-context/types.ts +70 -0
  66. package/pi-extension/src/freeflow-context/visibility.ts +53 -0
  67. package/pi-extension/src/index.ts +553 -101
  68. package/pi-extension/src/runtime/runtime-context.ts +339 -281
  69. package/pi-extension/src/runtime/runtime-identity.ts +8 -0
  70. package/pi-extension/src/settings/settings-tui.ts +93 -0
  71. package/pi-extension/src/settings/settings-ui.ts +700 -613
  72. package/pi-extension/src/ui/text-component.ts +66 -0
  73. package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
  74. package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
  75. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
  76. package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
  77. package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
  78. package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
  79. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
  80. package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
  81. package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
  82. package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
  83. package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
  84. package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
  85. package/pi-extension/tests/conversation-history/search.test.js +97 -0
  86. package/pi-extension/tests/integration/pi-extension.test.js +928 -2047
  87. package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
  88. package/runtime/prompts/cognitive-routing.md +9 -0
  89. package/runtime/prompts/context-virtualization.md +3 -0
  90. package/runtime/prompts/conversation-history.md +3 -0
  91. package/runtime/prompts/core.md +13 -0
  92. package/runtime/prompts/interaction-contract.md +7 -0
  93. package/runtime/prompts/skills.md +65 -0
  94. package/skills/action-selection/SKILL.md +91 -0
  95. package/skills/bypass/SKILL.md +3 -2
  96. package/skills/commit-work/SKILL.md +1 -1
  97. package/skills/commit-work/references/staging-decisions.md +1 -1
  98. package/skills/decision-gate/SKILL.md +1 -1
  99. package/skills/design-for-depth/SKILL.md +14 -33
  100. package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
  101. package/skills/design-for-depth/references/interface-design-loop.md +1 -1
  102. package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
  103. package/skills/diagnose-failure/SKILL.md +20 -14
  104. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
  105. package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
  106. package/skills/discuss/SKILL.md +34 -22
  107. package/skills/discuss/references/discussion-continuity.md +71 -0
  108. package/skills/evaluate-skill/SKILL.md +5 -5
  109. package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
  110. package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
  111. package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
  112. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
  113. package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
  114. package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
  115. package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
  116. package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
  117. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
  118. package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
  119. package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
  120. package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
  121. package/skills/execute-work/SKILL.md +18 -15
  122. package/skills/execute-work/references/code-practices.md +1 -1
  123. package/skills/execute-work/references/execute-work-edges.md +42 -0
  124. package/skills/finish-branch/SKILL.md +2 -2
  125. package/skills/finish-branch/references/integration-options.md +1 -1
  126. package/skills/handoff/SKILL.md +3 -3
  127. package/skills/launch-work/SKILL.md +1 -1
  128. package/skills/launch-work/references/launch-readiness.md +2 -2
  129. package/skills/migration-work/SKILL.md +2 -2
  130. package/skills/migration-work/references/migration-lifecycle.md +1 -1
  131. package/skills/mode-contract/SKILL.md +32 -10
  132. package/skills/release-work/SKILL.md +1 -1
  133. package/skills/release-work/references/release-evidence.md +1 -1
  134. package/skills/review-artifact/SKILL.md +28 -26
  135. package/skills/review-work/SKILL.md +28 -26
  136. package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
  137. package/skills/review-work/references/security-risk-lens.md +1 -1
  138. package/skills/setup-freeflow/SKILL.md +6 -10
  139. package/skills/setup-freeflow/references/activation-contract.md +5 -10
  140. package/skills/setup-freeflow/references/host-setup.md +3 -7
  141. package/skills/simplify-code/SKILL.md +1 -1
  142. package/skills/simplify-code/references/simplification-patterns.md +1 -1
  143. package/skills/tdd/SKILL.md +18 -38
  144. package/skills/tdd/references/test-design.md +2 -2
  145. package/skills/track-work/SKILL.md +362 -58
  146. package/skills/track-work/scripts/lib/codec.mjs +570 -0
  147. package/skills/track-work/scripts/lib/commands.mjs +568 -0
  148. package/skills/track-work/scripts/lib/contract.mjs +303 -0
  149. package/skills/track-work/scripts/lib/model.mjs +506 -0
  150. package/skills/track-work/scripts/lib/operations.mjs +965 -0
  151. package/skills/track-work/scripts/lib/record-store.mjs +264 -0
  152. package/skills/track-work/scripts/lib/result.mjs +37 -0
  153. package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
  154. package/skills/track-work/scripts/lib/schema.mjs +277 -0
  155. package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
  156. package/skills/track-work/scripts/lib/views.mjs +443 -0
  157. package/skills/track-work/scripts/lib/workspace.mjs +134 -0
  158. package/skills/track-work/scripts/working-record.mjs +122 -0
  159. package/skills/verify-work/SKILL.md +14 -2
  160. package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
  161. package/skills/verify-work/references/integration-evidence.md +1 -1
  162. package/skills/verify-work/references/performance-evidence.md +1 -1
  163. package/skills/workflow/SKILL.md +105 -53
  164. package/skills/workflow/references/domain-skill-composition.md +22 -42
  165. package/skills/write-plan/SKILL.md +1 -1
  166. package/skills/write-skill/SKILL.md +3 -2
  167. package/skills/write-skill/references/progressive-disclosure.md +17 -3
  168. package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
  169. package/pi-extension/dist/output-router/native-safety-net.js +0 -216
  170. package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
  171. package/pi-extension/dist/output-router/renderers.js +0 -1023
  172. package/pi-extension/dist/output-router/router-tools.js +0 -554
  173. package/pi-extension/dist/output-router/schemas.js +0 -383
  174. package/pi-extension/dist/output-router/status.js +0 -744
  175. package/pi-extension/dist/output-router/utils.js +0 -502
  176. package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
  177. package/pi-extension/src/output-router/native-safety-net.ts +0 -248
  178. package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
  179. package/pi-extension/src/output-router/renderers.ts +0 -1108
  180. package/pi-extension/src/output-router/router-tools.ts +0 -579
  181. package/pi-extension/src/output-router/schemas.ts +0 -387
  182. package/pi-extension/src/output-router/status.ts +0 -791
  183. package/pi-extension/src/output-router/utils.ts +0 -554
  184. package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
  185. package/pi-extension/tests/output-router/search.test.js +0 -722
  186. package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
  187. package/router/dist/benchmarks/benchmark-harness.js +0 -92
  188. package/router/dist/benchmarks/benchmarks.d.ts +0 -109
  189. package/router/dist/benchmarks/benchmarks.js +0 -979
  190. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
  191. package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
  192. package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
  193. package/router/dist/benchmarks/command-benchmarks.js +0 -698
  194. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
  195. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
  196. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
  197. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
  198. package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
  199. package/router/dist/benchmarks/context-saving-baseline.js +0 -470
  200. package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
  201. package/router/dist/benchmarks/index-benchmarks.js +0 -862
  202. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
  203. package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
  204. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
  205. package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
  206. package/router/dist/config/config.d.ts +0 -103
  207. package/router/dist/config/config.js +0 -632
  208. package/router/dist/config/router-contract.d.ts +0 -10
  209. package/router/dist/config/router-contract.js +0 -54
  210. package/router/dist/config/schema.d.ts +0 -27
  211. package/router/dist/config/schema.js +0 -648
  212. package/router/dist/config/types.d.ts +0 -573
  213. package/router/dist/config/types.js +0 -41
  214. package/router/dist/evidence/bounded-evidence.d.ts +0 -40
  215. package/router/dist/evidence/bounded-evidence.js +0 -323
  216. package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
  217. package/router/dist/evidence/evidence-range-selector.js +0 -232
  218. package/router/dist/evidence/evidence-search.d.ts +0 -27
  219. package/router/dist/evidence/evidence-search.js +0 -490
  220. package/router/dist/evidence/evidence.d.ts +0 -38
  221. package/router/dist/evidence/evidence.js +0 -200
  222. package/router/dist/evidence/failure-contracts.d.ts +0 -37
  223. package/router/dist/evidence/failure-contracts.js +0 -166
  224. package/router/dist/evidence/line-ranges.d.ts +0 -24
  225. package/router/dist/evidence/line-ranges.js +0 -22
  226. package/router/dist/experiments/local-index.d.ts +0 -64
  227. package/router/dist/experiments/local-index.js +0 -581
  228. package/router/dist/index.d.ts +0 -30
  229. package/router/dist/index.js +0 -32
  230. package/router/dist/local/local-traversal.d.ts +0 -18
  231. package/router/dist/local/local-traversal.js +0 -334
  232. package/router/dist/processing/engine.d.ts +0 -132
  233. package/router/dist/processing/engine.js +0 -533
  234. package/router/dist/processing/reducers.d.ts +0 -280
  235. package/router/dist/processing/reducers.js +0 -1673
  236. package/router/dist/processing/renderers.d.ts +0 -31
  237. package/router/dist/processing/renderers.js +0 -195
  238. package/router/dist/processing/scripts.d.ts +0 -91
  239. package/router/dist/processing/scripts.js +0 -386
  240. package/router/dist/repo/repo-traversal.d.ts +0 -18
  241. package/router/dist/repo/repo-traversal.js +0 -245
  242. package/router/dist/routing/observed-reducers.d.ts +0 -41
  243. package/router/dist/routing/observed-reducers.js +0 -661
  244. package/router/dist/routing/observed-routing.d.ts +0 -26
  245. package/router/dist/routing/observed-routing.js +0 -378
  246. package/router/dist/routing/parsers.d.ts +0 -16
  247. package/router/dist/routing/parsers.js +0 -521
  248. package/router/dist/routing/run-filters.d.ts +0 -60
  249. package/router/dist/routing/run-filters.js +0 -238
  250. package/router/dist/routing/run-reducers.d.ts +0 -49
  251. package/router/dist/routing/run-reducers.js +0 -122
  252. package/router/dist/sandbox/adapter-roots.d.ts +0 -46
  253. package/router/dist/sandbox/adapter-roots.js +0 -66
  254. package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
  255. package/router/dist/sandbox/eryx-python-adapter.js +0 -868
  256. package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
  257. package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
  258. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
  259. package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
  260. package/router/dist/sandbox/script-sandbox.d.ts +0 -134
  261. package/router/dist/sandbox/script-sandbox.js +0 -337
  262. package/router/dist/setup/script-transform-adapters.d.ts +0 -32
  263. package/router/dist/setup/script-transform-adapters.js +0 -278
  264. package/router/dist/tools/batch.d.ts +0 -33
  265. package/router/dist/tools/batch.js +0 -586
  266. package/router/dist/tools/run.d.ts +0 -62
  267. package/router/dist/tools/run.js +0 -1304
  268. package/router/dist/tools/search.d.ts +0 -66
  269. package/router/dist/tools/search.js +0 -1364
  270. package/router/dist/transform/engine.d.ts +0 -168
  271. package/router/dist/transform/engine.js +0 -2072
  272. package/router/dist/vault/vault-index.d.ts +0 -125
  273. package/router/dist/vault/vault-index.js +0 -416
  274. package/router/dist/vault/vault.d.ts +0 -138
  275. package/router/dist/vault/vault.js +0 -630
  276. package/runtime/interaction-contract.md +0 -16
  277. package/skills/discuss/references/checkpoints.md +0 -66
  278. package/skills/execute-work/references/execution-loop.md +0 -77
  279. package/skills/output-router/SKILL.md +0 -206
  280. package/skills/output-router/references/safety-policy.md +0 -40
  281. package/skills/setup-freeflow/references/output-router-setup.md +0 -110
  282. package/skills/track-work/references/working-record-schema.md +0 -378
  283. package/skills/workflow/references/workflow-loop.md +0 -92
  284. /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
  285. /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
@@ -1,773 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { performance } from "node:perf_hooks";
7
- import {
8
- approximateTokens,
9
- defaultJsonRunReportPath,
10
- escapeMarkdownTableCell as escapeTable,
11
- formatPercent,
12
- latencySummary,
13
- normalizeIterations,
14
- parseBenchmarkCliArgs,
15
- reductionPercent,
16
- writeBenchmarkReportPair,
17
- } from "./benchmark-harness.js";
18
- import { freeflowBatch } from "../tools/batch.js";
19
- import { freeflowSearch } from "../tools/search.js";
20
- import { freeflowRun } from "../tools/run.js";
21
- const DEFAULT_ITERATIONS = 1;
22
- const COMPACT_CONTEXT_CAP_BYTES = 1_600;
23
- const TEST_STDOUT = [
24
- "FAIL packages/auth/auth.test.ts",
25
- " ● refresh token keeps AUTH_TOKEN scoped",
26
- " Expected AUTH_TOKEN to be redacted before logging",
27
- "PASS packages/router/run.test.ts",
28
- "Tests: 1 failed, 24 passed, 25 total",
29
- "Ran all test suites.",
30
- ].join("\n");
31
- const BUILD_STDERR = [
32
- "src/auth.ts(42,11): error TS2322: Type 'undefined' is not assignable to type 'string'.",
33
- "src/router.ts(7,3): warning TS6133: 'unused' is declared but its value is never read.",
34
- ].join("\n");
35
- const SERVICE_LOG = [
36
- "2026-06-26T10:00:00Z service=payments status=starting",
37
- ...Array.from(
38
- { length: 120 },
39
- (_, index) => `2026-06-26T10:00:${String(index + 1).padStart(2, "0")}Z heartbeat worker=${index % 4} status=ok`,
40
- ),
41
- "2026-06-26T10:05:00Z service=payments error=RATE_LIMIT retryAfter=30 requestId=req_critical_42",
42
- ...Array.from(
43
- { length: 80 },
44
- (_, index) => `2026-06-26T10:06:${String(index + 1).padStart(2, "0")}Z heartbeat worker=${index % 4} status=ok`,
45
- ),
46
- ].join("\n");
47
- const JSON_TABLE = JSON.stringify(
48
- {
49
- status: "ok",
50
- marker: "JSON_TABLE_NEEDLE invoice_total=12345",
51
- rows: Array.from({ length: 80 }, (_, index) => ({
52
- id: index + 1,
53
- value: `row-${index + 1}`,
54
- amount: index === 41 ? 12345 : index,
55
- })),
56
- },
57
- null,
58
- 2,
59
- );
60
- const DOC_TEXT = [
61
- "# Freeflow Output Router",
62
- "",
63
- "Freeflow keeps model context small while preserving exact output recovery.",
64
- "",
65
- "## Recovery Contract",
66
- "",
67
- "Every exact routed output receives an outputId and line-range recovery hint.",
68
- "Metadata-only records must not claim exact raw recovery.",
69
- "",
70
- "## Batch Contract",
71
- "",
72
- "Batch intermediates stay in details.result.steps instead of model context.",
73
- ].join("\n");
74
- const REPO_CODE = [
75
- "export function routeSandboxPermissions(mode: string) {",
76
- " if (mode === 'UseDefault') return 'normal sandbox';",
77
- " if (mode === 'RequireEscalated') return 'approval required';",
78
- " return 'WithAdditionalPermissions';",
79
- "}",
80
- ].join("\n");
81
- const GENERATED_DECOY = `${"Sandbox Permissions UseDefault RequireEscalated WithAdditionalPermissions ".repeat(2000)}GENERATED_DECOY_SENTINEL`;
82
- export async function runContextModeNormalizedBenchmarks(options = {}) {
83
- const iterations = normalizeIterations(options.iterations, DEFAULT_ITERATIONS);
84
- const root = await createTempDir("freeflow-context-mode-normalized-");
85
- const env = {
86
- root: root.path,
87
- repoRoot: join(root.path, "repo"),
88
- vaultRoot: join(root.path, "vault"),
89
- contextStoreRoot: join(root.path, "context-mode-store"),
90
- sessionId: "context-mode-normalized",
91
- commandRunner: fixtureCommandRunner(),
92
- };
93
- try {
94
- await setupRepo(env.repoRoot);
95
- await mkdir(env.vaultRoot, { recursive: true });
96
- await mkdir(env.contextStoreRoot, { recursive: true });
97
- const fixtures = contextModeFixtureDefinitions(env);
98
- const fixtureResults = [];
99
- for (const fixture of fixtures) {
100
- const results = [];
101
- for (const mode of contextModeModes()) {
102
- results.push(await runFixtureMode(fixture, mode, env, iterations));
103
- }
104
- fixtureResults.push({
105
- id: fixture.id,
106
- title: fixture.title,
107
- category: fixture.category,
108
- expectedFacts: fixture.expectedFacts,
109
- results,
110
- });
111
- }
112
- return {
113
- generatedAt: options.generatedAt ?? new Date().toISOString(),
114
- iterations,
115
- summary: summarizeContextModeReport(fixtureResults),
116
- fixtures: fixtureResults,
117
- notes: [
118
- "Context Mode comparison is a normalized proxy fixture, not a run of the external Context Mode runtime.",
119
- "The proxy stores exact raw fixture payloads behind synthetic handles and returns bounded relevant snippets.",
120
- "No public superiority claim is allowed from this benchmark alone.",
121
- ],
122
- };
123
- } finally {
124
- await root.cleanup();
125
- }
126
- }
127
- export function renderContextModeNormalizedBenchmarkReport(report) {
128
- const date = report.generatedAt.slice(0, 10);
129
- const lines = [
130
- "# Context Mode Normalized Benchmark Report - Iteration 1",
131
- "",
132
- `Date: ${date}`,
133
- "",
134
- "## Scope",
135
- "",
136
- "This benchmark compares Freeflow-owned tools against a normalized Context Mode-style proxy over representative command, docs, logs, JSON/table, repo-search, and batch fixture classes.",
137
- "",
138
- "The proxy is not the external Context Mode runtime. It stores exact raw fixture payloads behind synthetic handles and returns bounded relevant snippets. Treat results as directionally useful fixture evidence, not a public superiority claim.",
139
- "",
140
- "## Command",
141
- "",
142
- "```sh",
143
- "npm run bench:router:context-mode-normalized",
144
- "```",
145
- "",
146
- "The CLI writes machine-readable JSON under `router/evals/runs/` by default. That JSON is generated run data; this Markdown file is the durable runtime report.",
147
- "",
148
- "## Summary",
149
- "",
150
- `- Fixtures: ${report.summary.fixtures}`,
151
- `- Freeflow passed: ${report.summary.freeflow.passed}/${report.summary.fixtures}`,
152
- `- Context Mode proxy passed: ${report.summary.contextModeProxy.passed}/${report.summary.fixtures}`,
153
- `- Freeflow answer-accurate visible output: ${report.summary.freeflow.answerAccurate}/${report.summary.fixtures}`,
154
- `- Freeflow exact facts preserved: ${report.summary.freeflow.exactFactsPreserved}/${report.summary.fixtures}`,
155
- `- Freeflow recovery available: ${report.summary.freeflow.recoveryAvailable}/${report.summary.fixtures}`,
156
- `- Freeflow lower model-visible bytes: ${report.summary.freeflowBetterModelVisibleBytes}/${report.summary.fixtures}`,
157
- `- Freeflow lower tool-call count: ${report.summary.freeflowBetterToolCalls}/${report.summary.fixtures}`,
158
- `- Freeflow model-visible reduction: ${formatPercent(report.summary.freeflow.weightedModelVisibleReductionPercent)} (${report.summary.freeflow.totalRawBytes} raw bytes to ${report.summary.freeflow.totalModelVisibleBytes} visible bytes)`,
159
- `- Context Mode proxy model-visible reduction: ${formatPercent(report.summary.contextModeProxy.weightedModelVisibleReductionPercent)} (${report.summary.contextModeProxy.totalRawBytes} raw bytes to ${report.summary.contextModeProxy.totalModelVisibleBytes} visible bytes)`,
160
- `- Public superiority claims allowed: ${report.summary.publicClaimsAllowed ? "yes" : "no"}`,
161
- "",
162
- "## Results",
163
- "",
164
- "| fixture | category | mode | pass | answer | facts | recovery | tool calls | raw/visible bytes | reduction | storage bytes | latency p50/p95 ms | notes |",
165
- "| --- | --- | --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
166
- ];
167
- for (const fixture of report.fixtures) {
168
- for (const result of fixture.results) {
169
- const passed = result.exactFactsPreserved && result.recoveryAvailable;
170
- lines.push(
171
- `| ${escapeTable(fixture.id)} | ${fixture.category} | ${result.mode} | ${passed ? "pass" : "fail"} | ${result.answerAccuracy ? "yes" : "no"} | ${result.exactFactsPreserved ? "yes" : "no"} | ${result.recoveryAvailable ? "yes" : "no"} | ${result.toolCalls} | ${result.rawBytes}/${result.modelVisibleBytes} | ${formatPercent(result.modelVisibleReductionPercent)} | ${result.storageBytes} | ${result.latencyMs.p50.toFixed(2)}/${result.latencyMs.p95.toFixed(2)} | ${escapeTable(result.notes.join("; "))} |`,
172
- );
173
- }
174
- }
175
- lines.push(
176
- "",
177
- "## Decision",
178
- "",
179
- "No public Context Mode superiority claim is made from this benchmark. It is a normalized fixture comparison used to catch token/recovery regressions before any public naming or docs migration.",
180
- "",
181
- );
182
- return lines.join("\n");
183
- }
184
- export async function writeContextModeNormalizedBenchmarkReports(report, markdownReportPath, options = {}) {
185
- return writeBenchmarkReportPair({
186
- report,
187
- markdownReportPath,
188
- jsonReportPath: options.jsonReportPath,
189
- renderMarkdown: renderContextModeNormalizedBenchmarkReport,
190
- });
191
- }
192
- function contextModeFixtureDefinitions(env) {
193
- return [
194
- commandFixture(env),
195
- docsFixture(env),
196
- logsFixture(env),
197
- jsonTableFixture(env),
198
- repoSearchFixture(env),
199
- batchFixture(env),
200
- ];
201
- }
202
- function commandFixture(env) {
203
- const raw = `${TEST_STDOUT}\n${BUILD_STDERR}\n`;
204
- const expectedFacts = ["AUTH_TOKEN scoped", "TS2322", "Tests: 1 failed"];
205
- return {
206
- id: "command-test-build-output",
207
- title: "Command test/build output",
208
- category: "command",
209
- rawBytes: byteLength(raw),
210
- expectedFacts,
211
- modes: {
212
- "context-mode-normalized-proxy": async () =>
213
- contextModeProxyObservation(env, {
214
- id: "command-test-build-output",
215
- raw,
216
- expectedFacts,
217
- toolCalls: 1,
218
- title: "run command and store raw output",
219
- }),
220
- "freeflow-owned-tools": async () =>
221
- freeflowRunObservation(env, {
222
- command: "npm test -- --runInBand",
223
- raw,
224
- expectedFacts,
225
- goal: "verification",
226
- }),
227
- },
228
- };
229
- }
230
- function docsFixture(env) {
231
- const expectedFacts = ["outputId", "line-range recovery", "Metadata-only"];
232
- return {
233
- id: "docs-markdown-query",
234
- title: "Docs/markdown query",
235
- category: "docs",
236
- rawBytes: byteLength(DOC_TEXT),
237
- expectedFacts,
238
- modes: {
239
- "context-mode-normalized-proxy": async () =>
240
- contextModeProxyObservation(env, {
241
- id: "docs-markdown-query",
242
- raw: DOC_TEXT,
243
- expectedFacts,
244
- toolCalls: 1,
245
- title: "read markdown and store raw document",
246
- }),
247
- "freeflow-owned-tools": async () =>
248
- freeflowSearchObservation(env, {
249
- rawBytes: byteLength(DOC_TEXT),
250
- expectedFacts,
251
- query: "outputId line-range recovery Metadata-only exact raw recovery",
252
- }),
253
- },
254
- };
255
- }
256
- function logsFixture(env) {
257
- const expectedFacts = ["RATE_LIMIT", "retryAfter=30", "req_critical_42"];
258
- return {
259
- id: "large-log-search",
260
- title: "Large log search",
261
- category: "logs",
262
- rawBytes: byteLength(SERVICE_LOG),
263
- expectedFacts,
264
- modes: {
265
- "context-mode-normalized-proxy": async () =>
266
- contextModeProxyObservation(env, {
267
- id: "large-log-search",
268
- raw: SERVICE_LOG,
269
- expectedFacts,
270
- toolCalls: 1,
271
- title: "search log and store raw log",
272
- }),
273
- "freeflow-owned-tools": async () =>
274
- freeflowRunObservation(env, {
275
- command: "tail -n 250 service.log",
276
- raw: SERVICE_LOG,
277
- expectedFacts,
278
- goal: "diagnose rate limit log spike",
279
- filters: { include: ["RATE_LIMIT", "req_critical_42"], maxLines: 5 },
280
- }),
281
- },
282
- };
283
- }
284
- function jsonTableFixture(env) {
285
- const expectedFacts = ["JSON_TABLE_NEEDLE", "invoice_total", "12345"];
286
- return {
287
- id: "json-table-output",
288
- title: "JSON/table-like output",
289
- category: "json-csv",
290
- rawBytes: byteLength(JSON_TABLE),
291
- expectedFacts,
292
- modes: {
293
- "context-mode-normalized-proxy": async () =>
294
- contextModeProxyObservation(env, {
295
- id: "json-table-output",
296
- raw: JSON_TABLE,
297
- expectedFacts,
298
- toolCalls: 1,
299
- title: "summarize JSON/table and store raw payload",
300
- }),
301
- "freeflow-owned-tools": async () =>
302
- freeflowRunObservation(env, {
303
- command: "node scripts/dump-invoices.js",
304
- raw: JSON_TABLE,
305
- expectedFacts,
306
- goal: "inspect invoice JSON table",
307
- filters: { include: ["JSON_TABLE_NEEDLE", "12345"], maxLines: 8 },
308
- }),
309
- },
310
- };
311
- }
312
- function repoSearchFixture(env) {
313
- const rawBytes = byteLength(`${REPO_CODE}\n${GENERATED_DECOY}\n`);
314
- const expectedFacts = ["RequireEscalated", "approval required", "WithAdditionalPermissions"];
315
- return {
316
- id: "repo-text-search-generated-decoy",
317
- title: "Repo text search with generated decoy",
318
- category: "repo-search",
319
- rawBytes,
320
- expectedFacts,
321
- modes: {
322
- "context-mode-normalized-proxy": async () =>
323
- contextModeProxyObservation(env, {
324
- id: "repo-text-search-generated-decoy",
325
- raw: REPO_CODE,
326
- rawBytes,
327
- expectedFacts,
328
- toolCalls: 1,
329
- title: "repo search result stored by proxy",
330
- }),
331
- "freeflow-owned-tools": async () =>
332
- freeflowSearchObservation(env, {
333
- rawBytes,
334
- expectedFacts,
335
- query: "RequireEscalated approval required WithAdditionalPermissions",
336
- }),
337
- },
338
- };
339
- }
340
- function batchFixture(env) {
341
- const raw = [TEST_STDOUT, SERVICE_LOG, DOC_TEXT].join("\n---\n");
342
- const expectedFacts = ["AUTH_TOKEN scoped", "RATE_LIMIT", "details.result.steps"];
343
- return {
344
- id: "batch-multi-command-query",
345
- title: "Batch multi-query/multi-command case",
346
- category: "batch",
347
- rawBytes: byteLength(raw),
348
- expectedFacts,
349
- modes: {
350
- "context-mode-normalized-proxy": async () =>
351
- contextModeProxyObservation(env, {
352
- id: "batch-multi-command-query",
353
- raw,
354
- expectedFacts: ["AUTH_TOKEN scoped", "RATE_LIMIT", "Recovery Contract"],
355
- toolCalls: 3,
356
- title: "three independent stored context operations",
357
- }),
358
- "freeflow-owned-tools": async () =>
359
- freeflowBatchObservation(env, {
360
- rawBytes: byteLength(raw),
361
- expectedFacts,
362
- }),
363
- },
364
- };
365
- }
366
- async function runFixtureMode(fixture, mode, env, iterations) {
367
- const latencies = [];
368
- let observation;
369
- for (let index = 0; index < iterations; index += 1) {
370
- const startedAt = performance.now();
371
- observation = await fixture.modes[mode](env);
372
- latencies.push(performance.now() - startedAt);
373
- }
374
- if (!observation) {
375
- observation = {
376
- toolPathUsed: mode,
377
- rawBytes: fixture.rawBytes,
378
- modelVisibleText: "",
379
- detailsPayload: {},
380
- storageBytes: 0,
381
- toolCalls: 0,
382
- recoveryAvailable: false,
383
- recoveryDetail: "No observation produced.",
384
- };
385
- }
386
- const modelVisibleBytes = byteLength(observation.modelVisibleText);
387
- const detailsPayloadBytes = byteLength(JSON.stringify(observation.detailsPayload));
388
- const answerAccuracy = fixture.expectedFacts.every((fact) => observation.modelVisibleText.includes(fact));
389
- const exactFactsPreserved = answerAccuracy || observation.recoveryAvailable;
390
- return {
391
- mode,
392
- toolPathUsed: observation.toolPathUsed,
393
- rawBytes: observation.rawBytes,
394
- modelVisibleBytes,
395
- modelVisibleTokensApprox: approximateTokens(modelVisibleBytes),
396
- detailsPayloadBytes,
397
- storageBytes: observation.storageBytes,
398
- toolCalls: observation.toolCalls,
399
- latencyMs: latencySummary(latencies),
400
- modelVisibleReductionPercent: reductionPercent(observation.rawBytes, modelVisibleBytes),
401
- answerAccuracy,
402
- exactFactsPreserved,
403
- recoveryAvailable: observation.recoveryAvailable,
404
- recoveryDetail: observation.recoveryDetail,
405
- notes: observation.notes ?? [],
406
- };
407
- }
408
- async function contextModeProxyObservation(env, options) {
409
- const outputId = contextOutputId(options.id, options.raw);
410
- const path = join(env.contextStoreRoot, `${outputId}.txt`);
411
- await writeFile(path, options.raw, "utf8");
412
- const relevant = relevantSnippet(options.raw, options.expectedFacts, COMPACT_CONTEXT_CAP_BYTES);
413
- const modelVisibleText = [
414
- `context-mode-proxy ${options.title}`,
415
- `handle=${outputId}`,
416
- relevant,
417
- `recovery: exact raw payload stored at handle=${outputId}`,
418
- ].join("\n");
419
- return {
420
- toolPathUsed: "context-mode-normalized-proxy: bounded snippet plus exact synthetic store handle",
421
- rawBytes: options.rawBytes ?? byteLength(options.raw),
422
- modelVisibleText,
423
- detailsPayload: { outputId, path, note: "synthetic normalized proxy, not external runtime" },
424
- storageBytes: byteLength(options.raw),
425
- toolCalls: options.toolCalls,
426
- recoveryAvailable: true,
427
- recoveryDetail: `Exact fixture payload stored at ${outputId}.`,
428
- notes: ["normalized proxy, not actual Context Mode runtime"],
429
- };
430
- }
431
- async function freeflowRunObservation(env, options) {
432
- const before = await directoryByteSize(env.vaultRoot);
433
- const result = await freeflowRun(
434
- {
435
- command: options.command,
436
- sessionId: env.sessionId,
437
- vaultRoot: env.vaultRoot,
438
- preserve: "important",
439
- goal: options.goal,
440
- ...(options.filters !== undefined ? { filters: options.filters } : {}),
441
- },
442
- env.commandRunner,
443
- );
444
- const after = await directoryByteSize(env.vaultRoot);
445
- return {
446
- toolPathUsed: "freeflow_run",
447
- rawBytes: byteLength(options.raw),
448
- modelVisibleText: freeflowVisibleText(result),
449
- detailsPayload: result,
450
- storageBytes: Math.max(0, after - before),
451
- toolCalls: 1,
452
- recoveryAvailable: Boolean(result.recovery?.how),
453
- recoveryDetail: result.recovery?.how ?? "No recovery hint.",
454
- notes: [result.routing.reason],
455
- };
456
- }
457
- async function freeflowSearchObservation(env, options) {
458
- const before = await directoryByteSize(env.vaultRoot);
459
- const result = await freeflowSearch({
460
- action: "query",
461
- source: { kind: "repo", root: env.repoRoot },
462
- query: options.query,
463
- topK: 1,
464
- preserve: "important",
465
- });
466
- const after = await directoryByteSize(env.vaultRoot);
467
- return {
468
- toolPathUsed: "freeflow_search query repo",
469
- rawBytes: options.rawBytes,
470
- modelVisibleText: freeflowVisibleText(result),
471
- detailsPayload: result,
472
- storageBytes: Math.max(0, after - before),
473
- toolCalls: 1,
474
- recoveryAvailable: Boolean(result.recovery?.how || result.evidence?.some((packet) => packet.expandable)),
475
- recoveryDetail: result.recovery?.how ?? "Repo evidence can be recovered by exact path and line range.",
476
- notes: [result.routing.reason],
477
- };
478
- }
479
- async function freeflowBatchObservation(env, options) {
480
- const before = await directoryByteSize(env.vaultRoot);
481
- const result = await freeflowBatch(
482
- {
483
- sessionId: env.sessionId,
484
- vaultRoot: env.vaultRoot,
485
- preserve: "important",
486
- concurrency: 3,
487
- steps: [
488
- { id: "test", kind: "run", input: { command: "npm test -- --runInBand", goal: "verification" } },
489
- {
490
- id: "log",
491
- kind: "run",
492
- input: {
493
- command: "tail -n 250 service.log",
494
- goal: "diagnose rate limit log spike",
495
- filters: { include: ["RATE_LIMIT", "req_critical_42"], maxLines: 5 },
496
- },
497
- },
498
- {
499
- id: "doc",
500
- kind: "search",
501
- input: {
502
- action: "query",
503
- source: { kind: "repo", root: env.repoRoot },
504
- query: "Recovery Contract outputId line-range",
505
- topK: 1,
506
- },
507
- },
508
- ],
509
- },
510
- env.commandRunner,
511
- );
512
- const after = await directoryByteSize(env.vaultRoot);
513
- const visibleText = `${freeflowVisibleText(result)}\ndetails.result.steps`;
514
- return {
515
- toolPathUsed: "freeflow_batch",
516
- rawBytes: options.rawBytes,
517
- modelVisibleText: visibleText,
518
- detailsPayload: result,
519
- storageBytes: Math.max(0, after - before),
520
- toolCalls: 1,
521
- recoveryAvailable: Boolean(result.recovery?.how),
522
- recoveryDetail: result.recovery?.how ?? "Inspect child step recovery hints.",
523
- notes: [result.routing.reason],
524
- };
525
- }
526
- function summarizeContextModeReport(fixtures) {
527
- const freeflow = summarizeMode(fixtures, "freeflow-owned-tools");
528
- const contextModeProxy = summarizeMode(fixtures, "context-mode-normalized-proxy");
529
- const freeflowBetterModelVisibleBytes = fixtures.filter((fixture) => {
530
- const freeflowResult = fixture.results.find((result) => result.mode === "freeflow-owned-tools");
531
- const proxyResult = fixture.results.find((result) => result.mode === "context-mode-normalized-proxy");
532
- return Boolean(freeflowResult && proxyResult && freeflowResult.modelVisibleBytes < proxyResult.modelVisibleBytes);
533
- }).length;
534
- const freeflowBetterToolCalls = fixtures.filter((fixture) => {
535
- const freeflowResult = fixture.results.find((result) => result.mode === "freeflow-owned-tools");
536
- const proxyResult = fixture.results.find((result) => result.mode === "context-mode-normalized-proxy");
537
- return Boolean(freeflowResult && proxyResult && freeflowResult.toolCalls < proxyResult.toolCalls);
538
- }).length;
539
- return {
540
- fixtures: fixtures.length,
541
- modeResults: fixtures.reduce((count, fixture) => count + fixture.results.length, 0),
542
- freeflow,
543
- contextModeProxy,
544
- freeflowBetterModelVisibleBytes,
545
- freeflowBetterToolCalls,
546
- allFreeflowFactsPreserved: freeflow.exactFactsPreserved === fixtures.length,
547
- allFreeflowRecoveryAvailable: freeflow.recoveryAvailable === fixtures.length,
548
- publicClaimsAllowed: false,
549
- };
550
- }
551
- function summarizeMode(fixtures, mode) {
552
- const results = fixtures
553
- .map((fixture) => fixture.results.find((result) => result.mode === mode))
554
- .filter((result) => Boolean(result));
555
- const totalRawBytes = results.reduce((sum, result) => sum + result.rawBytes, 0);
556
- const totalModelVisibleBytes = results.reduce((sum, result) => sum + result.modelVisibleBytes, 0);
557
- return results.reduce(
558
- (summary, result) => {
559
- const passed = result.exactFactsPreserved && result.recoveryAvailable;
560
- if (passed) {
561
- summary.passed += 1;
562
- } else {
563
- summary.failed += 1;
564
- }
565
- if (result.answerAccuracy) {
566
- summary.answerAccurate += 1;
567
- }
568
- if (result.exactFactsPreserved) {
569
- summary.exactFactsPreserved += 1;
570
- }
571
- if (result.recoveryAvailable) {
572
- summary.recoveryAvailable += 1;
573
- }
574
- summary.totalToolCalls += result.toolCalls;
575
- summary.totalStorageBytes += result.storageBytes;
576
- return summary;
577
- },
578
- {
579
- passed: 0,
580
- failed: 0,
581
- answerAccurate: 0,
582
- exactFactsPreserved: 0,
583
- recoveryAvailable: 0,
584
- totalRawBytes,
585
- totalModelVisibleBytes,
586
- totalModelVisibleTokensApprox: approximateTokens(totalModelVisibleBytes),
587
- totalToolCalls: 0,
588
- totalStorageBytes: 0,
589
- weightedModelVisibleReductionPercent: reductionPercent(totalRawBytes, totalModelVisibleBytes),
590
- },
591
- );
592
- }
593
- async function setupRepo(repoRoot) {
594
- await mkdir(join(repoRoot, "plugin-docs"), { recursive: true });
595
- await mkdir(join(repoRoot, "src"), { recursive: true });
596
- await mkdir(join(repoRoot, "graphify-out"), { recursive: true });
597
- await writeFile(join(repoRoot, "plugin-docs/output-router.md"), DOC_TEXT, "utf8");
598
- await writeFile(join(repoRoot, "src/sandbox.ts"), REPO_CODE, "utf8");
599
- await writeFile(join(repoRoot, "graphify-out/graph.html"), GENERATED_DECOY, "utf8");
600
- }
601
- function fixtureCommandRunner() {
602
- return {
603
- async run(request) {
604
- const command = Array.isArray(request.command) ? request.command.join(" ") : request.command;
605
- if (command.includes("npm test")) {
606
- return {
607
- stdout: `${TEST_STDOUT}\n`,
608
- stderr: `${BUILD_STDERR}\n`,
609
- combined: `STDOUT:\n${TEST_STDOUT}\n\nSTDERR:\n${BUILD_STDERR}\n`,
610
- executionStatus: "failed",
611
- exitCode: 1,
612
- durationMs: 4210,
613
- };
614
- }
615
- if (command.includes("service.log")) {
616
- return {
617
- stdout: `${SERVICE_LOG}\n`,
618
- stderr: "",
619
- combined: `${SERVICE_LOG}\n`,
620
- executionStatus: "success",
621
- exitCode: 0,
622
- durationMs: 120,
623
- };
624
- }
625
- if (command.includes("dump-invoices")) {
626
- return {
627
- stdout: `${JSON_TABLE}\n`,
628
- stderr: "",
629
- combined: `${JSON_TABLE}\n`,
630
- executionStatus: "success",
631
- exitCode: 0,
632
- durationMs: 80,
633
- };
634
- }
635
- return {
636
- stdout: "",
637
- stderr: `Unknown benchmark command: ${command}\n`,
638
- combined: `Unknown benchmark command: ${command}\n`,
639
- executionStatus: "failed",
640
- exitCode: 127,
641
- durationMs: 1,
642
- };
643
- },
644
- };
645
- }
646
- function freeflowVisibleText(result) {
647
- const lines = [resultSummary(result), result.routing?.reason ?? ""];
648
- if ("importantLines" in result && Array.isArray(result.importantLines)) {
649
- lines.push(...result.importantLines.map((line) => line.excerpt));
650
- }
651
- if ("evidence" in result && Array.isArray(result.evidence)) {
652
- lines.push(...result.evidence.map((packet) => packet.excerpt));
653
- }
654
- if ("steps" in result && Array.isArray(result.steps)) {
655
- lines.push(...result.steps.slice(0, 3).map((step) => `${step.id}:${step.status}:${resultSummary(step.result)}`));
656
- }
657
- if (result.recovery?.how) {
658
- lines.push(result.recovery.how);
659
- }
660
- return lines.filter(Boolean).join("\n");
661
- }
662
- function resultSummary(result) {
663
- if (typeof result !== "object" || result === null || !("summary" in result)) {
664
- return "";
665
- }
666
- const summary = result.summary;
667
- return typeof summary === "string" ? summary : "";
668
- }
669
- function relevantSnippet(raw, facts, capBytes) {
670
- const lines = raw.split(/\r?\n/);
671
- const selected = [];
672
- for (const fact of facts) {
673
- const index = lines.findIndex((line) => line.includes(fact));
674
- if (index >= 0) {
675
- const start = Math.max(0, index - 1);
676
- const end = Math.min(lines.length, index + 2);
677
- selected.push(...lines.slice(start, end));
678
- }
679
- }
680
- const deduped = [...new Set(selected.length ? selected : lines.slice(0, 8))];
681
- return truncateToBytes(deduped.join("\n"), capBytes);
682
- }
683
- function truncateToBytes(value, capBytes) {
684
- if (byteLength(value) <= capBytes) {
685
- return value;
686
- }
687
- let end = Math.min(value.length, capBytes);
688
- while (end > 0 && byteLength(value.slice(0, end)) > capBytes) {
689
- end -= 1;
690
- }
691
- return `${value.slice(0, end)}\n… [truncated]`;
692
- }
693
- async function directoryByteSize(path) {
694
- try {
695
- const entries = await readDirectoryRecursive(path);
696
- let total = 0;
697
- for (const file of entries) {
698
- try {
699
- total += byteLength(await readFile(file, "utf8"));
700
- } catch {
701
- // ignore non-text or transient files in benchmark temp dirs
702
- }
703
- }
704
- return total;
705
- } catch {
706
- return 0;
707
- }
708
- }
709
- async function readDirectoryRecursive(path) {
710
- const { readdir, stat } = await import("node:fs/promises");
711
- const entries = await readdir(path, { withFileTypes: true });
712
- const files = [];
713
- for (const entry of entries) {
714
- const child = join(path, entry.name);
715
- if (entry.isDirectory()) {
716
- files.push(...(await readDirectoryRecursive(child)));
717
- } else if (entry.isFile()) {
718
- const childStat = await stat(child);
719
- if (childStat.size <= 2_000_000) {
720
- files.push(child);
721
- }
722
- }
723
- }
724
- return files;
725
- }
726
- async function createTempDir(prefix) {
727
- const path = await mkdtemp(resolve(tmpdir(), prefix));
728
- return {
729
- path,
730
- cleanup: async () => {
731
- await rm(path, { recursive: true, force: true });
732
- },
733
- };
734
- }
735
- function contextModeModes() {
736
- return ["context-mode-normalized-proxy", "freeflow-owned-tools"];
737
- }
738
- function contextOutputId(id, raw) {
739
- return `ctxout_${createHash("sha256").update(`${id}\0${raw}`).digest("hex").slice(0, 24)}`;
740
- }
741
- function byteLength(value) {
742
- return Buffer.byteLength(value, "utf8");
743
- }
744
- function defaultReportPath() {
745
- return resolve(process.cwd(), "router/evals/reports/context-mode-normalized-benchmark-1-report.md");
746
- }
747
- async function runCli() {
748
- const { iterations, reportPath, jsonReportPath } = parseBenchmarkCliArgs(process.argv.slice(2), {
749
- reportPath: defaultReportPath(),
750
- });
751
- const options = {};
752
- if (iterations !== undefined) {
753
- options.iterations = iterations;
754
- }
755
- const report = await runContextModeNormalizedBenchmarks(options);
756
- const reports = await writeContextModeNormalizedBenchmarkReports(report, reportPath, {
757
- jsonReportPath: jsonReportPath === undefined ? defaultJsonRunReportPath(reportPath) : jsonReportPath,
758
- });
759
- const shortId = createHash("sha256").update(JSON.stringify(report.summary)).digest("hex").slice(0, 8);
760
- console.log(
761
- `Freeflow Context Mode normalized benchmark ${shortId}: freeflow ${report.summary.freeflow.passed}/${report.summary.fixtures}, proxy ${report.summary.contextModeProxy.passed}/${report.summary.fixtures}`,
762
- );
763
- console.log(`Markdown report: ${reports.markdown}`);
764
- if (reports.json) {
765
- console.log(`JSON run data: ${reports.json}`);
766
- }
767
- if (report.summary.freeflow.failed > 0 || report.summary.contextModeProxy.failed > 0) {
768
- process.exitCode = 1;
769
- }
770
- }
771
- if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
772
- await runCli();
773
- }