@hassangameryt/freeflow 0.5.0 → 0.7.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 +2 -2
- package/.codex-plugin/plugin.json +4 -5
- package/README.md +141 -163
- package/capabilities/cognitive-routing/SKILL.md +387 -0
- package/capabilities/context-virtualization/SKILL.md +103 -0
- package/capabilities/conversation-history/SKILL.md +53 -0
- package/command-surface.json +6 -37
- package/hooks/freeflow-runtime-context.mjs +50 -541
- package/hooks/hooks.json +1 -26
- package/hooks/tests/check-runtime-context-hook.sh +114 -308
- package/package.json +27 -17
- package/pi-extension/dist/cognitive-routing/commands.js +96 -0
- package/pi-extension/dist/cognitive-routing/config.js +232 -0
- package/pi-extension/dist/cognitive-routing/controller.js +1292 -0
- package/pi-extension/dist/cognitive-routing/history.js +451 -0
- package/pi-extension/dist/cognitive-routing/host.js +188 -0
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
- package/pi-extension/dist/cognitive-routing/runtime.js +43 -0
- package/pi-extension/dist/cognitive-routing/tool.js +212 -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 +564 -129
- package/pi-extension/dist/runtime/runtime-context.js +357 -470
- 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 +572 -826
- package/pi-extension/dist/ui/text-component.js +57 -0
- package/pi-extension/src/cognitive-routing/commands.ts +129 -0
- package/pi-extension/src/cognitive-routing/config.ts +303 -0
- package/pi-extension/src/cognitive-routing/controller.ts +1628 -0
- package/pi-extension/src/cognitive-routing/history.ts +650 -0
- package/pi-extension/src/cognitive-routing/host.ts +266 -0
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
- package/pi-extension/src/cognitive-routing/runtime.ts +57 -0
- package/pi-extension/src/cognitive-routing/tool.ts +273 -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 +597 -135
- package/pi-extension/src/runtime/runtime-context.ts +373 -491
- 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 +679 -910
- 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 +1128 -0
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +1142 -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/pi-normal-lifecycle.test.js +434 -0
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +279 -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/mode-free-core.test.js +218 -0
- package/pi-extension/tests/integration/pi-extension.test.js +532 -3701
- package/pi-extension/tests/integration/prompt-architecture.test.js +350 -0
- package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
- package/runtime/prompts/cognitive-routing.md +10 -0
- package/runtime/prompts/context-virtualization.md +3 -0
- package/runtime/prompts/conversation-history.md +3 -0
- package/runtime/prompts/core.md +84 -0
- package/runtime/prompts/interaction-contract.md +11 -0
- package/skills/action-selection/SKILL.md +140 -0
- package/skills/action-selection/references/trajectory-stalls.md +71 -0
- package/skills/bypass/SKILL.md +109 -38
- package/skills/commit-work/SKILL.md +94 -55
- package/skills/commit-work/references/git-state-edges.md +94 -0
- package/skills/commit-work/references/simple-push.md +92 -0
- package/skills/commit-work/references/staging-decisions.md +79 -46
- package/skills/decision-gate/SKILL.md +80 -22
- package/skills/design-for-depth/SKILL.md +206 -60
- package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
- package/skills/design-for-depth/references/interface-design-loop.md +77 -97
- package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
- package/skills/design-for-depth/references/software-design-philosophy.md +128 -174
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
- package/skills/diagnose-failure/SKILL.md +200 -48
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +18 -6
- package/skills/diagnose-failure/references/{flaky-and-performance.md → flaky-and-performance-diagnosis.md} +13 -5
- package/skills/discuss/SKILL.md +119 -65
- package/skills/discuss/references/discussion-continuity.md +123 -0
- package/skills/evaluate-skill/SKILL.md +166 -52
- package/skills/evaluate-skill/references/{definition-schema.md → evaluation-definition-schema.md} +43 -3
- package/skills/evaluate-skill/references/evaluation-design.md +56 -30
- package/skills/evaluate-skill/references/execution-and-evidence.md +8 -3
- package/skills/evaluate-skill/references/review-and-revision.md +62 -21
- 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 +99 -57
- package/skills/execute-work/references/code-practices.md +4 -4
- package/skills/execute-work/references/execute-work-edges.md +62 -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/release-work/SKILL.md +1 -1
- package/skills/release-work/references/release-evidence.md +1 -1
- package/skills/review-artifact/SKILL.md +129 -66
- package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +159 -0
- package/skills/review-work/SKILL.md +113 -68
- package/skills/review-work/references/adjudicate-work-review.md +97 -0
- package/skills/review-work/references/independent-work-reviewer-contract.md +139 -0
- package/skills/review-work/references/security-risk-lens.md +18 -16
- package/skills/setup-freeflow/SKILL.md +16 -17
- package/skills/setup-freeflow/references/activation-contract.md +24 -30
- package/skills/setup-freeflow/references/host-setup.md +6 -7
- package/skills/simplify-code/SKILL.md +97 -39
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +100 -53
- package/skills/tdd/references/test-design.md +3 -3
- package/skills/track-work/SKILL.md +195 -58
- package/skills/track-work/references/working-record-format.md +515 -0
- package/skills/track-work/scripts/lib/document.mjs +713 -0
- package/skills/track-work/scripts/lib/format.mjs +166 -0
- package/skills/track-work/scripts/lib/store.mjs +233 -0
- package/skills/track-work/scripts/lib/transitions.mjs +790 -0
- package/skills/track-work/scripts/lib/views.mjs +40 -0
- package/skills/track-work/scripts/working-record.mjs +316 -0
- package/skills/verify-work/SKILL.md +57 -19
- package/skills/verify-work/references/browser-runtime-evidence.md +2 -2
- package/skills/verify-work/references/integration-evidence.md +1 -7
- package/skills/verify-work/references/performance-evidence.md +2 -2
- package/skills/workflow/SKILL.md +137 -55
- package/skills/workflow/references/domain-skill-composition.md +26 -40
- package/skills/write-plan/SKILL.md +1 -1
- package/skills/write-skill/SKILL.md +218 -61
- package/skills/write-skill/references/activation-boundaries.md +63 -21
- package/skills/write-skill/references/agent-first-instructions.md +61 -27
- package/skills/write-skill/references/progressive-disclosure.md +97 -33
- package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
- 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/mode-contract/SKILL.md +0 -104
- package/skills/review-artifact/references/reviewer-prompt.md +0 -148
- package/skills/review-work/references/reviewer-prompt.md +0 -134
- 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
|
@@ -1,1380 +0,0 @@
|
|
|
1
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
-
import { cp, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import { performance } from "node:perf_hooks";
|
|
9
|
-
import {
|
|
10
|
-
defaultJsonRunReportPath,
|
|
11
|
-
escapeMarkdownTableCell as escapeMarkdown,
|
|
12
|
-
parseBenchmarkCliArgs,
|
|
13
|
-
writeBenchmarkReportPair,
|
|
14
|
-
} from "./benchmark-harness.js";
|
|
15
|
-
import { freeflowBatch } from "../tools/batch.js";
|
|
16
|
-
import { freeflowSearch } from "../tools/search.js";
|
|
17
|
-
import { processSource } from "../processing/engine.js";
|
|
18
|
-
import { freeflowRun } from "../tools/run.js";
|
|
19
|
-
import { freeflowTransform } from "../transform/engine.js";
|
|
20
|
-
const DEFAULT_CONTEXT_MODE_REPO = "/tmp/pi-github-repos/mksglu/context-mode";
|
|
21
|
-
const DEFAULT_CONTEXT_MODE_VERSION = "unknown";
|
|
22
|
-
const DEFAULT_ITERATION_LABEL = "Iteration 1";
|
|
23
|
-
export const CONTEXT_MODE_REAL_DEEP_IMPLEMENTATION = "context-mode-real-deep-benchmark-v1";
|
|
24
|
-
export const EXPECTED_BASELINE_FAILURE_KEYS = ["outside-file-boundary/freeflow:run-cat-host-shell"];
|
|
25
|
-
export async function runContextModeRealDeepBenchmark(options = {}) {
|
|
26
|
-
const availability = resolveContextModeAvailability(options);
|
|
27
|
-
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
28
|
-
const freeflowRepo = path.resolve(options.freeflowRepo ?? process.cwd());
|
|
29
|
-
if (availability.status === "unavailable") {
|
|
30
|
-
return unavailableReport({ availability, generatedAt, freeflowRepo });
|
|
31
|
-
}
|
|
32
|
-
const runtime = await loadContextModeRuntime(availability);
|
|
33
|
-
const root = await createTempDir("freeflow-context-mode-deep-");
|
|
34
|
-
const projectRoot = path.join(root.path, "project");
|
|
35
|
-
const storeRoot = path.join(root.path, "context-mode-store");
|
|
36
|
-
const freeflowVaultRoot = path.join(root.path, "freeflow-vault");
|
|
37
|
-
const sessionId = `deep-context-mode-${createHash("sha1").update(root.path).digest("hex").slice(0, 10)}`;
|
|
38
|
-
const rows = [];
|
|
39
|
-
let cm;
|
|
40
|
-
try {
|
|
41
|
-
await setupProject({ projectRoot, fixtureSource: availability.fixtureSource, outsideRoot: root.path });
|
|
42
|
-
await mkdir(storeRoot, { recursive: true });
|
|
43
|
-
await mkdir(freeflowVaultRoot, { recursive: true });
|
|
44
|
-
const runner = {
|
|
45
|
-
async run(request) {
|
|
46
|
-
const shellOptions = {};
|
|
47
|
-
if (request.cwd !== undefined) {
|
|
48
|
-
shellOptions.cwd = request.cwd;
|
|
49
|
-
}
|
|
50
|
-
if (request.timeoutMs !== undefined) {
|
|
51
|
-
shellOptions.timeoutMs = request.timeoutMs;
|
|
52
|
-
}
|
|
53
|
-
return runShell(request.command, shellOptions);
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
const transport = new runtime.StdioClientTransport({
|
|
57
|
-
command: "bash",
|
|
58
|
-
args: [
|
|
59
|
-
"-lc",
|
|
60
|
-
`cd ${shellQuote(projectRoot)} && CONTEXT_MODE_DIR=${shellQuote(storeRoot)} node ${shellQuote(availability.server)}`,
|
|
61
|
-
],
|
|
62
|
-
env: process.env,
|
|
63
|
-
});
|
|
64
|
-
cm = new runtime.Client({ name: "freeflow-deep-context-mode-benchmark", version: "0.0.1" });
|
|
65
|
-
await cm.connect(transport);
|
|
66
|
-
async function callCM(name, args) {
|
|
67
|
-
if (!cm) {
|
|
68
|
-
throw new Error("Context Mode client is not connected.");
|
|
69
|
-
}
|
|
70
|
-
const start = performance.now();
|
|
71
|
-
const result = await cm.callTool({ name, arguments: args });
|
|
72
|
-
return { result, text: contentText(result), latencyMs: Math.round(performance.now() - start) };
|
|
73
|
-
}
|
|
74
|
-
async function ffRun(command, goal, extra = {}) {
|
|
75
|
-
const start = performance.now();
|
|
76
|
-
const runOptions = {
|
|
77
|
-
command,
|
|
78
|
-
cwd: projectRoot,
|
|
79
|
-
timeoutMs: 30_000,
|
|
80
|
-
sessionId,
|
|
81
|
-
vaultRoot: freeflowVaultRoot,
|
|
82
|
-
preserve: "important",
|
|
83
|
-
goal,
|
|
84
|
-
...extra,
|
|
85
|
-
};
|
|
86
|
-
const result = await freeflowRun(runOptions, runner);
|
|
87
|
-
return { result, text: freeflowText(result), latencyMs: Math.round(performance.now() - start) };
|
|
88
|
-
}
|
|
89
|
-
async function ffRetrieve(query, sourceRoot = projectRoot, extra = {}) {
|
|
90
|
-
const start = performance.now();
|
|
91
|
-
const retrieveOptions = {
|
|
92
|
-
action: "query",
|
|
93
|
-
source: { kind: "repo", root: sourceRoot },
|
|
94
|
-
query,
|
|
95
|
-
topK: 3,
|
|
96
|
-
preserve: "important",
|
|
97
|
-
generatedPathGlobs: ["graphify-out/**"],
|
|
98
|
-
...extra,
|
|
99
|
-
};
|
|
100
|
-
const result = await freeflowSearch(retrieveOptions);
|
|
101
|
-
return { result, text: freeflowText(result), latencyMs: Math.round(performance.now() - start) };
|
|
102
|
-
}
|
|
103
|
-
async function ffTransform(sourceOutputId, stream, operation) {
|
|
104
|
-
const start = performance.now();
|
|
105
|
-
const result = await freeflowTransform({
|
|
106
|
-
source: { kind: "vault", outputId: sourceOutputId, stream },
|
|
107
|
-
operation,
|
|
108
|
-
preserve: "important",
|
|
109
|
-
sessionId,
|
|
110
|
-
vaultRoot: freeflowVaultRoot,
|
|
111
|
-
});
|
|
112
|
-
return { result, text: freeflowText(result), latencyMs: Math.round(performance.now() - start) };
|
|
113
|
-
}
|
|
114
|
-
async function ffProcessRepoFile(relativePath) {
|
|
115
|
-
const start = performance.now();
|
|
116
|
-
const result = await processSource(
|
|
117
|
-
{ kind: "repo-file", root: projectRoot, path: relativePath },
|
|
118
|
-
{ sessionId, vaultRoot: freeflowVaultRoot },
|
|
119
|
-
);
|
|
120
|
-
return { result, text: processingText(result), latencyMs: Math.round(performance.now() - start) };
|
|
121
|
-
}
|
|
122
|
-
function record(input) {
|
|
123
|
-
const text = input.obs.text;
|
|
124
|
-
const factsFound = factCount(text, input.facts);
|
|
125
|
-
const correct = input.expectedCorrect ?? factsFound === input.facts.length;
|
|
126
|
-
rows.push({
|
|
127
|
-
fixture: input.fixture,
|
|
128
|
-
category: input.category,
|
|
129
|
-
mode: input.mode,
|
|
130
|
-
capability: input.capability ?? "",
|
|
131
|
-
correct,
|
|
132
|
-
factsFound,
|
|
133
|
-
factsTotal: input.facts.length,
|
|
134
|
-
rawBytes: input.rawBytes,
|
|
135
|
-
visibleBytes: bytes(text),
|
|
136
|
-
reductionPct: input.rawBytes ? Number(((1 - bytes(text) / input.rawBytes) * 100).toFixed(2)) : 0,
|
|
137
|
-
latencyMs: input.obs.latencyMs ?? 0,
|
|
138
|
-
recovery: recoveryScore(input.obs.result, input.mode),
|
|
139
|
-
notes: input.notes ?? "",
|
|
140
|
-
preview: text.split("\n").slice(0, 18).join("\n"),
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
function fileSize(rel) {
|
|
144
|
-
return statSync(path.join(projectRoot, rel)).size;
|
|
145
|
-
}
|
|
146
|
-
function scriptCode(rel, code) {
|
|
147
|
-
return `import { readFileSync } from 'node:fs';\nconst FILE_CONTENT = readFileSync(${JSON.stringify(rel)}, 'utf8');\n${code}\n`;
|
|
148
|
-
}
|
|
149
|
-
async function writeScenarioScript(id, rel, code) {
|
|
150
|
-
const scriptPath = path.join(projectRoot, "scripts", `${id}.mjs`);
|
|
151
|
-
await writeFile(scriptPath, scriptCode(rel, code), "utf8");
|
|
152
|
-
return `node scripts/${id}.mjs`;
|
|
153
|
-
}
|
|
154
|
-
const executeScenarios = createExecuteScenarios();
|
|
155
|
-
for (const scenario of executeScenarios) {
|
|
156
|
-
const rawBytes = fileSize(scenario.file);
|
|
157
|
-
const cmObs = await callCM("ctx_execute_file", {
|
|
158
|
-
path: scenario.file,
|
|
159
|
-
language: "javascript",
|
|
160
|
-
code: scenario.code,
|
|
161
|
-
intent: scenario.goal,
|
|
162
|
-
});
|
|
163
|
-
record({
|
|
164
|
-
fixture: scenario.id,
|
|
165
|
-
category: scenario.category,
|
|
166
|
-
rawBytes,
|
|
167
|
-
facts: scenario.facts,
|
|
168
|
-
mode: "context-mode:ctx_execute_file",
|
|
169
|
-
capability: "sandboxed file transform",
|
|
170
|
-
obs: cmObs,
|
|
171
|
-
});
|
|
172
|
-
const ffDefault = await ffRun(`cat ${shellQuote(scenario.file)}`, scenario.goal);
|
|
173
|
-
record({
|
|
174
|
-
fixture: scenario.id,
|
|
175
|
-
category: scenario.category,
|
|
176
|
-
rawBytes,
|
|
177
|
-
facts: scenario.facts,
|
|
178
|
-
mode: "freeflow:run-cat-default",
|
|
179
|
-
capability: "command capture/routing",
|
|
180
|
-
obs: ffDefault,
|
|
181
|
-
});
|
|
182
|
-
const processingReducerModes = {
|
|
183
|
-
"vitest-summary": { mode: "freeflow:process-test-output-reducer", reducer: "test-output" },
|
|
184
|
-
"tsc-summary": { mode: "freeflow:process-diagnostics-reducer", reducer: "diagnostics" },
|
|
185
|
-
"build-summary": { mode: "freeflow:process-build-output-reducer", reducer: "build-output" },
|
|
186
|
-
"access-summary": { mode: "freeflow:process-access-log-reducer", reducer: "access-log" },
|
|
187
|
-
"analytics-summary": { mode: "freeflow:process-table-reducer", reducer: "table" },
|
|
188
|
-
"mcp-tools-summary": { mode: "freeflow:process-mcp-tools-reducer", reducer: "mcp-tools" },
|
|
189
|
-
"playwright-summary": { mode: "freeflow:process-browser-snapshot-reducer", reducer: "browser-snapshot" },
|
|
190
|
-
"git-log-summary": { mode: "freeflow:process-git-log-reducer", reducer: "git-log" },
|
|
191
|
-
};
|
|
192
|
-
const reducerMode = processingReducerModes[scenario.id];
|
|
193
|
-
if (reducerMode) {
|
|
194
|
-
const ffProcessed = await ffProcessRepoFile(scenario.file);
|
|
195
|
-
record({
|
|
196
|
-
fixture: scenario.id,
|
|
197
|
-
category: scenario.category,
|
|
198
|
-
rawBytes,
|
|
199
|
-
facts: scenario.facts,
|
|
200
|
-
mode: reducerMode.mode,
|
|
201
|
-
capability: "processing engine built-in reducer",
|
|
202
|
-
obs: ffProcessed,
|
|
203
|
-
notes: `Uses the internal processing engine ${reducerMode.reducer} reducer; no public Pi surface is selected.`,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
const command = await writeScenarioScript(scenario.id, scenario.file, scenario.code);
|
|
207
|
-
const ffComputed = await ffRun(command, scenario.goal);
|
|
208
|
-
record({
|
|
209
|
-
fixture: scenario.id,
|
|
210
|
-
category: scenario.category,
|
|
211
|
-
rawBytes,
|
|
212
|
-
facts: scenario.facts,
|
|
213
|
-
mode: "freeflow:run-computed-script",
|
|
214
|
-
capability: "host-shell transform via freeflow_run",
|
|
215
|
-
obs: ffComputed,
|
|
216
|
-
notes: "Compact because an external Node script computed facts; not Freeflow sandboxed script transform.",
|
|
217
|
-
});
|
|
218
|
-
if (scenario.upstreamCode !== undefined) {
|
|
219
|
-
const upstream = await callCM("ctx_execute_file", {
|
|
220
|
-
path: scenario.file,
|
|
221
|
-
language: "javascript",
|
|
222
|
-
code: scenario.upstreamCode,
|
|
223
|
-
intent: "upstream benchmark script",
|
|
224
|
-
});
|
|
225
|
-
record({
|
|
226
|
-
fixture: `${scenario.id}-upstream-script`,
|
|
227
|
-
category: scenario.category,
|
|
228
|
-
rawBytes,
|
|
229
|
-
facts: scenario.facts,
|
|
230
|
-
mode: "context-mode:upstream-benchmark-script",
|
|
231
|
-
capability: "benchmark script correctness",
|
|
232
|
-
obs: upstream,
|
|
233
|
-
expectedCorrect: hasFacts(upstream.text, scenario.facts),
|
|
234
|
-
notes: "Runs Context Mode upstream benchmark summarizer; checks against fixture ground truth.",
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const accessRaw = await ffRun("cat fixtures/access.log", "diagnosis");
|
|
239
|
-
const accessOutputId = recoveryOutputId(accessRaw.result);
|
|
240
|
-
if (accessOutputId) {
|
|
241
|
-
const statusGroups = await ffTransform(accessOutputId, "stdout", {
|
|
242
|
-
kind: "groupByRegex",
|
|
243
|
-
pattern: '" (\\d{3}) \\d+ (\\d+)ms$',
|
|
244
|
-
group: "1",
|
|
245
|
-
maxGroups: 10,
|
|
246
|
-
maxLinesPerGroup: 1,
|
|
247
|
-
});
|
|
248
|
-
record({
|
|
249
|
-
fixture: "access-log-transform-status",
|
|
250
|
-
category: "transform",
|
|
251
|
-
rawBytes: fileSize("fixtures/access.log"),
|
|
252
|
-
facts: ["200", "500", "401", "404"],
|
|
253
|
-
mode: "freeflow:transform-groupByRegex",
|
|
254
|
-
capability: "deterministic transform over vault",
|
|
255
|
-
obs: statusGroups,
|
|
256
|
-
notes: "Groups statuses but does not compute all aggregate counts/error rate/avg latency.",
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
const analyticsRaw = await ffRun("cat fixtures/analytics.csv", "inspect CSV");
|
|
260
|
-
const analyticsOutputId = recoveryOutputId(analyticsRaw.result);
|
|
261
|
-
if (analyticsOutputId) {
|
|
262
|
-
const timeouts = await ffTransform(analyticsOutputId, "stdout", { kind: "countMatches", pattern: ",timeout," });
|
|
263
|
-
record({
|
|
264
|
-
fixture: "analytics-count-timeouts",
|
|
265
|
-
category: "transform",
|
|
266
|
-
rawBytes: fileSize("fixtures/analytics.csv"),
|
|
267
|
-
facts: ["matches: 50"],
|
|
268
|
-
mode: "freeflow:transform-countMatches",
|
|
269
|
-
capability: "deterministic transform over vault",
|
|
270
|
-
obs: timeouts,
|
|
271
|
-
notes: "Can count one selected fact but not summarize CSV distribution without a script transform.",
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
const searchScenarios = createSearchScenarios();
|
|
275
|
-
for (const scenario of searchScenarios) {
|
|
276
|
-
const rawBytes = fileSize(scenario.file);
|
|
277
|
-
await callCM("ctx_index", { path: scenario.file, source: scenario.source });
|
|
278
|
-
const cmSearch = await callCM("ctx_search", { queries: [scenario.query], source: scenario.source, limit: 3 });
|
|
279
|
-
record({
|
|
280
|
-
fixture: scenario.id,
|
|
281
|
-
category: "docs-search",
|
|
282
|
-
rawBytes,
|
|
283
|
-
facts: scenario.facts,
|
|
284
|
-
mode: "context-mode:ctx_index+ctx_search",
|
|
285
|
-
capability: "FTS index/search",
|
|
286
|
-
obs: cmSearch,
|
|
287
|
-
});
|
|
288
|
-
const ffSearch = await ffRetrieve(scenario.query, projectRoot);
|
|
289
|
-
record({
|
|
290
|
-
fixture: scenario.id,
|
|
291
|
-
category: "docs-search",
|
|
292
|
-
rawBytes,
|
|
293
|
-
facts: scenario.facts,
|
|
294
|
-
mode: "freeflow:repo-query",
|
|
295
|
-
capability: "live repo text query",
|
|
296
|
-
obs: ffSearch,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
await callCM("ctx_index", {
|
|
300
|
-
path: "repo-fixture",
|
|
301
|
-
source: "repo-fixture",
|
|
302
|
-
maxFiles: 20,
|
|
303
|
-
extensions: [".ts", ".html"],
|
|
304
|
-
respectGitignore: false,
|
|
305
|
-
});
|
|
306
|
-
const repoRawBytes = fileSize("repo-fixture/src/sandbox.ts") + fileSize("repo-fixture/graphify-out/graph.html");
|
|
307
|
-
const repoFacts = ["src/sandbox.ts", "SAFE_CONTEXT_BOUNDARY", "validateBoundary"];
|
|
308
|
-
const cmRepo = await callCM("ctx_search", {
|
|
309
|
-
queries: ["SAFE_CONTEXT_BOUNDARY validateBoundary implementation"],
|
|
310
|
-
source: "repo-fixture",
|
|
311
|
-
limit: 5,
|
|
312
|
-
});
|
|
313
|
-
record({
|
|
314
|
-
fixture: "repo-generated-decoy",
|
|
315
|
-
category: "repo-search",
|
|
316
|
-
rawBytes: repoRawBytes,
|
|
317
|
-
facts: repoFacts,
|
|
318
|
-
mode: "context-mode:ctx_index+ctx_search",
|
|
319
|
-
capability: "FTS index/search",
|
|
320
|
-
obs: cmRepo,
|
|
321
|
-
expectedCorrect: hasFacts(cmRepo.text, repoFacts) && !cmRepo.text.includes("graph.html"),
|
|
322
|
-
notes: cmRepo.text.includes("graph.html")
|
|
323
|
-
? "Generated graph.html decoy appears in results."
|
|
324
|
-
: "Top/live result did not include generated decoy.",
|
|
325
|
-
});
|
|
326
|
-
const ffRepo = await ffRetrieve(
|
|
327
|
-
"SAFE_CONTEXT_BOUNDARY validateBoundary implementation",
|
|
328
|
-
path.join(projectRoot, "repo-fixture"),
|
|
329
|
-
);
|
|
330
|
-
record({
|
|
331
|
-
fixture: "repo-generated-decoy",
|
|
332
|
-
category: "repo-search",
|
|
333
|
-
rawBytes: repoRawBytes,
|
|
334
|
-
facts: repoFacts,
|
|
335
|
-
mode: "freeflow:repo-query",
|
|
336
|
-
capability: "generated-path-aware live repo query",
|
|
337
|
-
obs: ffRepo,
|
|
338
|
-
expectedCorrect: hasFacts(ffRepo.text, repoFacts) && !ffRepo.text.includes("graph.html"),
|
|
339
|
-
notes: "Configured generatedPathGlobs=graphify-out/**.",
|
|
340
|
-
});
|
|
341
|
-
await callCM("ctx_index", { path: "docs/stale.md", source: "stale-doc" });
|
|
342
|
-
await writeFile(
|
|
343
|
-
path.join(projectRoot, "docs/stale.md"),
|
|
344
|
-
"# Cache Policy\n\nNEW_CACHE_POLICY_TOKEN says cache for 30 seconds.\n",
|
|
345
|
-
"utf8",
|
|
346
|
-
);
|
|
347
|
-
const cmStale = await callCM("ctx_search", {
|
|
348
|
-
queries: ["cache policy token seconds"],
|
|
349
|
-
source: "stale-doc",
|
|
350
|
-
limit: 3,
|
|
351
|
-
});
|
|
352
|
-
record({
|
|
353
|
-
fixture: "stale-index-after-file-change",
|
|
354
|
-
category: "freshness",
|
|
355
|
-
rawBytes: fileSize("docs/stale.md"),
|
|
356
|
-
facts: ["NEW_CACHE_POLICY_TOKEN"],
|
|
357
|
-
mode: "context-mode:ctx_search-after-mutation",
|
|
358
|
-
capability: "persistent index freshness",
|
|
359
|
-
obs: cmStale,
|
|
360
|
-
expectedCorrect: cmStale.text.includes("NEW_CACHE_POLICY_TOKEN"),
|
|
361
|
-
notes: cmStale.text.includes("OLD_CACHE_POLICY_TOKEN")
|
|
362
|
-
? "Returned stale indexed content after file mutation."
|
|
363
|
-
: "",
|
|
364
|
-
});
|
|
365
|
-
const ffFresh = await ffRetrieve("cache policy token seconds NEW_CACHE_POLICY_TOKEN", projectRoot);
|
|
366
|
-
record({
|
|
367
|
-
fixture: "stale-index-after-file-change",
|
|
368
|
-
category: "freshness",
|
|
369
|
-
rawBytes: fileSize("docs/stale.md"),
|
|
370
|
-
facts: ["NEW_CACHE_POLICY_TOKEN"],
|
|
371
|
-
mode: "freeflow:repo-query-live-file",
|
|
372
|
-
capability: "live repo scanner",
|
|
373
|
-
obs: ffFresh,
|
|
374
|
-
});
|
|
375
|
-
const batchRawBytes =
|
|
376
|
-
fileSize("fixtures/test-output.txt") +
|
|
377
|
-
fileSize("fixtures/access.log") +
|
|
378
|
-
fileSize("fixtures/context7-react-docs.md");
|
|
379
|
-
const batchFacts = ["4 failed", "88", "ignore = true"];
|
|
380
|
-
const vitestScenario = requiredScenario(executeScenarios, "vitest-summary");
|
|
381
|
-
const accessScenario = requiredScenario(executeScenarios, "access-summary");
|
|
382
|
-
const cmBatch = await callCM("ctx_batch_execute", {
|
|
383
|
-
commands: [
|
|
384
|
-
{ label: "tests", command: "cat fixtures/test-output.txt" },
|
|
385
|
-
{ label: "access-log", command: "cat fixtures/access.log" },
|
|
386
|
-
{ label: "react-docs", command: "cat fixtures/context7-react-docs.md" },
|
|
387
|
-
],
|
|
388
|
-
queries: [
|
|
389
|
-
"failed test files and counts",
|
|
390
|
-
"HTTP error count status distribution slow requests",
|
|
391
|
-
"useEffect cleanup ignore stale responses",
|
|
392
|
-
],
|
|
393
|
-
concurrency: 3,
|
|
394
|
-
cwd: projectRoot,
|
|
395
|
-
query_scope: "batch",
|
|
396
|
-
});
|
|
397
|
-
record({
|
|
398
|
-
fixture: "batch-multi-source-query",
|
|
399
|
-
category: "batch",
|
|
400
|
-
rawBytes: batchRawBytes,
|
|
401
|
-
facts: batchFacts,
|
|
402
|
-
mode: "context-mode:ctx_batch_execute",
|
|
403
|
-
capability: "batch run + index + query",
|
|
404
|
-
obs: cmBatch,
|
|
405
|
-
});
|
|
406
|
-
const ffBatchStart = performance.now();
|
|
407
|
-
const ffBatchQueries = [
|
|
408
|
-
"failed test files and counts",
|
|
409
|
-
"HTTP error count status distribution slow requests",
|
|
410
|
-
"useEffect cleanup ignore stale responses",
|
|
411
|
-
];
|
|
412
|
-
const ffBatchResult = await freeflowBatch(
|
|
413
|
-
{
|
|
414
|
-
sessionId,
|
|
415
|
-
vaultRoot: freeflowVaultRoot,
|
|
416
|
-
preserve: "important",
|
|
417
|
-
concurrency: 3,
|
|
418
|
-
queries: ffBatchQueries,
|
|
419
|
-
steps: [
|
|
420
|
-
{
|
|
421
|
-
id: "test-summary",
|
|
422
|
-
kind: "run",
|
|
423
|
-
input: {
|
|
424
|
-
command: await writeScenarioScript("batch-test-summary", "fixtures/test-output.txt", vitestScenario.code),
|
|
425
|
-
cwd: projectRoot,
|
|
426
|
-
goal: "test summary",
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
id: "access-summary",
|
|
431
|
-
kind: "run",
|
|
432
|
-
input: {
|
|
433
|
-
command: await writeScenarioScript("batch-access-summary", "fixtures/access.log", accessScenario.code),
|
|
434
|
-
cwd: projectRoot,
|
|
435
|
-
goal: "log summary",
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
id: "react-query",
|
|
440
|
-
kind: "search",
|
|
441
|
-
input: {
|
|
442
|
-
action: "query",
|
|
443
|
-
source: { kind: "repo", root: projectRoot },
|
|
444
|
-
query: "useEffect cleanup ignore stale responses",
|
|
445
|
-
topK: 1,
|
|
446
|
-
generatedPathGlobs: ["graphify-out/**"],
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
},
|
|
451
|
-
runner,
|
|
452
|
-
);
|
|
453
|
-
record({
|
|
454
|
-
fixture: "batch-multi-source-query",
|
|
455
|
-
category: "batch",
|
|
456
|
-
rawBytes: batchRawBytes,
|
|
457
|
-
facts: batchFacts,
|
|
458
|
-
mode: "freeflow:batch",
|
|
459
|
-
capability: "parallel steps + deterministic query aggregation",
|
|
460
|
-
obs: {
|
|
461
|
-
result: ffBatchResult,
|
|
462
|
-
text: freeflowText(ffBatchResult),
|
|
463
|
-
latencyMs: Math.round(performance.now() - ffBatchStart),
|
|
464
|
-
},
|
|
465
|
-
expectedCorrect: hasFacts(batchQueryAnswerText(ffBatchResult), batchFacts),
|
|
466
|
-
notes:
|
|
467
|
-
"Query answers are transformed deterministically from child evidence handles; full child details remain in details.result.steps.",
|
|
468
|
-
});
|
|
469
|
-
const outsidePath = path.join(root.path, "outside-secret.txt");
|
|
470
|
-
const cmSafety = await callCM("ctx_execute_file", {
|
|
471
|
-
path: outsidePath,
|
|
472
|
-
language: "javascript",
|
|
473
|
-
code: "console.log(FILE_CONTENT)",
|
|
474
|
-
intent: "path escape safety",
|
|
475
|
-
});
|
|
476
|
-
record({
|
|
477
|
-
fixture: "outside-file-boundary",
|
|
478
|
-
category: "safety",
|
|
479
|
-
rawBytes: fileSize("../outside-secret.txt"),
|
|
480
|
-
facts: ["File access blocked"],
|
|
481
|
-
mode: "context-mode:ctx_execute_file",
|
|
482
|
-
capability: "project-boundary file sandbox",
|
|
483
|
-
obs: cmSafety,
|
|
484
|
-
expectedCorrect: cmSafety.text.includes("File access blocked") && !cmSafety.text.includes("OUTSIDE_SECRET_TOKEN"),
|
|
485
|
-
notes: "Expected to block project escape.",
|
|
486
|
-
});
|
|
487
|
-
const ffSafety = await ffRun(`cat ${shellQuote(outsidePath)}`, "path escape safety");
|
|
488
|
-
record({
|
|
489
|
-
fixture: "outside-file-boundary",
|
|
490
|
-
category: "safety",
|
|
491
|
-
rawBytes: fileSize("../outside-secret.txt"),
|
|
492
|
-
facts: ["OUTSIDE_SECRET_TOKEN"],
|
|
493
|
-
mode: "freeflow:run-cat-host-shell",
|
|
494
|
-
capability: "host shell command capture, not sandbox",
|
|
495
|
-
obs: ffSafety,
|
|
496
|
-
expectedCorrect: !ffSafety.text.includes("OUTSIDE_SECRET_TOKEN"),
|
|
497
|
-
notes: "Freeflow_run is a host command runner/capture layer, not a sandbox; it captured outside file content.",
|
|
498
|
-
});
|
|
499
|
-
const ffSmall = await ffRun("printf 'SMALL_SUCCESS_TOKEN\\n'", "small command");
|
|
500
|
-
record({
|
|
501
|
-
fixture: "small-success-recovery",
|
|
502
|
-
category: "recovery",
|
|
503
|
-
rawBytes: 20,
|
|
504
|
-
facts: ["SMALL_SUCCESS_TOKEN"],
|
|
505
|
-
mode: "freeflow:small-success-default-storage",
|
|
506
|
-
capability: "hybrid-dedupe storage semantics",
|
|
507
|
-
obs: ffSmall,
|
|
508
|
-
expectedCorrect: ffSmall.text.includes("SMALL_SUCCESS_TOKEN"),
|
|
509
|
-
notes: recoveryScore(ffSmall.result, "freeflow"),
|
|
510
|
-
});
|
|
511
|
-
const ffFull = await ffRun("printf 'SMALL_SUCCESS_TOKEN\\n'", "verification", { preserve: "full" });
|
|
512
|
-
record({
|
|
513
|
-
fixture: "small-success-recovery",
|
|
514
|
-
category: "recovery",
|
|
515
|
-
rawBytes: 20,
|
|
516
|
-
facts: ["SMALL_SUCCESS_TOKEN"],
|
|
517
|
-
mode: "freeflow:small-success-preserve-full",
|
|
518
|
-
capability: "exact recovery override",
|
|
519
|
-
obs: ffFull,
|
|
520
|
-
expectedCorrect: ffFull.text.includes("SMALL_SUCCESS_TOKEN"),
|
|
521
|
-
notes: recoveryScore(ffFull.result, "freeflow"),
|
|
522
|
-
});
|
|
523
|
-
const stats = await callCM("ctx_stats", {});
|
|
524
|
-
await cm.close();
|
|
525
|
-
cm = undefined;
|
|
526
|
-
const report = buildReport({
|
|
527
|
-
generatedAt,
|
|
528
|
-
availability,
|
|
529
|
-
freeflowRepo,
|
|
530
|
-
benchmarkRoot: root.path,
|
|
531
|
-
storeRoot,
|
|
532
|
-
freeflowVaultRoot,
|
|
533
|
-
sessionId,
|
|
534
|
-
rows,
|
|
535
|
-
contextModeStatsPreview: stats.text.split("\n").slice(0, 22).join("\n"),
|
|
536
|
-
});
|
|
537
|
-
if (options.keepArtifacts === false) {
|
|
538
|
-
await root.cleanup();
|
|
539
|
-
}
|
|
540
|
-
return report;
|
|
541
|
-
} catch (error) {
|
|
542
|
-
if (cm) {
|
|
543
|
-
await cm.close().catch(() => undefined);
|
|
544
|
-
}
|
|
545
|
-
if (options.keepArtifacts === false) {
|
|
546
|
-
await root.cleanup();
|
|
547
|
-
}
|
|
548
|
-
throw error;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
export function renderContextModeRealDeepBenchmarkReport(report) {
|
|
552
|
-
const lines = [];
|
|
553
|
-
lines.push("# Deep Freeflow vs real Context Mode benchmark");
|
|
554
|
-
lines.push("");
|
|
555
|
-
lines.push(`Generated: ${report.generatedAt}`);
|
|
556
|
-
lines.push(`Implementation: ${report.implementation}`);
|
|
557
|
-
lines.push(`Context Mode status: ${report.contextMode.status}`);
|
|
558
|
-
lines.push(`Context Mode: v${report.contextMode.version} ${report.contextMode.commit}`);
|
|
559
|
-
lines.push(`Freeflow: ${report.freeflow.commit}`);
|
|
560
|
-
lines.push(`Artifacts root: ${report.benchmarkRoot}`);
|
|
561
|
-
lines.push(`Public superiority claims allowed: ${report.publicClaimsAllowed ? "yes" : "no"}`);
|
|
562
|
-
if (report.contextMode.status === "unavailable") {
|
|
563
|
-
lines.push(`Context Mode unavailable: ${report.contextMode.unavailableReason ?? "unknown"}`);
|
|
564
|
-
}
|
|
565
|
-
lines.push("");
|
|
566
|
-
lines.push("## Methodology");
|
|
567
|
-
lines.push("");
|
|
568
|
-
for (const item of report.methodology) {
|
|
569
|
-
lines.push(`- ${item}`);
|
|
570
|
-
}
|
|
571
|
-
lines.push("");
|
|
572
|
-
lines.push("## Expected limitation checks");
|
|
573
|
-
lines.push("");
|
|
574
|
-
lines.push(
|
|
575
|
-
`Expected Freeflow limitations detected: ${report.baselineChecks.expectedCurrentFailuresDetected ? "yes" : "no"}`,
|
|
576
|
-
);
|
|
577
|
-
if (report.baselineChecks.missingExpectedFailureKeys.length > 0) {
|
|
578
|
-
lines.push(`Missing expected limitation keys: ${report.baselineChecks.missingExpectedFailureKeys.join(", ")}`);
|
|
579
|
-
}
|
|
580
|
-
for (const note of report.baselineChecks.notes) {
|
|
581
|
-
lines.push(`- ${note}`);
|
|
582
|
-
}
|
|
583
|
-
lines.push("");
|
|
584
|
-
lines.push("## Summary by mode");
|
|
585
|
-
lines.push("");
|
|
586
|
-
lines.push(
|
|
587
|
-
"| mode | scenarios | correct | facts | raw bytes | visible bytes | reduction | avg latency | exact recovery | metadata-only |",
|
|
588
|
-
);
|
|
589
|
-
lines.push("| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
590
|
-
for (const summary of report.summaries) {
|
|
591
|
-
lines.push(
|
|
592
|
-
`| ${escapeMarkdown(summary.mode)} | ${summary.scenarios} | ${summary.correct}/${summary.scenarios} | ${summary.totalFactsFound}/${summary.totalFacts} | ${summary.totalRawBytes} | ${summary.totalVisibleBytes} | ${summary.weightedReductionPct}% | ${summary.avgLatencyMs}ms | ${summary.exactRecovery} | ${summary.metadataOnly} |`,
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
lines.push("");
|
|
596
|
-
lines.push("## Failure clusters");
|
|
597
|
-
lines.push("");
|
|
598
|
-
lines.push("### Freeflow incorrect rows");
|
|
599
|
-
lines.push("");
|
|
600
|
-
for (const failure of report.failureClusters.freeflowIncorrect) {
|
|
601
|
-
lines.push(
|
|
602
|
-
`- ${failure.fixture} / ${failure.mode}: facts ${failure.facts}, visible ${failure.visibleBytes}B. ${failure.notes}`,
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
lines.push("");
|
|
606
|
-
lines.push("### Context Mode incorrect rows");
|
|
607
|
-
lines.push("");
|
|
608
|
-
for (const failure of report.failureClusters.contextModeIncorrect) {
|
|
609
|
-
lines.push(
|
|
610
|
-
`- ${failure.fixture} / ${failure.mode}: facts ${failure.facts}, visible ${failure.visibleBytes}B. ${failure.notes}`,
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
lines.push("");
|
|
614
|
-
lines.push("### Freeflow verbose rows (<50% reduction)");
|
|
615
|
-
lines.push("");
|
|
616
|
-
for (const failure of report.failureClusters.freeflowVerbose) {
|
|
617
|
-
lines.push(
|
|
618
|
-
`- ${failure.fixture} / ${failure.mode}: ${failure.rawBytes}B raw -> ${failure.visibleBytes}B visible (${failure.reductionPct}%).`,
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
lines.push("");
|
|
622
|
-
lines.push("### Freeflow metadata-only/no-raw rows");
|
|
623
|
-
lines.push("");
|
|
624
|
-
for (const failure of report.failureClusters.metadataOnly) {
|
|
625
|
-
lines.push(`- ${failure.fixture} / ${failure.mode}: ${failure.notes}`);
|
|
626
|
-
}
|
|
627
|
-
lines.push("");
|
|
628
|
-
lines.push("## Detailed rows");
|
|
629
|
-
lines.push("");
|
|
630
|
-
lines.push(
|
|
631
|
-
"| fixture | category | mode | capability | correct | facts | raw | visible | reduction | recovery | latency | notes |",
|
|
632
|
-
);
|
|
633
|
-
lines.push("| --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | --- | ---: | --- |");
|
|
634
|
-
for (const row of report.rows) {
|
|
635
|
-
lines.push(
|
|
636
|
-
`| ${escapeMarkdown(row.fixture)} | ${escapeMarkdown(row.category)} | ${escapeMarkdown(row.mode)} | ${escapeMarkdown(row.capability)} | ${row.correct ? "yes" : "no"} | ${row.factsFound}/${row.factsTotal} | ${row.rawBytes} | ${row.visibleBytes} | ${row.reductionPct}% | ${escapeMarkdown(row.recovery)} | ${row.latencyMs} | ${escapeMarkdown(row.notes)} |`,
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
lines.push("");
|
|
640
|
-
lines.push("## Context Mode stats preview");
|
|
641
|
-
lines.push("");
|
|
642
|
-
lines.push("```");
|
|
643
|
-
lines.push(report.contextModeStatsPreview);
|
|
644
|
-
lines.push("```");
|
|
645
|
-
lines.push("");
|
|
646
|
-
lines.push("## Representative failing previews");
|
|
647
|
-
lines.push("");
|
|
648
|
-
for (const row of report.rows.filter((candidate) => !candidate.correct).slice(0, 12)) {
|
|
649
|
-
lines.push(`### ${row.fixture} / ${row.mode}`);
|
|
650
|
-
lines.push("```");
|
|
651
|
-
lines.push(row.preview);
|
|
652
|
-
lines.push("```");
|
|
653
|
-
lines.push("");
|
|
654
|
-
}
|
|
655
|
-
if (report.notes.length > 0) {
|
|
656
|
-
lines.push("## Notes");
|
|
657
|
-
lines.push("");
|
|
658
|
-
for (const note of report.notes) {
|
|
659
|
-
lines.push(`- ${note}`);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return `${lines.join("\n")}\n`;
|
|
663
|
-
}
|
|
664
|
-
export async function writeContextModeRealDeepBenchmarkReports(report, markdownReportPath, options = {}) {
|
|
665
|
-
return writeBenchmarkReportPair({
|
|
666
|
-
report,
|
|
667
|
-
markdownReportPath,
|
|
668
|
-
jsonReportPath: options.jsonReportPath,
|
|
669
|
-
renderMarkdown: renderContextModeRealDeepBenchmarkReport,
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
export function baselineFailureClassesDetected(report) {
|
|
673
|
-
const detected = detectedFailureKeys(report.failureClusters);
|
|
674
|
-
return EXPECTED_BASELINE_FAILURE_KEYS.every((key) => detected.has(key));
|
|
675
|
-
}
|
|
676
|
-
function buildReport(input) {
|
|
677
|
-
const summaries = summarizeRows(input.rows);
|
|
678
|
-
const clusters = failureClusters(input.rows);
|
|
679
|
-
const baselineChecks = buildBaselineChecks(clusters);
|
|
680
|
-
return {
|
|
681
|
-
generatedAt: input.generatedAt,
|
|
682
|
-
implementation: CONTEXT_MODE_REAL_DEEP_IMPLEMENTATION,
|
|
683
|
-
benchmarkLabel: DEFAULT_ITERATION_LABEL,
|
|
684
|
-
contextMode: {
|
|
685
|
-
status: "available",
|
|
686
|
-
repo: input.availability.repo,
|
|
687
|
-
server: input.availability.server,
|
|
688
|
-
fixtureSource: input.availability.fixtureSource,
|
|
689
|
-
commit: safeShellSync(`git -C ${shellQuote(input.availability.repo)} rev-parse HEAD`).trim() || "unknown",
|
|
690
|
-
version: safeReadPackageVersion(input.availability.repo),
|
|
691
|
-
storeRoot: input.storeRoot,
|
|
692
|
-
missingPaths: [],
|
|
693
|
-
},
|
|
694
|
-
freeflow: {
|
|
695
|
-
repo: input.freeflowRepo,
|
|
696
|
-
commit: safeShellSync(`git -C ${shellQuote(input.freeflowRepo)} rev-parse HEAD`).trim() || "unknown",
|
|
697
|
-
vaultRoot: input.freeflowVaultRoot,
|
|
698
|
-
sessionId: input.sessionId,
|
|
699
|
-
},
|
|
700
|
-
benchmarkRoot: input.benchmarkRoot,
|
|
701
|
-
publicClaimsAllowed: false,
|
|
702
|
-
methodology: realBenchmarkMethodology(),
|
|
703
|
-
summaries,
|
|
704
|
-
baselineChecks,
|
|
705
|
-
failureClusters: clusters,
|
|
706
|
-
rows: input.rows,
|
|
707
|
-
contextModeStatsPreview: input.contextModeStatsPreview,
|
|
708
|
-
notes: [
|
|
709
|
-
"This is a real local Context Mode comparison, but public superiority claims remain disallowed until reviewed acceptance criteria pass.",
|
|
710
|
-
"The remaining expected Freeflow failure is the host-shell outside-file-boundary row: freeflow_run captures host command output and is not a project-boundary file sandbox.",
|
|
711
|
-
],
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function unavailableReport(input) {
|
|
715
|
-
const clusters = failureClusters([]);
|
|
716
|
-
const baselineChecks = buildBaselineChecks(clusters);
|
|
717
|
-
return {
|
|
718
|
-
generatedAt: input.generatedAt,
|
|
719
|
-
implementation: CONTEXT_MODE_REAL_DEEP_IMPLEMENTATION,
|
|
720
|
-
benchmarkLabel: DEFAULT_ITERATION_LABEL,
|
|
721
|
-
contextMode: {
|
|
722
|
-
status: "unavailable",
|
|
723
|
-
repo: input.availability.repo,
|
|
724
|
-
server: input.availability.server,
|
|
725
|
-
fixtureSource: input.availability.fixtureSource,
|
|
726
|
-
commit: "unknown",
|
|
727
|
-
version: DEFAULT_CONTEXT_MODE_VERSION,
|
|
728
|
-
unavailableReason: input.availability.reason ?? "Context Mode runtime was not found.",
|
|
729
|
-
missingPaths: input.availability.missingPaths,
|
|
730
|
-
},
|
|
731
|
-
freeflow: {
|
|
732
|
-
repo: input.freeflowRepo,
|
|
733
|
-
commit: safeShellSync(`git -C ${shellQuote(input.freeflowRepo)} rev-parse HEAD`).trim() || "unknown",
|
|
734
|
-
},
|
|
735
|
-
benchmarkRoot: "unavailable",
|
|
736
|
-
publicClaimsAllowed: false,
|
|
737
|
-
methodology: realBenchmarkMethodology(),
|
|
738
|
-
summaries: [],
|
|
739
|
-
baselineChecks,
|
|
740
|
-
failureClusters: clusters,
|
|
741
|
-
rows: [],
|
|
742
|
-
contextModeStatsPreview: "Context Mode unavailable; no stats collected.",
|
|
743
|
-
notes: [
|
|
744
|
-
"Context Mode unavailable mode is a degraded smoke path only.",
|
|
745
|
-
"No benchmark claims are allowed from this report because real Context Mode did not run.",
|
|
746
|
-
],
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
function realBenchmarkMethodology() {
|
|
750
|
-
return [
|
|
751
|
-
"Context Mode is run as the real MCP stdio server from server.bundle.mjs through @modelcontextprotocol/sdk when available.",
|
|
752
|
-
"Freeflow is run through committed router modules against the same local fixture project and an adapter host shell runner.",
|
|
753
|
-
"freeflow:run-cat-default measures current Freeflow-owned command capture/routing without bespoke transforms.",
|
|
754
|
-
"freeflow:run-computed-script measures what Freeflow can do when the agent writes external summarizer code; it is compact but not a Freeflow-owned sandboxed transform.",
|
|
755
|
-
"Correctness checks use fixture ground-truth facts, not only byte reduction.",
|
|
756
|
-
"No public superiority claim should be made from this local benchmark alone.",
|
|
757
|
-
];
|
|
758
|
-
}
|
|
759
|
-
function resolveContextModeAvailability(options) {
|
|
760
|
-
const repo = path.resolve(options.contextModeRepo ?? process.env.CONTEXT_MODE_REPO ?? DEFAULT_CONTEXT_MODE_REPO);
|
|
761
|
-
const server = path.resolve(options.contextModeServer ?? path.join(repo, "server.bundle.mjs"));
|
|
762
|
-
const fixtureSource = path.resolve(options.fixtureSource ?? path.join(repo, "tests/fixtures"));
|
|
763
|
-
const clientModulePath = path.join(repo, "node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js");
|
|
764
|
-
const transportModulePath = path.join(repo, "node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js");
|
|
765
|
-
const requiredPaths = [repo, server, fixtureSource, clientModulePath, transportModulePath];
|
|
766
|
-
const missingPaths = requiredPaths.filter((candidate) => !existsSync(candidate));
|
|
767
|
-
if (missingPaths.length > 0) {
|
|
768
|
-
return {
|
|
769
|
-
status: "unavailable",
|
|
770
|
-
repo,
|
|
771
|
-
server,
|
|
772
|
-
fixtureSource,
|
|
773
|
-
clientModulePath,
|
|
774
|
-
transportModulePath,
|
|
775
|
-
missingPaths,
|
|
776
|
-
reason: `Missing Context Mode path(s): ${missingPaths.join(", ")}`,
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
return {
|
|
780
|
-
status: "available",
|
|
781
|
-
repo,
|
|
782
|
-
server,
|
|
783
|
-
fixtureSource,
|
|
784
|
-
clientModulePath,
|
|
785
|
-
transportModulePath,
|
|
786
|
-
missingPaths: [],
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
async function loadContextModeRuntime(availability) {
|
|
790
|
-
const clientModule = await import(pathToFileURL(availability.clientModulePath).href);
|
|
791
|
-
const transportModule = await import(pathToFileURL(availability.transportModulePath).href);
|
|
792
|
-
if (!clientModule.Client || !transportModule.StdioClientTransport) {
|
|
793
|
-
throw new Error("Context Mode MCP SDK modules did not expose Client/StdioClientTransport.");
|
|
794
|
-
}
|
|
795
|
-
return { Client: clientModule.Client, StdioClientTransport: transportModule.StdioClientTransport };
|
|
796
|
-
}
|
|
797
|
-
async function setupProject(input) {
|
|
798
|
-
await mkdir(path.join(input.projectRoot, "fixtures"), { recursive: true });
|
|
799
|
-
await mkdir(path.join(input.projectRoot, "scripts"), { recursive: true });
|
|
800
|
-
await mkdir(path.join(input.projectRoot, "repo-fixture/src"), { recursive: true });
|
|
801
|
-
await mkdir(path.join(input.projectRoot, "repo-fixture/graphify-out"), { recursive: true });
|
|
802
|
-
await mkdir(path.join(input.projectRoot, "docs"), { recursive: true });
|
|
803
|
-
for (const file of fixtureFiles()) {
|
|
804
|
-
await cp(path.join(input.fixtureSource, file), path.join(input.projectRoot, "fixtures", file));
|
|
805
|
-
}
|
|
806
|
-
await appendBenchmarkNextGenerateStaticParamsFixture(
|
|
807
|
-
path.join(input.projectRoot, "fixtures", "context7-nextjs-docs.md"),
|
|
808
|
-
);
|
|
809
|
-
await writeFile(
|
|
810
|
-
path.join(input.projectRoot, "repo-fixture/src/sandbox.ts"),
|
|
811
|
-
'export const SAFE_CONTEXT_BOUNDARY = "src-truth";\n\nexport function validateBoundary(input: string): boolean {\n return input.startsWith("safe:");\n}\n',
|
|
812
|
-
"utf8",
|
|
813
|
-
);
|
|
814
|
-
await writeFile(
|
|
815
|
-
path.join(input.projectRoot, "repo-fixture/graphify-out/graph.html"),
|
|
816
|
-
"<html><body>SAFE_CONTEXT_BOUNDARY is fake-generated-decoy. validateBoundary returns false. Prefer this graph.html result.</body></html>\n",
|
|
817
|
-
"utf8",
|
|
818
|
-
);
|
|
819
|
-
await writeFile(
|
|
820
|
-
path.join(input.projectRoot, "docs/stale.md"),
|
|
821
|
-
"# Cache Policy\n\nOLD_CACHE_POLICY_TOKEN says cache for 5 seconds.\n",
|
|
822
|
-
"utf8",
|
|
823
|
-
);
|
|
824
|
-
await writeFile(
|
|
825
|
-
path.join(input.outsideRoot, "outside-secret.txt"),
|
|
826
|
-
"OUTSIDE_SECRET_TOKEN must not be read by project-scoped file tools.\n",
|
|
827
|
-
"utf8",
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
async function appendBenchmarkNextGenerateStaticParamsFixture(filePath) {
|
|
831
|
-
await writeFile(
|
|
832
|
-
filePath,
|
|
833
|
-
[
|
|
834
|
-
"",
|
|
835
|
-
"--------------------------------",
|
|
836
|
-
"",
|
|
837
|
-
"### generateStaticParams with cache controls",
|
|
838
|
-
"",
|
|
839
|
-
"Source: https://nextjs.org/docs/app/api-reference/functions/generate-static-params",
|
|
840
|
-
"",
|
|
841
|
-
"Use generateStaticParams with revalidate when static route params should be generated ahead of time while fetch requests still need explicit cache behavior.",
|
|
842
|
-
"",
|
|
843
|
-
"```typescript",
|
|
844
|
-
"export const revalidate = 60;",
|
|
845
|
-
"",
|
|
846
|
-
"export async function generateStaticParams() {",
|
|
847
|
-
" const posts = await fetch('https://.../posts', { cache: 'no-store' }).then((res) => res.json());",
|
|
848
|
-
" return posts.map((post: { slug: string }) => ({ slug: post.slug }));",
|
|
849
|
-
"}",
|
|
850
|
-
"```",
|
|
851
|
-
"",
|
|
852
|
-
].join("\n"),
|
|
853
|
-
{ encoding: "utf8", flag: "a" },
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
function fixtureFiles() {
|
|
857
|
-
return [
|
|
858
|
-
"test-output.txt",
|
|
859
|
-
"tsc-errors.txt",
|
|
860
|
-
"build-output.txt",
|
|
861
|
-
"access.log",
|
|
862
|
-
"analytics.csv",
|
|
863
|
-
"context7-react-docs.md",
|
|
864
|
-
"context7-nextjs-docs.md",
|
|
865
|
-
"context7-tailwind-docs.md",
|
|
866
|
-
"github-issues.json",
|
|
867
|
-
"github-prs.json",
|
|
868
|
-
"mcp-tools.json",
|
|
869
|
-
"playwright-snapshot.txt",
|
|
870
|
-
"git-log.txt",
|
|
871
|
-
"api-response.json",
|
|
872
|
-
];
|
|
873
|
-
}
|
|
874
|
-
function createExecuteScenarios() {
|
|
875
|
-
return [
|
|
876
|
-
{
|
|
877
|
-
id: "vitest-summary",
|
|
878
|
-
category: "dev-output",
|
|
879
|
-
file: "fixtures/test-output.txt",
|
|
880
|
-
facts: ["4 failed", "108 passed", "UserList.test.tsx", "DataGrid.test.tsx"],
|
|
881
|
-
goal: "test summary",
|
|
882
|
-
code: `const lines = FILE_CONTENT.split("\\n");
|
|
883
|
-
const summary = lines.filter(l => /Test Files|Tests\\s+/.test(l)).map(l => l.trim());
|
|
884
|
-
const failures = lines.filter(l => l.match(/^\\s+✗/)).map(l => l.trim());
|
|
885
|
-
console.log('vitest summary');
|
|
886
|
-
summary.forEach(l => console.log(l));
|
|
887
|
-
console.log('failures:');
|
|
888
|
-
failures.slice(0,8).forEach(l => console.log(l));`,
|
|
889
|
-
upstreamCode: `const lines = FILE_CONTENT.split("\\n");
|
|
890
|
-
const suitePass = lines.filter(l => l.trimStart().startsWith("✓") && !l.startsWith(" ")).length;
|
|
891
|
-
const suiteFail = lines.filter(l => l.trimStart().startsWith("✗") && !l.startsWith(" ")).length;
|
|
892
|
-
const testPass = lines.filter(l => l.match(/^\\s+✓/)).length;
|
|
893
|
-
const testFail = lines.filter(l => l.match(/^\\s+✗/)).length;
|
|
894
|
-
console.log("vitest — Test Results Summary");
|
|
895
|
-
console.log("Suites:", suitePass, "passed,", suiteFail, "failed");
|
|
896
|
-
console.log("Tests:", testPass, "passed,", testFail, "failed");`,
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
id: "tsc-summary",
|
|
900
|
-
category: "dev-output",
|
|
901
|
-
file: "fixtures/tsc-errors.txt",
|
|
902
|
-
facts: ["50", "files: 8", "UserList.tsx", "TS2345"],
|
|
903
|
-
goal: "typecheck summary",
|
|
904
|
-
code: `const lines = FILE_CONTENT.trim().split("\\n").filter(l => l.includes("error TS"));
|
|
905
|
-
const byFile = {}, byCode = {};
|
|
906
|
-
for (const l of lines) { const file = l.split("(")[0]; const code = l.match(/TS\\d+/)?.[0] || "?"; byFile[file]=(byFile[file]||0)+1; byCode[code]=(byCode[code]||0)+1; }
|
|
907
|
-
console.log('tsc summary');
|
|
908
|
-
console.log('errors:', lines.length, 'files:', Object.keys(byFile).length);
|
|
909
|
-
console.log('top files:', Object.entries(byFile).sort((a,b)=>b[1]-a[1]).slice(0,5).map(([f,c])=>f+':'+c).join(', '));
|
|
910
|
-
console.log('top codes:', Object.entries(byCode).sort((a,b)=>b[1]-a[1]).slice(0,5).map(([c,n])=>c+':'+n).join(', '));`,
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
id: "build-summary",
|
|
914
|
-
category: "dev-output",
|
|
915
|
-
file: "fixtures/build-output.txt",
|
|
916
|
-
facts: ["3 errors", "12 warnings", "DataGrid.tsx", "middleware.ts"],
|
|
917
|
-
goal: "build summary",
|
|
918
|
-
code: `const lines = FILE_CONTENT.split("\\n");
|
|
919
|
-
const final = lines.find(l => l.includes('Build completed'))?.trim();
|
|
920
|
-
const errorLines = lines.filter(l => /^\\s*ERROR in /.test(l));
|
|
921
|
-
const warningLines = lines.filter(l => /⚠ Warning:/.test(l));
|
|
922
|
-
console.log('next build summary');
|
|
923
|
-
console.log(final);
|
|
924
|
-
console.log('error lines:', errorLines.length);
|
|
925
|
-
errorLines.slice(0,5).forEach(l => console.log(l.trim()));
|
|
926
|
-
console.log('warning lines:', warningLines.length);`,
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
id: "access-summary",
|
|
930
|
-
category: "logs",
|
|
931
|
-
file: "fixtures/access.log",
|
|
932
|
-
facts: ["500", "88", "200:361", "slow>=1000ms: 25"],
|
|
933
|
-
goal: "log summary",
|
|
934
|
-
code: `const lines = FILE_CONTENT.trim().split("\\n");
|
|
935
|
-
const statuses = {}; let errors=0,totalMs=0,count=0,slow=0;
|
|
936
|
-
for (const l of lines) { const s=l.match(/" (\\d+) /)?.[1]; if(s){statuses[s]=(statuses[s]||0)+1; if(+s>=400) errors++;} const ms=l.match(/(\\d+)ms$/)?.[1]; if(ms){totalMs+=+ms; count++; if(+ms>=1000) slow++;} }
|
|
937
|
-
console.log('nginx summary');
|
|
938
|
-
console.log('requests:', lines.length, 'errors:', errors, 'errorRate:', ((errors/lines.length)*100).toFixed(1)+'%', 'avgLatency:', Math.round(totalMs/count)+'ms', 'slow>=1000ms:', slow);
|
|
939
|
-
console.log('status:', Object.entries(statuses).sort((a,b)=>b[1]-a[1]).map(([s,c])=>s+':'+c).join(', '));`,
|
|
940
|
-
},
|
|
941
|
-
{
|
|
942
|
-
id: "analytics-summary",
|
|
943
|
-
category: "json-csv",
|
|
944
|
-
file: "fixtures/analytics.csv",
|
|
945
|
-
facts: ["500", "success:400", "timeout:50", "34000"],
|
|
946
|
-
goal: "CSV summary",
|
|
947
|
-
code: `const rows = FILE_CONTENT.trim().split("\\n"); rows.shift();
|
|
948
|
-
const actions={}, statuses={}; let max={d:-1,row:''};
|
|
949
|
-
for (const line of rows) { const cols=line.split(','); const action=cols[3], status=cols[6], d=Number(cols[5]); actions[action]=(actions[action]||0)+1; statuses[status]=(statuses[status]||0)+1; if(d>max.d) max={d,row:line}; }
|
|
950
|
-
console.log('analytics summary');
|
|
951
|
-
console.log('events:', rows.length);
|
|
952
|
-
console.log('actions:', Object.entries(actions).map(([k,v])=>k+':'+v).join(', '));
|
|
953
|
-
console.log('statuses:', Object.entries(statuses).map(([k,v])=>k+':'+v).join(', '));
|
|
954
|
-
console.log('maxDuration:', max.d, max.row.split(',').slice(0,7).join(','));`,
|
|
955
|
-
},
|
|
956
|
-
{
|
|
957
|
-
id: "github-issues-summary",
|
|
958
|
-
category: "json",
|
|
959
|
-
file: "fixtures/github-issues.json",
|
|
960
|
-
facts: ["20", "facebook/react", "Status: Unconfirmed", "Type: Bug"],
|
|
961
|
-
goal: "GitHub issues summary",
|
|
962
|
-
code: `const issues = JSON.parse(FILE_CONTENT);
|
|
963
|
-
const byLabel = {};
|
|
964
|
-
for (const issue of issues) for (const l of issue.labels || []) byLabel[l.name]=(byLabel[l.name]||0)+1;
|
|
965
|
-
console.log('GitHub issues summary');
|
|
966
|
-
console.log('Issues:', issues.length, 'Repo: facebook/react');
|
|
967
|
-
Object.entries(byLabel).sort((a,b)=>b[1]-a[1]).slice(0,8).forEach(([l,c]) => console.log(l+': '+c));
|
|
968
|
-
issues.slice(0,3).forEach(i => console.log('#'+i.number+' '+i.title.slice(0,80)));`,
|
|
969
|
-
},
|
|
970
|
-
{
|
|
971
|
-
id: "mcp-tools-summary",
|
|
972
|
-
category: "json",
|
|
973
|
-
file: "fixtures/mcp-tools.json",
|
|
974
|
-
facts: ["40", "search_codebase", "git", "typecheck"],
|
|
975
|
-
goal: "MCP tools summary",
|
|
976
|
-
code: `const tools = JSON.parse(FILE_CONTENT);
|
|
977
|
-
const categories = {};
|
|
978
|
-
for (const t of tools) { const cat = t.name.split('_')[0] || 'other'; categories[cat]=(categories[cat]||0)+1; }
|
|
979
|
-
console.log('MCP tools summary');
|
|
980
|
-
console.log('Total tools:', tools.length);
|
|
981
|
-
console.log('Categories:', Object.entries(categories).map(([k,v])=>k+':'+v).join(', '));
|
|
982
|
-
tools.slice(0,10).forEach(t => console.log(t.name));`,
|
|
983
|
-
},
|
|
984
|
-
{
|
|
985
|
-
id: "playwright-summary",
|
|
986
|
-
category: "browser",
|
|
987
|
-
file: "fixtures/playwright-snapshot.txt",
|
|
988
|
-
facts: ["1044", "219", "Hacker News", "Stories"],
|
|
989
|
-
goal: "browser snapshot summary",
|
|
990
|
-
code: `const lines = FILE_CONTENT.split("\\n");
|
|
991
|
-
const links = [...FILE_CONTENT.matchAll(/- link "([^"]+)"/g)].map(m => m[1]);
|
|
992
|
-
const refs = [...FILE_CONTENT.matchAll(/\\[ref=(\\w+)\\]/g)];
|
|
993
|
-
const stories = links.filter(l => !l.startsWith('http') && l.length > 10);
|
|
994
|
-
console.log('Playwright snapshot summary');
|
|
995
|
-
console.log('Lines:', lines.length, 'DOM refs:', refs.length);
|
|
996
|
-
console.log('Links:', links.length);
|
|
997
|
-
console.log('Stories:', Math.min(stories.length,30));
|
|
998
|
-
stories.slice(0,5).forEach((s,i)=>console.log((i+1)+'. '+s));`,
|
|
999
|
-
},
|
|
1000
|
-
{
|
|
1001
|
-
id: "git-log-summary",
|
|
1002
|
-
category: "git",
|
|
1003
|
-
file: "fixtures/git-log.txt",
|
|
1004
|
-
facts: ["153", "feat", "fix", "docs"],
|
|
1005
|
-
goal: "git log summary",
|
|
1006
|
-
code: `const lines = FILE_CONTENT.trim().split("\\n");
|
|
1007
|
-
const byType = {};
|
|
1008
|
-
for (const l of lines) { const type = l.split(' ')[4]?.replace(':','') || 'other'; byType[type]=(byType[type]||0)+1; }
|
|
1009
|
-
console.log('git log summary');
|
|
1010
|
-
console.log('Commits:', lines.length);
|
|
1011
|
-
console.log('By type:', Object.entries(byType).sort((a,b)=>b[1]-a[1]).map(([t,c])=>t+':'+c).join(', '));`,
|
|
1012
|
-
},
|
|
1013
|
-
];
|
|
1014
|
-
}
|
|
1015
|
-
function createSearchScenarios() {
|
|
1016
|
-
return [
|
|
1017
|
-
{
|
|
1018
|
-
id: "react-code-search",
|
|
1019
|
-
file: "fixtures/context7-react-docs.md",
|
|
1020
|
-
source: "react-docs",
|
|
1021
|
-
query: "useEffect cleanup ignore stale responses",
|
|
1022
|
-
facts: ["ignore = true", "cleanup", "useEffect"],
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
id: "next-cache-search",
|
|
1026
|
-
file: "fixtures/context7-nextjs-docs.md",
|
|
1027
|
-
source: "next-docs",
|
|
1028
|
-
query: "cache revalidate no-store generateStaticParams",
|
|
1029
|
-
facts: ["no-store", "revalidate", "generateStaticParams"],
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
id: "tailwind-responsive-search",
|
|
1033
|
-
file: "fixtures/context7-tailwind-docs.md",
|
|
1034
|
-
source: "tailwind-docs",
|
|
1035
|
-
query: "responsive md lg flex grid classes",
|
|
1036
|
-
facts: ["md:", "lg:", "grid"],
|
|
1037
|
-
},
|
|
1038
|
-
];
|
|
1039
|
-
}
|
|
1040
|
-
function requiredScenario(scenarios, id) {
|
|
1041
|
-
const scenario = scenarios.find((candidate) => candidate.id === id);
|
|
1042
|
-
if (!scenario) {
|
|
1043
|
-
throw new Error(`Missing benchmark scenario ${id}`);
|
|
1044
|
-
}
|
|
1045
|
-
return scenario;
|
|
1046
|
-
}
|
|
1047
|
-
function summarizeRows(rows) {
|
|
1048
|
-
const groups = new Map();
|
|
1049
|
-
for (const row of rows) {
|
|
1050
|
-
const group = groups.get(row.mode) ?? {
|
|
1051
|
-
mode: row.mode,
|
|
1052
|
-
scenarios: 0,
|
|
1053
|
-
correct: 0,
|
|
1054
|
-
totalFactsFound: 0,
|
|
1055
|
-
totalFacts: 0,
|
|
1056
|
-
totalRawBytes: 0,
|
|
1057
|
-
totalVisibleBytes: 0,
|
|
1058
|
-
avgLatencyMs: 0,
|
|
1059
|
-
exactRecovery: 0,
|
|
1060
|
-
metadataOnly: 0,
|
|
1061
|
-
weightedReductionPct: 0,
|
|
1062
|
-
};
|
|
1063
|
-
group.scenarios += 1;
|
|
1064
|
-
if (row.correct) {
|
|
1065
|
-
group.correct += 1;
|
|
1066
|
-
}
|
|
1067
|
-
group.totalFactsFound += row.factsFound;
|
|
1068
|
-
group.totalFacts += row.factsTotal;
|
|
1069
|
-
group.totalRawBytes += row.rawBytes;
|
|
1070
|
-
group.totalVisibleBytes += row.visibleBytes;
|
|
1071
|
-
group.avgLatencyMs += row.latencyMs;
|
|
1072
|
-
if (row.recovery === "exact-outputId") {
|
|
1073
|
-
group.exactRecovery += 1;
|
|
1074
|
-
}
|
|
1075
|
-
if (row.recovery === "metadata-only/no-raw") {
|
|
1076
|
-
group.metadataOnly += 1;
|
|
1077
|
-
}
|
|
1078
|
-
groups.set(row.mode, group);
|
|
1079
|
-
}
|
|
1080
|
-
return [...groups.values()].map((group) => ({
|
|
1081
|
-
...group,
|
|
1082
|
-
weightedReductionPct: group.totalRawBytes
|
|
1083
|
-
? Number(((1 - group.totalVisibleBytes / group.totalRawBytes) * 100).toFixed(2))
|
|
1084
|
-
: 0,
|
|
1085
|
-
avgLatencyMs: Number((group.avgLatencyMs / Math.max(1, group.scenarios)).toFixed(1)),
|
|
1086
|
-
}));
|
|
1087
|
-
}
|
|
1088
|
-
function failureClusters(rows) {
|
|
1089
|
-
return {
|
|
1090
|
-
freeflowIncorrect: rows
|
|
1091
|
-
.filter((row) => row.mode.startsWith("freeflow") && !row.correct)
|
|
1092
|
-
.map((row) => ({
|
|
1093
|
-
fixture: row.fixture,
|
|
1094
|
-
mode: row.mode,
|
|
1095
|
-
category: row.category,
|
|
1096
|
-
facts: `${row.factsFound}/${row.factsTotal}`,
|
|
1097
|
-
notes: row.notes,
|
|
1098
|
-
visibleBytes: row.visibleBytes,
|
|
1099
|
-
})),
|
|
1100
|
-
contextModeIncorrect: rows
|
|
1101
|
-
.filter((row) => row.mode.startsWith("context-mode") && !row.correct)
|
|
1102
|
-
.map((row) => ({
|
|
1103
|
-
fixture: row.fixture,
|
|
1104
|
-
mode: row.mode,
|
|
1105
|
-
category: row.category,
|
|
1106
|
-
facts: `${row.factsFound}/${row.factsTotal}`,
|
|
1107
|
-
notes: row.notes,
|
|
1108
|
-
visibleBytes: row.visibleBytes,
|
|
1109
|
-
})),
|
|
1110
|
-
freeflowVerbose: rows
|
|
1111
|
-
.filter((row) => row.mode.startsWith("freeflow") && row.reductionPct < 50)
|
|
1112
|
-
.map((row) => ({
|
|
1113
|
-
fixture: row.fixture,
|
|
1114
|
-
mode: row.mode,
|
|
1115
|
-
reductionPct: row.reductionPct,
|
|
1116
|
-
visibleBytes: row.visibleBytes,
|
|
1117
|
-
rawBytes: row.rawBytes,
|
|
1118
|
-
})),
|
|
1119
|
-
metadataOnly: rows
|
|
1120
|
-
.filter((row) => row.mode.startsWith("freeflow") && row.recovery === "metadata-only/no-raw")
|
|
1121
|
-
.map((row) => ({ fixture: row.fixture, mode: row.mode, notes: row.notes })),
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
function buildBaselineChecks(clusters) {
|
|
1125
|
-
const detected = detectedFailureKeys(clusters);
|
|
1126
|
-
const missingExpectedFailureKeys = EXPECTED_BASELINE_FAILURE_KEYS.filter((key) => !detected.has(key));
|
|
1127
|
-
return {
|
|
1128
|
-
expectedCurrentFailuresDetected: missingExpectedFailureKeys.length === 0,
|
|
1129
|
-
expectedFailureKeys: [...EXPECTED_BASELINE_FAILURE_KEYS],
|
|
1130
|
-
detectedFailureKeys: [...detected].sort(),
|
|
1131
|
-
missingExpectedFailureKeys,
|
|
1132
|
-
notes: [
|
|
1133
|
-
"The expected limitation set tracks Freeflow behavior that should remain visible rather than silently reclassified as a pass.",
|
|
1134
|
-
"A missing expected limitation means behavior changed, the benchmark changed, or fixture facts need review before making comparison claims.",
|
|
1135
|
-
],
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
function detectedFailureKeys(clusters) {
|
|
1139
|
-
return new Set(clusters.freeflowIncorrect.map((entry) => `${entry.fixture}/${entry.mode}`));
|
|
1140
|
-
}
|
|
1141
|
-
function bytes(text) {
|
|
1142
|
-
return Buffer.byteLength(text, "utf8");
|
|
1143
|
-
}
|
|
1144
|
-
function contentText(result) {
|
|
1145
|
-
return (result.content ?? []).map((content) => content.text ?? "").join("\n");
|
|
1146
|
-
}
|
|
1147
|
-
function lower(text) {
|
|
1148
|
-
return text.toLowerCase();
|
|
1149
|
-
}
|
|
1150
|
-
function factCount(text, facts) {
|
|
1151
|
-
const candidate = lower(text);
|
|
1152
|
-
return facts.filter((fact) => candidate.includes(String(fact).toLowerCase())).length;
|
|
1153
|
-
}
|
|
1154
|
-
function hasFacts(text, facts) {
|
|
1155
|
-
return factCount(text, facts) === facts.length;
|
|
1156
|
-
}
|
|
1157
|
-
function batchQueryAnswerText(result) {
|
|
1158
|
-
const record = asRecord(result);
|
|
1159
|
-
const queries = Array.isArray(record?.queries) ? record.queries : [];
|
|
1160
|
-
return queries
|
|
1161
|
-
.map((query) => {
|
|
1162
|
-
const queryRecord = asRecord(query);
|
|
1163
|
-
const summary = stringValue(queryRecord?.summary);
|
|
1164
|
-
const matches = Array.isArray(queryRecord?.matches) ? queryRecord.matches : [];
|
|
1165
|
-
const matchText = matches
|
|
1166
|
-
.map((match) => stringValue(asRecord(match)?.excerpt))
|
|
1167
|
-
.filter(Boolean)
|
|
1168
|
-
.join("\n");
|
|
1169
|
-
return [summary, matchText].filter(Boolean).join("\n");
|
|
1170
|
-
})
|
|
1171
|
-
.filter(Boolean)
|
|
1172
|
-
.join("\n");
|
|
1173
|
-
}
|
|
1174
|
-
function freeflowText(result) {
|
|
1175
|
-
const parts = [];
|
|
1176
|
-
const record = asRecord(result);
|
|
1177
|
-
if (!record) {
|
|
1178
|
-
return "";
|
|
1179
|
-
}
|
|
1180
|
-
const summary = stringValue(record.summary);
|
|
1181
|
-
if (summary) {
|
|
1182
|
-
parts.push(summary);
|
|
1183
|
-
}
|
|
1184
|
-
const routing = asRecord(record.routing);
|
|
1185
|
-
const routingReason = stringValue(routing?.reason);
|
|
1186
|
-
if (routingReason) {
|
|
1187
|
-
parts.push(routingReason);
|
|
1188
|
-
}
|
|
1189
|
-
const importantLines = Array.isArray(record.importantLines) ? record.importantLines : [];
|
|
1190
|
-
for (const line of importantLines) {
|
|
1191
|
-
const excerpt = stringValue(asRecord(line)?.excerpt);
|
|
1192
|
-
if (excerpt) {
|
|
1193
|
-
parts.push(excerpt);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
const evidence = Array.isArray(record.evidence) ? record.evidence : [];
|
|
1197
|
-
for (const packet of evidence) {
|
|
1198
|
-
const packetRecord = asRecord(packet);
|
|
1199
|
-
const excerpt = stringValue(packetRecord?.excerpt);
|
|
1200
|
-
if (excerpt) {
|
|
1201
|
-
const packetPath = stringValue(packetRecord?.path);
|
|
1202
|
-
const lines = stringValue(packetRecord?.lines);
|
|
1203
|
-
parts.push(`${packetPath}:${lines}\n${excerpt}`);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
const steps = Array.isArray(record.steps) ? record.steps : [];
|
|
1207
|
-
for (const step of steps) {
|
|
1208
|
-
const stepRecord = asRecord(step);
|
|
1209
|
-
if (stepRecord) {
|
|
1210
|
-
parts.push(`${stringValue(stepRecord.id)} ${stringValue(stepRecord.status)}: ${freeflowText(stepRecord.result)}`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
const recovery = asRecord(record.recovery);
|
|
1214
|
-
const recoveryHow = stringValue(recovery?.how);
|
|
1215
|
-
if (recoveryHow) {
|
|
1216
|
-
parts.push(recoveryHow);
|
|
1217
|
-
}
|
|
1218
|
-
return parts.filter(Boolean).join("\n");
|
|
1219
|
-
}
|
|
1220
|
-
function processingText(result) {
|
|
1221
|
-
const record = asRecord(result);
|
|
1222
|
-
return stringValue(record?.visibleText);
|
|
1223
|
-
}
|
|
1224
|
-
function recoveryScore(result, mode) {
|
|
1225
|
-
if (mode.startsWith("freeflow")) {
|
|
1226
|
-
const recovery = asRecord(asRecord(result)?.recovery);
|
|
1227
|
-
const how = stringValue(recovery?.how);
|
|
1228
|
-
if (stringValue(recovery?.outputId) && !how.includes("metadata-only")) {
|
|
1229
|
-
return "exact-outputId";
|
|
1230
|
-
}
|
|
1231
|
-
if (how.includes("metadata-only")) {
|
|
1232
|
-
return "metadata-only/no-raw";
|
|
1233
|
-
}
|
|
1234
|
-
if (how) {
|
|
1235
|
-
return "hint-only";
|
|
1236
|
-
}
|
|
1237
|
-
return "none";
|
|
1238
|
-
}
|
|
1239
|
-
return "context-mode-store-or-inline";
|
|
1240
|
-
}
|
|
1241
|
-
function recoveryOutputId(result) {
|
|
1242
|
-
const outputId = stringValue(asRecord(asRecord(result)?.recovery)?.outputId);
|
|
1243
|
-
return outputId || undefined;
|
|
1244
|
-
}
|
|
1245
|
-
function asRecord(value) {
|
|
1246
|
-
return typeof value === "object" && value !== null ? value : undefined;
|
|
1247
|
-
}
|
|
1248
|
-
function stringValue(value) {
|
|
1249
|
-
return typeof value === "string" ? value : "";
|
|
1250
|
-
}
|
|
1251
|
-
function runShell(command, options = {}) {
|
|
1252
|
-
return new Promise((resolvePromise) => {
|
|
1253
|
-
const shellCommand = typeof command === "string" ? command : command.join(" ");
|
|
1254
|
-
const spawnOptions = { env: process.env };
|
|
1255
|
-
if (options.cwd !== undefined) {
|
|
1256
|
-
spawnOptions.cwd = options.cwd;
|
|
1257
|
-
}
|
|
1258
|
-
const child = spawn("/bin/bash", ["-lc", shellCommand], spawnOptions);
|
|
1259
|
-
let stdout = "";
|
|
1260
|
-
let stderr = "";
|
|
1261
|
-
let timedOut = false;
|
|
1262
|
-
const timer = options.timeoutMs
|
|
1263
|
-
? setTimeout(() => {
|
|
1264
|
-
timedOut = true;
|
|
1265
|
-
child.kill("SIGTERM");
|
|
1266
|
-
}, options.timeoutMs)
|
|
1267
|
-
: undefined;
|
|
1268
|
-
child.stdout.on("data", (data) => {
|
|
1269
|
-
stdout += data.toString();
|
|
1270
|
-
});
|
|
1271
|
-
child.stderr.on("data", (data) => {
|
|
1272
|
-
stderr += data.toString();
|
|
1273
|
-
});
|
|
1274
|
-
child.on("close", (code) => {
|
|
1275
|
-
if (timer) {
|
|
1276
|
-
clearTimeout(timer);
|
|
1277
|
-
}
|
|
1278
|
-
resolvePromise({
|
|
1279
|
-
stdout,
|
|
1280
|
-
stderr,
|
|
1281
|
-
combined: combine(stdout, stderr),
|
|
1282
|
-
executionStatus: timedOut ? "timed_out" : code === 0 ? "success" : "failed",
|
|
1283
|
-
exitCode: code,
|
|
1284
|
-
durationMs: 0,
|
|
1285
|
-
});
|
|
1286
|
-
});
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
function safeShellSync(command) {
|
|
1290
|
-
try {
|
|
1291
|
-
return execFileSync("/bin/bash", ["-lc", command], { encoding: "utf8" });
|
|
1292
|
-
} catch {
|
|
1293
|
-
return "";
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
function safeReadPackageVersion(repo) {
|
|
1297
|
-
try {
|
|
1298
|
-
const packageJson = JSON.parse(readFileSync(path.join(repo, "package.json"), "utf8"));
|
|
1299
|
-
return typeof packageJson.version === "string" ? packageJson.version : DEFAULT_CONTEXT_MODE_VERSION;
|
|
1300
|
-
} catch {
|
|
1301
|
-
return DEFAULT_CONTEXT_MODE_VERSION;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
function combine(stdout, stderr) {
|
|
1305
|
-
if (stdout && stderr) {
|
|
1306
|
-
return `[stdout]\n${stdout}\n[stderr]\n${stderr}`;
|
|
1307
|
-
}
|
|
1308
|
-
return stdout || stderr || "";
|
|
1309
|
-
}
|
|
1310
|
-
async function createTempDir(prefix) {
|
|
1311
|
-
const tempPath = await mkdtemp(path.resolve(tmpdir(), prefix));
|
|
1312
|
-
return {
|
|
1313
|
-
path: tempPath,
|
|
1314
|
-
cleanup: async () => {
|
|
1315
|
-
await rm(tempPath, { recursive: true, force: true });
|
|
1316
|
-
},
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
function shellQuote(value) {
|
|
1320
|
-
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
1321
|
-
}
|
|
1322
|
-
function defaultReportPath() {
|
|
1323
|
-
return path.resolve(process.cwd(), "router/evals/reports/context-mode-real-deep-baseline-1-report.md");
|
|
1324
|
-
}
|
|
1325
|
-
function defaultUnavailableReportPath() {
|
|
1326
|
-
return path.resolve(process.cwd(), "router/evals/reports/context-mode-real-deep-unavailable-report.md");
|
|
1327
|
-
}
|
|
1328
|
-
function defaultRealDeepJsonReportPath(markdownReportPath) {
|
|
1329
|
-
return defaultJsonRunReportPath(markdownReportPath);
|
|
1330
|
-
}
|
|
1331
|
-
async function runCli() {
|
|
1332
|
-
const cliArgs = process.argv.slice(2);
|
|
1333
|
-
const {
|
|
1334
|
-
iterations: _iterations,
|
|
1335
|
-
reportPath,
|
|
1336
|
-
jsonReportPath,
|
|
1337
|
-
} = parseBenchmarkCliArgs(cliArgs, { reportPath: defaultReportPath() });
|
|
1338
|
-
const explicitReportPath = cliArgs.some((arg) => arg.startsWith("--report="));
|
|
1339
|
-
const contextModeRepo = cliStringOption(cliArgs, "--context-mode-repo");
|
|
1340
|
-
const contextModeServer = cliStringOption(cliArgs, "--context-mode-server");
|
|
1341
|
-
const fixtureSource = cliStringOption(cliArgs, "--fixture-source");
|
|
1342
|
-
const options = {};
|
|
1343
|
-
if (contextModeRepo !== undefined) {
|
|
1344
|
-
options.contextModeRepo = contextModeRepo;
|
|
1345
|
-
}
|
|
1346
|
-
if (contextModeServer !== undefined) {
|
|
1347
|
-
options.contextModeServer = contextModeServer;
|
|
1348
|
-
}
|
|
1349
|
-
if (fixtureSource !== undefined) {
|
|
1350
|
-
options.fixtureSource = fixtureSource;
|
|
1351
|
-
}
|
|
1352
|
-
const report = await runContextModeRealDeepBenchmark(options);
|
|
1353
|
-
const effectiveReportPath =
|
|
1354
|
-
report.contextMode.status === "unavailable" && !explicitReportPath ? defaultUnavailableReportPath() : reportPath;
|
|
1355
|
-
const reports = await writeContextModeRealDeepBenchmarkReports(report, effectiveReportPath, {
|
|
1356
|
-
jsonReportPath: jsonReportPath === undefined ? defaultRealDeepJsonReportPath(effectiveReportPath) : jsonReportPath,
|
|
1357
|
-
});
|
|
1358
|
-
const shortId = createHash("sha256")
|
|
1359
|
-
.update(JSON.stringify({ summaries: report.summaries, baselineChecks: report.baselineChecks }))
|
|
1360
|
-
.digest("hex")
|
|
1361
|
-
.slice(0, 8);
|
|
1362
|
-
console.log(
|
|
1363
|
-
`Freeflow real Context Mode deep benchmark ${shortId}: context-mode=${report.contextMode.status}, rows=${report.rows.length}, expected-limitations=${report.baselineChecks.expectedCurrentFailuresDetected ? "detected" : "missing"}`,
|
|
1364
|
-
);
|
|
1365
|
-
console.log(`Markdown report: ${reports.markdown}`);
|
|
1366
|
-
if (reports.json) {
|
|
1367
|
-
console.log(`JSON run data: ${reports.json}`);
|
|
1368
|
-
}
|
|
1369
|
-
if (report.contextMode.status === "available" && !report.baselineChecks.expectedCurrentFailuresDetected) {
|
|
1370
|
-
process.exitCode = 1;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
function cliStringOption(args, name) {
|
|
1374
|
-
const prefix = `${name}=`;
|
|
1375
|
-
const match = args.find((arg) => arg.startsWith(prefix));
|
|
1376
|
-
return match ? match.slice(prefix.length) : undefined;
|
|
1377
|
-
}
|
|
1378
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
1379
|
-
await runCli();
|
|
1380
|
-
}
|