@skein-code/cli 0.3.14 → 0.3.15
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/README.md +7 -4
- package/dist/cli.js +609 -100
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +19 -5
- package/docs/NEXT_STEPS.md +19 -16
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.15",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,6 +236,9 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Warning-only TS/JS duplication audit compares new functions with the pre-write index generation",
|
|
240
|
+
"Deterministic Type-1/2 and Type-3 receipts retain only hashes, locations, scores, and bounded counts",
|
|
241
|
+
"Ordinary edits, renames, moves, deletions, small functions, tests, generated paths, and failed writes stay quiet",
|
|
239
242
|
"Warning-only Reuse Gate records content-free candidate evidence before the first substantive write",
|
|
240
243
|
"Reuse receipts surface unresolved index/read failures instead of claiming a safe new implementation",
|
|
241
244
|
"Prompt and timeline diagnostics expose the repository-first reuse ladder without blocking legitimate writes",
|
|
@@ -993,16 +996,16 @@ async function hashRegularFile(path) {
|
|
|
993
996
|
try {
|
|
994
997
|
const info = await handle.stat();
|
|
995
998
|
if (!info.isFile()) throw new Error(`Namespace entry is not a regular file: ${path}`);
|
|
996
|
-
const
|
|
999
|
+
const hash4 = createHash2("sha256");
|
|
997
1000
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
998
1001
|
let position = 0;
|
|
999
1002
|
while (true) {
|
|
1000
1003
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
1001
1004
|
if (!bytesRead) break;
|
|
1002
|
-
|
|
1005
|
+
hash4.update(buffer.subarray(0, bytesRead));
|
|
1003
1006
|
position += bytesRead;
|
|
1004
1007
|
}
|
|
1005
|
-
return { sha256:
|
|
1008
|
+
return { sha256: hash4.digest("hex"), size: position };
|
|
1006
1009
|
} finally {
|
|
1007
1010
|
await handle.close();
|
|
1008
1011
|
}
|
|
@@ -1203,18 +1206,18 @@ var MemoryStore = class {
|
|
|
1203
1206
|
input2.lastVerifiedAt ?? (explicit ? now : void 0),
|
|
1204
1207
|
"Memory verification time"
|
|
1205
1208
|
);
|
|
1206
|
-
const
|
|
1209
|
+
const hash4 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1207
1210
|
database.exec("BEGIN IMMEDIATE");
|
|
1208
1211
|
try {
|
|
1209
1212
|
const existing = database.prepare(
|
|
1210
1213
|
"SELECT * FROM memories WHERE content_hash = ?"
|
|
1211
|
-
).get(
|
|
1214
|
+
).get(hash4);
|
|
1212
1215
|
const conflicting = conflictKey ? database.prepare(`
|
|
1213
1216
|
SELECT * FROM memories
|
|
1214
1217
|
WHERE scope = ? AND scope_key = ? AND conflict_key = ?
|
|
1215
1218
|
AND status = 'active' AND content_hash <> ?
|
|
1216
1219
|
ORDER BY updated_at DESC
|
|
1217
|
-
`).all(input2.scope, scopeKey2, conflictKey,
|
|
1220
|
+
`).all(input2.scope, scopeKey2, conflictKey, hash4) : [];
|
|
1218
1221
|
const supersedesId = normalizeOptional(input2.supersedesId, 80) ?? conflicting[0]?.id;
|
|
1219
1222
|
let id;
|
|
1220
1223
|
if (existing) {
|
|
@@ -1259,7 +1262,7 @@ var MemoryStore = class {
|
|
|
1259
1262
|
importance,
|
|
1260
1263
|
confidence,
|
|
1261
1264
|
source,
|
|
1262
|
-
|
|
1265
|
+
hash4,
|
|
1263
1266
|
now,
|
|
1264
1267
|
now,
|
|
1265
1268
|
now,
|
|
@@ -1362,10 +1365,10 @@ var MemoryStore = class {
|
|
|
1362
1365
|
const conflictKey = normalizeOptional(input2.conflictKey, 240);
|
|
1363
1366
|
const candidateDefaultExpiry = !input2.expiresAt ? new Date(Date.now() + MEMORY_CANDIDATE_TTL_MS).toISOString() : void 0;
|
|
1364
1367
|
const expiresAt = normalizeTimestamp(input2.expiresAt ?? candidateDefaultExpiry, "Memory expiration");
|
|
1365
|
-
const
|
|
1368
|
+
const hash4 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1366
1369
|
const existing = database.prepare(
|
|
1367
1370
|
"SELECT * FROM memory_candidates WHERE content_hash = ?"
|
|
1368
|
-
).get(
|
|
1371
|
+
).get(hash4);
|
|
1369
1372
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1370
1373
|
if (existing) {
|
|
1371
1374
|
if (existing.status === "approved" && existing.approved_memory_id) {
|
|
@@ -1412,7 +1415,7 @@ var MemoryStore = class {
|
|
|
1412
1415
|
confidence,
|
|
1413
1416
|
source,
|
|
1414
1417
|
rationale,
|
|
1415
|
-
|
|
1418
|
+
hash4,
|
|
1416
1419
|
now,
|
|
1417
1420
|
now,
|
|
1418
1421
|
revision ?? null,
|
|
@@ -1699,8 +1702,8 @@ async function rejectSymlink(path, allowMissing = false) {
|
|
|
1699
1702
|
throw error;
|
|
1700
1703
|
}
|
|
1701
1704
|
}
|
|
1702
|
-
function clamp(value,
|
|
1703
|
-
return Math.min(maximum, Math.max(
|
|
1705
|
+
function clamp(value, minimum2, maximum) {
|
|
1706
|
+
return Math.min(maximum, Math.max(minimum2, value));
|
|
1704
1707
|
}
|
|
1705
1708
|
|
|
1706
1709
|
// src/tools/write.ts
|
|
@@ -2594,9 +2597,9 @@ function countExplicitPaths(value) {
|
|
|
2594
2597
|
}
|
|
2595
2598
|
|
|
2596
2599
|
// src/context/local-index.ts
|
|
2597
|
-
import { createHash as
|
|
2600
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
2598
2601
|
import { lstat as lstat8, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
2599
|
-
import { basename as basename5, dirname as dirname7, extname, isAbsolute as isAbsolute3, join as join7, relative as relative5, resolve as resolve8, sep as sep4 } from "node:path";
|
|
2602
|
+
import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute3, join as join7, relative as relative5, resolve as resolve8, sep as sep4 } from "node:path";
|
|
2600
2603
|
import fg from "fast-glob";
|
|
2601
2604
|
import { z as z3 } from "zod";
|
|
2602
2605
|
|
|
@@ -2765,6 +2768,269 @@ function isEmojiOrSupplementarySymbol(character, codePoint) {
|
|
|
2765
2768
|
return codePoint > 65535 || new RegExp("\\p{Extended_Pictographic}", "u").test(character);
|
|
2766
2769
|
}
|
|
2767
2770
|
|
|
2771
|
+
// src/context/function-fingerprint.ts
|
|
2772
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2773
|
+
import { extname } from "node:path";
|
|
2774
|
+
var SUPPORTED = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
2775
|
+
var MIN_FUNCTION_TOKENS = 40;
|
|
2776
|
+
var SHINGLE_SIZE = 10;
|
|
2777
|
+
var WINDOW_SIZE = 6;
|
|
2778
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2779
|
+
"async",
|
|
2780
|
+
"await",
|
|
2781
|
+
"break",
|
|
2782
|
+
"case",
|
|
2783
|
+
"catch",
|
|
2784
|
+
"class",
|
|
2785
|
+
"const",
|
|
2786
|
+
"continue",
|
|
2787
|
+
"default",
|
|
2788
|
+
"delete",
|
|
2789
|
+
"do",
|
|
2790
|
+
"else",
|
|
2791
|
+
"extends",
|
|
2792
|
+
"false",
|
|
2793
|
+
"finally",
|
|
2794
|
+
"for",
|
|
2795
|
+
"function",
|
|
2796
|
+
"if",
|
|
2797
|
+
"in",
|
|
2798
|
+
"instanceof",
|
|
2799
|
+
"let",
|
|
2800
|
+
"new",
|
|
2801
|
+
"null",
|
|
2802
|
+
"of",
|
|
2803
|
+
"return",
|
|
2804
|
+
"static",
|
|
2805
|
+
"super",
|
|
2806
|
+
"switch",
|
|
2807
|
+
"this",
|
|
2808
|
+
"throw",
|
|
2809
|
+
"true",
|
|
2810
|
+
"try",
|
|
2811
|
+
"typeof",
|
|
2812
|
+
"undefined",
|
|
2813
|
+
"var",
|
|
2814
|
+
"while",
|
|
2815
|
+
"yield"
|
|
2816
|
+
]);
|
|
2817
|
+
function supportsFunctionFingerprintPath(path) {
|
|
2818
|
+
return SUPPORTED.has(extname(path).toLocaleLowerCase()) && !isNoisePath(path);
|
|
2819
|
+
}
|
|
2820
|
+
function extractFunctionFingerprints(path, content) {
|
|
2821
|
+
return extractFunctionFingerprintReport(path, content).functions;
|
|
2822
|
+
}
|
|
2823
|
+
function extractFunctionFingerprintReport(path, content) {
|
|
2824
|
+
if (!supportsFunctionFingerprintPath(path)) return { functions: [], skippedSmallFunctions: 0 };
|
|
2825
|
+
const masked = maskCommentsAndStrings(content);
|
|
2826
|
+
const lineOffsets = lineStartOffsets(content);
|
|
2827
|
+
const declarations2 = findDeclarations(masked);
|
|
2828
|
+
const functions = [];
|
|
2829
|
+
let skippedSmallFunctions = 0;
|
|
2830
|
+
for (const declaration of declarations2) {
|
|
2831
|
+
const open3 = declaration.open;
|
|
2832
|
+
const close = matchingBrace(masked, open3);
|
|
2833
|
+
if (close < 0) continue;
|
|
2834
|
+
const body = content.slice(open3 + 1, close);
|
|
2835
|
+
const normalizedTokens = normalizeFunctionTokens(body);
|
|
2836
|
+
if (normalizedTokens.length < MIN_FUNCTION_TOKENS) {
|
|
2837
|
+
skippedSmallFunctions += 1;
|
|
2838
|
+
continue;
|
|
2839
|
+
}
|
|
2840
|
+
const startLine = lineAtOffset(lineOffsets, declaration.start);
|
|
2841
|
+
const endLine = lineAtOffset(lineOffsets, close);
|
|
2842
|
+
functions.push({
|
|
2843
|
+
path,
|
|
2844
|
+
symbol: declaration.symbol,
|
|
2845
|
+
startLine,
|
|
2846
|
+
endLine,
|
|
2847
|
+
tokenCount: normalizedTokens.length,
|
|
2848
|
+
exactHash: hash(normalizedTokens.join(" ")),
|
|
2849
|
+
fingerprints: winnow(normalizedTokens),
|
|
2850
|
+
normalizedTokens
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
return { functions, skippedSmallFunctions };
|
|
2854
|
+
}
|
|
2855
|
+
function fingerprintSimilarity(left, right) {
|
|
2856
|
+
if (left.exactHash === right.exactHash) return 1;
|
|
2857
|
+
const a = new Set(left.fingerprints);
|
|
2858
|
+
const b = new Set(right.fingerprints);
|
|
2859
|
+
if (!a.size || !b.size) return 0;
|
|
2860
|
+
let intersection = 0;
|
|
2861
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
2862
|
+
return intersection / (a.size + b.size - intersection);
|
|
2863
|
+
}
|
|
2864
|
+
function findDeclarations(masked) {
|
|
2865
|
+
const output2 = [];
|
|
2866
|
+
const classBodies = findClassBodies(masked);
|
|
2867
|
+
const patterns = [
|
|
2868
|
+
{
|
|
2869
|
+
kind: "function",
|
|
2870
|
+
pattern: /(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)(?:\s*<[^>{}\n]+>)?\s*\([^)]*\)\s*(?::\s*[^{}\n=]+)?\s*\{/g
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
kind: "arrow",
|
|
2874
|
+
pattern: /(?:^|\n)\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=\n]+)?\s*=\s*(?:async\s*)?(?:<[^>{}\n]+>\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*(?::\s*[^={}\n]+)?=>\s*\{/g
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
kind: "method",
|
|
2878
|
+
pattern: /(?:^|\n)\s*(?:(?:public|private|protected|static|readonly|abstract|override|get|set|async)\s+)*\*?\s*([A-Za-z_$][\w$]*)(?:\s*<[^>{}\n]+>)?\s*\([^)]*\)\s*(?::\s*[^{}\n=]+)?\s*\{/g
|
|
2879
|
+
}
|
|
2880
|
+
];
|
|
2881
|
+
for (const { kind, pattern } of patterns) {
|
|
2882
|
+
for (const match of masked.matchAll(pattern)) {
|
|
2883
|
+
const symbol = match[1];
|
|
2884
|
+
if (!symbol || match.index === void 0) continue;
|
|
2885
|
+
if (KEYWORDS.has(symbol) || symbol === "constructor") continue;
|
|
2886
|
+
const start = match.index + match[0].indexOf(symbol);
|
|
2887
|
+
if (kind === "method" && !isDirectClassMember(masked, start, classBodies)) continue;
|
|
2888
|
+
output2.push({
|
|
2889
|
+
symbol,
|
|
2890
|
+
start,
|
|
2891
|
+
open: match.index + match[0].lastIndexOf("{")
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return output2.sort((left, right) => left.start - right.start);
|
|
2896
|
+
}
|
|
2897
|
+
function findClassBodies(masked) {
|
|
2898
|
+
const output2 = [];
|
|
2899
|
+
const pattern = /(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class(?:\s+[A-Za-z_$][\w$]*)?[^\n{]*\{/g;
|
|
2900
|
+
for (const match of masked.matchAll(pattern)) {
|
|
2901
|
+
if (match.index === void 0) continue;
|
|
2902
|
+
const open3 = match.index + match[0].lastIndexOf("{");
|
|
2903
|
+
const close = matchingBrace(masked, open3);
|
|
2904
|
+
if (close >= 0) output2.push({ open: open3, close });
|
|
2905
|
+
}
|
|
2906
|
+
return output2.sort((left, right) => left.close - left.open - (right.close - right.open));
|
|
2907
|
+
}
|
|
2908
|
+
function isDirectClassMember(masked, start, classBodies) {
|
|
2909
|
+
for (const body of classBodies) {
|
|
2910
|
+
if (start <= body.open || start >= body.close) continue;
|
|
2911
|
+
let depth = 1;
|
|
2912
|
+
for (let index = body.open + 1; index < start; index += 1) {
|
|
2913
|
+
if (masked[index] === "{") depth += 1;
|
|
2914
|
+
else if (masked[index] === "}") depth -= 1;
|
|
2915
|
+
}
|
|
2916
|
+
if (depth === 1) return true;
|
|
2917
|
+
}
|
|
2918
|
+
return false;
|
|
2919
|
+
}
|
|
2920
|
+
function normalizeFunctionTokens(content) {
|
|
2921
|
+
const masked = maskCommentsAndStrings(content, true);
|
|
2922
|
+
const raw = masked.match(/[A-Za-z_$][\w$]*|\d+(?:\.\d+)?|===|!==|=>|==|!=|<=|>=|&&|\|\||\+\+|--|\?\?|\?\.|[{}()[\].,;:+\-*/%<>=!?&|^~]/g) ?? [];
|
|
2923
|
+
return raw.map((token) => {
|
|
2924
|
+
if (token === "LIT") return token;
|
|
2925
|
+
if (/^[A-Za-z_$]/.test(token)) return KEYWORDS.has(token) ? token : "ID";
|
|
2926
|
+
if (/^\d/.test(token)) return "LIT";
|
|
2927
|
+
return token;
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
function winnow(tokens2) {
|
|
2931
|
+
if (tokens2.length < SHINGLE_SIZE) return [];
|
|
2932
|
+
const shingles = Array.from({ length: tokens2.length - SHINGLE_SIZE + 1 }, (_, index) => hash(tokens2.slice(index, index + SHINGLE_SIZE).join(" ")).slice(0, 16));
|
|
2933
|
+
if (shingles.length <= WINDOW_SIZE) return [minimum(shingles)];
|
|
2934
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2935
|
+
for (let index = 0; index <= shingles.length - WINDOW_SIZE; index += 1) {
|
|
2936
|
+
selected.add(minimum(shingles.slice(index, index + WINDOW_SIZE)));
|
|
2937
|
+
}
|
|
2938
|
+
return [...selected].sort();
|
|
2939
|
+
}
|
|
2940
|
+
function minimum(values) {
|
|
2941
|
+
return values.reduce((smallest, value) => value < smallest ? value : smallest);
|
|
2942
|
+
}
|
|
2943
|
+
function maskCommentsAndStrings(content, preserveLiterals = false) {
|
|
2944
|
+
let state = "code";
|
|
2945
|
+
let escaped = false;
|
|
2946
|
+
let output2 = "";
|
|
2947
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
2948
|
+
const character = content[index] ?? "";
|
|
2949
|
+
const next = content[index + 1] ?? "";
|
|
2950
|
+
if (state === "code") {
|
|
2951
|
+
if (character === "/" && next === "/") {
|
|
2952
|
+
state = "line";
|
|
2953
|
+
output2 += " ";
|
|
2954
|
+
index += 1;
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
if (character === "/" && next === "*") {
|
|
2958
|
+
state = "block";
|
|
2959
|
+
output2 += " ";
|
|
2960
|
+
index += 1;
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2963
|
+
if (character === "'") state = "single";
|
|
2964
|
+
else if (character === '"') state = "double";
|
|
2965
|
+
else if (character === "`") state = "template";
|
|
2966
|
+
output2 += state === "code" ? character : preserveLiterals ? " LIT " : character === "\n" ? "\n" : " ";
|
|
2967
|
+
escaped = false;
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
if (state === "line") {
|
|
2971
|
+
if (character === "\n") {
|
|
2972
|
+
state = "code";
|
|
2973
|
+
output2 += "\n";
|
|
2974
|
+
} else output2 += " ";
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
if (state === "block") {
|
|
2978
|
+
if (character === "*" && next === "/") {
|
|
2979
|
+
state = "code";
|
|
2980
|
+
output2 += " ";
|
|
2981
|
+
index += 1;
|
|
2982
|
+
} else output2 += character === "\n" ? "\n" : " ";
|
|
2983
|
+
continue;
|
|
2984
|
+
}
|
|
2985
|
+
output2 += character === "\n" ? "\n" : " ";
|
|
2986
|
+
if (escaped) {
|
|
2987
|
+
escaped = false;
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
if (character === "\\") {
|
|
2991
|
+
escaped = true;
|
|
2992
|
+
continue;
|
|
2993
|
+
}
|
|
2994
|
+
if (state === "single" && character === "'" || state === "double" && character === '"' || state === "template" && character === "`") state = "code";
|
|
2995
|
+
}
|
|
2996
|
+
return output2;
|
|
2997
|
+
}
|
|
2998
|
+
function matchingBrace(masked, open3) {
|
|
2999
|
+
let depth = 0;
|
|
3000
|
+
for (let index = open3; index < masked.length; index += 1) {
|
|
3001
|
+
if (masked[index] === "{") depth += 1;
|
|
3002
|
+
else if (masked[index] === "}") {
|
|
3003
|
+
depth -= 1;
|
|
3004
|
+
if (depth === 0) return index;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
return -1;
|
|
3008
|
+
}
|
|
3009
|
+
function lineStartOffsets(content) {
|
|
3010
|
+
const offsets = [0];
|
|
3011
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
3012
|
+
if (content[index] === "\n") offsets.push(index + 1);
|
|
3013
|
+
}
|
|
3014
|
+
return offsets;
|
|
3015
|
+
}
|
|
3016
|
+
function lineAtOffset(offsets, offset) {
|
|
3017
|
+
let low = 0;
|
|
3018
|
+
let high = offsets.length;
|
|
3019
|
+
while (low < high) {
|
|
3020
|
+
const middle = Math.floor((low + high) / 2);
|
|
3021
|
+
if ((offsets[middle] ?? 0) <= offset) low = middle + 1;
|
|
3022
|
+
else high = middle;
|
|
3023
|
+
}
|
|
3024
|
+
return Math.max(1, low);
|
|
3025
|
+
}
|
|
3026
|
+
function isNoisePath(path) {
|
|
3027
|
+
const normalized = path.replaceAll("\\", "/").toLocaleLowerCase();
|
|
3028
|
+
return /(^|\/)(test|tests|__tests__|fixtures|fixture|generated|vendor|dist|build)(\/|$)/.test(normalized) || /(?:\.d|\.generated|\.min)\.[cm]?[jt]sx?$/.test(normalized);
|
|
3029
|
+
}
|
|
3030
|
+
function hash(value) {
|
|
3031
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
3032
|
+
}
|
|
3033
|
+
|
|
2768
3034
|
// src/context/local-index.ts
|
|
2769
3035
|
var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
|
|
2770
3036
|
var indexedChunkSchema = z3.object({
|
|
@@ -2835,6 +3101,7 @@ var LocalContextIndex = class {
|
|
|
2835
3101
|
index;
|
|
2836
3102
|
workspace;
|
|
2837
3103
|
queryCache = /* @__PURE__ */ new Map();
|
|
3104
|
+
fingerprintCache;
|
|
2838
3105
|
dirtyPaths = /* @__PURE__ */ new Set();
|
|
2839
3106
|
refreshState = "current";
|
|
2840
3107
|
refreshError;
|
|
@@ -2868,6 +3135,7 @@ var LocalContextIndex = class {
|
|
|
2868
3135
|
}
|
|
2869
3136
|
this.index = { ...parsed, files };
|
|
2870
3137
|
this.queryCache.clear();
|
|
3138
|
+
this.fingerprintCache = void 0;
|
|
2871
3139
|
this.refreshState = this.dirtyPaths.size ? "dirty" : "current";
|
|
2872
3140
|
this.refreshError = void 0;
|
|
2873
3141
|
return true;
|
|
@@ -2875,6 +3143,7 @@ var LocalContextIndex = class {
|
|
|
2875
3143
|
if (error.code === "ENOENT") return false;
|
|
2876
3144
|
delete this.index;
|
|
2877
3145
|
this.queryCache.clear();
|
|
3146
|
+
this.fingerprintCache = void 0;
|
|
2878
3147
|
return false;
|
|
2879
3148
|
}
|
|
2880
3149
|
}
|
|
@@ -2984,6 +3253,7 @@ var LocalContextIndex = class {
|
|
|
2984
3253
|
files: nextFiles
|
|
2985
3254
|
};
|
|
2986
3255
|
this.queryCache.clear();
|
|
3256
|
+
this.fingerprintCache = void 0;
|
|
2987
3257
|
await ensureWorkspaceStorageDirectory(workspace, dirname7(this.indexPath), {
|
|
2988
3258
|
requireActiveNamespace: true
|
|
2989
3259
|
});
|
|
@@ -3003,6 +3273,24 @@ var LocalContextIndex = class {
|
|
|
3003
3273
|
const hits = await this.search(query, topK);
|
|
3004
3274
|
return packContextHits(hits, this.roots, maxTokens, "local");
|
|
3005
3275
|
}
|
|
3276
|
+
async functionFingerprints() {
|
|
3277
|
+
await this.flushDirty();
|
|
3278
|
+
await this.ensureCurrentIndex();
|
|
3279
|
+
const index = this.index;
|
|
3280
|
+
if (!index) throw new Error("The local context index is unavailable.");
|
|
3281
|
+
if (this.fingerprintCache?.generation === index.generation) {
|
|
3282
|
+
return cloneDuplicationBaseline(this.fingerprintCache);
|
|
3283
|
+
}
|
|
3284
|
+
const functions = index.files.flatMap((file) => {
|
|
3285
|
+
const content = reconstructIndexedContent(file.chunks);
|
|
3286
|
+
if (hashContent(content) !== file.contentHash) {
|
|
3287
|
+
throw new Error("The fingerprint baseline could not reconstruct current indexed files.");
|
|
3288
|
+
}
|
|
3289
|
+
return extractFunctionFingerprints(file.absolutePath, content).map(({ normalizedTokens: _tokens, ...fingerprint }) => fingerprint);
|
|
3290
|
+
});
|
|
3291
|
+
this.fingerprintCache = { generation: index.generation, functions };
|
|
3292
|
+
return cloneDuplicationBaseline(this.fingerprintCache);
|
|
3293
|
+
}
|
|
3006
3294
|
status() {
|
|
3007
3295
|
return {
|
|
3008
3296
|
available: Boolean(this.index),
|
|
@@ -3104,6 +3392,7 @@ var LocalContextIndex = class {
|
|
|
3104
3392
|
files
|
|
3105
3393
|
};
|
|
3106
3394
|
this.queryCache.clear();
|
|
3395
|
+
this.fingerprintCache = void 0;
|
|
3107
3396
|
onProgress?.({ phase: "write", completed: files.length, total: files.length });
|
|
3108
3397
|
await ensureWorkspaceStorageDirectory(
|
|
3109
3398
|
this.roots[0] ?? process.cwd(),
|
|
@@ -3365,7 +3654,7 @@ function isIncludedSourcePath(path) {
|
|
|
3365
3654
|
".proto",
|
|
3366
3655
|
".tf",
|
|
3367
3656
|
".hcl"
|
|
3368
|
-
])).has(
|
|
3657
|
+
])).has(extname2(name).toLowerCase());
|
|
3369
3658
|
}
|
|
3370
3659
|
function chunksMatch(expected, actual) {
|
|
3371
3660
|
if (expected.length !== actual.length) return false;
|
|
@@ -3446,11 +3735,29 @@ ${hit.content}
|
|
|
3446
3735
|
function cloneHits(hits) {
|
|
3447
3736
|
return hits.map((hit) => ({ ...hit }));
|
|
3448
3737
|
}
|
|
3738
|
+
function cloneDuplicationBaseline(baseline) {
|
|
3739
|
+
return {
|
|
3740
|
+
generation: baseline.generation,
|
|
3741
|
+
functions: baseline.functions.map((item) => ({ ...item, fingerprints: [...item.fingerprints] }))
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function reconstructIndexedContent(chunks) {
|
|
3745
|
+
const ordered = chunks.slice().sort((left, right) => left.startLine - right.startLine);
|
|
3746
|
+
const totalLines = ordered.reduce((maximum, chunk) => Math.max(maximum, chunk.endLine), 0);
|
|
3747
|
+
const lines = Array.from({ length: totalLines });
|
|
3748
|
+
for (const chunk of ordered) {
|
|
3749
|
+
for (const [offset, line] of chunk.content.split("\n").entries()) {
|
|
3750
|
+
const index = chunk.startLine - 1 + offset;
|
|
3751
|
+
if (lines[index] === void 0) lines[index] = line;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
return lines.map((line) => line ?? "").join("\n");
|
|
3755
|
+
}
|
|
3449
3756
|
function hashContent(content) {
|
|
3450
|
-
return
|
|
3757
|
+
return createHash6("sha256").update(content, "utf8").digest("hex");
|
|
3451
3758
|
}
|
|
3452
3759
|
function createGeneration(files) {
|
|
3453
|
-
return
|
|
3760
|
+
return createHash6("sha256").update(files.slice().sort((left, right) => left.absolutePath.localeCompare(right.absolutePath)).map((file) => `${file.absolutePath}\0${file.contentHash}`).join("\n"), "utf8").digest("hex").slice(0, 16);
|
|
3454
3761
|
}
|
|
3455
3762
|
function hasSubstantialOverlap(left, right) {
|
|
3456
3763
|
if (left.path !== right.path) return false;
|
|
@@ -3497,7 +3804,7 @@ function appendChunkRange(chunks, file, lines, rangeStart, rangeEnd) {
|
|
|
3497
3804
|
}
|
|
3498
3805
|
}
|
|
3499
3806
|
function structuralStarts(path, lines) {
|
|
3500
|
-
const extension =
|
|
3807
|
+
const extension = extname2(path).toLocaleLowerCase();
|
|
3501
3808
|
return lines.flatMap((line, index) => isStructuralLine(line, extension) ? [index] : []);
|
|
3502
3809
|
}
|
|
3503
3810
|
function isStructuralLine(line, extension) {
|
|
@@ -3663,6 +3970,9 @@ var ContextEngine = class {
|
|
|
3663
3970
|
lastDegradation() {
|
|
3664
3971
|
return this.degradation ? { ...this.degradation } : void 0;
|
|
3665
3972
|
}
|
|
3973
|
+
async functionFingerprints() {
|
|
3974
|
+
return this.local.functionFingerprints();
|
|
3975
|
+
}
|
|
3666
3976
|
};
|
|
3667
3977
|
function formatContextHits(hits, roots) {
|
|
3668
3978
|
return hits.map((hit) => {
|
|
@@ -4827,7 +5137,7 @@ function createProvider(config) {
|
|
|
4827
5137
|
}
|
|
4828
5138
|
|
|
4829
5139
|
// src/checkpoint/store.ts
|
|
4830
|
-
import { createHash as
|
|
5140
|
+
import { createHash as createHash7, randomUUID as randomUUID7 } from "node:crypto";
|
|
4831
5141
|
import { lstat as lstat9, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
4832
5142
|
import { basename as basename6, dirname as dirname8, join as join8, relative as relative6, resolve as resolve10 } from "node:path";
|
|
4833
5143
|
import { z as z4 } from "zod";
|
|
@@ -4880,7 +5190,7 @@ var CheckpointStore = class {
|
|
|
4880
5190
|
if (info.size > 25e6) {
|
|
4881
5191
|
throw new Error(`File is too large to checkpoint (${info.size} bytes): ${input2}`);
|
|
4882
5192
|
}
|
|
4883
|
-
const blob = `${
|
|
5193
|
+
const blob = `${createHash7("sha256").update(path).digest("hex")}.bin`;
|
|
4884
5194
|
await atomicWrite(join8(blobDirectory, blob), await readFile5(path), 384);
|
|
4885
5195
|
entries.push({
|
|
4886
5196
|
path,
|
|
@@ -5363,11 +5673,32 @@ var reuseReceiptSchema = z5.object({
|
|
|
5363
5673
|
status: z5.enum(["warning", "skipped", "unresolved"]),
|
|
5364
5674
|
warningOnly: z5.literal(true)
|
|
5365
5675
|
}).strict();
|
|
5676
|
+
var duplicationAuditSchema = z5.object({
|
|
5677
|
+
baselineGeneration: z5.string().min(1),
|
|
5678
|
+
changeSequence: z5.number().int().nonnegative(),
|
|
5679
|
+
status: z5.enum(["clear", "warning", "unresolved"]),
|
|
5680
|
+
warningOnly: z5.literal(true),
|
|
5681
|
+
checkedFunctions: z5.number().int().nonnegative(),
|
|
5682
|
+
skippedSmallFunctions: z5.number().int().nonnegative(),
|
|
5683
|
+
matches: z5.array(z5.object({
|
|
5684
|
+
changedPath: z5.string(),
|
|
5685
|
+
changedSymbol: z5.string(),
|
|
5686
|
+
candidatePath: z5.string(),
|
|
5687
|
+
candidateSymbol: z5.string(),
|
|
5688
|
+
kind: z5.enum(["type-1-or-2", "type-3"]),
|
|
5689
|
+
similarity: z5.number().min(0).max(1)
|
|
5690
|
+
}).strict()).max(8),
|
|
5691
|
+
rationale: z5.string().max(500)
|
|
5692
|
+
}).strict();
|
|
5366
5693
|
var auditMetadataSchema = z5.record(z5.string(), z5.unknown()).superRefine((metadata, ctx) => {
|
|
5367
5694
|
const receipt = metadata.reuseReceipt;
|
|
5368
5695
|
if (receipt !== void 0 && !reuseReceiptSchema.safeParse(receipt).success) {
|
|
5369
5696
|
ctx.addIssue({ code: "custom", message: "Invalid reuse receipt" });
|
|
5370
5697
|
}
|
|
5698
|
+
const duplication = metadata.duplicationAudit;
|
|
5699
|
+
if (duplication !== void 0 && !duplicationAuditSchema.safeParse(duplication).success) {
|
|
5700
|
+
ctx.addIssue({ code: "custom", message: "Invalid duplication audit receipt" });
|
|
5701
|
+
}
|
|
5371
5702
|
});
|
|
5372
5703
|
var auditSchema = z5.object({
|
|
5373
5704
|
id: z5.string(),
|
|
@@ -5779,7 +6110,7 @@ async function exists2(path) {
|
|
|
5779
6110
|
}
|
|
5780
6111
|
|
|
5781
6112
|
// src/session/tool-artifacts.ts
|
|
5782
|
-
import { createHash as
|
|
6113
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
5783
6114
|
import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
5784
6115
|
import { join as join11, resolve as resolve13 } from "node:path";
|
|
5785
6116
|
import { z as z6 } from "zod";
|
|
@@ -5839,7 +6170,7 @@ var ToolArtifactStore = class {
|
|
|
5839
6170
|
const artifacts = await this.loadArtifacts();
|
|
5840
6171
|
await this.removeExpired(artifacts, now);
|
|
5841
6172
|
const active = artifacts.filter((artifact2) => artifact2.expiresAt > now.toISOString());
|
|
5842
|
-
const sha256 =
|
|
6173
|
+
const sha256 = hash2(content);
|
|
5843
6174
|
const existing = active.find((artifact2) => artifact2.sessionId === sessionId && artifact2.toolCallId === toolCallId);
|
|
5844
6175
|
if (existing) {
|
|
5845
6176
|
if (existing.sha256 === sha256 && existing.bytes === bytes && existing.redacted === options.redacted) {
|
|
@@ -5954,7 +6285,7 @@ var ToolArtifactStore = class {
|
|
|
5954
6285
|
if (artifact.sessionId !== sessionId || artifact.toolCallId !== toolCallId) {
|
|
5955
6286
|
throw new Error("Retained tool output does not belong to this session.");
|
|
5956
6287
|
}
|
|
5957
|
-
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !==
|
|
6288
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash2(artifact.content)) {
|
|
5958
6289
|
throw new Error("Retained tool output failed its integrity check.");
|
|
5959
6290
|
}
|
|
5960
6291
|
return artifact;
|
|
@@ -5974,7 +6305,7 @@ var ToolArtifactStore = class {
|
|
|
5974
6305
|
await this.assertRegularFile(path);
|
|
5975
6306
|
try {
|
|
5976
6307
|
const artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
|
|
5977
|
-
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !==
|
|
6308
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash2(artifact.content)) {
|
|
5978
6309
|
throw new Error("integrity");
|
|
5979
6310
|
}
|
|
5980
6311
|
if (sessionDirectory !== this.sessionDirectoryFor(artifact.sessionId) || path !== this.pathFor(artifact.sessionId, artifact.toolCallId)) {
|
|
@@ -5997,10 +6328,10 @@ var ToolArtifactStore = class {
|
|
|
5997
6328
|
await rm2(this.pathFor(artifact.sessionId, artifact.toolCallId), { force: true });
|
|
5998
6329
|
}
|
|
5999
6330
|
pathFor(sessionId, toolCallId) {
|
|
6000
|
-
return join11(this.sessionDirectoryFor(sessionId), `${
|
|
6331
|
+
return join11(this.sessionDirectoryFor(sessionId), `${hash2(`call\0${toolCallId}`)}.json`);
|
|
6001
6332
|
}
|
|
6002
6333
|
sessionDirectoryFor(sessionId) {
|
|
6003
|
-
return join11(this.directory,
|
|
6334
|
+
return join11(this.directory, hash2(`session\0${sessionId}`));
|
|
6004
6335
|
}
|
|
6005
6336
|
async ensureDirectory() {
|
|
6006
6337
|
await ensureWorkspaceStorageDirectory(this.workspace, this.directory, {
|
|
@@ -6057,8 +6388,8 @@ function reference(artifact) {
|
|
|
6057
6388
|
redacted: artifact.redacted
|
|
6058
6389
|
};
|
|
6059
6390
|
}
|
|
6060
|
-
function
|
|
6061
|
-
return
|
|
6391
|
+
function hash2(value) {
|
|
6392
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
6062
6393
|
}
|
|
6063
6394
|
function storedBytes(artifact) {
|
|
6064
6395
|
return Buffer.byteLength(JSON.stringify(artifact)) + 1;
|
|
@@ -6403,13 +6734,13 @@ ${preview}`);
|
|
|
6403
6734
|
}
|
|
6404
6735
|
return `${lines.join("\n")}${forceFinalNewline && lines.length ? "\n" : ""}`;
|
|
6405
6736
|
}
|
|
6406
|
-
function findSequence(haystack, needle, hint,
|
|
6737
|
+
function findSequence(haystack, needle, hint, minimum2) {
|
|
6407
6738
|
const matchesAt = (start, relaxed) => needle.every((line, offset) => {
|
|
6408
6739
|
const candidate = haystack[start + offset];
|
|
6409
6740
|
return relaxed ? candidate?.trimEnd() === line.trimEnd() : candidate === line;
|
|
6410
6741
|
});
|
|
6411
6742
|
const starts = [];
|
|
6412
|
-
for (let index = Math.max(0,
|
|
6743
|
+
for (let index = Math.max(0, minimum2); index <= haystack.length - needle.length; index += 1) {
|
|
6413
6744
|
starts.push(index);
|
|
6414
6745
|
}
|
|
6415
6746
|
starts.sort((a, b) => Math.abs(a - hint) - Math.abs(b - hint));
|
|
@@ -7380,7 +7711,7 @@ ${hit.content}`
|
|
|
7380
7711
|
}
|
|
7381
7712
|
|
|
7382
7713
|
// src/tools/shell.ts
|
|
7383
|
-
import { createHash as
|
|
7714
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
7384
7715
|
import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
|
|
7385
7716
|
import { join as join13 } from "node:path";
|
|
7386
7717
|
import { z as z13 } from "zod";
|
|
@@ -7426,10 +7757,10 @@ var shellTool = {
|
|
|
7426
7757
|
validateEnvironment(input2.env);
|
|
7427
7758
|
const cwd = await context.workspace.resolveDirectory(input2.cwd ?? ".");
|
|
7428
7759
|
const candidates = appearsToModifyWorkspace(input2.command) ? await collectAffectedPaths(input2.command, input2.cwd ?? ".", context) : [];
|
|
7429
|
-
const
|
|
7430
|
-
const roots =
|
|
7760
|
+
const unresolved2 = appearsToModifyWorkspace(input2.command) && candidates.length === 0;
|
|
7761
|
+
const roots = unresolved2 ? context.workspace.roots : [];
|
|
7431
7762
|
const before = await snapshotPaths(candidates);
|
|
7432
|
-
const beforeWorkspace =
|
|
7763
|
+
const beforeWorkspace = unresolved2 ? await captureWorkspaceSnapshot(roots) : void 0;
|
|
7433
7764
|
let result;
|
|
7434
7765
|
try {
|
|
7435
7766
|
result = await runShell(input2.command, cwd, {
|
|
@@ -7594,7 +7925,7 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
7594
7925
|
files.set(path, {
|
|
7595
7926
|
size: info.size,
|
|
7596
7927
|
mtimeMs: info.mtimeMs,
|
|
7597
|
-
hash:
|
|
7928
|
+
hash: createHash9("sha256").update(content).digest("hex")
|
|
7598
7929
|
});
|
|
7599
7930
|
} catch (error) {
|
|
7600
7931
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -7797,14 +8128,14 @@ function compact(value, limit) {
|
|
|
7797
8128
|
}
|
|
7798
8129
|
|
|
7799
8130
|
// src/agent/completion-gate.ts
|
|
7800
|
-
import { createHash as
|
|
8131
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
7801
8132
|
|
|
7802
8133
|
// src/tools/permissions.ts
|
|
7803
|
-
import { createHash as
|
|
8134
|
+
import { createHash as createHash10, createHmac, randomBytes } from "node:crypto";
|
|
7804
8135
|
var permissionScopeSecret = randomBytes(32);
|
|
7805
8136
|
function permissionKey(call, category) {
|
|
7806
8137
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
7807
|
-
const digest =
|
|
8138
|
+
const digest = createHash10("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
7808
8139
|
return `${category}:${call.name}:${digest}`;
|
|
7809
8140
|
}
|
|
7810
8141
|
function permissionTarget(call) {
|
|
@@ -7998,7 +8329,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
|
7998
8329
|
kind,
|
|
7999
8330
|
ok: result.ok,
|
|
8000
8331
|
changeSequence,
|
|
8001
|
-
commandKey:
|
|
8332
|
+
commandKey: createHash11("sha256").update(normalized).digest("hex")
|
|
8002
8333
|
};
|
|
8003
8334
|
}
|
|
8004
8335
|
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
|
|
@@ -8077,11 +8408,11 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8077
8408
|
}
|
|
8078
8409
|
function completionRecoveryDirective(completion) {
|
|
8079
8410
|
if (completion.acceptance && acceptanceUnresolved(completion.acceptance)) {
|
|
8080
|
-
const
|
|
8411
|
+
const unresolved2 = completion.acceptance.unresolved.map((item) => `- [${item.status}] ${item.id}: ${item.description}`).join("\n");
|
|
8081
8412
|
const failed = completion.checks.filter((check) => !check.ok).map((check) => `- ${check.command} (tool call ${check.toolCallId})`).join("\n");
|
|
8082
8413
|
return `<runtime-completion-gate status="acceptance_unresolved" authorization="none">
|
|
8083
8414
|
The run cannot be marked complete while required Task Contract criteria remain unresolved:
|
|
8084
|
-
${
|
|
8415
|
+
${unresolved2 || "- Acceptance criteria are satisfied."}${completion.acceptance.missingVerification.length ? `
|
|
8085
8416
|
Missing required verification:
|
|
8086
8417
|
${completion.acceptance.missingVerification.map((item) => `- ${item}`).join("\n")}` : ""}${failed ? `
|
|
8087
8418
|
Current failed checks:
|
|
@@ -8151,14 +8482,14 @@ function acceptanceDetail(acceptance) {
|
|
|
8151
8482
|
acceptance.blocked ? `${acceptance.blocked} blocked` : "",
|
|
8152
8483
|
acceptance.missingVerification.length ? `${acceptance.missingVerification.length} verification requirements missing` : ""
|
|
8153
8484
|
].filter(Boolean).join(" and ");
|
|
8154
|
-
const
|
|
8155
|
-
return `Task Contract acceptance is unresolved: ${parts} required ${
|
|
8485
|
+
const unresolved2 = acceptance.pending + acceptance.blocked;
|
|
8486
|
+
return `Task Contract acceptance is unresolved: ${parts} required ${unresolved2 === 1 ? "criterion" : "criteria"}.`;
|
|
8156
8487
|
}
|
|
8157
8488
|
function verificationRequirementMet(requirement, checks) {
|
|
8158
8489
|
const normalized = normalizeCommand2(requirement);
|
|
8159
8490
|
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
8160
8491
|
if (broad) return checks.length > 0;
|
|
8161
|
-
const commandKey =
|
|
8492
|
+
const commandKey = createHash11("sha256").update(normalized).digest("hex");
|
|
8162
8493
|
return checks.some((item) => item.commandKey === commandKey);
|
|
8163
8494
|
}
|
|
8164
8495
|
function acceptanceUnresolved(acceptance) {
|
|
@@ -8617,7 +8948,7 @@ function escapeAttribute3(value) {
|
|
|
8617
8948
|
}
|
|
8618
8949
|
|
|
8619
8950
|
// src/agent/tool-recovery.ts
|
|
8620
|
-
import { createHash as
|
|
8951
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
8621
8952
|
var RETRY_BUDGET = {
|
|
8622
8953
|
schema_input: 3,
|
|
8623
8954
|
unknown_tool: 2,
|
|
@@ -8689,7 +9020,7 @@ var ToolRecoveryController = class {
|
|
|
8689
9020
|
recordEvidence(call, result) {
|
|
8690
9021
|
if (call.name !== "search_code" || !result.ok) return void 0;
|
|
8691
9022
|
const callKey = callSignature(call);
|
|
8692
|
-
const fingerprint =
|
|
9023
|
+
const fingerprint = createHash12("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
|
|
8693
9024
|
const count = typeof result.metadata?.count === "number" ? result.metadata.count : void 0;
|
|
8694
9025
|
const current = this.evidence.get(callKey);
|
|
8695
9026
|
const repeated = current?.fingerprint === fingerprint;
|
|
@@ -8699,7 +9030,7 @@ var ToolRecoveryController = class {
|
|
|
8699
9030
|
status: count === 0 ? "empty" : repeated ? "repeated" : "new",
|
|
8700
9031
|
repeatCount: repeats,
|
|
8701
9032
|
stop: repeats >= 2,
|
|
8702
|
-
signature:
|
|
9033
|
+
signature: createHash12("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
|
|
8703
9034
|
};
|
|
8704
9035
|
}
|
|
8705
9036
|
receipt(call, failureClass, attempt, circuitOpen) {
|
|
@@ -8735,10 +9066,10 @@ function isRetryable(failureClass) {
|
|
|
8735
9066
|
return RETRY_BUDGET[failureClass] > 0;
|
|
8736
9067
|
}
|
|
8737
9068
|
function callSignature(call) {
|
|
8738
|
-
return
|
|
9069
|
+
return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
8739
9070
|
}
|
|
8740
9071
|
function failureSignature(call, failureClass) {
|
|
8741
|
-
return
|
|
9072
|
+
return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
8742
9073
|
}
|
|
8743
9074
|
function redact(value, key = "") {
|
|
8744
9075
|
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
@@ -8905,8 +9236,8 @@ ${tail}`;
|
|
|
8905
9236
|
}
|
|
8906
9237
|
return best || sliceStartByTokens(value, budget);
|
|
8907
9238
|
}
|
|
8908
|
-
function clamp2(value,
|
|
8909
|
-
return Math.max(
|
|
9239
|
+
function clamp2(value, minimum2, maximum) {
|
|
9240
|
+
return Math.max(minimum2, Math.min(maximum, value));
|
|
8910
9241
|
}
|
|
8911
9242
|
function boundedInlineByTokens(value, maxTokens) {
|
|
8912
9243
|
const normalized = value.replace(/[\r\n\t]+/gu, " ").trim();
|
|
@@ -9086,9 +9417,9 @@ function escapeAttribute5(value) {
|
|
|
9086
9417
|
}
|
|
9087
9418
|
|
|
9088
9419
|
// src/agent/reuse-gate.ts
|
|
9089
|
-
import { createHash as
|
|
9420
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
9090
9421
|
import { readFile as readFile14, stat as stat10 } from "node:fs/promises";
|
|
9091
|
-
import { basename as basename8, extname as
|
|
9422
|
+
import { basename as basename8, extname as extname3 } from "node:path";
|
|
9092
9423
|
var MAX_CANDIDATES = 5;
|
|
9093
9424
|
var SKIPPED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9094
9425
|
".md",
|
|
@@ -9153,7 +9484,7 @@ async function evaluateReuseGate(input2) {
|
|
|
9153
9484
|
if (preview.paths.every(isExemptPath)) return { triggered: false };
|
|
9154
9485
|
const targetPaths = preview.paths.slice(0, MAX_CANDIDATES);
|
|
9155
9486
|
const query = [input2.request, ...preview.symbols, ...targetPaths.map((path) => basename8(path))].join(" ").slice(0, 8e3);
|
|
9156
|
-
const queryHash =
|
|
9487
|
+
const queryHash = hash3(query);
|
|
9157
9488
|
let refresh;
|
|
9158
9489
|
try {
|
|
9159
9490
|
refresh = input2.context.flushDirty ? await input2.context.flushDirty() : { status: "current", paths: 0 };
|
|
@@ -9276,7 +9607,7 @@ function isExemptPath(path) {
|
|
|
9276
9607
|
const normalized = path.replaceAll("\\", "/").toLocaleLowerCase();
|
|
9277
9608
|
if (/(^|\/)(test|tests|__tests__|fixtures|fixture|generated|vendor|dist|build)(\/|$)/.test(normalized)) return true;
|
|
9278
9609
|
if (/(\.generated|\.min)\.[^.]+$/.test(normalized)) return true;
|
|
9279
|
-
const extension =
|
|
9610
|
+
const extension = extname3(normalized);
|
|
9280
9611
|
if (SKIPPED_EXTENSIONS.has(extension)) return true;
|
|
9281
9612
|
return !SOURCE_EXTENSIONS.has(extension);
|
|
9282
9613
|
}
|
|
@@ -9294,13 +9625,164 @@ function unresolvedReceipt(input2, queryHash, targetPaths, trigger, detail) {
|
|
|
9294
9625
|
warningOnly: true
|
|
9295
9626
|
};
|
|
9296
9627
|
}
|
|
9297
|
-
function
|
|
9298
|
-
return
|
|
9628
|
+
function hash3(value) {
|
|
9629
|
+
return createHash13("sha256").update(value).digest("hex");
|
|
9299
9630
|
}
|
|
9300
9631
|
function roundScore(value) {
|
|
9301
9632
|
return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : 0;
|
|
9302
9633
|
}
|
|
9303
9634
|
|
|
9635
|
+
// src/agent/duplication-audit.ts
|
|
9636
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
9637
|
+
var NEAR_CLONE_THRESHOLD = 0.55;
|
|
9638
|
+
var MAX_MATCHES = 8;
|
|
9639
|
+
async function auditChangedFunctions(input2) {
|
|
9640
|
+
const auditableFiles = input2.changedFiles.filter(supportsFunctionFingerprintPath);
|
|
9641
|
+
if (!auditableFiles.length) return void 0;
|
|
9642
|
+
const reports = [];
|
|
9643
|
+
let skippedSmallFunctions = 0;
|
|
9644
|
+
try {
|
|
9645
|
+
for (const path of auditableFiles) {
|
|
9646
|
+
let content;
|
|
9647
|
+
try {
|
|
9648
|
+
content = await readFile15(path, "utf8");
|
|
9649
|
+
} catch (error) {
|
|
9650
|
+
if (error.code === "ENOENT") continue;
|
|
9651
|
+
throw error;
|
|
9652
|
+
}
|
|
9653
|
+
const report = extractFunctionFingerprintReport(path, content);
|
|
9654
|
+
skippedSmallFunctions += report.skippedSmallFunctions;
|
|
9655
|
+
reports.push(report);
|
|
9656
|
+
}
|
|
9657
|
+
} catch {
|
|
9658
|
+
return unresolved(input2.changeSequence, input2.baseline?.generation);
|
|
9659
|
+
}
|
|
9660
|
+
const currentFunctions = reports.flatMap((report) => report.functions.map(withoutTokens));
|
|
9661
|
+
if (!currentFunctions.length) return void 0;
|
|
9662
|
+
if (!input2.baseline) return unresolved(input2.changeSequence);
|
|
9663
|
+
const lookup = createBaselineLookup(input2.baseline.functions);
|
|
9664
|
+
const previousByCurrent = matchCurrentFunctions(input2.baseline.functions, currentFunctions);
|
|
9665
|
+
const added = currentFunctions.flatMap((current) => {
|
|
9666
|
+
const previous = previousByCurrent.get(locationIdentity(current));
|
|
9667
|
+
return !previous || current.tokenCount >= previous.tokenCount * 1.5 ? [{ current, previous }] : [];
|
|
9668
|
+
});
|
|
9669
|
+
if (!added.length) return void 0;
|
|
9670
|
+
const matches = [];
|
|
9671
|
+
for (const { current: item, previous } of added) {
|
|
9672
|
+
let best;
|
|
9673
|
+
for (const candidate of findCandidateFunctions(lookup, item)) {
|
|
9674
|
+
if (previous && locationIdentity(candidate) === locationIdentity(previous)) continue;
|
|
9675
|
+
const similarity = fingerprintSimilarity(item, candidate);
|
|
9676
|
+
if (similarity < NEAR_CLONE_THRESHOLD || best && best.similarity >= similarity) continue;
|
|
9677
|
+
best = { candidate, similarity };
|
|
9678
|
+
}
|
|
9679
|
+
if (!best) continue;
|
|
9680
|
+
matches.push({
|
|
9681
|
+
changedPath: item.path,
|
|
9682
|
+
changedSymbol: item.symbol,
|
|
9683
|
+
candidatePath: best.candidate.path,
|
|
9684
|
+
candidateSymbol: best.candidate.symbol,
|
|
9685
|
+
kind: best.similarity === 1 ? "type-1-or-2" : "type-3",
|
|
9686
|
+
similarity: round(best.similarity)
|
|
9687
|
+
});
|
|
9688
|
+
}
|
|
9689
|
+
matches.sort((left, right) => right.similarity - left.similarity || left.changedPath.localeCompare(right.changedPath));
|
|
9690
|
+
const bounded = matches.slice(0, MAX_MATCHES);
|
|
9691
|
+
return {
|
|
9692
|
+
baselineGeneration: input2.baseline.generation,
|
|
9693
|
+
changeSequence: input2.changeSequence,
|
|
9694
|
+
status: bounded.length ? "warning" : "clear",
|
|
9695
|
+
warningOnly: true,
|
|
9696
|
+
checkedFunctions: added.length,
|
|
9697
|
+
skippedSmallFunctions,
|
|
9698
|
+
matches: bounded,
|
|
9699
|
+
rationale: bounded.length ? `${bounded.length} deterministic duplicate candidate${bounded.length === 1 ? "" : "s"} found; review for reuse.` : "No deterministic duplicate candidate exceeded the warning threshold."
|
|
9700
|
+
};
|
|
9701
|
+
}
|
|
9702
|
+
function withoutTokens(value) {
|
|
9703
|
+
const { normalizedTokens: _tokens, ...fingerprint } = value;
|
|
9704
|
+
return fingerprint;
|
|
9705
|
+
}
|
|
9706
|
+
function identity(value) {
|
|
9707
|
+
return `${value.path}\0${value.symbol}`;
|
|
9708
|
+
}
|
|
9709
|
+
function locationIdentity(value) {
|
|
9710
|
+
return `${identity(value)}\0${value.startLine}\0${value.endLine}`;
|
|
9711
|
+
}
|
|
9712
|
+
function createBaselineLookup(functions) {
|
|
9713
|
+
const lookup = {
|
|
9714
|
+
byIdentity: /* @__PURE__ */ new Map(),
|
|
9715
|
+
byPath: /* @__PURE__ */ new Map(),
|
|
9716
|
+
byExactHash: /* @__PURE__ */ new Map(),
|
|
9717
|
+
byFingerprint: /* @__PURE__ */ new Map()
|
|
9718
|
+
};
|
|
9719
|
+
for (const item of functions) {
|
|
9720
|
+
appendLookup(lookup.byIdentity, identity(item), item);
|
|
9721
|
+
appendLookup(lookup.byPath, item.path, item);
|
|
9722
|
+
appendLookup(lookup.byExactHash, item.exactHash, item);
|
|
9723
|
+
for (const fingerprint of new Set(item.fingerprints)) {
|
|
9724
|
+
appendLookup(lookup.byFingerprint, fingerprint, item);
|
|
9725
|
+
}
|
|
9726
|
+
}
|
|
9727
|
+
return lookup;
|
|
9728
|
+
}
|
|
9729
|
+
function appendLookup(lookup, key, item) {
|
|
9730
|
+
const values = lookup.get(key);
|
|
9731
|
+
if (values) values.push(item);
|
|
9732
|
+
else lookup.set(key, [item]);
|
|
9733
|
+
}
|
|
9734
|
+
function findCandidateFunctions(lookup, current) {
|
|
9735
|
+
const exact = lookup.byExactHash.get(current.exactHash);
|
|
9736
|
+
if (exact?.length) return exact;
|
|
9737
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
9738
|
+
for (const fingerprint of new Set(current.fingerprints)) {
|
|
9739
|
+
for (const candidate of lookup.byFingerprint.get(fingerprint) ?? []) {
|
|
9740
|
+
candidates.set(locationIdentity(candidate), candidate);
|
|
9741
|
+
}
|
|
9742
|
+
}
|
|
9743
|
+
return [...candidates.values()];
|
|
9744
|
+
}
|
|
9745
|
+
function matchCurrentFunctions(baseline, current) {
|
|
9746
|
+
const matches = /* @__PURE__ */ new Map();
|
|
9747
|
+
const available = new Map(baseline.map((item) => [locationIdentity(item), item]));
|
|
9748
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item) && linesOverlap(candidate, item)), false);
|
|
9749
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item)), true);
|
|
9750
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => candidate.path === item.path && linesOverlap(candidate, item)), true);
|
|
9751
|
+
return matches;
|
|
9752
|
+
}
|
|
9753
|
+
function assignMatches(current, matches, available, candidatesFor, requireSimilarity) {
|
|
9754
|
+
for (const item of current) {
|
|
9755
|
+
const currentId = locationIdentity(item);
|
|
9756
|
+
if (matches.has(currentId)) continue;
|
|
9757
|
+
let best;
|
|
9758
|
+
for (const candidate of candidatesFor(item)) {
|
|
9759
|
+
const similarity = fingerprintSimilarity(candidate, item);
|
|
9760
|
+
if (!best || similarity > best.similarity) best = { candidate, similarity };
|
|
9761
|
+
}
|
|
9762
|
+
if (!best || requireSimilarity && best.similarity < NEAR_CLONE_THRESHOLD) continue;
|
|
9763
|
+
matches.set(currentId, best.candidate);
|
|
9764
|
+
available.delete(locationIdentity(best.candidate));
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
function linesOverlap(left, right) {
|
|
9768
|
+
return Math.max(left.startLine, right.startLine) <= Math.min(left.endLine, right.endLine);
|
|
9769
|
+
}
|
|
9770
|
+
function unresolved(changeSequence, generation = "unavailable") {
|
|
9771
|
+
return {
|
|
9772
|
+
baselineGeneration: generation,
|
|
9773
|
+
changeSequence,
|
|
9774
|
+
status: "unresolved",
|
|
9775
|
+
warningOnly: true,
|
|
9776
|
+
checkedFunctions: 0,
|
|
9777
|
+
skippedSmallFunctions: 0,
|
|
9778
|
+
matches: [],
|
|
9779
|
+
rationale: "Duplication evidence is incomplete; the write remains allowed in warning-only mode."
|
|
9780
|
+
};
|
|
9781
|
+
}
|
|
9782
|
+
function round(value) {
|
|
9783
|
+
return Math.round(value * 1e3) / 1e3;
|
|
9784
|
+
}
|
|
9785
|
+
|
|
9304
9786
|
// src/agent/runner.ts
|
|
9305
9787
|
var AgentRunner = class {
|
|
9306
9788
|
config;
|
|
@@ -9816,6 +10298,8 @@ ${input2}`
|
|
|
9816
10298
|
try {
|
|
9817
10299
|
let reuseReceipt;
|
|
9818
10300
|
let reuseWarning;
|
|
10301
|
+
let duplicationBaseline;
|
|
10302
|
+
const duplicationAuditEnabled = categories.includes("write") && (call.name === "write_file" || call.name === "apply_patch") && Boolean(this.contextEngine.functionFingerprints);
|
|
9819
10303
|
if (categories.includes("write") && this.activeReuseGate && !this.activeReuseGate.attempted && (call.name === "write_file" || call.name === "apply_patch")) {
|
|
9820
10304
|
this.activeReuseGate.attempted = true;
|
|
9821
10305
|
try {
|
|
@@ -9835,6 +10319,13 @@ ${input2}`
|
|
|
9835
10319
|
reuseWarning = "Reuse check (warning-only) was inconclusive.";
|
|
9836
10320
|
}
|
|
9837
10321
|
}
|
|
10322
|
+
if (duplicationAuditEnabled) {
|
|
10323
|
+
try {
|
|
10324
|
+
duplicationBaseline = await this.contextEngine.functionFingerprints?.();
|
|
10325
|
+
} catch {
|
|
10326
|
+
duplicationBaseline = void 0;
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
9838
10329
|
let checkpointId;
|
|
9839
10330
|
if (this.config.agent.checkpointBeforeWrite && categories.includes("write") && tool.affectedPaths) {
|
|
9840
10331
|
const paths = await tool.affectedPaths(call.arguments, executionContext);
|
|
@@ -9852,6 +10343,11 @@ ${input2}`
|
|
|
9852
10343
|
throwIfAborted(options.signal);
|
|
9853
10344
|
const execution = await tool.execute(call.arguments, toolExecutionContext);
|
|
9854
10345
|
const changedFiles = await this.acceptChangedFiles(execution.changedFiles ?? []);
|
|
10346
|
+
const duplicationAudit = duplicationAuditEnabled ? await auditChangedFunctions({
|
|
10347
|
+
...duplicationBaseline ? { baseline: duplicationBaseline } : {},
|
|
10348
|
+
changedFiles,
|
|
10349
|
+
changeSequence: this.changeSequence
|
|
10350
|
+
}) : void 0;
|
|
9855
10351
|
const tasksBefore = JSON.stringify(this.session.tasks);
|
|
9856
10352
|
let afterHookError;
|
|
9857
10353
|
let afterHooks = [];
|
|
@@ -9870,11 +10366,17 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
|
|
|
9870
10366
|
if (reuseWarning) completeContent = `${reuseWarning}
|
|
9871
10367
|
|
|
9872
10368
|
${completeContent}`;
|
|
10369
|
+
if (duplicationAudit?.status === "warning" || duplicationAudit?.status === "unresolved") {
|
|
10370
|
+
completeContent = `Duplication audit (warning-only): ${duplicationAudit.rationale}
|
|
10371
|
+
|
|
10372
|
+
${completeContent}`;
|
|
10373
|
+
}
|
|
9873
10374
|
const metadata = {
|
|
9874
10375
|
...execution.metadata ?? {},
|
|
9875
10376
|
...changedFiles.length ? { changedFiles } : {},
|
|
9876
10377
|
...checkpointId ? { checkpointId } : {},
|
|
9877
10378
|
...reuseReceipt ? { reuseReceipt } : {},
|
|
10379
|
+
...duplicationAudit ? { duplicationAudit } : {},
|
|
9878
10380
|
...beforeHooks.length || afterHooks.length ? { hooks: { before: beforeHooks.length, after: afterHooks.length } } : {},
|
|
9879
10381
|
...afterHookError ? { toolSucceeded: true, hookError: afterHookError.message } : {}
|
|
9880
10382
|
};
|
|
@@ -10361,7 +10863,7 @@ async function safeEmit(emit, event) {
|
|
|
10361
10863
|
}
|
|
10362
10864
|
|
|
10363
10865
|
// src/agent/profiles.ts
|
|
10364
|
-
import { lstat as lstat17, readFile as
|
|
10866
|
+
import { lstat as lstat17, readFile as readFile16, readdir as readdir6 } from "node:fs/promises";
|
|
10365
10867
|
import { homedir as homedir3 } from "node:os";
|
|
10366
10868
|
import { basename as basename9, join as join15 } from "node:path";
|
|
10367
10869
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -10489,7 +10991,7 @@ async function readProfile(path, source) {
|
|
|
10489
10991
|
try {
|
|
10490
10992
|
const info = await lstat17(path);
|
|
10491
10993
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 1e5) return void 0;
|
|
10492
|
-
const raw = await
|
|
10994
|
+
const raw = await readFile16(path, "utf8");
|
|
10493
10995
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
10494
10996
|
const frontmatter = match ? parseYaml2(match[1] ?? "") : {};
|
|
10495
10997
|
const prompt = (match ? raw.slice(match[0].length) : raw).trim();
|
|
@@ -10645,8 +11147,8 @@ function numeric(...values) {
|
|
|
10645
11147
|
}
|
|
10646
11148
|
|
|
10647
11149
|
// src/agent/team-store.ts
|
|
10648
|
-
import { createHash as
|
|
10649
|
-
import { lstat as lstat18, readFile as
|
|
11150
|
+
import { createHash as createHash14, randomUUID as randomUUID13 } from "node:crypto";
|
|
11151
|
+
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
10650
11152
|
import { join as join16, resolve as resolve16 } from "node:path";
|
|
10651
11153
|
import { z as z17 } from "zod";
|
|
10652
11154
|
var runIdSchema = z17.string().uuid();
|
|
@@ -10819,7 +11321,7 @@ var TeamRunStore = class {
|
|
|
10819
11321
|
await this.assertRunDirectory(runId);
|
|
10820
11322
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
10821
11323
|
await this.assertRegularFile(path);
|
|
10822
|
-
const manifest = manifestSchema2.parse(JSON.parse(await
|
|
11324
|
+
const manifest = manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
10823
11325
|
if (manifest.id !== runId || resolve16(manifest.workspace) !== this.workspace) {
|
|
10824
11326
|
throw new Error("Team run manifest identity does not match its location.");
|
|
10825
11327
|
}
|
|
@@ -10834,7 +11336,7 @@ var TeamRunStore = class {
|
|
|
10834
11336
|
}
|
|
10835
11337
|
async readArtifact(runId, artifact) {
|
|
10836
11338
|
await this.verifyArtifact(runId, artifact);
|
|
10837
|
-
return
|
|
11339
|
+
return readFile17(this.artifactPath(runId, artifact.sha256), "utf8");
|
|
10838
11340
|
}
|
|
10839
11341
|
async list() {
|
|
10840
11342
|
await this.writes;
|
|
@@ -10890,7 +11392,7 @@ var TeamRunStore = class {
|
|
|
10890
11392
|
await this.assertRunDirectory(runId);
|
|
10891
11393
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
10892
11394
|
await this.assertRegularFile(path);
|
|
10893
|
-
return manifestSchema2.parse(JSON.parse(await
|
|
11395
|
+
return manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
10894
11396
|
}
|
|
10895
11397
|
async writeManifest(manifest) {
|
|
10896
11398
|
const directory = this.runDirectory(manifest.id);
|
|
@@ -10903,7 +11405,7 @@ var TeamRunStore = class {
|
|
|
10903
11405
|
async writeArtifact(runId, content, truncate = true) {
|
|
10904
11406
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
10905
11407
|
const bytes = Buffer.byteLength(data);
|
|
10906
|
-
const sha256 =
|
|
11408
|
+
const sha256 = createHash14("sha256").update(data).digest("hex");
|
|
10907
11409
|
const directory = join16(this.runDirectory(runId), "blobs");
|
|
10908
11410
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
10909
11411
|
requireActiveNamespace: this.managedDirectory
|
|
@@ -10911,8 +11413,8 @@ var TeamRunStore = class {
|
|
|
10911
11413
|
const path = join16(directory, `${sha256}.txt`);
|
|
10912
11414
|
try {
|
|
10913
11415
|
await this.assertRegularFile(path);
|
|
10914
|
-
const existing = await
|
|
10915
|
-
if (
|
|
11416
|
+
const existing = await readFile17(path);
|
|
11417
|
+
if (createHash14("sha256").update(existing).digest("hex") !== sha256) {
|
|
10916
11418
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
10917
11419
|
}
|
|
10918
11420
|
} catch (error) {
|
|
@@ -10932,9 +11434,9 @@ var TeamRunStore = class {
|
|
|
10932
11434
|
hashSchema.parse(artifact.sha256);
|
|
10933
11435
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
10934
11436
|
await this.assertRegularFile(path);
|
|
10935
|
-
const data = await
|
|
10936
|
-
const
|
|
10937
|
-
if (
|
|
11437
|
+
const data = await readFile17(path);
|
|
11438
|
+
const hash4 = createHash14("sha256").update(data).digest("hex");
|
|
11439
|
+
if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
|
|
10938
11440
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
10939
11441
|
}
|
|
10940
11442
|
}
|
|
@@ -10996,7 +11498,7 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
10996
11498
|
}
|
|
10997
11499
|
|
|
10998
11500
|
// src/agent/writer-lane.ts
|
|
10999
|
-
import { createHash as
|
|
11501
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
11000
11502
|
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
11001
11503
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11002
11504
|
import { isAbsolute as isAbsolute6, join as join17, resolve as resolve17 } from "node:path";
|
|
@@ -11094,7 +11596,7 @@ var WriterLane = class {
|
|
|
11094
11596
|
return {
|
|
11095
11597
|
baseCommit,
|
|
11096
11598
|
patch,
|
|
11097
|
-
patchSha256:
|
|
11599
|
+
patchSha256: createHash15("sha256").update(patch).digest("hex"),
|
|
11098
11600
|
files,
|
|
11099
11601
|
worktreeCleaned,
|
|
11100
11602
|
value
|
|
@@ -12811,10 +13313,10 @@ function supportsNodeVersion(version) {
|
|
|
12811
13313
|
const match = version.trim().replace(/^v/u, "").match(/^(\d+)\.(\d+)\.(\d+)/u);
|
|
12812
13314
|
if (!match) return false;
|
|
12813
13315
|
const current = match.slice(1).map(Number);
|
|
12814
|
-
const
|
|
12815
|
-
for (let index = 0; index <
|
|
12816
|
-
if ((current[index] ?? 0) > (
|
|
12817
|
-
if ((current[index] ?? 0) < (
|
|
13316
|
+
const minimum2 = MINIMUM_NODE_VERSION.split(".").map(Number);
|
|
13317
|
+
for (let index = 0; index < minimum2.length; index += 1) {
|
|
13318
|
+
if ((current[index] ?? 0) > (minimum2[index] ?? 0)) return true;
|
|
13319
|
+
if ((current[index] ?? 0) < (minimum2[index] ?? 0)) return false;
|
|
12818
13320
|
}
|
|
12819
13321
|
return true;
|
|
12820
13322
|
}
|
|
@@ -13454,7 +13956,7 @@ function graphemes(value) {
|
|
|
13454
13956
|
}
|
|
13455
13957
|
|
|
13456
13958
|
// src/ui/theme.ts
|
|
13457
|
-
import { lstat as lstat20, readdir as readdir8, readFile as
|
|
13959
|
+
import { lstat as lstat20, readdir as readdir8, readFile as readFile18 } from "node:fs/promises";
|
|
13458
13960
|
import { basename as basename10, join as join19, resolve as resolve19 } from "node:path";
|
|
13459
13961
|
import React, { createContext, useContext } from "react";
|
|
13460
13962
|
function defineTheme(seed) {
|
|
@@ -13595,7 +14097,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
13595
14097
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 64e3) {
|
|
13596
14098
|
throw new Error("must be a regular JSON file smaller than 64 KB");
|
|
13597
14099
|
}
|
|
13598
|
-
const parsed = JSON.parse(await
|
|
14100
|
+
const parsed = JSON.parse(await readFile18(path, "utf8"));
|
|
13599
14101
|
const name = themeName(parsed, basename10(entry, ".json"));
|
|
13600
14102
|
if (builtInThemeNames.has(name)) throw new Error(`cannot replace built-in theme \`${name}\``);
|
|
13601
14103
|
themes[name] = defineTheme(themeSeed(parsed, name));
|
|
@@ -13816,10 +14318,10 @@ function Header({ config, askMode, planMode = false, width = 80, glyphMode = "au
|
|
|
13816
14318
|
const separator = ` ${glyphs.separator} `;
|
|
13817
14319
|
const model = sanitizeInlineTerminalText(`${config.model.provider}/${config.model.model}`);
|
|
13818
14320
|
const repository = sanitizeInlineTerminalText(basename11(root) || root);
|
|
13819
|
-
const
|
|
14321
|
+
const minimum2 = `${brand} ${modeLabel}`;
|
|
13820
14322
|
const withRepository = `${brand}${separator}${repository}${separator}${modeLabel}`;
|
|
13821
14323
|
const showRepository = terminalWidth >= 32 && displayWidth(withRepository) <= terminalWidth;
|
|
13822
|
-
const leftWidth = displayWidth(showRepository ? withRepository :
|
|
14324
|
+
const leftWidth = displayWidth(showRepository ? withRepository : minimum2);
|
|
13823
14325
|
const modelSpace = terminalWidth - leftWidth - 2;
|
|
13824
14326
|
const showModel = terminalWidth >= 72 && modelSpace >= 12;
|
|
13825
14327
|
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, height: 1, overflowY: "hidden", children: [
|
|
@@ -14837,7 +15339,7 @@ function safeWidth(width) {
|
|
|
14837
15339
|
}
|
|
14838
15340
|
|
|
14839
15341
|
// src/utils/update-check.ts
|
|
14840
|
-
import { mkdir as mkdir9, readFile as
|
|
15342
|
+
import { mkdir as mkdir9, readFile as readFile19, writeFile as writeFile2 } from "node:fs/promises";
|
|
14841
15343
|
import { join as join20 } from "node:path";
|
|
14842
15344
|
import stripAnsi3 from "strip-ansi";
|
|
14843
15345
|
var PACKAGE_NAME = "@skein-code/cli";
|
|
@@ -14935,7 +15437,7 @@ function noticeIfNewer(latest, current, highlights) {
|
|
|
14935
15437
|
}
|
|
14936
15438
|
async function readUpdateCache(env = process.env) {
|
|
14937
15439
|
try {
|
|
14938
|
-
const raw = await
|
|
15440
|
+
const raw = await readFile19(updateCachePath(env), "utf8");
|
|
14939
15441
|
const parsed = JSON.parse(raw);
|
|
14940
15442
|
if (typeof parsed?.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && parsed.checkedAt >= 0 && (parsed.latest === null || typeof parsed.latest === "string" && parseVersion(parsed.latest))) {
|
|
14941
15443
|
const latest = typeof parsed.latest === "string" ? parsed.latest : null;
|
|
@@ -15535,9 +16037,9 @@ function uniqueNewestFirst(history) {
|
|
|
15535
16037
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
15536
16038
|
const entry = history[index];
|
|
15537
16039
|
if (!entry.trim()) continue;
|
|
15538
|
-
const
|
|
15539
|
-
if (seen.has(
|
|
15540
|
-
seen.add(
|
|
16040
|
+
const identity2 = entry.normalize("NFC");
|
|
16041
|
+
if (seen.has(identity2)) continue;
|
|
16042
|
+
seen.add(identity2);
|
|
15541
16043
|
entries.push(entry);
|
|
15542
16044
|
}
|
|
15543
16045
|
return entries;
|
|
@@ -15722,6 +16224,13 @@ function toolMetaSummary(metadata) {
|
|
|
15722
16224
|
parts.push(`reuse ${decision}${status === "unresolved" ? " (incomplete)" : " (warning)"}`);
|
|
15723
16225
|
}
|
|
15724
16226
|
}
|
|
16227
|
+
const duplication = metadata.duplicationAudit;
|
|
16228
|
+
if (duplication && typeof duplication === "object") {
|
|
16229
|
+
const status = duplication.status;
|
|
16230
|
+
const matches = Number(duplication.matches?.length ?? 0);
|
|
16231
|
+
if (status === "warning") parts.push(`duplicates ${matches} (warning)`);
|
|
16232
|
+
else if (status === "unresolved") parts.push("duplicates incomplete");
|
|
16233
|
+
}
|
|
15725
16234
|
const hooks = metadata.hooks;
|
|
15726
16235
|
if (hooks && typeof hooks === "object") {
|
|
15727
16236
|
const before = Number(hooks.before ?? 0);
|
|
@@ -18208,7 +18717,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
18208
18717
|
import stripAnsi5 from "strip-ansi";
|
|
18209
18718
|
|
|
18210
18719
|
// src/mcp/tool.ts
|
|
18211
|
-
import { createHash as
|
|
18720
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
18212
18721
|
import stripAnsi4 from "strip-ansi";
|
|
18213
18722
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
18214
18723
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
@@ -18372,13 +18881,13 @@ function normalizeToolSegment(value, fallback) {
|
|
|
18372
18881
|
if (!normalized) return fallback;
|
|
18373
18882
|
return /^[a-z]/.test(normalized) ? normalized : `${fallback}_${normalized}`;
|
|
18374
18883
|
}
|
|
18375
|
-
function fitToolName(name,
|
|
18884
|
+
function fitToolName(name, identity2) {
|
|
18376
18885
|
if (name.length <= 64) return name;
|
|
18377
|
-
const suffix = `_${shortHash(
|
|
18886
|
+
const suffix = `_${shortHash(identity2)}`;
|
|
18378
18887
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
18379
18888
|
}
|
|
18380
18889
|
function shortHash(value) {
|
|
18381
|
-
return
|
|
18890
|
+
return createHash16("sha256").update(value).digest("hex").slice(0, 8);
|
|
18382
18891
|
}
|
|
18383
18892
|
function sanitizeOutputText(value) {
|
|
18384
18893
|
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
@@ -18874,12 +19383,12 @@ var McpManager = class {
|
|
|
18874
19383
|
);
|
|
18875
19384
|
for (const remoteTool of remoteTools) {
|
|
18876
19385
|
let exposedName = makeMcpToolName(namespace, remoteTool.name);
|
|
18877
|
-
const
|
|
18878
|
-
if (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName,
|
|
19386
|
+
const identity2 = `${serverName}\0${remoteTool.name}`;
|
|
19387
|
+
if (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName, identity2)) {
|
|
18879
19388
|
exposedName = disambiguateMcpToolName(exposedName, serverName, remoteTool.name);
|
|
18880
19389
|
}
|
|
18881
19390
|
let collision = 0;
|
|
18882
|
-
while (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName,
|
|
19391
|
+
while (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName, identity2)) {
|
|
18883
19392
|
collision += 1;
|
|
18884
19393
|
exposedName = disambiguateMcpToolName(
|
|
18885
19394
|
`${makeMcpToolName(namespace, remoteTool.name)}_${collision}`,
|
|
@@ -18895,7 +19404,7 @@ var McpManager = class {
|
|
|
18895
19404
|
}
|
|
18896
19405
|
return active.client.callTool(params, void 0, options);
|
|
18897
19406
|
};
|
|
18898
|
-
let adapter = this.stableAdapters.get(
|
|
19407
|
+
let adapter = this.stableAdapters.get(identity2);
|
|
18899
19408
|
if (!adapter) {
|
|
18900
19409
|
adapter = createMcpToolAdapter({
|
|
18901
19410
|
serverName,
|
|
@@ -18904,17 +19413,17 @@ var McpManager = class {
|
|
|
18904
19413
|
timeoutMs,
|
|
18905
19414
|
callTool
|
|
18906
19415
|
});
|
|
18907
|
-
this.stableAdapters.set(
|
|
19416
|
+
this.stableAdapters.set(identity2, adapter);
|
|
18908
19417
|
}
|
|
18909
19418
|
result.set(exposedName, adapter);
|
|
18910
19419
|
seen.add(exposedName);
|
|
18911
|
-
this.toolOwners.set(exposedName,
|
|
19420
|
+
this.toolOwners.set(exposedName, identity2);
|
|
18912
19421
|
}
|
|
18913
19422
|
return result;
|
|
18914
19423
|
}
|
|
18915
|
-
isToolNameOwnedByAnother(toolName,
|
|
19424
|
+
isToolNameOwnedByAnother(toolName, identity2) {
|
|
18916
19425
|
const owner = this.toolOwners.get(toolName);
|
|
18917
|
-
return owner !== void 0 && owner !==
|
|
19426
|
+
return owner !== void 0 && owner !== identity2;
|
|
18918
19427
|
}
|
|
18919
19428
|
handleUnexpectedClose(name) {
|
|
18920
19429
|
const connection = this.connections.get(name);
|
|
@@ -19137,7 +19646,7 @@ function scopeKey(scope, context) {
|
|
|
19137
19646
|
}
|
|
19138
19647
|
|
|
19139
19648
|
// src/skills/catalog.ts
|
|
19140
|
-
import { lstat as lstat21, readFile as
|
|
19649
|
+
import { lstat as lstat21, readFile as readFile20, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
19141
19650
|
import { homedir as homedir4 } from "node:os";
|
|
19142
19651
|
import { basename as basename12, join as join21, resolve as resolve21 } from "node:path";
|
|
19143
19652
|
import { parse as parseYaml3 } from "yaml";
|
|
@@ -19269,7 +19778,7 @@ async function safeRead(path, maxBytes) {
|
|
|
19269
19778
|
const resolvedParent = await realpath9(parent);
|
|
19270
19779
|
const resolvedPath = await realpath9(path);
|
|
19271
19780
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
19272
|
-
return await
|
|
19781
|
+
return await readFile20(path, "utf8");
|
|
19273
19782
|
} catch {
|
|
19274
19783
|
return void 0;
|
|
19275
19784
|
}
|