@hassangameryt/freeflow 0.5.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +148 -154
- package/capabilities/cognitive-routing/SKILL.md +229 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +6 -13
- package/hooks/freeflow-runtime-context.mjs +35 -91
- package/hooks/tests/check-runtime-context-hook.sh +56 -39
- package/package.json +24 -16
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +236 -0
- package/pi-extension/dist/cognitive-routing/controller.js +862 -0
- package/pi-extension/dist/cognitive-routing/history.js +439 -0
- package/pi-extension/dist/cognitive-routing/host.js +126 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +211 -0
- package/pi-extension/dist/cognitive-routing/types.js +6 -0
- package/pi-extension/dist/context-virtualization/commands.js +89 -0
- package/pi-extension/dist/context-virtualization/projector.js +42 -0
- package/pi-extension/dist/context-virtualization/runtime.js +356 -0
- package/pi-extension/dist/context-virtualization/state.js +57 -0
- package/pi-extension/dist/context-virtualization/types.js +10 -0
- package/pi-extension/dist/conversation-history/passages.js +66 -0
- package/pi-extension/dist/conversation-history/ranking.js +105 -0
- package/pi-extension/dist/conversation-history/runtime.js +226 -0
- package/pi-extension/dist/conversation-history/search.js +56 -0
- package/pi-extension/dist/conversation-history/types.js +1 -0
- package/pi-extension/dist/freeflow-context/resolver.js +120 -0
- package/pi-extension/dist/freeflow-context/runtime.js +80 -0
- package/pi-extension/dist/freeflow-context/source-projector.js +95 -0
- package/pi-extension/dist/freeflow-context/tool.js +308 -0
- package/pi-extension/dist/freeflow-context/types.js +12 -0
- package/pi-extension/dist/freeflow-context/visibility.js +28 -0
- package/pi-extension/dist/index.js +516 -98
- package/pi-extension/dist/runtime/runtime-context.js +318 -271
- package/pi-extension/dist/runtime/runtime-identity.js +5 -0
- package/pi-extension/dist/settings/settings-tui.js +78 -0
- package/pi-extension/dist/settings/settings-ui.js +593 -563
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +119 -0
- package/pi-extension/src/cognitive-routing/config.ts +307 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1118 -0
- package/pi-extension/src/cognitive-routing/history.ts +638 -0
- package/pi-extension/src/cognitive-routing/host.ts +172 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +272 -0
- package/pi-extension/src/cognitive-routing/types.ts +89 -0
- package/pi-extension/src/context-virtualization/commands.ts +111 -0
- package/pi-extension/src/context-virtualization/projector.ts +62 -0
- package/pi-extension/src/context-virtualization/runtime.ts +406 -0
- package/pi-extension/src/context-virtualization/state.ts +79 -0
- package/pi-extension/src/context-virtualization/types.ts +92 -0
- package/pi-extension/src/conversation-history/passages.ts +81 -0
- package/pi-extension/src/conversation-history/ranking.ts +155 -0
- package/pi-extension/src/conversation-history/runtime.ts +250 -0
- package/pi-extension/src/conversation-history/search.ts +79 -0
- package/pi-extension/src/conversation-history/types.ts +31 -0
- package/pi-extension/src/freeflow-context/resolver.ts +139 -0
- package/pi-extension/src/freeflow-context/runtime.ts +100 -0
- package/pi-extension/src/freeflow-context/source-projector.ts +106 -0
- package/pi-extension/src/freeflow-context/tool.ts +360 -0
- package/pi-extension/src/freeflow-context/types.ts +70 -0
- package/pi-extension/src/freeflow-context/visibility.ts +53 -0
- package/pi-extension/src/index.ts +553 -101
- package/pi-extension/src/runtime/runtime-context.ts +339 -281
- package/pi-extension/src/runtime/runtime-identity.ts +8 -0
- package/pi-extension/src/settings/settings-tui.ts +93 -0
- package/pi-extension/src/settings/settings-ui.ts +700 -613
- package/pi-extension/src/ui/text-component.ts +66 -0
- package/pi-extension/tests/cognitive-routing/commands.test.js +134 -0
- package/pi-extension/tests/cognitive-routing/controller.test.js +1099 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1071 -0
- package/pi-extension/tests/cognitive-routing/history.test.js +274 -0
- package/pi-extension/tests/cognitive-routing/host-fixture.js +4 -0
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +63 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +236 -0
- package/pi-extension/tests/cognitive-routing/tool.test.js +230 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +749 -0
- package/pi-extension/tests/context-virtualization/projector.test.js +110 -0
- package/pi-extension/tests/context-virtualization/runtime.test.js +216 -0
- package/pi-extension/tests/conversation-history/runtime.test.js +98 -0
- package/pi-extension/tests/conversation-history/search.test.js +97 -0
- package/pi-extension/tests/integration/pi-extension.test.js +927 -2048
- package/pi-extension/tests/integration/prompt-architecture.test.js +305 -0
- package/runtime/prompts/cognitive-routing.md +9 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +13 -0
- package/runtime/prompts/interaction-contract.md +7 -0
- package/runtime/prompts/skills.md +65 -0
- package/skills/action-selection/SKILL.md +91 -0
- package/skills/bypass/SKILL.md +3 -2
- package/skills/commit-work/SKILL.md +1 -1
- package/skills/commit-work/references/staging-decisions.md +1 -1
- package/skills/decision-gate/SKILL.md +1 -1
- package/skills/design-for-depth/SKILL.md +14 -33
- package/skills/design-for-depth/references/design-pressure-signals.md +1 -1
- package/skills/design-for-depth/references/interface-design-loop.md +1 -1
- package/skills/design-for-depth/references/software-design-philosophy.md +1 -1
- package/skills/diagnose-failure/SKILL.md +20 -14
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +1 -3
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +1 -1
- package/skills/discuss/SKILL.md +34 -22
- package/skills/discuss/references/discussion-continuity.md +71 -0
- package/skills/evaluate-skill/SKILL.md +5 -5
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +42 -2
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +114 -3
- package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +156 -1
- package/skills/evaluate-skill/scripts/lib/grade.mjs +9 -1
- package/skills/evaluate-skill/scripts/lib/pi.mjs +147 -32
- package/skills/evaluate-skill/scripts/lib/rpc.mjs +4 -4
- package/skills/evaluate-skill/scripts/lib/runner.mjs +36 -14
- package/skills/evaluate-skill/scripts/lib/sandbox.mjs +92 -0
- package/skills/evaluate-skill/scripts/pi-guard.mjs +1 -1
- package/skills/evaluate-skill/scripts/pi-observer.mjs +66 -0
- package/skills/evaluate-skill/scripts/skill-eval.mjs +6 -5
- package/skills/execute-work/SKILL.md +18 -15
- package/skills/execute-work/references/code-practices.md +1 -1
- package/skills/execute-work/references/execute-work-edges.md +42 -0
- package/skills/finish-branch/SKILL.md +2 -2
- package/skills/finish-branch/references/integration-options.md +1 -1
- package/skills/handoff/SKILL.md +3 -3
- package/skills/launch-work/SKILL.md +1 -1
- package/skills/launch-work/references/launch-readiness.md +2 -2
- package/skills/migration-work/SKILL.md +2 -2
- package/skills/migration-work/references/migration-lifecycle.md +1 -1
- package/skills/mode-contract/SKILL.md +4 -4
- package/skills/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +28 -26
- package/skills/review-work/SKILL.md +28 -26
- package/skills/review-work/references/{reviewer-prompt.md → independent-work-reviewer-contract.md} +1 -1
- package/skills/review-work/references/security-risk-lens.md +1 -1
- package/skills/setup-freeflow/SKILL.md +3 -1
- package/skills/setup-freeflow/references/activation-contract.md +2 -2
- package/skills/setup-freeflow/references/host-setup.md +2 -2
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +18 -38
- package/skills/tdd/references/test-design.md +2 -2
- package/skills/track-work/SKILL.md +362 -58
- package/skills/track-work/scripts/lib/codec.mjs +570 -0
- package/skills/track-work/scripts/lib/commands.mjs +568 -0
- package/skills/track-work/scripts/lib/contract.mjs +303 -0
- package/skills/track-work/scripts/lib/model.mjs +506 -0
- package/skills/track-work/scripts/lib/operations.mjs +965 -0
- package/skills/track-work/scripts/lib/record-store.mjs +264 -0
- package/skills/track-work/scripts/lib/result.mjs +37 -0
- package/skills/track-work/scripts/lib/rewrites.mjs +740 -0
- package/skills/track-work/scripts/lib/schema.mjs +277 -0
- package/skills/track-work/scripts/lib/source-inventory.mjs +162 -0
- package/skills/track-work/scripts/lib/views.mjs +443 -0
- package/skills/track-work/scripts/lib/workspace.mjs +134 -0
- package/skills/track-work/scripts/working-record.mjs +122 -0
- package/skills/verify-work/SKILL.md +14 -2
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -1
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +105 -53
- package/skills/workflow/references/domain-skill-composition.md +22 -42
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +3 -2
- package/skills/write-skill/references/progressive-disclosure.md +17 -3
- package/capabilities/output-router/SKILL.md +0 -206
- package/capabilities/output-router/references/safety-policy.md +0 -40
- package/capabilities/output-router/references/setup.md +0 -110
- package/pi-extension/dist/output-router/host-producer-identification.js +0 -232
- package/pi-extension/dist/output-router/native-safety-net.js +0 -216
- package/pi-extension/dist/output-router/observed-tool-routing.js +0 -122
- package/pi-extension/dist/output-router/renderers.js +0 -1023
- package/pi-extension/dist/output-router/router-tools.js +0 -554
- package/pi-extension/dist/output-router/schemas.js +0 -383
- package/pi-extension/dist/output-router/status.js +0 -744
- package/pi-extension/dist/output-router/utils.js +0 -502
- package/pi-extension/src/output-router/host-producer-identification.ts +0 -256
- package/pi-extension/src/output-router/native-safety-net.ts +0 -248
- package/pi-extension/src/output-router/observed-tool-routing.ts +0 -134
- package/pi-extension/src/output-router/renderers.ts +0 -1108
- package/pi-extension/src/output-router/router-tools.ts +0 -579
- package/pi-extension/src/output-router/schemas.ts +0 -387
- package/pi-extension/src/output-router/status.ts +0 -791
- package/pi-extension/src/output-router/utils.ts +0 -554
- package/pi-extension/tests/output-router/observed-producers.test.js +0 -132
- package/pi-extension/tests/output-router/search.test.js +0 -722
- package/router/dist/benchmarks/benchmark-harness.d.ts +0 -35
- package/router/dist/benchmarks/benchmark-harness.js +0 -92
- package/router/dist/benchmarks/benchmarks.d.ts +0 -109
- package/router/dist/benchmarks/benchmarks.js +0 -979
- package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +0 -95
- package/router/dist/benchmarks/codex-qa-benchmarks.js +0 -500
- package/router/dist/benchmarks/command-benchmarks.d.ts +0 -143
- package/router/dist/benchmarks/command-benchmarks.js +0 -698
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +0 -82
- package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +0 -773
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +0 -132
- package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +0 -1380
- package/router/dist/benchmarks/context-saving-baseline.d.ts +0 -60
- package/router/dist/benchmarks/context-saving-baseline.js +0 -470
- package/router/dist/benchmarks/index-benchmarks.d.ts +0 -103
- package/router/dist/benchmarks/index-benchmarks.js +0 -862
- package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +0 -101
- package/router/dist/benchmarks/storage-policy-benchmarks.js +0 -618
- package/router/dist/benchmarks/vault-index-benchmarks.d.ts +0 -56
- package/router/dist/benchmarks/vault-index-benchmarks.js +0 -322
- package/router/dist/config/config.d.ts +0 -103
- package/router/dist/config/config.js +0 -632
- package/router/dist/config/router-contract.d.ts +0 -10
- package/router/dist/config/router-contract.js +0 -54
- package/router/dist/config/schema.d.ts +0 -27
- package/router/dist/config/schema.js +0 -648
- package/router/dist/config/types.d.ts +0 -573
- package/router/dist/config/types.js +0 -41
- package/router/dist/evidence/bounded-evidence.d.ts +0 -40
- package/router/dist/evidence/bounded-evidence.js +0 -323
- package/router/dist/evidence/evidence-range-selector.d.ts +0 -24
- package/router/dist/evidence/evidence-range-selector.js +0 -232
- package/router/dist/evidence/evidence-search.d.ts +0 -27
- package/router/dist/evidence/evidence-search.js +0 -490
- package/router/dist/evidence/evidence.d.ts +0 -38
- package/router/dist/evidence/evidence.js +0 -200
- package/router/dist/evidence/failure-contracts.d.ts +0 -37
- package/router/dist/evidence/failure-contracts.js +0 -166
- package/router/dist/evidence/line-ranges.d.ts +0 -24
- package/router/dist/evidence/line-ranges.js +0 -22
- package/router/dist/experiments/local-index.d.ts +0 -64
- package/router/dist/experiments/local-index.js +0 -581
- package/router/dist/index.d.ts +0 -30
- package/router/dist/index.js +0 -32
- package/router/dist/local/local-traversal.d.ts +0 -18
- package/router/dist/local/local-traversal.js +0 -334
- package/router/dist/processing/engine.d.ts +0 -132
- package/router/dist/processing/engine.js +0 -533
- package/router/dist/processing/reducers.d.ts +0 -280
- package/router/dist/processing/reducers.js +0 -1673
- package/router/dist/processing/renderers.d.ts +0 -31
- package/router/dist/processing/renderers.js +0 -195
- package/router/dist/processing/scripts.d.ts +0 -91
- package/router/dist/processing/scripts.js +0 -386
- package/router/dist/repo/repo-traversal.d.ts +0 -18
- package/router/dist/repo/repo-traversal.js +0 -245
- package/router/dist/routing/observed-reducers.d.ts +0 -41
- package/router/dist/routing/observed-reducers.js +0 -661
- package/router/dist/routing/observed-routing.d.ts +0 -26
- package/router/dist/routing/observed-routing.js +0 -378
- package/router/dist/routing/parsers.d.ts +0 -16
- package/router/dist/routing/parsers.js +0 -521
- package/router/dist/routing/run-filters.d.ts +0 -60
- package/router/dist/routing/run-filters.js +0 -238
- package/router/dist/routing/run-reducers.d.ts +0 -49
- package/router/dist/routing/run-reducers.js +0 -122
- package/router/dist/sandbox/adapter-roots.d.ts +0 -46
- package/router/dist/sandbox/adapter-roots.js +0 -66
- package/router/dist/sandbox/eryx-python-adapter.d.ts +0 -13
- package/router/dist/sandbox/eryx-python-adapter.js +0 -868
- package/router/dist/sandbox/jq-wasm-adapter.d.ts +0 -9
- package/router/dist/sandbox/jq-wasm-adapter.js +0 -429
- package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +0 -13
- package/router/dist/sandbox/quickjs-wasi-adapter.js +0 -384
- package/router/dist/sandbox/script-sandbox.d.ts +0 -134
- package/router/dist/sandbox/script-sandbox.js +0 -337
- package/router/dist/setup/script-transform-adapters.d.ts +0 -32
- package/router/dist/setup/script-transform-adapters.js +0 -278
- package/router/dist/tools/batch.d.ts +0 -33
- package/router/dist/tools/batch.js +0 -586
- package/router/dist/tools/run.d.ts +0 -62
- package/router/dist/tools/run.js +0 -1304
- package/router/dist/tools/search.d.ts +0 -66
- package/router/dist/tools/search.js +0 -1364
- package/router/dist/transform/engine.d.ts +0 -168
- package/router/dist/transform/engine.js +0 -2072
- package/router/dist/vault/vault-index.d.ts +0 -125
- package/router/dist/vault/vault-index.js +0 -416
- package/router/dist/vault/vault.d.ts +0 -138
- package/router/dist/vault/vault.js +0 -630
- package/runtime/interaction-contract.md +0 -16
- package/skills/discuss/references/checkpoints.md +0 -66
- package/skills/execute-work/references/execution-loop.md +0 -77
- package/skills/track-work/references/working-record-schema.md +0 -378
- package/skills/workflow/references/workflow-loop.md +0 -92
- /package/skills/handoff/references/{templates.md → handoff-templates.md} +0 -0
- /package/skills/review-artifact/references/{reviewer-prompt.md → independent-artifact-reviewer-contract.md} +0 -0
|
@@ -1,979 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, mkdtemp, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { performance } from "node:perf_hooks";
|
|
7
|
-
import {
|
|
8
|
-
approximateTokens,
|
|
9
|
-
averagePercent as average,
|
|
10
|
-
defaultJsonRunReportPath,
|
|
11
|
-
escapeMarkdownTableCell as escapeTable,
|
|
12
|
-
formatPercent,
|
|
13
|
-
latencySummary,
|
|
14
|
-
medianPercent as median,
|
|
15
|
-
normalizeIterations,
|
|
16
|
-
parseBenchmarkCliArgs,
|
|
17
|
-
reductionPercent,
|
|
18
|
-
writeBenchmarkReportPair,
|
|
19
|
-
} from "./benchmark-harness.js";
|
|
20
|
-
import { freeflowSearch } from "../tools/search.js";
|
|
21
|
-
import { createVault, storeCommandOutput } from "../vault/vault.js";
|
|
22
|
-
const DEFAULT_ITERATIONS = 3;
|
|
23
|
-
const DEFAULT_CONTEXT_LINES = 2;
|
|
24
|
-
const LEGACY_SKIP_DIRS = new Set([
|
|
25
|
-
".git",
|
|
26
|
-
"node_modules",
|
|
27
|
-
"dist",
|
|
28
|
-
"build",
|
|
29
|
-
"out",
|
|
30
|
-
".next",
|
|
31
|
-
".nuxt",
|
|
32
|
-
"coverage",
|
|
33
|
-
"target",
|
|
34
|
-
]);
|
|
35
|
-
const GENERATED_DIRS = new Set([
|
|
36
|
-
".git",
|
|
37
|
-
"node_modules",
|
|
38
|
-
"dist",
|
|
39
|
-
"build",
|
|
40
|
-
"out",
|
|
41
|
-
".next",
|
|
42
|
-
".nuxt",
|
|
43
|
-
"coverage",
|
|
44
|
-
"target",
|
|
45
|
-
"graphify-out",
|
|
46
|
-
".cache",
|
|
47
|
-
".tmp",
|
|
48
|
-
"tmp",
|
|
49
|
-
"temp",
|
|
50
|
-
"logs",
|
|
51
|
-
]);
|
|
52
|
-
const EXTERNAL_TOOL_SKIPS = [
|
|
53
|
-
{ name: "Graphify", reason: "Optional external comparator is not required for CI-friendly router benchmarks." },
|
|
54
|
-
{
|
|
55
|
-
name: "Claude Context",
|
|
56
|
-
reason: "Optional semantic/hybrid search comparator is skipped unless configured separately.",
|
|
57
|
-
},
|
|
58
|
-
{ name: "RTK", reason: "Command-output comparator belongs to the later command benchmark track." },
|
|
59
|
-
{ name: "Squeez", reason: "Session-efficiency comparator belongs to the later command/session benchmark track." },
|
|
60
|
-
];
|
|
61
|
-
export async function runRouterBenchmarks(options = {}) {
|
|
62
|
-
const iterations = normalizeIterations(options.iterations, DEFAULT_ITERATIONS);
|
|
63
|
-
const fixtures = await createBenchmarkFixtures();
|
|
64
|
-
try {
|
|
65
|
-
const fixtureResults = [];
|
|
66
|
-
for (const fixture of fixtures) {
|
|
67
|
-
const results = [];
|
|
68
|
-
for (const mode of benchmarkModes()) {
|
|
69
|
-
results.push(await runFixtureMode(fixture, mode, iterations));
|
|
70
|
-
}
|
|
71
|
-
fixtureResults.push({
|
|
72
|
-
id: fixture.id,
|
|
73
|
-
title: fixture.title,
|
|
74
|
-
kind: fixture.kind,
|
|
75
|
-
expected: fixture.expected,
|
|
76
|
-
results,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
generatedAt: options.generatedAt ?? new Date().toISOString(),
|
|
81
|
-
iterations,
|
|
82
|
-
summary: summarizeReport(fixtureResults),
|
|
83
|
-
fixtures: fixtureResults,
|
|
84
|
-
skippedExternalTools: EXTERNAL_TOOL_SKIPS,
|
|
85
|
-
};
|
|
86
|
-
} finally {
|
|
87
|
-
await Promise.all(fixtures.map((fixture) => fixture.cleanup()));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export function renderRouterBenchmarkReport(report) {
|
|
91
|
-
const date = report.generatedAt.slice(0, 10);
|
|
92
|
-
const lines = [
|
|
93
|
-
"# Output Router Benchmark Report - Iteration 1",
|
|
94
|
-
"",
|
|
95
|
-
`Date: ${date}`,
|
|
96
|
-
"",
|
|
97
|
-
"## Scope",
|
|
98
|
-
"",
|
|
99
|
-
"Deterministic, CI-friendly tool benchmark for Freeflow Router retrieval behavior. The runner compares a native text-search proxy, a pre-hardening Freeflow-style proxy, and the improved Freeflow Router implementation. Optional external comparators are recorded as skipped rather than failed.",
|
|
100
|
-
"",
|
|
101
|
-
"Reduction percentages compare routed/context bytes and approximate tokens against the raw source or direct output for that mode. Negative reduction means structured routing overhead is larger than the tiny raw output; native exact-search proxies can still be smaller than the router for simple lookups.",
|
|
102
|
-
"",
|
|
103
|
-
"## Baseline Caveat",
|
|
104
|
-
"",
|
|
105
|
-
"The pre-hardening Freeflow mode is a deterministic proxy for the old line-scoring behavior, not a checkout of an older runtime. It is useful for stable regression pressure, not historical performance archaeology.",
|
|
106
|
-
"",
|
|
107
|
-
"## Command",
|
|
108
|
-
"",
|
|
109
|
-
"```sh",
|
|
110
|
-
"npm run bench:router",
|
|
111
|
-
"```",
|
|
112
|
-
"",
|
|
113
|
-
"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.",
|
|
114
|
-
"",
|
|
115
|
-
"## Summary",
|
|
116
|
-
"",
|
|
117
|
-
`- Iterations per mode: ${report.iterations}`,
|
|
118
|
-
`- Fixtures: ${report.summary.fixtures}`,
|
|
119
|
-
`- Improved Freeflow Router gated pass: ${report.summary.improved.passed}/${report.summary.fixtures}`,
|
|
120
|
-
`- Native baseline proxy pass: ${report.summary.nativeBaseline.passed}/${report.summary.fixtures}`,
|
|
121
|
-
`- Pre-hardening Freeflow proxy pass: ${report.summary.freeflowBaseline.passed}/${report.summary.fixtures}`,
|
|
122
|
-
`- Generated false positives observed: ${report.summary.generatedFalsePositiveCount}/${report.summary.modeResults} mode results`,
|
|
123
|
-
`- Improved generated false positives: ${report.summary.improved.generatedFalsePositiveCount}/${report.summary.fixtures}`,
|
|
124
|
-
`- Improved weighted byte/token reduction: ${formatPercent(report.summary.improved.weightedByteReductionPercent)} / ${formatPercent(report.summary.improved.weightedTokenReductionPercent)} (${report.summary.improved.totalRawBytes}/${report.summary.improved.totalRawTokensApprox} raw to ${report.summary.improved.totalRoutedBytes}/${report.summary.improved.totalRoutedTokensApprox} routed)`,
|
|
125
|
-
`- Improved average byte/token reduction: ${formatPercent(report.summary.improved.averageByteReductionPercent)} / ${formatPercent(report.summary.improved.averageTokenReductionPercent)}`,
|
|
126
|
-
`- Improved median byte/token reduction: ${formatPercent(report.summary.improved.medianByteReductionPercent)} / ${formatPercent(report.summary.improved.medianTokenReductionPercent)}`,
|
|
127
|
-
`- Improved path/span/excerpt checks: ${report.summary.improved.pathCorrect}/${report.summary.fixtures} path, ${report.summary.improved.spanCorrect}/${report.summary.fixtures} span, ${report.summary.improved.excerptComplete}/${report.summary.fixtures} excerpt`,
|
|
128
|
-
`- Sandbox failure fixed: ${report.summary.sandboxFailureFixed ? "yes" : "no"}`,
|
|
129
|
-
"",
|
|
130
|
-
"## Results",
|
|
131
|
-
"",
|
|
132
|
-
"| fixture | mode | correctness | checks | path | lines | raw bytes/tokens | routed bytes/tokens | byte/token reduction | latency p50/p95 ms | recovery | notes |",
|
|
133
|
-
"| --- | --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | --- | --- |",
|
|
134
|
-
];
|
|
135
|
-
for (const fixture of report.fixtures) {
|
|
136
|
-
for (const result of fixture.results) {
|
|
137
|
-
const correctness = result.skipped ? "skipped" : result.correctness.passed ? "pass" : "fail";
|
|
138
|
-
const recovery = result.recovery.status;
|
|
139
|
-
const notes = result.notes.length ? result.notes.join("; ") : (result.skipReason ?? "");
|
|
140
|
-
const checks = formatCorrectnessChecks(result.correctness);
|
|
141
|
-
lines.push(
|
|
142
|
-
`| ${escapeTable(fixture.id)} | ${escapeTable(result.mode)} | ${correctness} | ${escapeTable(checks)} | ${escapeTable(result.actualPath ?? "-")} | ${escapeTable(result.actualLines ?? "-")} | ${result.rawBytes}/${result.rawTokensApprox} | ${result.routedBytes}/${result.routedTokensApprox} | ${formatPercent(result.byteReductionPercent)}/${formatPercent(result.tokenReductionPercent)} | ${result.latencyMs.p50.toFixed(2)}/${result.latencyMs.p95.toFixed(2)} | ${escapeTable(recovery)} | ${escapeTable(notes)} |`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
lines.push(
|
|
147
|
-
"",
|
|
148
|
-
"## Not Yet Measured",
|
|
149
|
-
"",
|
|
150
|
-
"- recall@3 / alternate candidates: not measured in this first deterministic runner.",
|
|
151
|
-
"- explanation quality: not measured beyond route reason capture.",
|
|
152
|
-
"- command-output parser benchmarks: deferred to the command benchmark track.",
|
|
153
|
-
"",
|
|
154
|
-
"## Skipped External Comparators",
|
|
155
|
-
"",
|
|
156
|
-
);
|
|
157
|
-
for (const tool of report.skippedExternalTools) {
|
|
158
|
-
lines.push(`- ${tool.name}: ${tool.reason}`);
|
|
159
|
-
}
|
|
160
|
-
lines.push(
|
|
161
|
-
"",
|
|
162
|
-
"## Regression Status",
|
|
163
|
-
"",
|
|
164
|
-
report.summary.improved.failed === 0
|
|
165
|
-
? "Improved Freeflow Router passed all gated benchmark fixtures."
|
|
166
|
-
: `Improved Freeflow Router failed ${report.summary.improved.failed} gated benchmark fixture(s).`,
|
|
167
|
-
"",
|
|
168
|
-
"The generated-artifact decoy benchmark preserves the original Sandbox Permissions false-positive shape and records it as fixed when the improved router selects the docs target instead of `graphify-out/graph.html`.",
|
|
169
|
-
"",
|
|
170
|
-
);
|
|
171
|
-
return `${lines.join("\n")}`;
|
|
172
|
-
}
|
|
173
|
-
export async function writeRouterBenchmarkReport(report, reportPath) {
|
|
174
|
-
await mkdir(dirname(reportPath), { recursive: true });
|
|
175
|
-
await writeFile(reportPath, renderRouterBenchmarkReport(report), "utf8");
|
|
176
|
-
}
|
|
177
|
-
export async function writeRouterBenchmarkJsonReport(report, reportPath) {
|
|
178
|
-
await mkdir(dirname(reportPath), { recursive: true });
|
|
179
|
-
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
180
|
-
}
|
|
181
|
-
export async function writeRouterBenchmarkReports(report, markdownReportPath, options = {}) {
|
|
182
|
-
return writeBenchmarkReportPair({
|
|
183
|
-
report,
|
|
184
|
-
markdownReportPath,
|
|
185
|
-
jsonReportPath: options.jsonReportPath,
|
|
186
|
-
renderMarkdown: renderRouterBenchmarkReport,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
async function runFixtureMode(fixture, mode, iterations) {
|
|
190
|
-
const latencies = [];
|
|
191
|
-
let observation = null;
|
|
192
|
-
for (let index = 0; index < iterations; index += 1) {
|
|
193
|
-
const startedAt = performance.now();
|
|
194
|
-
observation = await fixture.modes[mode]();
|
|
195
|
-
latencies.push(performance.now() - startedAt);
|
|
196
|
-
}
|
|
197
|
-
if (!observation) {
|
|
198
|
-
observation = skippedObservation("benchmark runner", "No observation was produced.");
|
|
199
|
-
}
|
|
200
|
-
const correctness = scoreCorrectness(fixture.expected, observation);
|
|
201
|
-
const rawTokensApprox = approximateTokens(observation.rawBytes);
|
|
202
|
-
const routedTokensApprox = approximateTokens(observation.routedBytes);
|
|
203
|
-
return {
|
|
204
|
-
mode,
|
|
205
|
-
toolPathUsed: observation.toolPathUsed,
|
|
206
|
-
skipped: observation.skipped ?? false,
|
|
207
|
-
...(observation.skipReason ? { skipReason: observation.skipReason } : {}),
|
|
208
|
-
rawBytes: observation.rawBytes,
|
|
209
|
-
rawTokensApprox,
|
|
210
|
-
routedBytes: observation.routedBytes,
|
|
211
|
-
routedTokensApprox,
|
|
212
|
-
byteReductionPercent: reductionPercent(observation.rawBytes, observation.routedBytes),
|
|
213
|
-
tokenReductionPercent: reductionPercent(rawTokensApprox, routedTokensApprox),
|
|
214
|
-
latencyMs: latencySummary(latencies),
|
|
215
|
-
...(observation.actualPath ? { actualPath: observation.actualPath } : {}),
|
|
216
|
-
...(observation.actualLines ? { actualLines: observation.actualLines } : {}),
|
|
217
|
-
correctness,
|
|
218
|
-
recovery: observation.recovery,
|
|
219
|
-
notes: observation.notes ?? [],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function summarizeReport(fixtures) {
|
|
223
|
-
const nativeBaseline = summarizeMode(fixtures, "native-baseline-proxy");
|
|
224
|
-
const freeflowBaseline = summarizeMode(fixtures, "pre-hardening-freeflow-proxy");
|
|
225
|
-
const improved = summarizeMode(fixtures, "improved-freeflow-router");
|
|
226
|
-
const generatedFalsePositiveCount = fixtures
|
|
227
|
-
.flatMap((fixture) => fixture.results)
|
|
228
|
-
.filter((result) => result.correctness.generatedFalsePositive).length;
|
|
229
|
-
const sandboxFixture = fixtures.find((fixture) => fixture.id === "generated-artifact-decoy");
|
|
230
|
-
const sandboxImproved = sandboxFixture?.results.find((result) => result.mode === "improved-freeflow-router");
|
|
231
|
-
return {
|
|
232
|
-
fixtures: fixtures.length,
|
|
233
|
-
modeResults: fixtures.reduce((count, fixture) => count + fixture.results.length, 0),
|
|
234
|
-
improved,
|
|
235
|
-
nativeBaseline,
|
|
236
|
-
freeflowBaseline,
|
|
237
|
-
generatedFalsePositiveCount,
|
|
238
|
-
sandboxFailureFixed: Boolean(
|
|
239
|
-
sandboxImproved?.correctness.passed && !sandboxImproved.correctness.generatedFalsePositive,
|
|
240
|
-
),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function summarizeMode(fixtures, mode) {
|
|
244
|
-
const modeResults = fixtures
|
|
245
|
-
.map((fixture) => fixture.results.find((candidate) => candidate.mode === mode))
|
|
246
|
-
.filter((result) => Boolean(result));
|
|
247
|
-
const measuredResults = modeResults.filter((result) => !result.skipped);
|
|
248
|
-
const totalRawBytes = measuredResults.reduce((sum, result) => sum + result.rawBytes, 0);
|
|
249
|
-
const totalRoutedBytes = measuredResults.reduce((sum, result) => sum + result.routedBytes, 0);
|
|
250
|
-
const totalRawTokensApprox = measuredResults.reduce((sum, result) => sum + result.rawTokensApprox, 0);
|
|
251
|
-
const totalRoutedTokensApprox = measuredResults.reduce((sum, result) => sum + result.routedTokensApprox, 0);
|
|
252
|
-
return modeResults.reduce(
|
|
253
|
-
(summary, result) => {
|
|
254
|
-
if (result.skipped) {
|
|
255
|
-
summary.skipped += 1;
|
|
256
|
-
} else if (isGatedPass(result, mode)) {
|
|
257
|
-
summary.passed += 1;
|
|
258
|
-
} else {
|
|
259
|
-
summary.failed += 1;
|
|
260
|
-
}
|
|
261
|
-
if (result.correctness.pathCorrect) {
|
|
262
|
-
summary.pathCorrect += 1;
|
|
263
|
-
}
|
|
264
|
-
if (result.correctness.spanCorrect) {
|
|
265
|
-
summary.spanCorrect += 1;
|
|
266
|
-
}
|
|
267
|
-
if (result.correctness.excerptComplete) {
|
|
268
|
-
summary.excerptComplete += 1;
|
|
269
|
-
}
|
|
270
|
-
if (result.correctness.generatedFalsePositive) {
|
|
271
|
-
summary.generatedFalsePositiveCount += 1;
|
|
272
|
-
}
|
|
273
|
-
return summary;
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
passed: 0,
|
|
277
|
-
failed: 0,
|
|
278
|
-
skipped: 0,
|
|
279
|
-
pathCorrect: 0,
|
|
280
|
-
spanCorrect: 0,
|
|
281
|
-
excerptComplete: 0,
|
|
282
|
-
generatedFalsePositiveCount: 0,
|
|
283
|
-
totalRawBytes,
|
|
284
|
-
totalRoutedBytes,
|
|
285
|
-
totalRawTokensApprox,
|
|
286
|
-
totalRoutedTokensApprox,
|
|
287
|
-
weightedByteReductionPercent: reductionPercent(totalRawBytes, totalRoutedBytes),
|
|
288
|
-
weightedTokenReductionPercent: reductionPercent(totalRawTokensApprox, totalRoutedTokensApprox),
|
|
289
|
-
averageByteReductionPercent: average(measuredResults.map((result) => result.byteReductionPercent)),
|
|
290
|
-
medianByteReductionPercent: median(measuredResults.map((result) => result.byteReductionPercent)),
|
|
291
|
-
averageTokenReductionPercent: average(measuredResults.map((result) => result.tokenReductionPercent)),
|
|
292
|
-
medianTokenReductionPercent: median(measuredResults.map((result) => result.tokenReductionPercent)),
|
|
293
|
-
},
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
function isGatedPass(result, mode) {
|
|
297
|
-
if (!result.correctness.passed) {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
if (mode !== "improved-freeflow-router") {
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
return result.recovery.status === "passed" || result.recovery.status === "not-applicable";
|
|
304
|
-
}
|
|
305
|
-
async function createBenchmarkFixtures() {
|
|
306
|
-
const fixtures = await Promise.all([
|
|
307
|
-
createExactCopiedTextFixture(),
|
|
308
|
-
createMarkdownSectionFixture(),
|
|
309
|
-
createGeneratedArtifactDecoyFixture(),
|
|
310
|
-
createHugeSingleLineDecoyFixture(),
|
|
311
|
-
createAmbiguousMultiFileFixture(),
|
|
312
|
-
createVaultedOutputFixture(),
|
|
313
|
-
createExpansionFixture(),
|
|
314
|
-
]);
|
|
315
|
-
return fixtures;
|
|
316
|
-
}
|
|
317
|
-
async function createExactCopiedTextFixture() {
|
|
318
|
-
const repo = await createTempRepo({
|
|
319
|
-
"target.md": ["# Target", "", "The output router vault preserves exact failure evidence for review."].join("\n"),
|
|
320
|
-
"notes.md": "vault evidence evidence evidence without the exact sentence",
|
|
321
|
-
});
|
|
322
|
-
return repoQueryFixture(repo, {
|
|
323
|
-
id: "exact-copied-text-block",
|
|
324
|
-
title: "Exact copied text block",
|
|
325
|
-
query: "output router vault preserves exact failure evidence",
|
|
326
|
-
expected: {
|
|
327
|
-
path: "target.md",
|
|
328
|
-
lines: "3-3",
|
|
329
|
-
requiredExcerpt: ["output router vault preserves exact failure evidence"],
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
async function createMarkdownSectionFixture() {
|
|
334
|
-
const repo = await createTempRepo({
|
|
335
|
-
"target.md": [
|
|
336
|
-
"# Target",
|
|
337
|
-
"",
|
|
338
|
-
"## Adaptive Compression",
|
|
339
|
-
"",
|
|
340
|
-
"Vault recovery remains exact for raw output.",
|
|
341
|
-
"Parser confidence labels routed diagnostics.",
|
|
342
|
-
].join("\n"),
|
|
343
|
-
"decoy.md": "adaptive adaptive adaptive adaptive adaptive",
|
|
344
|
-
});
|
|
345
|
-
return repoQueryFixture(repo, {
|
|
346
|
-
id: "markdown-heading-nearby-body",
|
|
347
|
-
title: "Markdown heading plus nearby body text",
|
|
348
|
-
query: "adaptive compression vault recovery parser confidence",
|
|
349
|
-
expected: {
|
|
350
|
-
path: "target.md",
|
|
351
|
-
lines: "3-6",
|
|
352
|
-
requiredExcerpt: ["Adaptive Compression", "Vault recovery", "Parser confidence"],
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
async function createGeneratedArtifactDecoyFixture() {
|
|
357
|
-
const repo = await createTempRepo({
|
|
358
|
-
"router/evals/fixtures/output-router-corpus/sandbox-permissions.md": [
|
|
359
|
-
"# Pass 3",
|
|
360
|
-
"",
|
|
361
|
-
"### Sandbox Permissions",
|
|
362
|
-
"",
|
|
363
|
-
"`SandboxPermissions` is a per-command request shape.",
|
|
364
|
-
"",
|
|
365
|
-
"Plain-language meaning:",
|
|
366
|
-
"",
|
|
367
|
-
"UseDefault: run with the turn's normal sandbox.",
|
|
368
|
-
].join("\n"),
|
|
369
|
-
"graphify-out/graph.html": [
|
|
370
|
-
"<html><body>",
|
|
371
|
-
`${"Sandbox Permissions SandboxPermissions Plain-language meaning ".repeat(5000)}GENERATED_GRAPH_DECOY_SENTINEL`,
|
|
372
|
-
"</body></html>",
|
|
373
|
-
].join("\n"),
|
|
374
|
-
});
|
|
375
|
-
return repoQueryFixture(repo, {
|
|
376
|
-
id: "generated-artifact-decoy",
|
|
377
|
-
title: "Generated-artifact decoy",
|
|
378
|
-
query: "Sandbox Permissions SandboxPermissions Plain-language meaning",
|
|
379
|
-
expected: {
|
|
380
|
-
path: "router/evals/fixtures/output-router-corpus/sandbox-permissions.md",
|
|
381
|
-
lines: "3-7",
|
|
382
|
-
requiredExcerpt: ["SandboxPermissions", "Plain-language meaning"],
|
|
383
|
-
},
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
async function createHugeSingleLineDecoyFixture() {
|
|
387
|
-
const repo = await createTempRepo({
|
|
388
|
-
"docs/router.md": ["# Router", "", "HUGE_MARKER belongs in bounded target evidence, not generated logs."].join(
|
|
389
|
-
"\n",
|
|
390
|
-
),
|
|
391
|
-
"debug.log": `${"HUGE_MARKER generated log noise ".repeat(6000)}HUGE_LOG_TAIL`,
|
|
392
|
-
});
|
|
393
|
-
return repoQueryFixture(repo, {
|
|
394
|
-
id: "huge-single-line-decoy",
|
|
395
|
-
title: "Huge single-line generated/log decoy",
|
|
396
|
-
query: "HUGE_MARKER bounded target evidence",
|
|
397
|
-
expected: {
|
|
398
|
-
path: "docs/router.md",
|
|
399
|
-
lines: "3-3",
|
|
400
|
-
requiredExcerpt: ["HUGE_MARKER belongs in bounded target evidence"],
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
async function createAmbiguousMultiFileFixture() {
|
|
405
|
-
const repo = await createTempRepo({
|
|
406
|
-
"target.md": [
|
|
407
|
-
"# Target",
|
|
408
|
-
"",
|
|
409
|
-
"Adaptive compression keeps vault recovery exact while parser confidence labels routed diagnostics.",
|
|
410
|
-
].join("\n"),
|
|
411
|
-
"decoy.md": "adaptive ".repeat(900),
|
|
412
|
-
"near-miss.md": "compression vault recovery without parser confidence",
|
|
413
|
-
});
|
|
414
|
-
return repoQueryFixture(repo, {
|
|
415
|
-
id: "ambiguous-multi-file-query",
|
|
416
|
-
title: "Ambiguous multi-file query",
|
|
417
|
-
query: "adaptive compression vault recovery parser confidence",
|
|
418
|
-
expected: {
|
|
419
|
-
path: "target.md",
|
|
420
|
-
lines: "3-3",
|
|
421
|
-
requiredExcerpt: ["Adaptive compression", "vault recovery", "parser confidence"],
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
async function createVaultedOutputFixture() {
|
|
426
|
-
const vaultRoot = await createTempDir("freeflow-router-benchmark-vault-");
|
|
427
|
-
const vault = createVault({ root: vaultRoot.path });
|
|
428
|
-
const sessionId = "benchmark-vault-session";
|
|
429
|
-
const stderr = [
|
|
430
|
-
"setup ok",
|
|
431
|
-
"ASSERTION_FAILED payments badge missing accessible label",
|
|
432
|
-
"stack: renderSettingsBadge at settings.test.ts:42",
|
|
433
|
-
].join("\n");
|
|
434
|
-
const record = await storeCommandOutput(vault, {
|
|
435
|
-
sessionId,
|
|
436
|
-
command: "npm test -- settings",
|
|
437
|
-
stdout: "",
|
|
438
|
-
stderr,
|
|
439
|
-
executionStatus: "failed",
|
|
440
|
-
exitCode: 1,
|
|
441
|
-
createdAt: "2026-06-16T00:00:00.000Z",
|
|
442
|
-
});
|
|
443
|
-
const expected = {
|
|
444
|
-
pathIncludes: `${record.outputId}:stderr`,
|
|
445
|
-
lines: "2-2",
|
|
446
|
-
requiredExcerpt: ["ASSERTION_FAILED", "payments badge"],
|
|
447
|
-
};
|
|
448
|
-
return {
|
|
449
|
-
id: "vaulted-output-query",
|
|
450
|
-
title: "Vaulted output query",
|
|
451
|
-
kind: "vault-query",
|
|
452
|
-
expected,
|
|
453
|
-
modes: {
|
|
454
|
-
"native-baseline-proxy": async () =>
|
|
455
|
-
directTextSearchObservation({
|
|
456
|
-
toolPathUsed: "native-baseline-proxy: direct command output text search",
|
|
457
|
-
text: stderr,
|
|
458
|
-
path: `${record.outputId}:stderr`,
|
|
459
|
-
query: "ASSERTION_FAILED payments badge",
|
|
460
|
-
recovery: {
|
|
461
|
-
status: "not-applicable",
|
|
462
|
-
detail: "Native direct output has no Freeflow vault recovery contract.",
|
|
463
|
-
},
|
|
464
|
-
}),
|
|
465
|
-
"pre-hardening-freeflow-proxy": async () =>
|
|
466
|
-
directTextSearchObservation({
|
|
467
|
-
toolPathUsed: "pre-hardening-freeflow-proxy: simple vaulted line scanner",
|
|
468
|
-
text: stderr,
|
|
469
|
-
path: `${record.outputId}:stderr`,
|
|
470
|
-
query: "ASSERTION_FAILED payments badge",
|
|
471
|
-
recovery: { status: "failed", detail: "Baseline proxy does not expose structured recovery metadata." },
|
|
472
|
-
}),
|
|
473
|
-
"improved-freeflow-router": async () =>
|
|
474
|
-
improvedRetrieveObservation(
|
|
475
|
-
await freeflowSearch({
|
|
476
|
-
action: "query",
|
|
477
|
-
source: { kind: "vault", root: vaultRoot.path, sessionId, outputId: record.outputId, stream: "stderr" },
|
|
478
|
-
query: "ASSERTION_FAILED payments badge",
|
|
479
|
-
preserve: "important",
|
|
480
|
-
}),
|
|
481
|
-
"improved-freeflow-router: freeflow_search vault query",
|
|
482
|
-
stderr,
|
|
483
|
-
(result) => verifyVaultEvidenceRecovery(vaultRoot.path, sessionId, result),
|
|
484
|
-
),
|
|
485
|
-
},
|
|
486
|
-
cleanup: vaultRoot.cleanup,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
async function createExpansionFixture() {
|
|
490
|
-
const body = [
|
|
491
|
-
"# Expansion Target",
|
|
492
|
-
"",
|
|
493
|
-
"EXPANSION_START router evidence begins here.",
|
|
494
|
-
...Array.from({ length: 20 }, (_, index) => `expansion context line ${index + 1}`),
|
|
495
|
-
"EXPANSION_END router evidence is complete here.",
|
|
496
|
-
];
|
|
497
|
-
const repo = await createTempRepo({
|
|
498
|
-
"target.md": body.join("\n"),
|
|
499
|
-
"decoy.md": "EXPANSION_START ".repeat(200),
|
|
500
|
-
});
|
|
501
|
-
const query = "EXPANSION_START router evidence";
|
|
502
|
-
const expected = {
|
|
503
|
-
path: "target.md",
|
|
504
|
-
lines: "1-24",
|
|
505
|
-
requiredExcerpt: ["EXPANSION_START", "EXPANSION_END"],
|
|
506
|
-
};
|
|
507
|
-
return {
|
|
508
|
-
id: "expand-narrow-evidence",
|
|
509
|
-
title: "Expansion from narrow evidence to complete block",
|
|
510
|
-
kind: "repo-expand",
|
|
511
|
-
expected,
|
|
512
|
-
modes: {
|
|
513
|
-
"native-baseline-proxy": async () =>
|
|
514
|
-
directFileObservation(repo.path, "target.md", {
|
|
515
|
-
toolPathUsed: "native-baseline-proxy: direct whole-file read",
|
|
516
|
-
recovery: { status: "not-applicable", detail: "Native read is exact direct output, not routed recovery." },
|
|
517
|
-
}),
|
|
518
|
-
"pre-hardening-freeflow-proxy": async () =>
|
|
519
|
-
repoBaselineObservation(repo.path, query, {
|
|
520
|
-
toolPathUsed: "pre-hardening-freeflow-proxy: narrow lexical line window without expansion",
|
|
521
|
-
skipDirs: LEGACY_SKIP_DIRS,
|
|
522
|
-
recovery: { status: "failed", detail: "Baseline proxy does not expose structured expansion recovery." },
|
|
523
|
-
}),
|
|
524
|
-
"improved-freeflow-router": async () => {
|
|
525
|
-
const queryResult = await freeflowSearch({
|
|
526
|
-
action: "query",
|
|
527
|
-
source: { kind: "repo", root: repo.path },
|
|
528
|
-
query,
|
|
529
|
-
preserve: "important",
|
|
530
|
-
});
|
|
531
|
-
const evidence = queryResult.evidence?.[0];
|
|
532
|
-
if (!evidence) {
|
|
533
|
-
return improvedRetrieveObservation(
|
|
534
|
-
queryResult,
|
|
535
|
-
"improved-freeflow-router: freeflow_search query before expand",
|
|
536
|
-
body.join("\n"),
|
|
537
|
-
(result) => verifyRepoEvidenceRecovery(repo.path, result),
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
return improvedRetrieveObservation(
|
|
541
|
-
await freeflowSearch({
|
|
542
|
-
action: "expand",
|
|
543
|
-
source: { kind: "repo", root: repo.path },
|
|
544
|
-
evidence,
|
|
545
|
-
expansion: "lines_30",
|
|
546
|
-
preserve: "important",
|
|
547
|
-
}),
|
|
548
|
-
"improved-freeflow-router: freeflow_search expand lines_30",
|
|
549
|
-
body.join("\n"),
|
|
550
|
-
(result) => verifyRepoEvidenceRecovery(repo.path, result),
|
|
551
|
-
);
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
cleanup: repo.cleanup,
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
function repoQueryFixture(repo, options) {
|
|
558
|
-
return {
|
|
559
|
-
id: options.id,
|
|
560
|
-
title: options.title,
|
|
561
|
-
kind: "repo-query",
|
|
562
|
-
expected: options.expected,
|
|
563
|
-
modes: {
|
|
564
|
-
"native-baseline-proxy": async () =>
|
|
565
|
-
repoBaselineObservation(repo.path, options.query, {
|
|
566
|
-
toolPathUsed: "native-baseline-proxy: recursive text scan (rg/read proxy)",
|
|
567
|
-
skipDirs: new Set(),
|
|
568
|
-
recovery: {
|
|
569
|
-
status: "not-applicable",
|
|
570
|
-
detail: "Native proxy returns direct text without Freeflow recovery metadata.",
|
|
571
|
-
},
|
|
572
|
-
}),
|
|
573
|
-
"pre-hardening-freeflow-proxy": async () =>
|
|
574
|
-
repoBaselineObservation(repo.path, options.query, {
|
|
575
|
-
toolPathUsed: "pre-hardening-freeflow-proxy: legacy line scorer",
|
|
576
|
-
skipDirs: LEGACY_SKIP_DIRS,
|
|
577
|
-
recovery: { status: "failed", detail: "Baseline proxy does not expose structured recovery metadata." },
|
|
578
|
-
}),
|
|
579
|
-
"improved-freeflow-router": async () =>
|
|
580
|
-
improvedRetrieveObservation(
|
|
581
|
-
await freeflowSearch({
|
|
582
|
-
action: "query",
|
|
583
|
-
source: { kind: "repo", root: repo.path },
|
|
584
|
-
query: options.query,
|
|
585
|
-
preserve: "important",
|
|
586
|
-
}),
|
|
587
|
-
"improved-freeflow-router: freeflow_search query",
|
|
588
|
-
await readRepoBytes(repo.path),
|
|
589
|
-
(result) => verifyRepoEvidenceRecovery(repo.path, result),
|
|
590
|
-
),
|
|
591
|
-
},
|
|
592
|
-
cleanup: repo.cleanup,
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
async function repoBaselineObservation(root, query, options) {
|
|
596
|
-
const files = await collectTextFiles(root, options.skipDirs);
|
|
597
|
-
const hit = findBestLineHit(files, query);
|
|
598
|
-
const rawBytes = byteLength(files.map((file) => file.text).join("\n"));
|
|
599
|
-
if (!hit) {
|
|
600
|
-
return {
|
|
601
|
-
toolPathUsed: options.toolPathUsed,
|
|
602
|
-
rawBytes,
|
|
603
|
-
routedBytes: 0,
|
|
604
|
-
excerpt: "",
|
|
605
|
-
recovery: options.recovery,
|
|
606
|
-
notes: ["no lexical hit"],
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
const range = contextRange(hit.file.lines, hit.lineIndex);
|
|
610
|
-
const excerpt = hit.file.lines.slice(range.start - 1, range.end).join("\n");
|
|
611
|
-
return {
|
|
612
|
-
toolPathUsed: options.toolPathUsed,
|
|
613
|
-
rawBytes,
|
|
614
|
-
routedBytes: byteLength(excerpt),
|
|
615
|
-
actualPath: hit.file.path,
|
|
616
|
-
actualLines: `${range.start}-${range.end}`,
|
|
617
|
-
excerpt,
|
|
618
|
-
recovery: options.recovery,
|
|
619
|
-
notes: [`score=${hit.score.toFixed(2)}`],
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
async function directFileObservation(root, path, options) {
|
|
623
|
-
const text = await readFile(resolve(root, path), "utf8");
|
|
624
|
-
const lines = splitLines(text);
|
|
625
|
-
return {
|
|
626
|
-
toolPathUsed: options.toolPathUsed,
|
|
627
|
-
rawBytes: byteLength(text),
|
|
628
|
-
routedBytes: byteLength(text),
|
|
629
|
-
actualPath: path,
|
|
630
|
-
actualLines: `1-${lines.length}`,
|
|
631
|
-
excerpt: text,
|
|
632
|
-
recovery: options.recovery,
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
async function directTextSearchObservation(options) {
|
|
636
|
-
const file = { path: options.path, text: options.text, lines: splitLines(options.text) };
|
|
637
|
-
const hit = findBestLineHit([file], options.query);
|
|
638
|
-
if (!hit) {
|
|
639
|
-
return {
|
|
640
|
-
toolPathUsed: options.toolPathUsed,
|
|
641
|
-
rawBytes: byteLength(options.text),
|
|
642
|
-
routedBytes: 0,
|
|
643
|
-
excerpt: "",
|
|
644
|
-
recovery: options.recovery,
|
|
645
|
-
notes: ["no lexical hit"],
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
const range = contextRange(file.lines, hit.lineIndex);
|
|
649
|
-
const excerpt = file.lines.slice(range.start - 1, range.end).join("\n");
|
|
650
|
-
return {
|
|
651
|
-
toolPathUsed: options.toolPathUsed,
|
|
652
|
-
rawBytes: byteLength(options.text),
|
|
653
|
-
routedBytes: byteLength(excerpt),
|
|
654
|
-
actualPath: options.path,
|
|
655
|
-
actualLines: `${range.start}-${range.end}`,
|
|
656
|
-
excerpt,
|
|
657
|
-
recovery: options.recovery,
|
|
658
|
-
notes: [`score=${hit.score.toFixed(2)}`],
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
async function improvedRetrieveObservation(result, toolPathUsed, rawSource, verifyRecovery) {
|
|
662
|
-
const evidence = result.evidence?.[0];
|
|
663
|
-
const excerpt = result.evidence?.map((packet) => packet.excerpt).join("\n") ?? "";
|
|
664
|
-
const recoveryGuidancePresent = Boolean(
|
|
665
|
-
result.recovery?.how &&
|
|
666
|
-
(result.recovery.outputId || result.recovery.evidenceId || result.evidence?.some((packet) => packet.expandable)),
|
|
667
|
-
);
|
|
668
|
-
const notes = [result.routing.reason];
|
|
669
|
-
if (result.evidence && result.evidence.length > 1) {
|
|
670
|
-
notes.push(`evidencePackets=${result.evidence.length}`);
|
|
671
|
-
}
|
|
672
|
-
let recovery = recoveryGuidancePresent
|
|
673
|
-
? { status: "passed", detail: result.recovery?.how ?? "Routed result exposes recovery guidance." }
|
|
674
|
-
: { status: "failed", detail: "Routed result did not expose recovery guidance." };
|
|
675
|
-
if (recoveryGuidancePresent && verifyRecovery) {
|
|
676
|
-
recovery = await verifyRecovery(result);
|
|
677
|
-
notes.push(`recovery=${recovery.status}: ${recovery.detail}`);
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
toolPathUsed,
|
|
681
|
-
rawBytes: byteLength(rawSource),
|
|
682
|
-
routedBytes: byteLength(JSON.stringify(result)),
|
|
683
|
-
...(evidence?.path ? { actualPath: evidence.path } : {}),
|
|
684
|
-
...(evidence?.lines ? { actualLines: evidence.lines } : {}),
|
|
685
|
-
excerpt,
|
|
686
|
-
recovery,
|
|
687
|
-
notes,
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
async function verifyRepoEvidenceRecovery(root, result) {
|
|
691
|
-
const evidence = result.evidence?.[0];
|
|
692
|
-
if (!evidence?.path || !evidence.lines) {
|
|
693
|
-
return { status: "failed", detail: "No repo evidence path/lines available for recovery verification." };
|
|
694
|
-
}
|
|
695
|
-
const range = parseRange(evidence.lines);
|
|
696
|
-
if (!range) {
|
|
697
|
-
return { status: "failed", detail: `Unsupported repo evidence range ${evidence.lines}.` };
|
|
698
|
-
}
|
|
699
|
-
const recovered = await freeflowSearch({
|
|
700
|
-
action: "retrieve",
|
|
701
|
-
source: { kind: "repo", root, path: evidence.path },
|
|
702
|
-
lineRange: range,
|
|
703
|
-
preserve: "full",
|
|
704
|
-
});
|
|
705
|
-
return verifyRecoveredEvidence(recovered, evidence, `Verified repo retrieve ${evidence.path}:${evidence.lines}.`);
|
|
706
|
-
}
|
|
707
|
-
async function verifyVaultEvidenceRecovery(vaultRoot, sessionId, result) {
|
|
708
|
-
const evidence = result.evidence?.[0];
|
|
709
|
-
if (!evidence?.lines || evidence.source.kind !== "vault") {
|
|
710
|
-
return { status: "failed", detail: "No vault evidence outputId/lines available for recovery verification." };
|
|
711
|
-
}
|
|
712
|
-
const range = parseRange(evidence.lines);
|
|
713
|
-
if (!range) {
|
|
714
|
-
return { status: "failed", detail: `Unsupported vault evidence range ${evidence.lines}.` };
|
|
715
|
-
}
|
|
716
|
-
const recovered = await freeflowSearch({
|
|
717
|
-
action: "retrieve",
|
|
718
|
-
source: {
|
|
719
|
-
kind: "vault",
|
|
720
|
-
root: vaultRoot,
|
|
721
|
-
sessionId,
|
|
722
|
-
outputId: evidence.source.outputId,
|
|
723
|
-
stream: evidence.source.stream ?? "combined",
|
|
724
|
-
},
|
|
725
|
-
lineRange: range,
|
|
726
|
-
preserve: "full",
|
|
727
|
-
});
|
|
728
|
-
return verifyRecoveredEvidence(
|
|
729
|
-
recovered,
|
|
730
|
-
evidence,
|
|
731
|
-
`Verified vault retrieve ${evidence.source.outputId}:${evidence.source.stream ?? "combined"}:${evidence.lines}.`,
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
function verifyRecoveredEvidence(recovered, originalEvidence, successDetail) {
|
|
735
|
-
const recoveredExcerpt = recovered.evidence?.map((packet) => packet.excerpt).join("\n") ?? "";
|
|
736
|
-
const anchor = firstRecoveryAnchor(originalEvidence.excerpt);
|
|
737
|
-
if (recovered.toolStatus !== "ok" || recovered.evidence?.length !== 1) {
|
|
738
|
-
return { status: "failed", detail: "Recovery retrieve did not return one ok evidence packet." };
|
|
739
|
-
}
|
|
740
|
-
if (anchor && !recoveredExcerpt.includes(anchor)) {
|
|
741
|
-
return { status: "failed", detail: "Recovery retrieve did not include the original evidence anchor." };
|
|
742
|
-
}
|
|
743
|
-
return { status: "passed", detail: successDetail };
|
|
744
|
-
}
|
|
745
|
-
function firstRecoveryAnchor(excerpt) {
|
|
746
|
-
return (
|
|
747
|
-
splitLines(excerpt)
|
|
748
|
-
.map((line) => line.trim())
|
|
749
|
-
.find((line) => line.length > 0)
|
|
750
|
-
?.slice(0, 80) ?? ""
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
function scoreCorrectness(expected, observation) {
|
|
754
|
-
if (observation.skipped) {
|
|
755
|
-
return {
|
|
756
|
-
passed: false,
|
|
757
|
-
pathCorrect: false,
|
|
758
|
-
spanCorrect: false,
|
|
759
|
-
excerptComplete: false,
|
|
760
|
-
generatedFalsePositive: false,
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
const pathCorrect = expected.path
|
|
764
|
-
? observation.actualPath === expected.path
|
|
765
|
-
: expected.pathIncludes
|
|
766
|
-
? Boolean(observation.actualPath?.includes(expected.pathIncludes))
|
|
767
|
-
: true;
|
|
768
|
-
const spanCorrect = expected.lines
|
|
769
|
-
? Boolean(observation.actualLines && rangesOverlap(expected.lines, observation.actualLines))
|
|
770
|
-
: true;
|
|
771
|
-
const excerptComplete = (expected.requiredExcerpt ?? []).every((snippet) =>
|
|
772
|
-
observation.excerpt.toLowerCase().includes(snippet.toLowerCase()),
|
|
773
|
-
);
|
|
774
|
-
const generatedFalsePositive = observation.actualPath ? isGeneratedBenchmarkPath(observation.actualPath) : false;
|
|
775
|
-
const passed = pathCorrect && spanCorrect && excerptComplete && !generatedFalsePositive;
|
|
776
|
-
return {
|
|
777
|
-
passed,
|
|
778
|
-
pathCorrect,
|
|
779
|
-
spanCorrect,
|
|
780
|
-
excerptComplete,
|
|
781
|
-
generatedFalsePositive,
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
async function createTempRepo(files) {
|
|
785
|
-
const root = await createTempDir("freeflow-router-benchmark-repo-");
|
|
786
|
-
for (const [path, text] of Object.entries(files)) {
|
|
787
|
-
const absolutePath = resolve(root.path, path);
|
|
788
|
-
await mkdir(dirname(absolutePath), { recursive: true });
|
|
789
|
-
await writeFile(absolutePath, text, "utf8");
|
|
790
|
-
}
|
|
791
|
-
return root;
|
|
792
|
-
}
|
|
793
|
-
async function createTempDir(prefix) {
|
|
794
|
-
const path = await mkdtemp(resolve(tmpdir(), prefix));
|
|
795
|
-
return {
|
|
796
|
-
path,
|
|
797
|
-
cleanup: async () => {
|
|
798
|
-
await rm(path, { recursive: true, force: true });
|
|
799
|
-
},
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
async function collectTextFiles(root, skipDirs) {
|
|
803
|
-
const files = [];
|
|
804
|
-
await collectTextFilesInto(resolve(root), resolve(root), skipDirs, files);
|
|
805
|
-
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
806
|
-
}
|
|
807
|
-
async function collectTextFilesInto(root, currentPath, skipDirs, files) {
|
|
808
|
-
const currentStat = await stat(currentPath);
|
|
809
|
-
const name = currentPath.split(/[\\/]+/).at(-1) ?? currentPath;
|
|
810
|
-
if (currentStat.isDirectory()) {
|
|
811
|
-
if (currentPath !== root && skipDirs.has(name)) {
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
815
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
816
|
-
await collectTextFilesInto(root, resolve(currentPath, entry.name), skipDirs, files);
|
|
817
|
-
}
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
if (!currentStat.isFile()) {
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
const text = await readFile(currentPath, "utf8");
|
|
824
|
-
if (text.includes("\0")) {
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
files.push({
|
|
828
|
-
path: normalizeRelativePath(relative(root, currentPath)),
|
|
829
|
-
text,
|
|
830
|
-
lines: splitLines(text),
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
function findBestLineHit(files, query) {
|
|
834
|
-
const tokens = tokenize(query);
|
|
835
|
-
let best = null;
|
|
836
|
-
for (const file of files) {
|
|
837
|
-
const pathScore = scoreText(file.path, tokens);
|
|
838
|
-
file.lines.forEach((line, lineIndex) => {
|
|
839
|
-
const lineScore = scoreText(line, tokens);
|
|
840
|
-
const headingBonus = line.trimStart().startsWith("#") ? 2 : 0;
|
|
841
|
-
const score = lineScore * 4 + pathScore + headingBonus;
|
|
842
|
-
if (score <= 0) {
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (!best || score > best.score) {
|
|
846
|
-
best = { file, lineIndex, score };
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
return best;
|
|
851
|
-
}
|
|
852
|
-
function contextRange(lines, lineIndex) {
|
|
853
|
-
return {
|
|
854
|
-
start: Math.max(1, lineIndex + 1 - DEFAULT_CONTEXT_LINES),
|
|
855
|
-
end: Math.min(lines.length, lineIndex + 1 + DEFAULT_CONTEXT_LINES),
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
async function readRepoBytes(root) {
|
|
859
|
-
const files = await collectTextFiles(root, new Set());
|
|
860
|
-
return files.map((file) => file.text).join("\n");
|
|
861
|
-
}
|
|
862
|
-
function rangesOverlap(left, right) {
|
|
863
|
-
const leftRange = parseRange(left);
|
|
864
|
-
const rightRange = parseRange(right);
|
|
865
|
-
if (!leftRange || !rightRange) {
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
return leftRange.start <= rightRange.end && rightRange.start <= leftRange.end;
|
|
869
|
-
}
|
|
870
|
-
function parseRange(range) {
|
|
871
|
-
const match = /^(\d+)-(\d+)$/.exec(range);
|
|
872
|
-
if (!match) {
|
|
873
|
-
return null;
|
|
874
|
-
}
|
|
875
|
-
const start = Number(match[1]);
|
|
876
|
-
const end = Number(match[2]);
|
|
877
|
-
return Number.isInteger(start) && Number.isInteger(end) ? { start, end } : null;
|
|
878
|
-
}
|
|
879
|
-
function isGeneratedBenchmarkPath(path) {
|
|
880
|
-
const segments = path.split(/[\\/]+/);
|
|
881
|
-
if (segments.some((segment) => GENERATED_DIRS.has(segment))) {
|
|
882
|
-
return true;
|
|
883
|
-
}
|
|
884
|
-
const name = segments.at(-1)?.toLowerCase() ?? path.toLowerCase();
|
|
885
|
-
return (
|
|
886
|
-
name.endsWith(".log") ||
|
|
887
|
-
name.endsWith(".map") ||
|
|
888
|
-
name.endsWith(".min.js") ||
|
|
889
|
-
name.endsWith(".min.css") ||
|
|
890
|
-
name.includes(".bundle.")
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
function scoreText(text, tokens) {
|
|
894
|
-
const lower = text.toLowerCase();
|
|
895
|
-
return tokens.reduce((score, token) => score + countOccurrences(lower, token), 0);
|
|
896
|
-
}
|
|
897
|
-
function countOccurrences(text, token) {
|
|
898
|
-
let count = 0;
|
|
899
|
-
let index = text.indexOf(token);
|
|
900
|
-
while (index !== -1) {
|
|
901
|
-
count += 1;
|
|
902
|
-
index = text.indexOf(token, index + token.length);
|
|
903
|
-
}
|
|
904
|
-
return count;
|
|
905
|
-
}
|
|
906
|
-
function tokenize(query) {
|
|
907
|
-
return query
|
|
908
|
-
.toLowerCase()
|
|
909
|
-
.split(/[^a-z0-9_./-]+/)
|
|
910
|
-
.map((token) => token.trim())
|
|
911
|
-
.filter((token) => token.length >= 2);
|
|
912
|
-
}
|
|
913
|
-
function splitLines(text) {
|
|
914
|
-
if (text.length === 0) {
|
|
915
|
-
return [];
|
|
916
|
-
}
|
|
917
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
918
|
-
}
|
|
919
|
-
function byteLength(value) {
|
|
920
|
-
return Buffer.byteLength(value, "utf8");
|
|
921
|
-
}
|
|
922
|
-
function formatCorrectnessChecks(correctness) {
|
|
923
|
-
return [
|
|
924
|
-
`path ${correctness.pathCorrect ? "✓" : "✗"}`,
|
|
925
|
-
`span ${correctness.spanCorrect ? "✓" : "✗"}`,
|
|
926
|
-
`excerpt ${correctness.excerptComplete ? "✓" : "✗"}`,
|
|
927
|
-
`gen-fp ${correctness.generatedFalsePositive ? "✗" : "✓"}`,
|
|
928
|
-
].join(" ");
|
|
929
|
-
}
|
|
930
|
-
function benchmarkModes() {
|
|
931
|
-
return ["native-baseline-proxy", "pre-hardening-freeflow-proxy", "improved-freeflow-router"];
|
|
932
|
-
}
|
|
933
|
-
function skippedObservation(toolPathUsed, reason) {
|
|
934
|
-
return {
|
|
935
|
-
toolPathUsed,
|
|
936
|
-
rawBytes: 0,
|
|
937
|
-
routedBytes: 0,
|
|
938
|
-
excerpt: "",
|
|
939
|
-
recovery: { status: "skipped", detail: reason },
|
|
940
|
-
skipped: true,
|
|
941
|
-
skipReason: reason,
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
function normalizeRelativePath(path) {
|
|
945
|
-
return path
|
|
946
|
-
.split(/[/\\]+/)
|
|
947
|
-
.filter(Boolean)
|
|
948
|
-
.join("/");
|
|
949
|
-
}
|
|
950
|
-
function defaultReportPath() {
|
|
951
|
-
return resolve(process.cwd(), "router/evals/reports/output-router-benchmark-1-report.md");
|
|
952
|
-
}
|
|
953
|
-
async function runCli() {
|
|
954
|
-
const { iterations, reportPath, jsonReportPath } = parseBenchmarkCliArgs(process.argv.slice(2), {
|
|
955
|
-
reportPath: defaultReportPath(),
|
|
956
|
-
});
|
|
957
|
-
const options = {};
|
|
958
|
-
if (iterations !== undefined) {
|
|
959
|
-
options.iterations = iterations;
|
|
960
|
-
}
|
|
961
|
-
const report = await runRouterBenchmarks(options);
|
|
962
|
-
const reports = await writeRouterBenchmarkReports(report, reportPath, {
|
|
963
|
-
jsonReportPath: jsonReportPath === undefined ? defaultJsonRunReportPath(reportPath) : jsonReportPath,
|
|
964
|
-
});
|
|
965
|
-
const shortId = createHash("sha256").update(JSON.stringify(report.summary)).digest("hex").slice(0, 8);
|
|
966
|
-
console.log(
|
|
967
|
-
`Freeflow router benchmark ${shortId}: improved ${report.summary.improved.passed}/${report.summary.fixtures} pass`,
|
|
968
|
-
);
|
|
969
|
-
console.log(`Markdown report: ${reports.markdown}`);
|
|
970
|
-
if (reports.json) {
|
|
971
|
-
console.log(`JSON run data: ${reports.json}`);
|
|
972
|
-
}
|
|
973
|
-
if (report.summary.improved.failed > 0) {
|
|
974
|
-
process.exitCode = 1;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
978
|
-
await runCli();
|
|
979
|
-
}
|