knosky 0.6.3 → 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/CHANGELOG.md +149 -93
- package/CREDITS.md +14 -14
- package/LICENSE.md +76 -76
- package/LIMITATIONS.md +33 -23
- package/PRIVACY.md +30 -30
- package/README.md +170 -117
- package/SECURITY.md +78 -46
- package/action/post-comment.mjs +94 -89
- package/action.yml +62 -62
- package/bin/knosky.mjs +279 -105
- package/core/CONTRACT.md +70 -70
- package/core/append-only-checkpoint.mjs +215 -0
- package/core/audit-writer.mjs +317 -0
- package/core/benchmark-results.mjs +225 -225
- package/core/bundle.mjs +178 -178
- package/core/churn.mjs +23 -23
- package/core/ci.mjs +268 -268
- package/core/comparison.mjs +189 -189
- package/core/config.mjs +189 -189
- package/core/constants.mjs +13 -13
- package/core/contract.mjs +123 -123
- package/core/cross-repo.mjs +111 -111
- package/core/decision-codes.mjs +92 -0
- package/core/destination.mjs +161 -161
- package/core/district-classification.mjs +111 -0
- package/core/doctor-scorecard.mjs +369 -0
- package/core/domain-store.mjs +347 -0
- package/core/edges.mjs +43 -43
- package/core/escalate.mjs +68 -68
- package/core/freshness.mjs +198 -194
- package/core/fs-indexer.mjs +218 -218
- package/core/key-store.mjs +348 -348
- package/core/layout.mjs +46 -46
- package/core/ledger.mjs +176 -141
- package/core/local-ipc-identity.mjs +500 -0
- package/core/lod.mjs +155 -155
- package/core/mode-b.mjs +410 -0
- package/core/multi-model-benchmark.mjs +405 -405
- package/core/net-lockdown.mjs +421 -0
- package/core/onboarding.mjs +223 -223
- package/core/operator-auth.mjs +317 -0
- package/core/overlays.mjs +45 -45
- package/core/policy-lattice.mjs +142 -0
- package/core/pr-comment.mjs +198 -198
- package/core/protocol-spec.mjs +460 -460
- package/core/provenance.mjs +320 -0
- package/core/retrieve.mjs +63 -63
- package/core/route.mjs +304 -304
- package/core/schema.mjs +275 -275
- package/core/signing-tiers.mjs +1265 -0
- package/core/swarm-bench.mjs +106 -0
- package/core/swarm-coordinator.mjs +867 -0
- package/core/trust-root-rekey.mjs +410 -0
- package/mcp/server.mjs +264 -108
- package/package.json +56 -46
- package/renderer/art/kenney/buildingTiles_sheet.xml +130 -130
- package/renderer/art/kenney/cityDetails_sheet.xml +12 -12
- package/renderer/art/kenney/landscapeTiles_sheet.xml +129 -129
- package/renderer/art/kenney/sheet_allCars.xml +545 -545
- package/renderer/build-rich.mjs +43 -43
- package/renderer/city.template.html +808 -808
- package/ssot/decision-codes.json +133 -0
- package/ssot/ladder-l0-l3.md +232 -0
- package/ssot/tool-menu.json +130 -0
|
@@ -1,225 +1,225 @@
|
|
|
1
|
-
// KnoSky token-efficiency benchmark results (SAT-439 / SAT-469).
|
|
2
|
-
//
|
|
3
|
-
// Source: five tasks run head-to-head — naive agent (no KnoSky guidance) vs.
|
|
4
|
-
// KnoSky-guided agent — measured with the `comparison-run` protocol defined in
|
|
5
|
-
// core/comparison.mjs. Summaries exported here are the single source of truth
|
|
6
|
-
// for all public copy (README.md, wiki, etc.); update the raw runs first and
|
|
7
|
-
// re-derive the summary constants rather than editing the constants directly.
|
|
8
|
-
//
|
|
9
|
-
// The runs are stored in the COMPARISON_RUNS array for traceability and to
|
|
10
|
-
// allow tests to re-derive summary statistics from the raw data.
|
|
11
|
-
|
|
12
|
-
import { makeComparisonRun, validateComparisonRun } from './comparison.mjs';
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Raw comparison runs (SAT-439)
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Five tasks measured head-to-head. Each entry is a valid `comparison-run`
|
|
20
|
-
* artifact per core/comparison.mjs. Fields:
|
|
21
|
-
* tokens_in / tokens_out : LLM tokens consumed / produced
|
|
22
|
-
* tool_calls : total tool-call round-trips
|
|
23
|
-
* time_to_relevant_file_ms : ms until the agent first cited the target file,
|
|
24
|
-
* or null when the naive agent never found it
|
|
25
|
-
* correct : whether the agent reached the correct answer
|
|
26
|
-
*/
|
|
27
|
-
export const COMPARISON_RUNS = [
|
|
28
|
-
makeComparisonRun({
|
|
29
|
-
task_id: 'sat439-t01',
|
|
30
|
-
task_description: 'Locate the authentication module and explain its entry point',
|
|
31
|
-
target_files: ['core/key-store.mjs'],
|
|
32
|
-
naive: { tokens_in: 7400, tokens_out: 780, tool_calls: 13, time_to_relevant_file_ms: 10800, correct: false },
|
|
33
|
-
guided: { tokens_in: 2250, tokens_out: 197, tool_calls: 4, time_to_relevant_file_ms: 1800, correct: true },
|
|
34
|
-
}),
|
|
35
|
-
makeComparisonRun({
|
|
36
|
-
task_id: 'sat439-t02',
|
|
37
|
-
task_description: 'Find every file that imports the ledger module',
|
|
38
|
-
target_files: ['core/ledger.mjs'],
|
|
39
|
-
naive: { tokens_in: 11600, tokens_out: 1040, tool_calls: 20, time_to_relevant_file_ms: null, correct: false },
|
|
40
|
-
guided: { tokens_in: 4000, tokens_out: 305, tool_calls: 5, time_to_relevant_file_ms: 2050, correct: true },
|
|
41
|
-
}),
|
|
42
|
-
makeComparisonRun({
|
|
43
|
-
task_id: 'sat439-t03',
|
|
44
|
-
task_description: 'Trace which modules contribute to the MCP server entrypoint',
|
|
45
|
-
target_files: ['mcp/server.mjs'],
|
|
46
|
-
naive: { tokens_in: 5800, tokens_out: 640, tool_calls: 11, time_to_relevant_file_ms: 8400, correct: false },
|
|
47
|
-
guided: { tokens_in: 1900, tokens_out: 185, tool_calls: 4, time_to_relevant_file_ms: 1400, correct: true },
|
|
48
|
-
}),
|
|
49
|
-
makeComparisonRun({
|
|
50
|
-
task_id: 'sat439-t04',
|
|
51
|
-
task_description: 'List all test files that cover the route engine',
|
|
52
|
-
target_files: ['core/route.mjs', 'test/route.test.mjs'],
|
|
53
|
-
naive: { tokens_in: 9200, tokens_out: 880, tool_calls: 16, time_to_relevant_file_ms: null, correct: false },
|
|
54
|
-
guided: { tokens_in: 3000, tokens_out: 220, tool_calls: 5, time_to_relevant_file_ms: 1900, correct: true },
|
|
55
|
-
}),
|
|
56
|
-
makeComparisonRun({
|
|
57
|
-
task_id: 'sat439-t05',
|
|
58
|
-
task_description: 'Identify the freshness / churn modules and their relationship',
|
|
59
|
-
target_files: ['core/freshness.mjs', 'core/churn.mjs'],
|
|
60
|
-
naive: { tokens_in: 8000, tokens_out: 760, tool_calls: 14, time_to_relevant_file_ms: 10200, correct: false },
|
|
61
|
-
guided: { tokens_in: 2500, tokens_out: 200, tool_calls: 4, time_to_relevant_file_ms: 1700, correct: true },
|
|
62
|
-
}),
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Derived summary constants
|
|
67
|
-
// Computed once at module load from COMPARISON_RUNS — never hand-tuned.
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Aggregate token counts across all runs.
|
|
72
|
-
* @type {{ naive: number, guided: number }}
|
|
73
|
-
*/
|
|
74
|
-
export const TOTAL_TOKENS = COMPARISON_RUNS.reduce(
|
|
75
|
-
(acc, run) => {
|
|
76
|
-
acc.naive += run.naive.tokens_in + run.naive.tokens_out;
|
|
77
|
-
acc.guided += run.guided.tokens_in + run.guided.tokens_out;
|
|
78
|
-
return acc;
|
|
79
|
-
},
|
|
80
|
-
{ naive: 0, guided: 0 },
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Aggregate tool-call counts across all runs.
|
|
85
|
-
* @type {{ naive: number, guided: number }}
|
|
86
|
-
*/
|
|
87
|
-
export const TOTAL_TOOL_CALLS = COMPARISON_RUNS.reduce(
|
|
88
|
-
(acc, run) => {
|
|
89
|
-
acc.naive += run.naive.tool_calls;
|
|
90
|
-
acc.guided += run.guided.tool_calls;
|
|
91
|
-
return acc;
|
|
92
|
-
},
|
|
93
|
-
{ naive: 0, guided: 0 },
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Token reduction percentage (rounded to nearest integer).
|
|
98
|
-
* "X% fewer tokens" claim derived from TOTAL_TOKENS.
|
|
99
|
-
* @type {number}
|
|
100
|
-
*/
|
|
101
|
-
export const TOKEN_REDUCTION_PCT = Math.round(
|
|
102
|
-
100 * (TOTAL_TOKENS.naive - TOTAL_TOKENS.guided) / TOTAL_TOKENS.naive,
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Tool-call reduction percentage (rounded to nearest integer).
|
|
107
|
-
* @type {number}
|
|
108
|
-
*/
|
|
109
|
-
export const TOOL_CALL_REDUCTION_PCT = Math.round(
|
|
110
|
-
100 * (TOTAL_TOOL_CALLS.naive - TOTAL_TOOL_CALLS.guided) / TOTAL_TOOL_CALLS.naive,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Mean time-to-relevant-file speedup (guided vs. naive), restricted to runs
|
|
115
|
-
* where both agents found the file (time_to_relevant_file_ms !== null).
|
|
116
|
-
* Rounded to one decimal place.
|
|
117
|
-
* @type {number}
|
|
118
|
-
*/
|
|
119
|
-
export const TTRF_SPEEDUP_X = (() => {
|
|
120
|
-
const both = COMPARISON_RUNS.filter(
|
|
121
|
-
r => r.naive.time_to_relevant_file_ms !== null &&
|
|
122
|
-
r.guided.time_to_relevant_file_ms !== null,
|
|
123
|
-
);
|
|
124
|
-
if (both.length === 0) return 0;
|
|
125
|
-
const naiveMean = both.reduce((s, r) => s + r.naive.time_to_relevant_file_ms, 0) / both.length;
|
|
126
|
-
const guidedMean = both.reduce((s, r) => s + r.guided.time_to_relevant_file_ms, 0) / both.length;
|
|
127
|
-
return Math.round(10 * naiveMean / guidedMean) / 10;
|
|
128
|
-
})();
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Human-readable headline in the form used in README.md.
|
|
132
|
-
* Built from the derived constants so README claims stay consistent.
|
|
133
|
-
* @type {string}
|
|
134
|
-
*/
|
|
135
|
-
export const HEADLINE_CLAIM =
|
|
136
|
-
`${TOKEN_REDUCTION_PCT}% fewer tokens, ` +
|
|
137
|
-
`${TOOL_CALL_REDUCTION_PCT}% fewer tool calls, ` +
|
|
138
|
-
`${TTRF_SPEEDUP_X}× faster to the right file`;
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Schema version for the benchmark-results module.
|
|
142
|
-
* Bump this when the run schema or summary fields change.
|
|
143
|
-
* @type {string}
|
|
144
|
-
*/
|
|
145
|
-
export const BENCHMARK_RESULTS_VERSION = '1.0';
|
|
146
|
-
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// formatBenchmarkReport (SAT-460)
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Produce a human-readable Markdown benchmark report from the comparison runs
|
|
153
|
-
* stored in this module. The report is deterministic — same input → same
|
|
154
|
-
* output — and safe to embed in wikis, PR comments, or printed to stdout.
|
|
155
|
-
*
|
|
156
|
-
* Layout:
|
|
157
|
-
* 1. Headline summary block (the three key numbers)
|
|
158
|
-
* 2. Per-run table (task id, tokens naive/guided, tool calls, TTRF, correct)
|
|
159
|
-
* 3. Footer with provenance note
|
|
160
|
-
*
|
|
161
|
-
* @param {object} [opts]
|
|
162
|
-
* @param {object[]} [opts.runs=COMPARISON_RUNS] — override for testing.
|
|
163
|
-
* @returns {string} Markdown string, no trailing newline.
|
|
164
|
-
*/
|
|
165
|
-
export function formatBenchmarkReport({ runs = COMPARISON_RUNS } = {}) {
|
|
166
|
-
// --- summary numbers (re-derived from the provided runs so the function is
|
|
167
|
-
// self-contained and testable with custom run sets) -------------------
|
|
168
|
-
const totalNaiveTok = runs.reduce((s, r) => s + r.naive.tokens_in + r.naive.tokens_out, 0);
|
|
169
|
-
const totalGuidedTok = runs.reduce((s, r) => s + r.guided.tokens_in + r.guided.tokens_out, 0);
|
|
170
|
-
const totalNaiveCalls = runs.reduce((s, r) => s + r.naive.tool_calls, 0);
|
|
171
|
-
const totalGuidedCalls = runs.reduce((s, r) => s + r.guided.tool_calls, 0);
|
|
172
|
-
|
|
173
|
-
const tokenPct = totalNaiveTok > 0
|
|
174
|
-
? Math.round(100 * (totalNaiveTok - totalGuidedTok) / totalNaiveTok)
|
|
175
|
-
: 0;
|
|
176
|
-
const callPct = totalNaiveCalls > 0
|
|
177
|
-
? Math.round(100 * (totalNaiveCalls - totalGuidedCalls) / totalNaiveCalls)
|
|
178
|
-
: 0;
|
|
179
|
-
|
|
180
|
-
const both = runs.filter(
|
|
181
|
-
r => r.naive.time_to_relevant_file_ms !== null &&
|
|
182
|
-
r.guided.time_to_relevant_file_ms !== null,
|
|
183
|
-
);
|
|
184
|
-
const speedup = both.length > 0
|
|
185
|
-
? Math.round(
|
|
186
|
-
10 * (both.reduce((s, r) => s + r.naive.time_to_relevant_file_ms, 0) / both.length) /
|
|
187
|
-
(both.reduce((s, r) => s + r.guided.time_to_relevant_file_ms, 0) / both.length),
|
|
188
|
-
) / 10
|
|
189
|
-
: 0;
|
|
190
|
-
const speedupStr = Number.isInteger(speedup) ? String(speedup) : speedup.toFixed(1);
|
|
191
|
-
|
|
192
|
-
const guidedCorrect = runs.filter(r => r.guided.correct).length;
|
|
193
|
-
const naiveCorrect = runs.filter(r => r.naive.correct).length;
|
|
194
|
-
|
|
195
|
-
// --- headline block -------------------------------------------------------
|
|
196
|
-
const lines = [
|
|
197
|
-
'## KnoSky token-efficiency benchmark (SAT-439)',
|
|
198
|
-
'',
|
|
199
|
-
`**${tokenPct}% fewer tokens · ${callPct}% fewer tool calls · ${speedupStr}× faster to the right file**`,
|
|
200
|
-
'',
|
|
201
|
-
`_${runs.length} tasks, naive agent vs. KnoSky-guided agent._`,
|
|
202
|
-
`_Guided: ${guidedCorrect}/${runs.length} correct. Naive: ${naiveCorrect}/${runs.length} correct._`,
|
|
203
|
-
'',
|
|
204
|
-
'### Per-task results',
|
|
205
|
-
'',
|
|
206
|
-
'| Task | Tokens (naive) | Tokens (guided) | Tool calls (naive) | Tool calls (guided) | TTRF naive (ms) | TTRF guided (ms) | Guided correct |',
|
|
207
|
-
'|------|---------------:|----------------:|-------------------:|--------------------:|----------------:|-----------------:|:--------------:|',
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
for (const run of runs) {
|
|
211
|
-
const tnaive = run.naive.tokens_in + run.naive.tokens_out;
|
|
212
|
-
const tguided = run.guided.tokens_in + run.guided.tokens_out;
|
|
213
|
-
const ttrfN = run.naive.time_to_relevant_file_ms === null ? '—' : String(run.naive.time_to_relevant_file_ms);
|
|
214
|
-
const ttrfG = run.guided.time_to_relevant_file_ms === null ? '—' : String(run.guided.time_to_relevant_file_ms);
|
|
215
|
-
const correct = run.guided.correct ? '✓' : '✗';
|
|
216
|
-
lines.push(
|
|
217
|
-
`| ${run.task_id} | ${tnaive} | ${tguided} | ${run.naive.tool_calls} | ${run.guided.tool_calls} | ${ttrfN} | ${ttrfG} | ${correct} |`,
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
lines.push('');
|
|
222
|
-
lines.push(`_Data source: \`core/benchmark-results.mjs\` (BENCHMARK_RESULTS_VERSION ${BENCHMARK_RESULTS_VERSION})._`);
|
|
223
|
-
|
|
224
|
-
return lines.join('\n');
|
|
225
|
-
}
|
|
1
|
+
// KnoSky token-efficiency benchmark results (SAT-439 / SAT-469).
|
|
2
|
+
//
|
|
3
|
+
// Source: five tasks run head-to-head — naive agent (no KnoSky guidance) vs.
|
|
4
|
+
// KnoSky-guided agent — measured with the `comparison-run` protocol defined in
|
|
5
|
+
// core/comparison.mjs. Summaries exported here are the single source of truth
|
|
6
|
+
// for all public copy (README.md, wiki, etc.); update the raw runs first and
|
|
7
|
+
// re-derive the summary constants rather than editing the constants directly.
|
|
8
|
+
//
|
|
9
|
+
// The runs are stored in the COMPARISON_RUNS array for traceability and to
|
|
10
|
+
// allow tests to re-derive summary statistics from the raw data.
|
|
11
|
+
|
|
12
|
+
import { makeComparisonRun, validateComparisonRun } from './comparison.mjs';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Raw comparison runs (SAT-439)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Five tasks measured head-to-head. Each entry is a valid `comparison-run`
|
|
20
|
+
* artifact per core/comparison.mjs. Fields:
|
|
21
|
+
* tokens_in / tokens_out : LLM tokens consumed / produced
|
|
22
|
+
* tool_calls : total tool-call round-trips
|
|
23
|
+
* time_to_relevant_file_ms : ms until the agent first cited the target file,
|
|
24
|
+
* or null when the naive agent never found it
|
|
25
|
+
* correct : whether the agent reached the correct answer
|
|
26
|
+
*/
|
|
27
|
+
export const COMPARISON_RUNS = [
|
|
28
|
+
makeComparisonRun({
|
|
29
|
+
task_id: 'sat439-t01',
|
|
30
|
+
task_description: 'Locate the authentication module and explain its entry point',
|
|
31
|
+
target_files: ['core/key-store.mjs'],
|
|
32
|
+
naive: { tokens_in: 7400, tokens_out: 780, tool_calls: 13, time_to_relevant_file_ms: 10800, correct: false },
|
|
33
|
+
guided: { tokens_in: 2250, tokens_out: 197, tool_calls: 4, time_to_relevant_file_ms: 1800, correct: true },
|
|
34
|
+
}),
|
|
35
|
+
makeComparisonRun({
|
|
36
|
+
task_id: 'sat439-t02',
|
|
37
|
+
task_description: 'Find every file that imports the ledger module',
|
|
38
|
+
target_files: ['core/ledger.mjs'],
|
|
39
|
+
naive: { tokens_in: 11600, tokens_out: 1040, tool_calls: 20, time_to_relevant_file_ms: null, correct: false },
|
|
40
|
+
guided: { tokens_in: 4000, tokens_out: 305, tool_calls: 5, time_to_relevant_file_ms: 2050, correct: true },
|
|
41
|
+
}),
|
|
42
|
+
makeComparisonRun({
|
|
43
|
+
task_id: 'sat439-t03',
|
|
44
|
+
task_description: 'Trace which modules contribute to the MCP server entrypoint',
|
|
45
|
+
target_files: ['mcp/server.mjs'],
|
|
46
|
+
naive: { tokens_in: 5800, tokens_out: 640, tool_calls: 11, time_to_relevant_file_ms: 8400, correct: false },
|
|
47
|
+
guided: { tokens_in: 1900, tokens_out: 185, tool_calls: 4, time_to_relevant_file_ms: 1400, correct: true },
|
|
48
|
+
}),
|
|
49
|
+
makeComparisonRun({
|
|
50
|
+
task_id: 'sat439-t04',
|
|
51
|
+
task_description: 'List all test files that cover the route engine',
|
|
52
|
+
target_files: ['core/route.mjs', 'test/route.test.mjs'],
|
|
53
|
+
naive: { tokens_in: 9200, tokens_out: 880, tool_calls: 16, time_to_relevant_file_ms: null, correct: false },
|
|
54
|
+
guided: { tokens_in: 3000, tokens_out: 220, tool_calls: 5, time_to_relevant_file_ms: 1900, correct: true },
|
|
55
|
+
}),
|
|
56
|
+
makeComparisonRun({
|
|
57
|
+
task_id: 'sat439-t05',
|
|
58
|
+
task_description: 'Identify the freshness / churn modules and their relationship',
|
|
59
|
+
target_files: ['core/freshness.mjs', 'core/churn.mjs'],
|
|
60
|
+
naive: { tokens_in: 8000, tokens_out: 760, tool_calls: 14, time_to_relevant_file_ms: 10200, correct: false },
|
|
61
|
+
guided: { tokens_in: 2500, tokens_out: 200, tool_calls: 4, time_to_relevant_file_ms: 1700, correct: true },
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Derived summary constants
|
|
67
|
+
// Computed once at module load from COMPARISON_RUNS — never hand-tuned.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Aggregate token counts across all runs.
|
|
72
|
+
* @type {{ naive: number, guided: number }}
|
|
73
|
+
*/
|
|
74
|
+
export const TOTAL_TOKENS = COMPARISON_RUNS.reduce(
|
|
75
|
+
(acc, run) => {
|
|
76
|
+
acc.naive += run.naive.tokens_in + run.naive.tokens_out;
|
|
77
|
+
acc.guided += run.guided.tokens_in + run.guided.tokens_out;
|
|
78
|
+
return acc;
|
|
79
|
+
},
|
|
80
|
+
{ naive: 0, guided: 0 },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Aggregate tool-call counts across all runs.
|
|
85
|
+
* @type {{ naive: number, guided: number }}
|
|
86
|
+
*/
|
|
87
|
+
export const TOTAL_TOOL_CALLS = COMPARISON_RUNS.reduce(
|
|
88
|
+
(acc, run) => {
|
|
89
|
+
acc.naive += run.naive.tool_calls;
|
|
90
|
+
acc.guided += run.guided.tool_calls;
|
|
91
|
+
return acc;
|
|
92
|
+
},
|
|
93
|
+
{ naive: 0, guided: 0 },
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Token reduction percentage (rounded to nearest integer).
|
|
98
|
+
* "X% fewer tokens" claim derived from TOTAL_TOKENS.
|
|
99
|
+
* @type {number}
|
|
100
|
+
*/
|
|
101
|
+
export const TOKEN_REDUCTION_PCT = Math.round(
|
|
102
|
+
100 * (TOTAL_TOKENS.naive - TOTAL_TOKENS.guided) / TOTAL_TOKENS.naive,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Tool-call reduction percentage (rounded to nearest integer).
|
|
107
|
+
* @type {number}
|
|
108
|
+
*/
|
|
109
|
+
export const TOOL_CALL_REDUCTION_PCT = Math.round(
|
|
110
|
+
100 * (TOTAL_TOOL_CALLS.naive - TOTAL_TOOL_CALLS.guided) / TOTAL_TOOL_CALLS.naive,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Mean time-to-relevant-file speedup (guided vs. naive), restricted to runs
|
|
115
|
+
* where both agents found the file (time_to_relevant_file_ms !== null).
|
|
116
|
+
* Rounded to one decimal place.
|
|
117
|
+
* @type {number}
|
|
118
|
+
*/
|
|
119
|
+
export const TTRF_SPEEDUP_X = (() => {
|
|
120
|
+
const both = COMPARISON_RUNS.filter(
|
|
121
|
+
r => r.naive.time_to_relevant_file_ms !== null &&
|
|
122
|
+
r.guided.time_to_relevant_file_ms !== null,
|
|
123
|
+
);
|
|
124
|
+
if (both.length === 0) return 0;
|
|
125
|
+
const naiveMean = both.reduce((s, r) => s + r.naive.time_to_relevant_file_ms, 0) / both.length;
|
|
126
|
+
const guidedMean = both.reduce((s, r) => s + r.guided.time_to_relevant_file_ms, 0) / both.length;
|
|
127
|
+
return Math.round(10 * naiveMean / guidedMean) / 10;
|
|
128
|
+
})();
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Human-readable headline in the form used in README.md.
|
|
132
|
+
* Built from the derived constants so README claims stay consistent.
|
|
133
|
+
* @type {string}
|
|
134
|
+
*/
|
|
135
|
+
export const HEADLINE_CLAIM =
|
|
136
|
+
`${TOKEN_REDUCTION_PCT}% fewer tokens, ` +
|
|
137
|
+
`${TOOL_CALL_REDUCTION_PCT}% fewer tool calls, ` +
|
|
138
|
+
`${TTRF_SPEEDUP_X}× faster to the right file`;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Schema version for the benchmark-results module.
|
|
142
|
+
* Bump this when the run schema or summary fields change.
|
|
143
|
+
* @type {string}
|
|
144
|
+
*/
|
|
145
|
+
export const BENCHMARK_RESULTS_VERSION = '1.0';
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// formatBenchmarkReport (SAT-460)
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Produce a human-readable Markdown benchmark report from the comparison runs
|
|
153
|
+
* stored in this module. The report is deterministic — same input → same
|
|
154
|
+
* output — and safe to embed in wikis, PR comments, or printed to stdout.
|
|
155
|
+
*
|
|
156
|
+
* Layout:
|
|
157
|
+
* 1. Headline summary block (the three key numbers)
|
|
158
|
+
* 2. Per-run table (task id, tokens naive/guided, tool calls, TTRF, correct)
|
|
159
|
+
* 3. Footer with provenance note
|
|
160
|
+
*
|
|
161
|
+
* @param {object} [opts]
|
|
162
|
+
* @param {object[]} [opts.runs=COMPARISON_RUNS] — override for testing.
|
|
163
|
+
* @returns {string} Markdown string, no trailing newline.
|
|
164
|
+
*/
|
|
165
|
+
export function formatBenchmarkReport({ runs = COMPARISON_RUNS } = {}) {
|
|
166
|
+
// --- summary numbers (re-derived from the provided runs so the function is
|
|
167
|
+
// self-contained and testable with custom run sets) -------------------
|
|
168
|
+
const totalNaiveTok = runs.reduce((s, r) => s + r.naive.tokens_in + r.naive.tokens_out, 0);
|
|
169
|
+
const totalGuidedTok = runs.reduce((s, r) => s + r.guided.tokens_in + r.guided.tokens_out, 0);
|
|
170
|
+
const totalNaiveCalls = runs.reduce((s, r) => s + r.naive.tool_calls, 0);
|
|
171
|
+
const totalGuidedCalls = runs.reduce((s, r) => s + r.guided.tool_calls, 0);
|
|
172
|
+
|
|
173
|
+
const tokenPct = totalNaiveTok > 0
|
|
174
|
+
? Math.round(100 * (totalNaiveTok - totalGuidedTok) / totalNaiveTok)
|
|
175
|
+
: 0;
|
|
176
|
+
const callPct = totalNaiveCalls > 0
|
|
177
|
+
? Math.round(100 * (totalNaiveCalls - totalGuidedCalls) / totalNaiveCalls)
|
|
178
|
+
: 0;
|
|
179
|
+
|
|
180
|
+
const both = runs.filter(
|
|
181
|
+
r => r.naive.time_to_relevant_file_ms !== null &&
|
|
182
|
+
r.guided.time_to_relevant_file_ms !== null,
|
|
183
|
+
);
|
|
184
|
+
const speedup = both.length > 0
|
|
185
|
+
? Math.round(
|
|
186
|
+
10 * (both.reduce((s, r) => s + r.naive.time_to_relevant_file_ms, 0) / both.length) /
|
|
187
|
+
(both.reduce((s, r) => s + r.guided.time_to_relevant_file_ms, 0) / both.length),
|
|
188
|
+
) / 10
|
|
189
|
+
: 0;
|
|
190
|
+
const speedupStr = Number.isInteger(speedup) ? String(speedup) : speedup.toFixed(1);
|
|
191
|
+
|
|
192
|
+
const guidedCorrect = runs.filter(r => r.guided.correct).length;
|
|
193
|
+
const naiveCorrect = runs.filter(r => r.naive.correct).length;
|
|
194
|
+
|
|
195
|
+
// --- headline block -------------------------------------------------------
|
|
196
|
+
const lines = [
|
|
197
|
+
'## KnoSky token-efficiency benchmark (SAT-439)',
|
|
198
|
+
'',
|
|
199
|
+
`**${tokenPct}% fewer tokens · ${callPct}% fewer tool calls · ${speedupStr}× faster to the right file**`,
|
|
200
|
+
'',
|
|
201
|
+
`_${runs.length} tasks, naive agent vs. KnoSky-guided agent._`,
|
|
202
|
+
`_Guided: ${guidedCorrect}/${runs.length} correct. Naive: ${naiveCorrect}/${runs.length} correct._`,
|
|
203
|
+
'',
|
|
204
|
+
'### Per-task results',
|
|
205
|
+
'',
|
|
206
|
+
'| Task | Tokens (naive) | Tokens (guided) | Tool calls (naive) | Tool calls (guided) | TTRF naive (ms) | TTRF guided (ms) | Guided correct |',
|
|
207
|
+
'|------|---------------:|----------------:|-------------------:|--------------------:|----------------:|-----------------:|:--------------:|',
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
for (const run of runs) {
|
|
211
|
+
const tnaive = run.naive.tokens_in + run.naive.tokens_out;
|
|
212
|
+
const tguided = run.guided.tokens_in + run.guided.tokens_out;
|
|
213
|
+
const ttrfN = run.naive.time_to_relevant_file_ms === null ? '—' : String(run.naive.time_to_relevant_file_ms);
|
|
214
|
+
const ttrfG = run.guided.time_to_relevant_file_ms === null ? '—' : String(run.guided.time_to_relevant_file_ms);
|
|
215
|
+
const correct = run.guided.correct ? '✓' : '✗';
|
|
216
|
+
lines.push(
|
|
217
|
+
`| ${run.task_id} | ${tnaive} | ${tguided} | ${run.naive.tool_calls} | ${run.guided.tool_calls} | ${ttrfN} | ${ttrfG} | ${correct} |`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
lines.push('');
|
|
222
|
+
lines.push(`_Data source: \`core/benchmark-results.mjs\` (BENCHMARK_RESULTS_VERSION ${BENCHMARK_RESULTS_VERSION})._`);
|
|
223
|
+
|
|
224
|
+
return lines.join('\n');
|
|
225
|
+
}
|