knodin 0.5.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/LICENSE +21 -0
- package/README.md +590 -0
- package/dist/bin/cli.js +1704 -0
- package/dist/src/agent-integration.js +250 -0
- package/dist/src/artifact-refresh.js +81 -0
- package/dist/src/cli-args.js +267 -0
- package/dist/src/cli-model.js +324 -0
- package/dist/src/compact-structural.js +96 -0
- package/dist/src/competitive-constraints.js +20 -0
- package/dist/src/competitive-manifest.js +330 -0
- package/dist/src/competitive-measurement.js +183 -0
- package/dist/src/competitive-runner.js +453 -0
- package/dist/src/competitive-sandbox.js +108 -0
- package/dist/src/context-export.js +422 -0
- package/dist/src/context.js +102 -0
- package/dist/src/docs-sections.js +141 -0
- package/dist/src/doctor.js +380 -0
- package/dist/src/engine/ann-hnsw.js +271 -0
- package/dist/src/engine/embeddings.js +193 -0
- package/dist/src/engine/file-walker.js +43 -0
- package/dist/src/engine/index.js +13030 -0
- package/dist/src/engine/perf.js +115 -0
- package/dist/src/engine/prune.js +112 -0
- package/dist/src/engine/source-policy.js +69 -0
- package/dist/src/engine/sqlite.js +71 -0
- package/dist/src/engine/symbol-delete.js +58 -0
- package/dist/src/failure-diagnosis.js +590 -0
- package/dist/src/fleet.js +7 -0
- package/dist/src/git-executable.js +31 -0
- package/dist/src/graph-query-health.js +115 -0
- package/dist/src/index-activity.js +125 -0
- package/dist/src/init-progress-worker.js +107 -0
- package/dist/src/init-progress.js +155 -0
- package/dist/src/init.js +985 -0
- package/dist/src/lifecycle-health.js +213 -0
- package/dist/src/lsp-readonly.js +217 -0
- package/dist/src/output-compression.js +629 -0
- package/dist/src/output-telemetry.js +359 -0
- package/dist/src/pr-triage.js +638 -0
- package/dist/src/relationship-adapters.js +370 -0
- package/dist/src/release-attestation.js +533 -0
- package/dist/src/repair-progress-worker.js +121 -0
- package/dist/src/repair-progress.js +262 -0
- package/dist/src/repository-init-process.js +173 -0
- package/dist/src/repository-management.js +1089 -0
- package/dist/src/response-budget.js +184 -0
- package/dist/src/server.js +53 -0
- package/dist/src/system-config.js +615 -0
- package/dist/src/terminal-help.js +83 -0
- package/dist/src/tools/knodin-tools.js +1438 -0
- package/dist/src/tools/reckon-tools.js +5 -0
- package/dist/src/update-policy.js +944 -0
- package/dist/src/update-trust.js +503 -0
- package/dist/src/version.js +13 -0
- package/dist/src/visualization.js +162 -0
- package/dist/src/wait-for-fresh.js +98 -0
- package/dist/src/worktree-lifecycle.js +231 -0
- package/docs/CLI.md +39 -0
- package/docs/COMMAND-OUTPUT-COMPRESSION.md +194 -0
- package/docs/DEAD-CODE-AND-IMPACT.md +27 -0
- package/docs/DOCTOR-AND-UPDATES.md +84 -0
- package/docs/INDEXING-POLICY-AND-PROVENANCE.md +37 -0
- package/docs/INSTALLATION.md +208 -0
- package/docs/MCP.md +100 -0
- package/docs/PT-ACCESS-RECOMMENDATION.md +91 -0
- package/docs/RELEASE-0.3-EVIDENCE.md +73 -0
- package/docs/REPOSITORIES-AND-WORKTREES.md +81 -0
- package/docs/SIGNED-UPDATES.md +146 -0
- package/docs/SYSTEMS-AND-RELATIONSHIPS.md +45 -0
- package/docs/TELEMETRY.md +42 -0
- package/docs/releases/0.3.0.md +46 -0
- package/docs/releases/0.4.0.md +68 -0
- package/docs/releases/0.4.1.md +28 -0
- package/docs/releases/0.4.2.md +27 -0
- package/docs/releases/0.4.3.md +23 -0
- package/docs/releases/0.5.0.md +29 -0
- package/package.json +110 -0
- package/schemas/release-attestation-v1.schema.json +210 -0
- package/tree-sitter-prisma.wasm +0 -0
- package/tree-sitter-sql.wasm +0 -0
- package/tree-sitter-xml.wasm +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { COMPETITIVE_MANIFEST, } from "./competitive-manifest.js";
|
|
4
|
+
const RESERVED_CASE_KEYS = new Set([
|
|
5
|
+
"id",
|
|
6
|
+
"axis",
|
|
7
|
+
"parameters",
|
|
8
|
+
"note",
|
|
9
|
+
"blocked",
|
|
10
|
+
"mutation",
|
|
11
|
+
"knodin",
|
|
12
|
+
"oracle",
|
|
13
|
+
"caseContract",
|
|
14
|
+
"partial",
|
|
15
|
+
]);
|
|
16
|
+
function responseOf(side) {
|
|
17
|
+
if (!side || side.ok !== true)
|
|
18
|
+
return null;
|
|
19
|
+
const response = side.response;
|
|
20
|
+
return typeof response === "string" && response.length > 0 ? response : null;
|
|
21
|
+
}
|
|
22
|
+
function normalizedWords(value) {
|
|
23
|
+
return new Set(value
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.split(/[^a-z0-9_.-]+/)
|
|
26
|
+
.filter((word) => word.length >= 4));
|
|
27
|
+
}
|
|
28
|
+
function runCheckedInOracle(oracleId, test, competitor) {
|
|
29
|
+
const contenderResponse = responseOf(findCompetitorSide(test, competitor));
|
|
30
|
+
const knodinResponse = responseOf(test.knodin);
|
|
31
|
+
if (!contenderResponse || !knodinResponse) {
|
|
32
|
+
return {
|
|
33
|
+
id: oracleId,
|
|
34
|
+
status: "unavailable",
|
|
35
|
+
reason: "Both successful response payloads are required for the checked-in oracle.",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const bothContain = (pattern) => pattern.test(contenderResponse) && pattern.test(knodinResponse);
|
|
39
|
+
let passed = false;
|
|
40
|
+
switch (oracleId) {
|
|
41
|
+
case "contains-create-engine":
|
|
42
|
+
passed = bothContain(/create[\s_-]*engine/i);
|
|
43
|
+
break;
|
|
44
|
+
case "contains-index":
|
|
45
|
+
passed = bothContain(/index|fresh|status/i);
|
|
46
|
+
break;
|
|
47
|
+
case "pack-two-files":
|
|
48
|
+
passed =
|
|
49
|
+
bothContain(/src\/context\.ts/i) &&
|
|
50
|
+
bothContain(/src\/cli-args\.ts/i) &&
|
|
51
|
+
!/(?:^|\/)[^/\n]*\.spec\.ts/i.test(contenderResponse) &&
|
|
52
|
+
!/(?:^|\/)[^/\n]*\.spec\.ts/i.test(knodinResponse);
|
|
53
|
+
break;
|
|
54
|
+
case "packed-read": {
|
|
55
|
+
const contenderWords = normalizedWords(contenderResponse);
|
|
56
|
+
const knodinWords = normalizedWords(knodinResponse);
|
|
57
|
+
const shared = [...contenderWords].filter((word) => knodinWords.has(word));
|
|
58
|
+
passed = shared.length >= 2;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "grep-create-engine":
|
|
62
|
+
passed = bothContain(/createEngine/i);
|
|
63
|
+
break;
|
|
64
|
+
case "architecture-facet": {
|
|
65
|
+
const facet = String(test.id ?? "")
|
|
66
|
+
.replace(/^architecture-/, "")
|
|
67
|
+
.replace("_", " ");
|
|
68
|
+
passed = facet.length > 0 && bothContain(new RegExp(facet.replace(" ", "[ _-]?"), "i"));
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
id: oracleId,
|
|
74
|
+
status: passed ? "passed" : "failed",
|
|
75
|
+
...(passed ? {} : { reason: `Checked-in ${oracleId} oracle did not match both responses.` }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function applyCompetitiveCaseContracts(competitor, raw, fixtureRevision) {
|
|
79
|
+
const source = raw && typeof raw === "object"
|
|
80
|
+
? raw
|
|
81
|
+
: {};
|
|
82
|
+
const results = Array.isArray(source.results) ? source.results : [];
|
|
83
|
+
const mappings = COMPETITIVE_MANIFEST.find((entry) => entry.id === competitor)?.caseMappings ?? [];
|
|
84
|
+
return {
|
|
85
|
+
...source,
|
|
86
|
+
results: results.map((test) => {
|
|
87
|
+
const mapping = mappings.find((candidate) => new RegExp(candidate.idPattern).test(String(test.id ?? "")));
|
|
88
|
+
if (!mapping) {
|
|
89
|
+
return {
|
|
90
|
+
...test,
|
|
91
|
+
caseContract: {
|
|
92
|
+
useCase: String(test.axis ?? test.id ?? "unlabeled competitive case"),
|
|
93
|
+
fixtureRevision,
|
|
94
|
+
mode: "warm",
|
|
95
|
+
scope: {
|
|
96
|
+
competitorOperation: "unmatched",
|
|
97
|
+
knodinOperation: "unmatched",
|
|
98
|
+
dimensions: [],
|
|
99
|
+
},
|
|
100
|
+
budgets: { bytes: null, tokens: null, items: null },
|
|
101
|
+
disposition: {
|
|
102
|
+
status: "incomparable",
|
|
103
|
+
reason: "No checked-in equivalent-operation mapping exists for this case ID; latency is excluded.",
|
|
104
|
+
},
|
|
105
|
+
oracle: {
|
|
106
|
+
status: "unavailable",
|
|
107
|
+
reason: "Correctness is not evaluated for an incomparable case.",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...test,
|
|
114
|
+
caseContract: {
|
|
115
|
+
useCase: mapping.useCase,
|
|
116
|
+
fixtureRevision,
|
|
117
|
+
mode: mapping.mode,
|
|
118
|
+
scope: mapping.scope,
|
|
119
|
+
budgets: mapping.budgets,
|
|
120
|
+
disposition: { status: "comparable", reason: mapping.reason },
|
|
121
|
+
oracle: runCheckedInOracle(mapping.oracleId, test, competitor),
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function selectCompetitors(manifest, explicitIds, roadmapIds) {
|
|
128
|
+
const known = new Set(manifest.map((entry) => entry.id));
|
|
129
|
+
for (const id of explicitIds) {
|
|
130
|
+
if (!known.has(id))
|
|
131
|
+
throw new Error(`Unknown competitor: ${id}`);
|
|
132
|
+
}
|
|
133
|
+
const requestedRoadmap = new Set(roadmapIds.map((id) => id.toUpperCase()));
|
|
134
|
+
const knownRoadmap = new Set(manifest.flatMap((entry) => [...entry.roadmap]).map((id) => id.toUpperCase()));
|
|
135
|
+
for (const id of requestedRoadmap) {
|
|
136
|
+
if (!knownRoadmap.has(id))
|
|
137
|
+
throw new Error(`Unknown roadmap item: ${id}`);
|
|
138
|
+
}
|
|
139
|
+
const selected = new Set(explicitIds);
|
|
140
|
+
for (const entry of manifest) {
|
|
141
|
+
if (entry.roadmap.some((id) => requestedRoadmap.has(id.toUpperCase())))
|
|
142
|
+
selected.add(entry.id);
|
|
143
|
+
}
|
|
144
|
+
if (selected.size === 0 && explicitIds.length === 0 && roadmapIds.length === 0) {
|
|
145
|
+
for (const entry of manifest)
|
|
146
|
+
selected.add(entry.id);
|
|
147
|
+
}
|
|
148
|
+
return manifest.filter((entry) => selected.has(entry.id)).map((entry) => entry.id);
|
|
149
|
+
}
|
|
150
|
+
export function createRunDirectory(root, timestamp, commit) {
|
|
151
|
+
const stamp = timestamp.replace(/[-:.]/g, "");
|
|
152
|
+
const directory = path.join(root, `${stamp}-${commit.slice(0, 12)}`);
|
|
153
|
+
try {
|
|
154
|
+
fs.mkdirSync(directory, { recursive: false });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.code === "EEXIST") {
|
|
158
|
+
throw new Error(`Competitive run directory already exists: ${directory}`);
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
return directory;
|
|
163
|
+
}
|
|
164
|
+
function findCompetitorSide(test, competitor) {
|
|
165
|
+
const candidates = [
|
|
166
|
+
competitor,
|
|
167
|
+
competitor.replaceAll("-", ""),
|
|
168
|
+
competitor.replaceAll("-", "_"),
|
|
169
|
+
"competitor",
|
|
170
|
+
"gitnexus",
|
|
171
|
+
"graphify",
|
|
172
|
+
"serena",
|
|
173
|
+
"grepai",
|
|
174
|
+
"repomix",
|
|
175
|
+
"aider",
|
|
176
|
+
"code2prompt",
|
|
177
|
+
"claudeContext",
|
|
178
|
+
"codegraph",
|
|
179
|
+
];
|
|
180
|
+
for (const key of candidates) {
|
|
181
|
+
const value = test[key];
|
|
182
|
+
if (value && typeof value === "object" && ("ok" in value || "medianMs" in value)) {
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const [key, value] of Object.entries(test)) {
|
|
187
|
+
if (RESERVED_CASE_KEYS.has(key) || !value || typeof value !== "object")
|
|
188
|
+
continue;
|
|
189
|
+
if ("ok" in value || "medianMs" in value)
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
export function normalizeRawResult(competitor, raw) {
|
|
195
|
+
const rawRecord = raw && typeof raw === "object"
|
|
196
|
+
? raw
|
|
197
|
+
: {};
|
|
198
|
+
const results = Array.isArray(rawRecord.results)
|
|
199
|
+
? (rawRecord.results ?? [])
|
|
200
|
+
: [];
|
|
201
|
+
const partialRun = rawRecord.partial === true || rawRecord.status === "partial";
|
|
202
|
+
let paired = 0;
|
|
203
|
+
let knodinFaster = 0;
|
|
204
|
+
let competitorFaster = 0;
|
|
205
|
+
let competitorErrors = 0;
|
|
206
|
+
let knodinErrors = 0;
|
|
207
|
+
let blocked = 0;
|
|
208
|
+
const ratios = [];
|
|
209
|
+
const precisions = [];
|
|
210
|
+
const recalls = [];
|
|
211
|
+
let oracleCases = 0;
|
|
212
|
+
let oraclePassed = 0;
|
|
213
|
+
let oracleFailed = 0;
|
|
214
|
+
let oracleUnavailable = 0;
|
|
215
|
+
let incomparable = 0;
|
|
216
|
+
let exactMatches = 0;
|
|
217
|
+
let exactMismatches = 0;
|
|
218
|
+
let falsePositives = 0;
|
|
219
|
+
let hasExactMatch = false;
|
|
220
|
+
let hasFalsePositives = false;
|
|
221
|
+
let knodinResponseBytes = 0;
|
|
222
|
+
let competitorResponseBytes = 0;
|
|
223
|
+
let hasKnodinResponseBytes = false;
|
|
224
|
+
let hasCompetitorResponseBytes = false;
|
|
225
|
+
const estimatedTokens = [];
|
|
226
|
+
const peakRssMb = [];
|
|
227
|
+
const knodinPeakRssMb = [];
|
|
228
|
+
const competitorPeakRssMb = [];
|
|
229
|
+
const diskBytes = [];
|
|
230
|
+
const modeRatios = {
|
|
231
|
+
cold: { p50: [], p95: [] },
|
|
232
|
+
warm: { p50: [], p95: [] },
|
|
233
|
+
};
|
|
234
|
+
const attributedResources = Array.isArray(rawRecord.resources)
|
|
235
|
+
? rawRecord.resources
|
|
236
|
+
: [];
|
|
237
|
+
for (const resource of attributedResources) {
|
|
238
|
+
for (const [owner, target] of [
|
|
239
|
+
["knodin", knodinPeakRssMb],
|
|
240
|
+
["competitor", competitorPeakRssMb],
|
|
241
|
+
]) {
|
|
242
|
+
const measurement = resource[owner];
|
|
243
|
+
if (measurement &&
|
|
244
|
+
typeof measurement === "object" &&
|
|
245
|
+
"rssBytes" in measurement &&
|
|
246
|
+
typeof measurement.rssBytes === "number" &&
|
|
247
|
+
measurement.rssBytes > 0) {
|
|
248
|
+
const megabytes = measurement.rssBytes / 1024 / 1024;
|
|
249
|
+
target.push(megabytes);
|
|
250
|
+
peakRssMb.push(megabytes);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const test of results) {
|
|
255
|
+
if (test.blocked)
|
|
256
|
+
blocked++;
|
|
257
|
+
const contender = findCompetitorSide(test, competitor);
|
|
258
|
+
const knodin = test.knodin;
|
|
259
|
+
if (contender && contender.ok === false)
|
|
260
|
+
competitorErrors++;
|
|
261
|
+
if (knodin && knodin.ok === false)
|
|
262
|
+
knodinErrors++;
|
|
263
|
+
const oracle = test.oracle;
|
|
264
|
+
const contract = test.caseContract;
|
|
265
|
+
if (contract?.disposition.status === "incomparable")
|
|
266
|
+
incomparable++;
|
|
267
|
+
if (!test.blocked) {
|
|
268
|
+
if (contract?.disposition.status === "comparable") {
|
|
269
|
+
oracleCases++;
|
|
270
|
+
if (contract.oracle.status === "passed")
|
|
271
|
+
oraclePassed++;
|
|
272
|
+
else if (contract.oracle.status === "failed")
|
|
273
|
+
oracleFailed++;
|
|
274
|
+
else
|
|
275
|
+
oracleUnavailable++;
|
|
276
|
+
}
|
|
277
|
+
else if (!contract) {
|
|
278
|
+
oracleUnavailable++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (oracle && typeof oracle === "object") {
|
|
282
|
+
if (contract?.disposition.status !== "comparable")
|
|
283
|
+
oracleCases++;
|
|
284
|
+
if (typeof oracle.exactMatch === "boolean") {
|
|
285
|
+
hasExactMatch = true;
|
|
286
|
+
if (oracle.exactMatch)
|
|
287
|
+
exactMatches++;
|
|
288
|
+
else
|
|
289
|
+
exactMismatches++;
|
|
290
|
+
}
|
|
291
|
+
if (typeof oracle.precision === "number")
|
|
292
|
+
precisions.push(oracle.precision);
|
|
293
|
+
if (typeof oracle.recall === "number")
|
|
294
|
+
recalls.push(oracle.recall);
|
|
295
|
+
if (typeof oracle.falsePositives === "number") {
|
|
296
|
+
hasFalsePositives = true;
|
|
297
|
+
falsePositives += oracle.falsePositives;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (typeof knodin?.responseBytes === "number") {
|
|
301
|
+
hasKnodinResponseBytes = true;
|
|
302
|
+
knodinResponseBytes += knodin.responseBytes;
|
|
303
|
+
}
|
|
304
|
+
if (typeof contender?.responseBytes === "number") {
|
|
305
|
+
hasCompetitorResponseBytes = true;
|
|
306
|
+
competitorResponseBytes += contender.responseBytes;
|
|
307
|
+
}
|
|
308
|
+
for (const value of [knodin?.estimatedTokens, contender?.estimatedTokens])
|
|
309
|
+
if (typeof value === "number")
|
|
310
|
+
estimatedTokens.push(value);
|
|
311
|
+
if (typeof knodin?.peakRssMb === "number") {
|
|
312
|
+
peakRssMb.push(knodin.peakRssMb);
|
|
313
|
+
knodinPeakRssMb.push(knodin.peakRssMb);
|
|
314
|
+
}
|
|
315
|
+
if (typeof contender?.peakRssMb === "number") {
|
|
316
|
+
peakRssMb.push(contender.peakRssMb);
|
|
317
|
+
competitorPeakRssMb.push(contender.peakRssMb);
|
|
318
|
+
}
|
|
319
|
+
for (const value of [knodin?.diskBytes, contender?.diskBytes])
|
|
320
|
+
if (typeof value === "number")
|
|
321
|
+
diskBytes.push(value);
|
|
322
|
+
if (!partialRun &&
|
|
323
|
+
test.partial !== true &&
|
|
324
|
+
contract?.disposition.status === "comparable" &&
|
|
325
|
+
contract.oracle.status === "passed" &&
|
|
326
|
+
contender?.ok === true &&
|
|
327
|
+
knodin?.ok === true &&
|
|
328
|
+
typeof contender.medianMs === "number" &&
|
|
329
|
+
typeof knodin.medianMs === "number" &&
|
|
330
|
+
knodin.medianMs > 0) {
|
|
331
|
+
const mode = contract.mode;
|
|
332
|
+
const contenderP50 = contender.p50Ms ?? contender.medianMs;
|
|
333
|
+
const knodinP50 = knodin.p50Ms ?? knodin.medianMs;
|
|
334
|
+
paired++;
|
|
335
|
+
if (contender.medianMs < knodin.medianMs)
|
|
336
|
+
competitorFaster++;
|
|
337
|
+
else if (knodin.medianMs < contender.medianMs)
|
|
338
|
+
knodinFaster++;
|
|
339
|
+
ratios.push(contender.medianMs / knodin.medianMs);
|
|
340
|
+
if (knodinP50 > 0)
|
|
341
|
+
modeRatios[mode].p50.push(contenderP50 / knodinP50);
|
|
342
|
+
if (typeof contender.p95Ms === "number" &&
|
|
343
|
+
typeof knodin.p95Ms === "number" &&
|
|
344
|
+
knodin.p95Ms > 0) {
|
|
345
|
+
modeRatios[mode].p95.push(contender.p95Ms / knodin.p95Ms);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
ratios.sort((a, b) => a - b);
|
|
350
|
+
const median = (values) => {
|
|
351
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
352
|
+
return sorted.length ? sorted[Math.floor(sorted.length / 2)] : null;
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
competitor,
|
|
356
|
+
cases: results.length,
|
|
357
|
+
paired,
|
|
358
|
+
knodinFaster,
|
|
359
|
+
competitorFaster,
|
|
360
|
+
competitorErrors,
|
|
361
|
+
knodinErrors,
|
|
362
|
+
blocked,
|
|
363
|
+
medianRatio: ratios.length ? ratios[Math.floor(ratios.length / 2)] : null,
|
|
364
|
+
correctness: {
|
|
365
|
+
oracleCases,
|
|
366
|
+
oraclePassed,
|
|
367
|
+
oracleFailed,
|
|
368
|
+
oracleUnavailable,
|
|
369
|
+
incomparable,
|
|
370
|
+
exactMatches: hasExactMatch ? exactMatches : null,
|
|
371
|
+
exactMismatches: hasExactMatch ? exactMismatches : null,
|
|
372
|
+
precision: precisions.length
|
|
373
|
+
? precisions.reduce((sum, value) => sum + value, 0) / precisions.length
|
|
374
|
+
: null,
|
|
375
|
+
recall: recalls.length
|
|
376
|
+
? recalls.reduce((sum, value) => sum + value, 0) / recalls.length
|
|
377
|
+
: null,
|
|
378
|
+
falsePositives: hasFalsePositives ? falsePositives : null,
|
|
379
|
+
},
|
|
380
|
+
performance: {
|
|
381
|
+
latencySamples: ratios.length,
|
|
382
|
+
modes: Object.fromEntries(["cold", "warm"]
|
|
383
|
+
.filter((mode) => modeRatios[mode].p50.length > 0)
|
|
384
|
+
.map((mode) => [
|
|
385
|
+
mode,
|
|
386
|
+
{
|
|
387
|
+
paired: modeRatios[mode].p50.length,
|
|
388
|
+
p50Ratio: median(modeRatios[mode].p50),
|
|
389
|
+
p95Ratio: median(modeRatios[mode].p95),
|
|
390
|
+
},
|
|
391
|
+
])),
|
|
392
|
+
knodinResponseBytes: hasKnodinResponseBytes ? knodinResponseBytes : null,
|
|
393
|
+
competitorResponseBytes: hasCompetitorResponseBytes ? competitorResponseBytes : null,
|
|
394
|
+
estimatedTokens: estimatedTokens.length
|
|
395
|
+
? estimatedTokens.reduce((sum, value) => sum + value, 0)
|
|
396
|
+
: null,
|
|
397
|
+
knodinPeakRssMb: knodinPeakRssMb.length ? Math.max(...knodinPeakRssMb) : null,
|
|
398
|
+
competitorPeakRssMb: competitorPeakRssMb.length ? Math.max(...competitorPeakRssMb) : null,
|
|
399
|
+
peakRssMb: peakRssMb.length ? Math.max(...peakRssMb) : null,
|
|
400
|
+
diskBytes: diskBytes.length ? Math.max(...diskBytes) : null,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
export function compareSummaries(baseline, current, latencyTolerance = 0.1) {
|
|
405
|
+
if (!Number.isFinite(latencyTolerance) || latencyTolerance < 0) {
|
|
406
|
+
throw new Error("Latency tolerance must be a non-negative number");
|
|
407
|
+
}
|
|
408
|
+
const reasons = [];
|
|
409
|
+
if (current.paired < baseline.paired)
|
|
410
|
+
reasons.push(`Paired cases fell by ${baseline.paired - current.paired}`);
|
|
411
|
+
if (current.knodinFaster < baseline.knodinFaster) {
|
|
412
|
+
reasons.push(`knodin wins fell by ${baseline.knodinFaster - current.knodinFaster}`);
|
|
413
|
+
}
|
|
414
|
+
if (current.knodinErrors > baseline.knodinErrors) {
|
|
415
|
+
reasons.push(`knodin errors increased by ${current.knodinErrors - baseline.knodinErrors}`);
|
|
416
|
+
}
|
|
417
|
+
if (baseline.performance && current.performance) {
|
|
418
|
+
for (const mode of ["cold", "warm"]) {
|
|
419
|
+
const before = baseline.performance.modes?.[mode];
|
|
420
|
+
const after = current.performance.modes?.[mode];
|
|
421
|
+
if (!before || !after)
|
|
422
|
+
continue;
|
|
423
|
+
for (const percentile of ["p50Ratio", "p95Ratio"]) {
|
|
424
|
+
const beforeValue = before[percentile];
|
|
425
|
+
const afterValue = after[percentile];
|
|
426
|
+
if (beforeValue !== null &&
|
|
427
|
+
afterValue !== null &&
|
|
428
|
+
afterValue < beforeValue * (1 - latencyTolerance)) {
|
|
429
|
+
reasons.push(`${mode} ${percentile.replace("Ratio", "")} competitor/knodin ratio fell from ${beforeValue.toFixed(3)} to ${afterValue.toFixed(3)}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else if (baseline.medianRatio !== null &&
|
|
435
|
+
current.medianRatio !== null &&
|
|
436
|
+
current.medianRatio < baseline.medianRatio * (1 - latencyTolerance)) {
|
|
437
|
+
reasons.push(`Competitor/knodin median ratio fell from ${baseline.medianRatio.toFixed(3)} to ${current.medianRatio.toFixed(3)}`);
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
regressed: reasons.length > 0,
|
|
441
|
+
reasons,
|
|
442
|
+
deltas: {
|
|
443
|
+
paired: current.paired - baseline.paired,
|
|
444
|
+
knodinFaster: current.knodinFaster - baseline.knodinFaster,
|
|
445
|
+
competitorFaster: current.competitorFaster - baseline.competitorFaster,
|
|
446
|
+
competitorErrors: current.competitorErrors - baseline.competitorErrors,
|
|
447
|
+
knodinErrors: current.knodinErrors - baseline.knodinErrors,
|
|
448
|
+
medianRatio: baseline.medianRatio === null || current.medianRatio === null
|
|
449
|
+
? null
|
|
450
|
+
: current.medianRatio - baseline.medianRatio,
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function runGit(args, cwd, environment) {
|
|
6
|
+
const result = childProcess.spawnSync("git", args, {
|
|
7
|
+
cwd,
|
|
8
|
+
env: environment,
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
});
|
|
11
|
+
if (result.status !== 0) {
|
|
12
|
+
throw new Error(`Unable to create competitive sandbox: git ${args.join(" ")} failed\n${result.stderr.trim()}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function executableOnPath(name) {
|
|
16
|
+
for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
17
|
+
if (!directory)
|
|
18
|
+
continue;
|
|
19
|
+
const candidate = path.join(directory, name);
|
|
20
|
+
try {
|
|
21
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
22
|
+
return fs.realpathSync(candidate);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Continue searching PATH.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Give a stateful competitor a disposable checkout and user-level state tree.
|
|
32
|
+
*
|
|
33
|
+
* The clone borrows immutable Git objects from the source repository, but owns
|
|
34
|
+
* its refs, index, worktree, and generated graph data. Dependencies are exposed
|
|
35
|
+
* read-only-by-convention through a node_modules symlink so benchmark adapters
|
|
36
|
+
* can execute without a second package installation.
|
|
37
|
+
*/
|
|
38
|
+
export function createCompetitiveSandbox(sourceRepository, sourceFixture, competitorId) {
|
|
39
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `knodin-${competitorId}-sandbox-`));
|
|
40
|
+
const repository = path.join(root, "repository");
|
|
41
|
+
const fixture = path.join(root, "fixture");
|
|
42
|
+
const home = path.join(root, "home");
|
|
43
|
+
const temporary = path.join(root, "tmp");
|
|
44
|
+
const cache = path.join(root, "cache");
|
|
45
|
+
const config = path.join(root, "config");
|
|
46
|
+
const data = path.join(root, "data");
|
|
47
|
+
const state = path.join(root, "state");
|
|
48
|
+
const binaries = path.join(root, "bin");
|
|
49
|
+
for (const directory of [home, temporary, cache, config, data, state, binaries]) {
|
|
50
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
const realBun = executableOnPath("bun");
|
|
53
|
+
const environment = {
|
|
54
|
+
...process.env,
|
|
55
|
+
HOME: home,
|
|
56
|
+
TMPDIR: temporary,
|
|
57
|
+
XDG_CACHE_HOME: cache,
|
|
58
|
+
XDG_CONFIG_HOME: config,
|
|
59
|
+
XDG_DATA_HOME: data,
|
|
60
|
+
XDG_STATE_HOME: state,
|
|
61
|
+
NPM_CONFIG_CACHE: path.join(cache, "npm"),
|
|
62
|
+
BUN_INSTALL_CACHE_DIR: path.join(cache, "bun"),
|
|
63
|
+
PIP_CACHE_DIR: path.join(cache, "pip"),
|
|
64
|
+
CLAUDE_CONFIG_DIR: path.join(config, "claude"),
|
|
65
|
+
GIT_CONFIG_GLOBAL: path.join(config, "gitconfig"),
|
|
66
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
67
|
+
PATH: `${binaries}${path.delimiter}${process.env.PATH ?? ""}`,
|
|
68
|
+
RECKON_BENCHMARK_SOURCE_REPOSITORY: sourceRepository,
|
|
69
|
+
...(realBun ? { RECKON_BENCHMARK_REAL_BUN: realBun } : {}),
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
if (realBun) {
|
|
73
|
+
const bunShim = path.join(binaries, "bun");
|
|
74
|
+
fs.writeFileSync(bunShim, `#!/bin/sh
|
|
75
|
+
if [ "$1" = "src/server.ts" ]; then
|
|
76
|
+
shift
|
|
77
|
+
exec node --import tsx "$RECKON_BENCHMARK_SOURCE_REPOSITORY/src/server.ts" "$@"
|
|
78
|
+
fi
|
|
79
|
+
exec "$RECKON_BENCHMARK_REAL_BUN" "$@"
|
|
80
|
+
`, { mode: 0o755 });
|
|
81
|
+
}
|
|
82
|
+
runGit(["clone", "--quiet", "--shared", "--no-checkout", sourceRepository, repository], root, environment);
|
|
83
|
+
const revision = childProcess
|
|
84
|
+
.spawnSync("git", ["rev-parse", "HEAD"], {
|
|
85
|
+
cwd: sourceRepository,
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
})
|
|
88
|
+
.stdout.trim();
|
|
89
|
+
runGit(["checkout", "--quiet", "--detach", revision], repository, environment);
|
|
90
|
+
const dependencies = path.join(sourceRepository, "node_modules");
|
|
91
|
+
if (fs.existsSync(dependencies)) {
|
|
92
|
+
fs.symlinkSync(dependencies, path.join(repository, "node_modules"), "dir");
|
|
93
|
+
}
|
|
94
|
+
fs.cpSync(sourceFixture, fixture, { recursive: true });
|
|
95
|
+
return {
|
|
96
|
+
root,
|
|
97
|
+
repository,
|
|
98
|
+
fixture,
|
|
99
|
+
output: path.join(root, "raw-result.json"),
|
|
100
|
+
environment,
|
|
101
|
+
dispose: () => fs.rmSync(root, { recursive: true, force: true }),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|