@skein-code/cli 0.3.14 → 0.3.16
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 +950 -211
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +29 -5
- package/docs/NEXT_STEPS.md +29 -16
- package/package.json +7 -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.16",
|
|
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,12 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Duplication warnings now aggregate into the single completion receipt without changing verified status",
|
|
240
|
+
"Exact match ids support bounded, reason-coded suppression with audit and credential/code-block safeguards",
|
|
241
|
+
"Duplication audit tools are disclosed only when active warnings exist and repaired paths clear stale matches",
|
|
242
|
+
"Warning-only TS/JS duplication audit compares new functions with the pre-write index generation",
|
|
243
|
+
"Deterministic Type-1/2 and Type-3 receipts retain only hashes, locations, scores, and bounded counts",
|
|
244
|
+
"Ordinary edits, renames, moves, deletions, small functions, tests, generated paths, and failed writes stay quiet",
|
|
239
245
|
"Warning-only Reuse Gate records content-free candidate evidence before the first substantive write",
|
|
240
246
|
"Reuse receipts surface unresolved index/read failures instead of claiming a safe new implementation",
|
|
241
247
|
"Prompt and timeline diagnostics expose the repository-first reuse ladder without blocking legitimate writes",
|
|
@@ -993,16 +999,16 @@ async function hashRegularFile(path) {
|
|
|
993
999
|
try {
|
|
994
1000
|
const info = await handle.stat();
|
|
995
1001
|
if (!info.isFile()) throw new Error(`Namespace entry is not a regular file: ${path}`);
|
|
996
|
-
const
|
|
1002
|
+
const hash4 = createHash2("sha256");
|
|
997
1003
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
998
1004
|
let position = 0;
|
|
999
1005
|
while (true) {
|
|
1000
1006
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
1001
1007
|
if (!bytesRead) break;
|
|
1002
|
-
|
|
1008
|
+
hash4.update(buffer.subarray(0, bytesRead));
|
|
1003
1009
|
position += bytesRead;
|
|
1004
1010
|
}
|
|
1005
|
-
return { sha256:
|
|
1011
|
+
return { sha256: hash4.digest("hex"), size: position };
|
|
1006
1012
|
} finally {
|
|
1007
1013
|
await handle.close();
|
|
1008
1014
|
}
|
|
@@ -1203,18 +1209,18 @@ var MemoryStore = class {
|
|
|
1203
1209
|
input2.lastVerifiedAt ?? (explicit ? now : void 0),
|
|
1204
1210
|
"Memory verification time"
|
|
1205
1211
|
);
|
|
1206
|
-
const
|
|
1212
|
+
const hash4 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1207
1213
|
database.exec("BEGIN IMMEDIATE");
|
|
1208
1214
|
try {
|
|
1209
1215
|
const existing = database.prepare(
|
|
1210
1216
|
"SELECT * FROM memories WHERE content_hash = ?"
|
|
1211
|
-
).get(
|
|
1217
|
+
).get(hash4);
|
|
1212
1218
|
const conflicting = conflictKey ? database.prepare(`
|
|
1213
1219
|
SELECT * FROM memories
|
|
1214
1220
|
WHERE scope = ? AND scope_key = ? AND conflict_key = ?
|
|
1215
1221
|
AND status = 'active' AND content_hash <> ?
|
|
1216
1222
|
ORDER BY updated_at DESC
|
|
1217
|
-
`).all(input2.scope, scopeKey2, conflictKey,
|
|
1223
|
+
`).all(input2.scope, scopeKey2, conflictKey, hash4) : [];
|
|
1218
1224
|
const supersedesId = normalizeOptional(input2.supersedesId, 80) ?? conflicting[0]?.id;
|
|
1219
1225
|
let id;
|
|
1220
1226
|
if (existing) {
|
|
@@ -1259,7 +1265,7 @@ var MemoryStore = class {
|
|
|
1259
1265
|
importance,
|
|
1260
1266
|
confidence,
|
|
1261
1267
|
source,
|
|
1262
|
-
|
|
1268
|
+
hash4,
|
|
1263
1269
|
now,
|
|
1264
1270
|
now,
|
|
1265
1271
|
now,
|
|
@@ -1362,10 +1368,10 @@ var MemoryStore = class {
|
|
|
1362
1368
|
const conflictKey = normalizeOptional(input2.conflictKey, 240);
|
|
1363
1369
|
const candidateDefaultExpiry = !input2.expiresAt ? new Date(Date.now() + MEMORY_CANDIDATE_TTL_MS).toISOString() : void 0;
|
|
1364
1370
|
const expiresAt = normalizeTimestamp(input2.expiresAt ?? candidateDefaultExpiry, "Memory expiration");
|
|
1365
|
-
const
|
|
1371
|
+
const hash4 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
|
|
1366
1372
|
const existing = database.prepare(
|
|
1367
1373
|
"SELECT * FROM memory_candidates WHERE content_hash = ?"
|
|
1368
|
-
).get(
|
|
1374
|
+
).get(hash4);
|
|
1369
1375
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1370
1376
|
if (existing) {
|
|
1371
1377
|
if (existing.status === "approved" && existing.approved_memory_id) {
|
|
@@ -1412,7 +1418,7 @@ var MemoryStore = class {
|
|
|
1412
1418
|
confidence,
|
|
1413
1419
|
source,
|
|
1414
1420
|
rationale,
|
|
1415
|
-
|
|
1421
|
+
hash4,
|
|
1416
1422
|
now,
|
|
1417
1423
|
now,
|
|
1418
1424
|
revision ?? null,
|
|
@@ -1699,8 +1705,8 @@ async function rejectSymlink(path, allowMissing = false) {
|
|
|
1699
1705
|
throw error;
|
|
1700
1706
|
}
|
|
1701
1707
|
}
|
|
1702
|
-
function clamp(value,
|
|
1703
|
-
return Math.min(maximum, Math.max(
|
|
1708
|
+
function clamp(value, minimum2, maximum) {
|
|
1709
|
+
return Math.min(maximum, Math.max(minimum2, value));
|
|
1704
1710
|
}
|
|
1705
1711
|
|
|
1706
1712
|
// src/tools/write.ts
|
|
@@ -2594,9 +2600,9 @@ function countExplicitPaths(value) {
|
|
|
2594
2600
|
}
|
|
2595
2601
|
|
|
2596
2602
|
// src/context/local-index.ts
|
|
2597
|
-
import { createHash as
|
|
2603
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
2598
2604
|
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";
|
|
2605
|
+
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
2606
|
import fg from "fast-glob";
|
|
2601
2607
|
import { z as z3 } from "zod";
|
|
2602
2608
|
|
|
@@ -2765,6 +2771,269 @@ function isEmojiOrSupplementarySymbol(character, codePoint) {
|
|
|
2765
2771
|
return codePoint > 65535 || new RegExp("\\p{Extended_Pictographic}", "u").test(character);
|
|
2766
2772
|
}
|
|
2767
2773
|
|
|
2774
|
+
// src/context/function-fingerprint.ts
|
|
2775
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2776
|
+
import { extname } from "node:path";
|
|
2777
|
+
var SUPPORTED = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
2778
|
+
var MIN_FUNCTION_TOKENS = 40;
|
|
2779
|
+
var SHINGLE_SIZE = 10;
|
|
2780
|
+
var WINDOW_SIZE = 6;
|
|
2781
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2782
|
+
"async",
|
|
2783
|
+
"await",
|
|
2784
|
+
"break",
|
|
2785
|
+
"case",
|
|
2786
|
+
"catch",
|
|
2787
|
+
"class",
|
|
2788
|
+
"const",
|
|
2789
|
+
"continue",
|
|
2790
|
+
"default",
|
|
2791
|
+
"delete",
|
|
2792
|
+
"do",
|
|
2793
|
+
"else",
|
|
2794
|
+
"extends",
|
|
2795
|
+
"false",
|
|
2796
|
+
"finally",
|
|
2797
|
+
"for",
|
|
2798
|
+
"function",
|
|
2799
|
+
"if",
|
|
2800
|
+
"in",
|
|
2801
|
+
"instanceof",
|
|
2802
|
+
"let",
|
|
2803
|
+
"new",
|
|
2804
|
+
"null",
|
|
2805
|
+
"of",
|
|
2806
|
+
"return",
|
|
2807
|
+
"static",
|
|
2808
|
+
"super",
|
|
2809
|
+
"switch",
|
|
2810
|
+
"this",
|
|
2811
|
+
"throw",
|
|
2812
|
+
"true",
|
|
2813
|
+
"try",
|
|
2814
|
+
"typeof",
|
|
2815
|
+
"undefined",
|
|
2816
|
+
"var",
|
|
2817
|
+
"while",
|
|
2818
|
+
"yield"
|
|
2819
|
+
]);
|
|
2820
|
+
function supportsFunctionFingerprintPath(path) {
|
|
2821
|
+
return SUPPORTED.has(extname(path).toLocaleLowerCase()) && !isNoisePath(path);
|
|
2822
|
+
}
|
|
2823
|
+
function extractFunctionFingerprints(path, content) {
|
|
2824
|
+
return extractFunctionFingerprintReport(path, content).functions;
|
|
2825
|
+
}
|
|
2826
|
+
function extractFunctionFingerprintReport(path, content) {
|
|
2827
|
+
if (!supportsFunctionFingerprintPath(path)) return { functions: [], skippedSmallFunctions: 0 };
|
|
2828
|
+
const masked = maskCommentsAndStrings(content);
|
|
2829
|
+
const lineOffsets = lineStartOffsets(content);
|
|
2830
|
+
const declarations2 = findDeclarations(masked);
|
|
2831
|
+
const functions = [];
|
|
2832
|
+
let skippedSmallFunctions = 0;
|
|
2833
|
+
for (const declaration of declarations2) {
|
|
2834
|
+
const open3 = declaration.open;
|
|
2835
|
+
const close = matchingBrace(masked, open3);
|
|
2836
|
+
if (close < 0) continue;
|
|
2837
|
+
const body = content.slice(open3 + 1, close);
|
|
2838
|
+
const normalizedTokens = normalizeFunctionTokens(body);
|
|
2839
|
+
if (normalizedTokens.length < MIN_FUNCTION_TOKENS) {
|
|
2840
|
+
skippedSmallFunctions += 1;
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
const startLine = lineAtOffset(lineOffsets, declaration.start);
|
|
2844
|
+
const endLine = lineAtOffset(lineOffsets, close);
|
|
2845
|
+
functions.push({
|
|
2846
|
+
path,
|
|
2847
|
+
symbol: declaration.symbol,
|
|
2848
|
+
startLine,
|
|
2849
|
+
endLine,
|
|
2850
|
+
tokenCount: normalizedTokens.length,
|
|
2851
|
+
exactHash: hash(normalizedTokens.join(" ")),
|
|
2852
|
+
fingerprints: winnow(normalizedTokens),
|
|
2853
|
+
normalizedTokens
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
return { functions, skippedSmallFunctions };
|
|
2857
|
+
}
|
|
2858
|
+
function fingerprintSimilarity(left, right) {
|
|
2859
|
+
if (left.exactHash === right.exactHash) return 1;
|
|
2860
|
+
const a = new Set(left.fingerprints);
|
|
2861
|
+
const b = new Set(right.fingerprints);
|
|
2862
|
+
if (!a.size || !b.size) return 0;
|
|
2863
|
+
let intersection = 0;
|
|
2864
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
2865
|
+
return intersection / (a.size + b.size - intersection);
|
|
2866
|
+
}
|
|
2867
|
+
function findDeclarations(masked) {
|
|
2868
|
+
const output2 = [];
|
|
2869
|
+
const classBodies = findClassBodies(masked);
|
|
2870
|
+
const patterns = [
|
|
2871
|
+
{
|
|
2872
|
+
kind: "function",
|
|
2873
|
+
pattern: /(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)(?:\s*<[^>{}\n]+>)?\s*\([^)]*\)\s*(?::\s*[^{}\n=]+)?\s*\{/g
|
|
2874
|
+
},
|
|
2875
|
+
{
|
|
2876
|
+
kind: "arrow",
|
|
2877
|
+
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
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
kind: "method",
|
|
2881
|
+
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
|
|
2882
|
+
}
|
|
2883
|
+
];
|
|
2884
|
+
for (const { kind, pattern } of patterns) {
|
|
2885
|
+
for (const match of masked.matchAll(pattern)) {
|
|
2886
|
+
const symbol = match[1];
|
|
2887
|
+
if (!symbol || match.index === void 0) continue;
|
|
2888
|
+
if (KEYWORDS.has(symbol) || symbol === "constructor") continue;
|
|
2889
|
+
const start = match.index + match[0].indexOf(symbol);
|
|
2890
|
+
if (kind === "method" && !isDirectClassMember(masked, start, classBodies)) continue;
|
|
2891
|
+
output2.push({
|
|
2892
|
+
symbol,
|
|
2893
|
+
start,
|
|
2894
|
+
open: match.index + match[0].lastIndexOf("{")
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return output2.sort((left, right) => left.start - right.start);
|
|
2899
|
+
}
|
|
2900
|
+
function findClassBodies(masked) {
|
|
2901
|
+
const output2 = [];
|
|
2902
|
+
const pattern = /(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class(?:\s+[A-Za-z_$][\w$]*)?[^\n{]*\{/g;
|
|
2903
|
+
for (const match of masked.matchAll(pattern)) {
|
|
2904
|
+
if (match.index === void 0) continue;
|
|
2905
|
+
const open3 = match.index + match[0].lastIndexOf("{");
|
|
2906
|
+
const close = matchingBrace(masked, open3);
|
|
2907
|
+
if (close >= 0) output2.push({ open: open3, close });
|
|
2908
|
+
}
|
|
2909
|
+
return output2.sort((left, right) => left.close - left.open - (right.close - right.open));
|
|
2910
|
+
}
|
|
2911
|
+
function isDirectClassMember(masked, start, classBodies) {
|
|
2912
|
+
for (const body of classBodies) {
|
|
2913
|
+
if (start <= body.open || start >= body.close) continue;
|
|
2914
|
+
let depth = 1;
|
|
2915
|
+
for (let index = body.open + 1; index < start; index += 1) {
|
|
2916
|
+
if (masked[index] === "{") depth += 1;
|
|
2917
|
+
else if (masked[index] === "}") depth -= 1;
|
|
2918
|
+
}
|
|
2919
|
+
if (depth === 1) return true;
|
|
2920
|
+
}
|
|
2921
|
+
return false;
|
|
2922
|
+
}
|
|
2923
|
+
function normalizeFunctionTokens(content) {
|
|
2924
|
+
const masked = maskCommentsAndStrings(content, true);
|
|
2925
|
+
const raw = masked.match(/[A-Za-z_$][\w$]*|\d+(?:\.\d+)?|===|!==|=>|==|!=|<=|>=|&&|\|\||\+\+|--|\?\?|\?\.|[{}()[\].,;:+\-*/%<>=!?&|^~]/g) ?? [];
|
|
2926
|
+
return raw.map((token) => {
|
|
2927
|
+
if (token === "LIT") return token;
|
|
2928
|
+
if (/^[A-Za-z_$]/.test(token)) return KEYWORDS.has(token) ? token : "ID";
|
|
2929
|
+
if (/^\d/.test(token)) return "LIT";
|
|
2930
|
+
return token;
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
function winnow(tokens2) {
|
|
2934
|
+
if (tokens2.length < SHINGLE_SIZE) return [];
|
|
2935
|
+
const shingles = Array.from({ length: tokens2.length - SHINGLE_SIZE + 1 }, (_, index) => hash(tokens2.slice(index, index + SHINGLE_SIZE).join(" ")).slice(0, 16));
|
|
2936
|
+
if (shingles.length <= WINDOW_SIZE) return [minimum(shingles)];
|
|
2937
|
+
const selected = /* @__PURE__ */ new Set();
|
|
2938
|
+
for (let index = 0; index <= shingles.length - WINDOW_SIZE; index += 1) {
|
|
2939
|
+
selected.add(minimum(shingles.slice(index, index + WINDOW_SIZE)));
|
|
2940
|
+
}
|
|
2941
|
+
return [...selected].sort();
|
|
2942
|
+
}
|
|
2943
|
+
function minimum(values) {
|
|
2944
|
+
return values.reduce((smallest, value) => value < smallest ? value : smallest);
|
|
2945
|
+
}
|
|
2946
|
+
function maskCommentsAndStrings(content, preserveLiterals = false) {
|
|
2947
|
+
let state = "code";
|
|
2948
|
+
let escaped = false;
|
|
2949
|
+
let output2 = "";
|
|
2950
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
2951
|
+
const character = content[index] ?? "";
|
|
2952
|
+
const next = content[index + 1] ?? "";
|
|
2953
|
+
if (state === "code") {
|
|
2954
|
+
if (character === "/" && next === "/") {
|
|
2955
|
+
state = "line";
|
|
2956
|
+
output2 += " ";
|
|
2957
|
+
index += 1;
|
|
2958
|
+
continue;
|
|
2959
|
+
}
|
|
2960
|
+
if (character === "/" && next === "*") {
|
|
2961
|
+
state = "block";
|
|
2962
|
+
output2 += " ";
|
|
2963
|
+
index += 1;
|
|
2964
|
+
continue;
|
|
2965
|
+
}
|
|
2966
|
+
if (character === "'") state = "single";
|
|
2967
|
+
else if (character === '"') state = "double";
|
|
2968
|
+
else if (character === "`") state = "template";
|
|
2969
|
+
output2 += state === "code" ? character : preserveLiterals ? " LIT " : character === "\n" ? "\n" : " ";
|
|
2970
|
+
escaped = false;
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
if (state === "line") {
|
|
2974
|
+
if (character === "\n") {
|
|
2975
|
+
state = "code";
|
|
2976
|
+
output2 += "\n";
|
|
2977
|
+
} else output2 += " ";
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
if (state === "block") {
|
|
2981
|
+
if (character === "*" && next === "/") {
|
|
2982
|
+
state = "code";
|
|
2983
|
+
output2 += " ";
|
|
2984
|
+
index += 1;
|
|
2985
|
+
} else output2 += character === "\n" ? "\n" : " ";
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
output2 += character === "\n" ? "\n" : " ";
|
|
2989
|
+
if (escaped) {
|
|
2990
|
+
escaped = false;
|
|
2991
|
+
continue;
|
|
2992
|
+
}
|
|
2993
|
+
if (character === "\\") {
|
|
2994
|
+
escaped = true;
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2997
|
+
if (state === "single" && character === "'" || state === "double" && character === '"' || state === "template" && character === "`") state = "code";
|
|
2998
|
+
}
|
|
2999
|
+
return output2;
|
|
3000
|
+
}
|
|
3001
|
+
function matchingBrace(masked, open3) {
|
|
3002
|
+
let depth = 0;
|
|
3003
|
+
for (let index = open3; index < masked.length; index += 1) {
|
|
3004
|
+
if (masked[index] === "{") depth += 1;
|
|
3005
|
+
else if (masked[index] === "}") {
|
|
3006
|
+
depth -= 1;
|
|
3007
|
+
if (depth === 0) return index;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return -1;
|
|
3011
|
+
}
|
|
3012
|
+
function lineStartOffsets(content) {
|
|
3013
|
+
const offsets = [0];
|
|
3014
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
3015
|
+
if (content[index] === "\n") offsets.push(index + 1);
|
|
3016
|
+
}
|
|
3017
|
+
return offsets;
|
|
3018
|
+
}
|
|
3019
|
+
function lineAtOffset(offsets, offset) {
|
|
3020
|
+
let low = 0;
|
|
3021
|
+
let high = offsets.length;
|
|
3022
|
+
while (low < high) {
|
|
3023
|
+
const middle = Math.floor((low + high) / 2);
|
|
3024
|
+
if ((offsets[middle] ?? 0) <= offset) low = middle + 1;
|
|
3025
|
+
else high = middle;
|
|
3026
|
+
}
|
|
3027
|
+
return Math.max(1, low);
|
|
3028
|
+
}
|
|
3029
|
+
function isNoisePath(path) {
|
|
3030
|
+
const normalized = path.replaceAll("\\", "/").toLocaleLowerCase();
|
|
3031
|
+
return /(^|\/)(test|tests|__tests__|fixtures|fixture|generated|vendor|dist|build)(\/|$)/.test(normalized) || /(?:\.d|\.generated|\.min)\.[cm]?[jt]sx?$/.test(normalized);
|
|
3032
|
+
}
|
|
3033
|
+
function hash(value) {
|
|
3034
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
3035
|
+
}
|
|
3036
|
+
|
|
2768
3037
|
// src/context/local-index.ts
|
|
2769
3038
|
var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
|
|
2770
3039
|
var indexedChunkSchema = z3.object({
|
|
@@ -2835,6 +3104,7 @@ var LocalContextIndex = class {
|
|
|
2835
3104
|
index;
|
|
2836
3105
|
workspace;
|
|
2837
3106
|
queryCache = /* @__PURE__ */ new Map();
|
|
3107
|
+
fingerprintCache;
|
|
2838
3108
|
dirtyPaths = /* @__PURE__ */ new Set();
|
|
2839
3109
|
refreshState = "current";
|
|
2840
3110
|
refreshError;
|
|
@@ -2868,6 +3138,7 @@ var LocalContextIndex = class {
|
|
|
2868
3138
|
}
|
|
2869
3139
|
this.index = { ...parsed, files };
|
|
2870
3140
|
this.queryCache.clear();
|
|
3141
|
+
this.fingerprintCache = void 0;
|
|
2871
3142
|
this.refreshState = this.dirtyPaths.size ? "dirty" : "current";
|
|
2872
3143
|
this.refreshError = void 0;
|
|
2873
3144
|
return true;
|
|
@@ -2875,6 +3146,7 @@ var LocalContextIndex = class {
|
|
|
2875
3146
|
if (error.code === "ENOENT") return false;
|
|
2876
3147
|
delete this.index;
|
|
2877
3148
|
this.queryCache.clear();
|
|
3149
|
+
this.fingerprintCache = void 0;
|
|
2878
3150
|
return false;
|
|
2879
3151
|
}
|
|
2880
3152
|
}
|
|
@@ -2984,6 +3256,7 @@ var LocalContextIndex = class {
|
|
|
2984
3256
|
files: nextFiles
|
|
2985
3257
|
};
|
|
2986
3258
|
this.queryCache.clear();
|
|
3259
|
+
this.fingerprintCache = void 0;
|
|
2987
3260
|
await ensureWorkspaceStorageDirectory(workspace, dirname7(this.indexPath), {
|
|
2988
3261
|
requireActiveNamespace: true
|
|
2989
3262
|
});
|
|
@@ -3003,6 +3276,24 @@ var LocalContextIndex = class {
|
|
|
3003
3276
|
const hits = await this.search(query, topK);
|
|
3004
3277
|
return packContextHits(hits, this.roots, maxTokens, "local");
|
|
3005
3278
|
}
|
|
3279
|
+
async functionFingerprints() {
|
|
3280
|
+
await this.flushDirty();
|
|
3281
|
+
await this.ensureCurrentIndex();
|
|
3282
|
+
const index = this.index;
|
|
3283
|
+
if (!index) throw new Error("The local context index is unavailable.");
|
|
3284
|
+
if (this.fingerprintCache?.generation === index.generation) {
|
|
3285
|
+
return cloneDuplicationBaseline(this.fingerprintCache);
|
|
3286
|
+
}
|
|
3287
|
+
const functions = index.files.flatMap((file) => {
|
|
3288
|
+
const content = reconstructIndexedContent(file.chunks);
|
|
3289
|
+
if (hashContent(content) !== file.contentHash) {
|
|
3290
|
+
throw new Error("The fingerprint baseline could not reconstruct current indexed files.");
|
|
3291
|
+
}
|
|
3292
|
+
return extractFunctionFingerprints(file.absolutePath, content).map(({ normalizedTokens: _tokens, ...fingerprint }) => fingerprint);
|
|
3293
|
+
});
|
|
3294
|
+
this.fingerprintCache = { generation: index.generation, functions };
|
|
3295
|
+
return cloneDuplicationBaseline(this.fingerprintCache);
|
|
3296
|
+
}
|
|
3006
3297
|
status() {
|
|
3007
3298
|
return {
|
|
3008
3299
|
available: Boolean(this.index),
|
|
@@ -3104,6 +3395,7 @@ var LocalContextIndex = class {
|
|
|
3104
3395
|
files
|
|
3105
3396
|
};
|
|
3106
3397
|
this.queryCache.clear();
|
|
3398
|
+
this.fingerprintCache = void 0;
|
|
3107
3399
|
onProgress?.({ phase: "write", completed: files.length, total: files.length });
|
|
3108
3400
|
await ensureWorkspaceStorageDirectory(
|
|
3109
3401
|
this.roots[0] ?? process.cwd(),
|
|
@@ -3365,7 +3657,7 @@ function isIncludedSourcePath(path) {
|
|
|
3365
3657
|
".proto",
|
|
3366
3658
|
".tf",
|
|
3367
3659
|
".hcl"
|
|
3368
|
-
])).has(
|
|
3660
|
+
])).has(extname2(name).toLowerCase());
|
|
3369
3661
|
}
|
|
3370
3662
|
function chunksMatch(expected, actual) {
|
|
3371
3663
|
if (expected.length !== actual.length) return false;
|
|
@@ -3446,11 +3738,29 @@ ${hit.content}
|
|
|
3446
3738
|
function cloneHits(hits) {
|
|
3447
3739
|
return hits.map((hit) => ({ ...hit }));
|
|
3448
3740
|
}
|
|
3741
|
+
function cloneDuplicationBaseline(baseline) {
|
|
3742
|
+
return {
|
|
3743
|
+
generation: baseline.generation,
|
|
3744
|
+
functions: baseline.functions.map((item) => ({ ...item, fingerprints: [...item.fingerprints] }))
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
function reconstructIndexedContent(chunks) {
|
|
3748
|
+
const ordered = chunks.slice().sort((left, right) => left.startLine - right.startLine);
|
|
3749
|
+
const totalLines = ordered.reduce((maximum, chunk) => Math.max(maximum, chunk.endLine), 0);
|
|
3750
|
+
const lines = Array.from({ length: totalLines });
|
|
3751
|
+
for (const chunk of ordered) {
|
|
3752
|
+
for (const [offset, line] of chunk.content.split("\n").entries()) {
|
|
3753
|
+
const index = chunk.startLine - 1 + offset;
|
|
3754
|
+
if (lines[index] === void 0) lines[index] = line;
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
return lines.map((line) => line ?? "").join("\n");
|
|
3758
|
+
}
|
|
3449
3759
|
function hashContent(content) {
|
|
3450
|
-
return
|
|
3760
|
+
return createHash6("sha256").update(content, "utf8").digest("hex");
|
|
3451
3761
|
}
|
|
3452
3762
|
function createGeneration(files) {
|
|
3453
|
-
return
|
|
3763
|
+
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
3764
|
}
|
|
3455
3765
|
function hasSubstantialOverlap(left, right) {
|
|
3456
3766
|
if (left.path !== right.path) return false;
|
|
@@ -3497,7 +3807,7 @@ function appendChunkRange(chunks, file, lines, rangeStart, rangeEnd) {
|
|
|
3497
3807
|
}
|
|
3498
3808
|
}
|
|
3499
3809
|
function structuralStarts(path, lines) {
|
|
3500
|
-
const extension =
|
|
3810
|
+
const extension = extname2(path).toLocaleLowerCase();
|
|
3501
3811
|
return lines.flatMap((line, index) => isStructuralLine(line, extension) ? [index] : []);
|
|
3502
3812
|
}
|
|
3503
3813
|
function isStructuralLine(line, extension) {
|
|
@@ -3663,6 +3973,9 @@ var ContextEngine = class {
|
|
|
3663
3973
|
lastDegradation() {
|
|
3664
3974
|
return this.degradation ? { ...this.degradation } : void 0;
|
|
3665
3975
|
}
|
|
3976
|
+
async functionFingerprints() {
|
|
3977
|
+
return this.local.functionFingerprints();
|
|
3978
|
+
}
|
|
3666
3979
|
};
|
|
3667
3980
|
function formatContextHits(hits, roots) {
|
|
3668
3981
|
return hits.map((hit) => {
|
|
@@ -4827,7 +5140,7 @@ function createProvider(config) {
|
|
|
4827
5140
|
}
|
|
4828
5141
|
|
|
4829
5142
|
// src/checkpoint/store.ts
|
|
4830
|
-
import { createHash as
|
|
5143
|
+
import { createHash as createHash7, randomUUID as randomUUID7 } from "node:crypto";
|
|
4831
5144
|
import { lstat as lstat9, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
4832
5145
|
import { basename as basename6, dirname as dirname8, join as join8, relative as relative6, resolve as resolve10 } from "node:path";
|
|
4833
5146
|
import { z as z4 } from "zod";
|
|
@@ -4880,7 +5193,7 @@ var CheckpointStore = class {
|
|
|
4880
5193
|
if (info.size > 25e6) {
|
|
4881
5194
|
throw new Error(`File is too large to checkpoint (${info.size} bytes): ${input2}`);
|
|
4882
5195
|
}
|
|
4883
|
-
const blob = `${
|
|
5196
|
+
const blob = `${createHash7("sha256").update(path).digest("hex")}.bin`;
|
|
4884
5197
|
await atomicWrite(join8(blobDirectory, blob), await readFile5(path), 384);
|
|
4885
5198
|
entries.push({
|
|
4886
5199
|
path,
|
|
@@ -5363,11 +5676,49 @@ var reuseReceiptSchema = z5.object({
|
|
|
5363
5676
|
status: z5.enum(["warning", "skipped", "unresolved"]),
|
|
5364
5677
|
warningOnly: z5.literal(true)
|
|
5365
5678
|
}).strict();
|
|
5679
|
+
var duplicationMatchSchema = z5.object({
|
|
5680
|
+
matchId: z5.string().regex(/^[a-f0-9]{24}$/u).optional(),
|
|
5681
|
+
changedPath: z5.string(),
|
|
5682
|
+
changedSymbol: z5.string(),
|
|
5683
|
+
candidatePath: z5.string(),
|
|
5684
|
+
candidateSymbol: z5.string(),
|
|
5685
|
+
kind: z5.enum(["type-1-or-2", "type-3"]),
|
|
5686
|
+
similarity: z5.number().min(0).max(1)
|
|
5687
|
+
}).strict();
|
|
5688
|
+
var duplicationAuditSchema = z5.object({
|
|
5689
|
+
baselineGeneration: z5.string().min(1),
|
|
5690
|
+
changeSequence: z5.number().int().nonnegative(),
|
|
5691
|
+
status: z5.enum(["clear", "warning", "unresolved"]),
|
|
5692
|
+
warningOnly: z5.literal(true),
|
|
5693
|
+
checkedFunctions: z5.number().int().nonnegative(),
|
|
5694
|
+
skippedSmallFunctions: z5.number().int().nonnegative(),
|
|
5695
|
+
matches: z5.array(duplicationMatchSchema).max(8),
|
|
5696
|
+
rationale: z5.string().max(500)
|
|
5697
|
+
}).strict();
|
|
5698
|
+
var duplicationSuppressionSchema = z5.object({
|
|
5699
|
+
matchId: z5.string().regex(/^[a-f0-9]{24}$/u),
|
|
5700
|
+
reasonCode: z5.enum(["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"]),
|
|
5701
|
+
reason: z5.string().min(12).max(240),
|
|
5702
|
+
createdAt: z5.string().datetime(),
|
|
5703
|
+
toolCallId: z5.string().min(1).max(512)
|
|
5704
|
+
}).strict();
|
|
5366
5705
|
var auditMetadataSchema = z5.record(z5.string(), z5.unknown()).superRefine((metadata, ctx) => {
|
|
5367
5706
|
const receipt = metadata.reuseReceipt;
|
|
5368
5707
|
if (receipt !== void 0 && !reuseReceiptSchema.safeParse(receipt).success) {
|
|
5369
5708
|
ctx.addIssue({ code: "custom", message: "Invalid reuse receipt" });
|
|
5370
5709
|
}
|
|
5710
|
+
const duplication = metadata.duplicationAudit;
|
|
5711
|
+
if (duplication !== void 0 && !duplicationAuditSchema.safeParse(duplication).success) {
|
|
5712
|
+
ctx.addIssue({ code: "custom", message: "Invalid duplication audit receipt" });
|
|
5713
|
+
}
|
|
5714
|
+
const suppression = metadata.duplicationSuppression;
|
|
5715
|
+
if (suppression !== void 0 && !duplicationSuppressionSchema.safeParse(suppression).success) {
|
|
5716
|
+
ctx.addIssue({ code: "custom", message: "Invalid duplication suppression receipt" });
|
|
5717
|
+
}
|
|
5718
|
+
const activeMatches = metadata.activeDuplicationMatches;
|
|
5719
|
+
if (activeMatches !== void 0 && !z5.array(z5.string().regex(/^[a-f0-9]{24}$/u)).max(8).safeParse(activeMatches).success) {
|
|
5720
|
+
ctx.addIssue({ code: "custom", message: "Invalid active duplication matches" });
|
|
5721
|
+
}
|
|
5371
5722
|
});
|
|
5372
5723
|
var auditSchema = z5.object({
|
|
5373
5724
|
id: z5.string(),
|
|
@@ -5407,6 +5758,16 @@ var lastRunSchema = z5.object({
|
|
|
5407
5758
|
checks: z5.array(verificationEvidenceSchema),
|
|
5408
5759
|
detail: z5.string(),
|
|
5409
5760
|
mutationTracking: z5.enum(["complete", "unknown"]).optional(),
|
|
5761
|
+
duplication: z5.object({
|
|
5762
|
+
enforcement: z5.literal("warning"),
|
|
5763
|
+
status: z5.enum(["clear", "warning", "unresolved", "suppressed"]),
|
|
5764
|
+
warningCount: z5.number().int().nonnegative(),
|
|
5765
|
+
unresolvedCount: z5.number().int().nonnegative(),
|
|
5766
|
+
suppressedCount: z5.number().int().nonnegative(),
|
|
5767
|
+
matches: z5.array(duplicationMatchSchema.extend({
|
|
5768
|
+
matchId: z5.string().regex(/^[a-f0-9]{24}$/u)
|
|
5769
|
+
})).max(8)
|
|
5770
|
+
}).strict().optional(),
|
|
5410
5771
|
acceptance: z5.object({
|
|
5411
5772
|
state: z5.enum(["draft", "active", "satisfied", "blocked"]),
|
|
5412
5773
|
total: z5.number().int().nonnegative(),
|
|
@@ -5472,6 +5833,7 @@ var sessionSchema = z5.object({
|
|
|
5472
5833
|
contextSources: z5.array(contextSourceSchema).max(64).optional(),
|
|
5473
5834
|
toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
|
|
5474
5835
|
taskContract: taskContractSchema.optional(),
|
|
5836
|
+
duplicationSuppressions: z5.array(duplicationSuppressionSchema).max(64).optional(),
|
|
5475
5837
|
lastRun: lastRunSchema.optional(),
|
|
5476
5838
|
usage: z5.object({
|
|
5477
5839
|
inputTokens: z5.number().nonnegative(),
|
|
@@ -5779,7 +6141,7 @@ async function exists2(path) {
|
|
|
5779
6141
|
}
|
|
5780
6142
|
|
|
5781
6143
|
// src/session/tool-artifacts.ts
|
|
5782
|
-
import { createHash as
|
|
6144
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
5783
6145
|
import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
5784
6146
|
import { join as join11, resolve as resolve13 } from "node:path";
|
|
5785
6147
|
import { z as z6 } from "zod";
|
|
@@ -5839,7 +6201,7 @@ var ToolArtifactStore = class {
|
|
|
5839
6201
|
const artifacts = await this.loadArtifacts();
|
|
5840
6202
|
await this.removeExpired(artifacts, now);
|
|
5841
6203
|
const active = artifacts.filter((artifact2) => artifact2.expiresAt > now.toISOString());
|
|
5842
|
-
const sha256 =
|
|
6204
|
+
const sha256 = hash2(content);
|
|
5843
6205
|
const existing = active.find((artifact2) => artifact2.sessionId === sessionId && artifact2.toolCallId === toolCallId);
|
|
5844
6206
|
if (existing) {
|
|
5845
6207
|
if (existing.sha256 === sha256 && existing.bytes === bytes && existing.redacted === options.redacted) {
|
|
@@ -5954,7 +6316,7 @@ var ToolArtifactStore = class {
|
|
|
5954
6316
|
if (artifact.sessionId !== sessionId || artifact.toolCallId !== toolCallId) {
|
|
5955
6317
|
throw new Error("Retained tool output does not belong to this session.");
|
|
5956
6318
|
}
|
|
5957
|
-
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !==
|
|
6319
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash2(artifact.content)) {
|
|
5958
6320
|
throw new Error("Retained tool output failed its integrity check.");
|
|
5959
6321
|
}
|
|
5960
6322
|
return artifact;
|
|
@@ -5974,7 +6336,7 @@ var ToolArtifactStore = class {
|
|
|
5974
6336
|
await this.assertRegularFile(path);
|
|
5975
6337
|
try {
|
|
5976
6338
|
const artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
|
|
5977
|
-
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !==
|
|
6339
|
+
if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash2(artifact.content)) {
|
|
5978
6340
|
throw new Error("integrity");
|
|
5979
6341
|
}
|
|
5980
6342
|
if (sessionDirectory !== this.sessionDirectoryFor(artifact.sessionId) || path !== this.pathFor(artifact.sessionId, artifact.toolCallId)) {
|
|
@@ -5997,10 +6359,10 @@ var ToolArtifactStore = class {
|
|
|
5997
6359
|
await rm2(this.pathFor(artifact.sessionId, artifact.toolCallId), { force: true });
|
|
5998
6360
|
}
|
|
5999
6361
|
pathFor(sessionId, toolCallId) {
|
|
6000
|
-
return join11(this.sessionDirectoryFor(sessionId), `${
|
|
6362
|
+
return join11(this.sessionDirectoryFor(sessionId), `${hash2(`call\0${toolCallId}`)}.json`);
|
|
6001
6363
|
}
|
|
6002
6364
|
sessionDirectoryFor(sessionId) {
|
|
6003
|
-
return join11(this.directory,
|
|
6365
|
+
return join11(this.directory, hash2(`session\0${sessionId}`));
|
|
6004
6366
|
}
|
|
6005
6367
|
async ensureDirectory() {
|
|
6006
6368
|
await ensureWorkspaceStorageDirectory(this.workspace, this.directory, {
|
|
@@ -6057,8 +6419,8 @@ function reference(artifact) {
|
|
|
6057
6419
|
redacted: artifact.redacted
|
|
6058
6420
|
};
|
|
6059
6421
|
}
|
|
6060
|
-
function
|
|
6061
|
-
return
|
|
6422
|
+
function hash2(value) {
|
|
6423
|
+
return createHash8("sha256").update(value).digest("hex");
|
|
6062
6424
|
}
|
|
6063
6425
|
function storedBytes(artifact) {
|
|
6064
6426
|
return Buffer.byteLength(JSON.stringify(artifact)) + 1;
|
|
@@ -6403,13 +6765,13 @@ ${preview}`);
|
|
|
6403
6765
|
}
|
|
6404
6766
|
return `${lines.join("\n")}${forceFinalNewline && lines.length ? "\n" : ""}`;
|
|
6405
6767
|
}
|
|
6406
|
-
function findSequence(haystack, needle, hint,
|
|
6768
|
+
function findSequence(haystack, needle, hint, minimum2) {
|
|
6407
6769
|
const matchesAt = (start, relaxed) => needle.every((line, offset) => {
|
|
6408
6770
|
const candidate = haystack[start + offset];
|
|
6409
6771
|
return relaxed ? candidate?.trimEnd() === line.trimEnd() : candidate === line;
|
|
6410
6772
|
});
|
|
6411
6773
|
const starts = [];
|
|
6412
|
-
for (let index = Math.max(0,
|
|
6774
|
+
for (let index = Math.max(0, minimum2); index <= haystack.length - needle.length; index += 1) {
|
|
6413
6775
|
starts.push(index);
|
|
6414
6776
|
}
|
|
6415
6777
|
starts.sort((a, b) => Math.abs(a - hint) - Math.abs(b - hint));
|
|
@@ -7380,7 +7742,7 @@ ${hit.content}`
|
|
|
7380
7742
|
}
|
|
7381
7743
|
|
|
7382
7744
|
// src/tools/shell.ts
|
|
7383
|
-
import { createHash as
|
|
7745
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
7384
7746
|
import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
|
|
7385
7747
|
import { join as join13 } from "node:path";
|
|
7386
7748
|
import { z as z13 } from "zod";
|
|
@@ -7426,10 +7788,10 @@ var shellTool = {
|
|
|
7426
7788
|
validateEnvironment(input2.env);
|
|
7427
7789
|
const cwd = await context.workspace.resolveDirectory(input2.cwd ?? ".");
|
|
7428
7790
|
const candidates = appearsToModifyWorkspace(input2.command) ? await collectAffectedPaths(input2.command, input2.cwd ?? ".", context) : [];
|
|
7429
|
-
const
|
|
7430
|
-
const roots =
|
|
7791
|
+
const unresolved2 = appearsToModifyWorkspace(input2.command) && candidates.length === 0;
|
|
7792
|
+
const roots = unresolved2 ? context.workspace.roots : [];
|
|
7431
7793
|
const before = await snapshotPaths(candidates);
|
|
7432
|
-
const beforeWorkspace =
|
|
7794
|
+
const beforeWorkspace = unresolved2 ? await captureWorkspaceSnapshot(roots) : void 0;
|
|
7433
7795
|
let result;
|
|
7434
7796
|
try {
|
|
7435
7797
|
result = await runShell(input2.command, cwd, {
|
|
@@ -7594,7 +7956,7 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
7594
7956
|
files.set(path, {
|
|
7595
7957
|
size: info.size,
|
|
7596
7958
|
mtimeMs: info.mtimeMs,
|
|
7597
|
-
hash:
|
|
7959
|
+
hash: createHash9("sha256").update(content).digest("hex")
|
|
7598
7960
|
});
|
|
7599
7961
|
} catch (error) {
|
|
7600
7962
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -7797,14 +8159,14 @@ function compact(value, limit) {
|
|
|
7797
8159
|
}
|
|
7798
8160
|
|
|
7799
8161
|
// src/agent/completion-gate.ts
|
|
7800
|
-
import { createHash as
|
|
8162
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
7801
8163
|
|
|
7802
8164
|
// src/tools/permissions.ts
|
|
7803
|
-
import { createHash as
|
|
8165
|
+
import { createHash as createHash10, createHmac, randomBytes } from "node:crypto";
|
|
7804
8166
|
var permissionScopeSecret = randomBytes(32);
|
|
7805
8167
|
function permissionKey(call, category) {
|
|
7806
8168
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
7807
|
-
const digest =
|
|
8169
|
+
const digest = createHash10("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
7808
8170
|
return `${category}:${call.name}:${digest}`;
|
|
7809
8171
|
}
|
|
7810
8172
|
function permissionTarget(call) {
|
|
@@ -7998,10 +8360,10 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
|
7998
8360
|
kind,
|
|
7999
8361
|
ok: result.ok,
|
|
8000
8362
|
changeSequence,
|
|
8001
|
-
commandKey:
|
|
8363
|
+
commandKey: createHash11("sha256").update(normalized).digest("hex")
|
|
8002
8364
|
};
|
|
8003
8365
|
}
|
|
8004
|
-
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
|
|
8366
|
+
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = [], duplication) {
|
|
8005
8367
|
const files = [...new Set(changedFiles)];
|
|
8006
8368
|
const acceptance = taskContract ? buildAcceptance(taskContract, audit, evidence, currentChangeSequence) : void 0;
|
|
8007
8369
|
if (mutationTracking === "unknown") {
|
|
@@ -8011,6 +8373,7 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8011
8373
|
checks: [],
|
|
8012
8374
|
detail: files.length ? `Workspace changes were observed, but a dynamic shell command prevented complete mutation tracking for ${fileCount(files.length)}.` : "A dynamic shell command may have changed workspace files, but reliable mutation tracking was unavailable.",
|
|
8013
8375
|
mutationTracking,
|
|
8376
|
+
...duplication ? { duplication } : {},
|
|
8014
8377
|
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
|
|
8015
8378
|
};
|
|
8016
8379
|
}
|
|
@@ -8021,7 +8384,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8021
8384
|
changedFiles: [],
|
|
8022
8385
|
checks: [],
|
|
8023
8386
|
detail: acceptanceDetail(acceptance),
|
|
8024
|
-
acceptance: acceptanceForCompletion(acceptance, "unverified")
|
|
8387
|
+
acceptance: acceptanceForCompletion(acceptance, "unverified"),
|
|
8388
|
+
...duplication ? { duplication } : {}
|
|
8025
8389
|
};
|
|
8026
8390
|
}
|
|
8027
8391
|
return {
|
|
@@ -8029,7 +8393,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8029
8393
|
changedFiles: [],
|
|
8030
8394
|
checks: [],
|
|
8031
8395
|
detail: "No workspace files changed in this run.",
|
|
8032
|
-
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "no_changes") } : {}
|
|
8396
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "no_changes") } : {},
|
|
8397
|
+
...duplication ? { duplication } : {}
|
|
8033
8398
|
};
|
|
8034
8399
|
}
|
|
8035
8400
|
const latestByCommand = /* @__PURE__ */ new Map();
|
|
@@ -8045,7 +8410,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8045
8410
|
changedFiles: files,
|
|
8046
8411
|
checks,
|
|
8047
8412
|
detail: `No successful verification was recorded after the last change to ${fileCount(files.length)}.`,
|
|
8048
|
-
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
|
|
8413
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {},
|
|
8414
|
+
...duplication ? { duplication } : {}
|
|
8049
8415
|
};
|
|
8050
8416
|
}
|
|
8051
8417
|
const failures = checks.filter((check) => !check.ok);
|
|
@@ -8055,7 +8421,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8055
8421
|
changedFiles: files,
|
|
8056
8422
|
checks,
|
|
8057
8423
|
detail: `${failures.length} of ${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} failed.`,
|
|
8058
|
-
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verification_failed") } : {}
|
|
8424
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verification_failed") } : {},
|
|
8425
|
+
...duplication ? { duplication } : {}
|
|
8059
8426
|
};
|
|
8060
8427
|
}
|
|
8061
8428
|
if (acceptance && acceptanceUnresolved(acceptance)) {
|
|
@@ -8064,7 +8431,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8064
8431
|
changedFiles: files,
|
|
8065
8432
|
checks,
|
|
8066
8433
|
detail: acceptanceDetail(acceptance),
|
|
8067
|
-
acceptance: acceptanceForCompletion(acceptance, "unverified")
|
|
8434
|
+
acceptance: acceptanceForCompletion(acceptance, "unverified"),
|
|
8435
|
+
...duplication ? { duplication } : {}
|
|
8068
8436
|
};
|
|
8069
8437
|
}
|
|
8070
8438
|
return {
|
|
@@ -8072,16 +8440,17 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
|
|
|
8072
8440
|
changedFiles: files,
|
|
8073
8441
|
checks,
|
|
8074
8442
|
detail: `${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} passed for ${fileCount(files.length)}.`,
|
|
8075
|
-
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verified") } : {}
|
|
8443
|
+
...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verified") } : {},
|
|
8444
|
+
...duplication ? { duplication } : {}
|
|
8076
8445
|
};
|
|
8077
8446
|
}
|
|
8078
8447
|
function completionRecoveryDirective(completion) {
|
|
8079
8448
|
if (completion.acceptance && acceptanceUnresolved(completion.acceptance)) {
|
|
8080
|
-
const
|
|
8449
|
+
const unresolved2 = completion.acceptance.unresolved.map((item) => `- [${item.status}] ${item.id}: ${item.description}`).join("\n");
|
|
8081
8450
|
const failed = completion.checks.filter((check) => !check.ok).map((check) => `- ${check.command} (tool call ${check.toolCallId})`).join("\n");
|
|
8082
8451
|
return `<runtime-completion-gate status="acceptance_unresolved" authorization="none">
|
|
8083
8452
|
The run cannot be marked complete while required Task Contract criteria remain unresolved:
|
|
8084
|
-
${
|
|
8453
|
+
${unresolved2 || "- Acceptance criteria are satisfied."}${completion.acceptance.missingVerification.length ? `
|
|
8085
8454
|
Missing required verification:
|
|
8086
8455
|
${completion.acceptance.missingVerification.map((item) => `- ${item}`).join("\n")}` : ""}${failed ? `
|
|
8087
8456
|
Current failed checks:
|
|
@@ -8151,14 +8520,14 @@ function acceptanceDetail(acceptance) {
|
|
|
8151
8520
|
acceptance.blocked ? `${acceptance.blocked} blocked` : "",
|
|
8152
8521
|
acceptance.missingVerification.length ? `${acceptance.missingVerification.length} verification requirements missing` : ""
|
|
8153
8522
|
].filter(Boolean).join(" and ");
|
|
8154
|
-
const
|
|
8155
|
-
return `Task Contract acceptance is unresolved: ${parts} required ${
|
|
8523
|
+
const unresolved2 = acceptance.pending + acceptance.blocked;
|
|
8524
|
+
return `Task Contract acceptance is unresolved: ${parts} required ${unresolved2 === 1 ? "criterion" : "criteria"}.`;
|
|
8156
8525
|
}
|
|
8157
8526
|
function verificationRequirementMet(requirement, checks) {
|
|
8158
8527
|
const normalized = normalizeCommand2(requirement);
|
|
8159
8528
|
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
8160
8529
|
if (broad) return checks.length > 0;
|
|
8161
|
-
const commandKey =
|
|
8530
|
+
const commandKey = createHash11("sha256").update(normalized).digest("hex");
|
|
8162
8531
|
return checks.some((item) => item.commandKey === commandKey);
|
|
8163
8532
|
}
|
|
8164
8533
|
function acceptanceUnresolved(acceptance) {
|
|
@@ -8484,6 +8853,139 @@ function render(memory) {
|
|
|
8484
8853
|
].join("\n");
|
|
8485
8854
|
}
|
|
8486
8855
|
|
|
8856
|
+
// src/tools/duplication.ts
|
|
8857
|
+
import { z as z17 } from "zod";
|
|
8858
|
+
|
|
8859
|
+
// src/agent/duplication-state.ts
|
|
8860
|
+
function buildDuplicationCompletion(audit, suppressions = [], runStartedAt) {
|
|
8861
|
+
const active = /* @__PURE__ */ new Map();
|
|
8862
|
+
const unresolved2 = /* @__PURE__ */ new Map();
|
|
8863
|
+
let sawCurrentReceipt = false;
|
|
8864
|
+
for (const event of audit) {
|
|
8865
|
+
if (runStartedAt && event.createdAt < runStartedAt) continue;
|
|
8866
|
+
const receipt = duplicationReceipt(event.metadata?.duplicationAudit);
|
|
8867
|
+
if (!receipt) continue;
|
|
8868
|
+
if (receipt.status === "clear" || receipt.status === "unresolved" || receipt.matches.some((match) => typeof match.matchId === "string")) sawCurrentReceipt = true;
|
|
8869
|
+
const changedPaths2 = changedPathsFor(event, receipt);
|
|
8870
|
+
for (const path of changedPaths2) {
|
|
8871
|
+
for (const [id, match] of active) if (match.changedPath === path) active.delete(id);
|
|
8872
|
+
unresolved2.delete(path);
|
|
8873
|
+
}
|
|
8874
|
+
if (receipt.status === "unresolved") {
|
|
8875
|
+
for (const path of changedPaths2) unresolved2.set(path, receipt.changeSequence);
|
|
8876
|
+
}
|
|
8877
|
+
for (const match of receipt.matches) {
|
|
8878
|
+
if (typeof match.matchId === "string") {
|
|
8879
|
+
active.set(match.matchId, match);
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
if (!active.size && !unresolved2.size && !sawCurrentReceipt) return void 0;
|
|
8884
|
+
const activeIds = new Set(active.keys());
|
|
8885
|
+
const suppressedIds = new Set(suppressions.filter((item) => activeIds.has(item.matchId)).map((item) => item.matchId));
|
|
8886
|
+
const warnings = [...active.values()].filter((match) => !suppressedIds.has(match.matchId));
|
|
8887
|
+
const suppressedCount = active.size - warnings.length;
|
|
8888
|
+
return {
|
|
8889
|
+
enforcement: "warning",
|
|
8890
|
+
status: unresolved2.size ? "unresolved" : warnings.length ? "warning" : suppressedCount ? "suppressed" : "clear",
|
|
8891
|
+
warningCount: warnings.length,
|
|
8892
|
+
unresolvedCount: unresolved2.size,
|
|
8893
|
+
suppressedCount,
|
|
8894
|
+
matches: warnings.slice(0, 8)
|
|
8895
|
+
};
|
|
8896
|
+
}
|
|
8897
|
+
function activeDuplicationMatchIds(audit, suppressions = []) {
|
|
8898
|
+
const completion = buildDuplicationCompletion(audit, suppressions);
|
|
8899
|
+
return new Set(completion?.matches.map((match) => match.matchId) ?? []);
|
|
8900
|
+
}
|
|
8901
|
+
function pruneDuplicationSuppressions(audit, suppressions = []) {
|
|
8902
|
+
if (!suppressions.length) return [];
|
|
8903
|
+
const active = new Set(buildDuplicationCompletion(audit, [])?.matches.map((match) => match.matchId) ?? []);
|
|
8904
|
+
return suppressions.filter((item) => active.has(item.matchId)).slice(-64);
|
|
8905
|
+
}
|
|
8906
|
+
function findActiveDuplicationMatches(audit, suppressions = []) {
|
|
8907
|
+
return buildDuplicationCompletion(audit, suppressions)?.matches ?? [];
|
|
8908
|
+
}
|
|
8909
|
+
function hasDuplicationActivity(audit, runStartedAt) {
|
|
8910
|
+
return audit.some(
|
|
8911
|
+
(event) => (!runStartedAt || event.createdAt >= runStartedAt) && (duplicationReceipt(event.metadata?.duplicationAudit) !== void 0 || event.metadata?.duplicationSuppression !== void 0 || event.metadata?.activeDuplicationMatches !== void 0)
|
|
8912
|
+
);
|
|
8913
|
+
}
|
|
8914
|
+
function duplicationReceipt(value) {
|
|
8915
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8916
|
+
const receipt = value;
|
|
8917
|
+
if (!Array.isArray(receipt.matches) || typeof receipt.changeSequence !== "number" || !["clear", "warning", "unresolved"].includes(String(receipt.status))) return void 0;
|
|
8918
|
+
return receipt;
|
|
8919
|
+
}
|
|
8920
|
+
function changedPathsFor(event, receipt) {
|
|
8921
|
+
const metadataPaths = event.metadata?.changedFiles;
|
|
8922
|
+
const fromMetadata = Array.isArray(metadataPaths) ? metadataPaths.filter((path) => typeof path === "string") : [];
|
|
8923
|
+
return [.../* @__PURE__ */ new Set([...fromMetadata, ...receipt.matches.map((match) => match.changedPath)])];
|
|
8924
|
+
}
|
|
8925
|
+
|
|
8926
|
+
// src/tools/duplication.ts
|
|
8927
|
+
var inputSchema12 = z17.discriminatedUnion("action", [
|
|
8928
|
+
z17.object({ action: z17.literal("show") }).strict(),
|
|
8929
|
+
z17.object({
|
|
8930
|
+
action: z17.literal("suppress"),
|
|
8931
|
+
match_id: z17.string().regex(/^[a-f0-9]{24}$/u),
|
|
8932
|
+
reason_code: z17.enum(["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"]),
|
|
8933
|
+
reason: z17.string().trim().min(12).max(240)
|
|
8934
|
+
}).strict()
|
|
8935
|
+
]);
|
|
8936
|
+
var duplicationTool = {
|
|
8937
|
+
definition: {
|
|
8938
|
+
name: "duplication_audit",
|
|
8939
|
+
description: "Inspect active duplicate-function warnings or suppress one exact match with an auditable reason. Suppression does not bypass correctness, security, accessibility, concurrency, or verification requirements.",
|
|
8940
|
+
category: "read",
|
|
8941
|
+
inputSchema: jsonSchema({
|
|
8942
|
+
action: { type: "string", enum: ["show", "suppress"] },
|
|
8943
|
+
match_id: { type: "string", description: "Exact 24-character match id from the current audit." },
|
|
8944
|
+
reason_code: { type: "string", enum: ["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"] },
|
|
8945
|
+
reason: { type: "string", description: "Specific 12-240 character explanation; code blocks and credentials are rejected." }
|
|
8946
|
+
}, ["action"])
|
|
8947
|
+
},
|
|
8948
|
+
async execute(arguments_, context) {
|
|
8949
|
+
const input2 = inputSchema12.parse(arguments_);
|
|
8950
|
+
const suppressions = pruneDuplicationSuppressions(
|
|
8951
|
+
context.session.audit ?? [],
|
|
8952
|
+
context.session.duplicationSuppressions ?? []
|
|
8953
|
+
);
|
|
8954
|
+
context.session.duplicationSuppressions = suppressions;
|
|
8955
|
+
const active = activeDuplicationMatchIds(context.session.audit ?? [], suppressions);
|
|
8956
|
+
if (input2.action === "suppress") {
|
|
8957
|
+
if (!active.has(input2.match_id)) {
|
|
8958
|
+
throw new Error(`Unknown, stale, or already suppressed duplication match: ${input2.match_id}`);
|
|
8959
|
+
}
|
|
8960
|
+
suppressions.push({
|
|
8961
|
+
matchId: input2.match_id,
|
|
8962
|
+
reasonCode: input2.reason_code,
|
|
8963
|
+
reason: cleanReason(input2.reason),
|
|
8964
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8965
|
+
toolCallId: context.toolCallId ?? "unavailable"
|
|
8966
|
+
});
|
|
8967
|
+
context.session.duplicationSuppressions = suppressions.slice(-64);
|
|
8968
|
+
return {
|
|
8969
|
+
content: `Suppressed duplication match ${input2.match_id}. This does not waive verification or safety requirements.`,
|
|
8970
|
+
metadata: { duplicationSuppression: context.session.duplicationSuppressions.at(-1) }
|
|
8971
|
+
};
|
|
8972
|
+
}
|
|
8973
|
+
const matches = findActiveDuplicationMatches(context.session.audit ?? [], suppressions);
|
|
8974
|
+
return {
|
|
8975
|
+
content: matches.length ? matches.map(
|
|
8976
|
+
(match) => `- ${match.matchId}: ${match.changedPath}#${match.changedSymbol} -> ${match.candidatePath}#${match.candidateSymbol} (${match.kind}, ${match.similarity.toFixed(3)})`
|
|
8977
|
+
).join("\n") : "No unsuppressed duplication matches.",
|
|
8978
|
+
metadata: { activeDuplicationMatches: matches.map((match) => match.matchId) }
|
|
8979
|
+
};
|
|
8980
|
+
}
|
|
8981
|
+
};
|
|
8982
|
+
function cleanReason(value) {
|
|
8983
|
+
if (/```|~~~|\b(?:api[_-]?key|access[_-]?token|authorization|cookie|password|secret)\s*[:=]/iu.test(value) || /\b(?:sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/u.test(value)) {
|
|
8984
|
+
throw new Error("Suppression reasons cannot contain code blocks or credentials.");
|
|
8985
|
+
}
|
|
8986
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
8987
|
+
}
|
|
8988
|
+
|
|
8487
8989
|
// src/tools/index.ts
|
|
8488
8990
|
function createDefaultToolRegistry(_options = {}) {
|
|
8489
8991
|
return new ToolRegistry([
|
|
@@ -8497,6 +8999,7 @@ function createDefaultToolRegistry(_options = {}) {
|
|
|
8497
8999
|
gitTool,
|
|
8498
9000
|
taskTool,
|
|
8499
9001
|
taskContractTool,
|
|
9002
|
+
duplicationTool,
|
|
8500
9003
|
workingMemoryTool
|
|
8501
9004
|
]);
|
|
8502
9005
|
}
|
|
@@ -8617,7 +9120,7 @@ function escapeAttribute3(value) {
|
|
|
8617
9120
|
}
|
|
8618
9121
|
|
|
8619
9122
|
// src/agent/tool-recovery.ts
|
|
8620
|
-
import { createHash as
|
|
9123
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
8621
9124
|
var RETRY_BUDGET = {
|
|
8622
9125
|
schema_input: 3,
|
|
8623
9126
|
unknown_tool: 2,
|
|
@@ -8689,7 +9192,7 @@ var ToolRecoveryController = class {
|
|
|
8689
9192
|
recordEvidence(call, result) {
|
|
8690
9193
|
if (call.name !== "search_code" || !result.ok) return void 0;
|
|
8691
9194
|
const callKey = callSignature(call);
|
|
8692
|
-
const fingerprint =
|
|
9195
|
+
const fingerprint = createHash12("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
|
|
8693
9196
|
const count = typeof result.metadata?.count === "number" ? result.metadata.count : void 0;
|
|
8694
9197
|
const current = this.evidence.get(callKey);
|
|
8695
9198
|
const repeated = current?.fingerprint === fingerprint;
|
|
@@ -8699,7 +9202,7 @@ var ToolRecoveryController = class {
|
|
|
8699
9202
|
status: count === 0 ? "empty" : repeated ? "repeated" : "new",
|
|
8700
9203
|
repeatCount: repeats,
|
|
8701
9204
|
stop: repeats >= 2,
|
|
8702
|
-
signature:
|
|
9205
|
+
signature: createHash12("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
|
|
8703
9206
|
};
|
|
8704
9207
|
}
|
|
8705
9208
|
receipt(call, failureClass, attempt, circuitOpen) {
|
|
@@ -8735,10 +9238,10 @@ function isRetryable(failureClass) {
|
|
|
8735
9238
|
return RETRY_BUDGET[failureClass] > 0;
|
|
8736
9239
|
}
|
|
8737
9240
|
function callSignature(call) {
|
|
8738
|
-
return
|
|
9241
|
+
return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
8739
9242
|
}
|
|
8740
9243
|
function failureSignature(call, failureClass) {
|
|
8741
|
-
return
|
|
9244
|
+
return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
8742
9245
|
}
|
|
8743
9246
|
function redact(value, key = "") {
|
|
8744
9247
|
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
@@ -8905,8 +9408,8 @@ ${tail}`;
|
|
|
8905
9408
|
}
|
|
8906
9409
|
return best || sliceStartByTokens(value, budget);
|
|
8907
9410
|
}
|
|
8908
|
-
function clamp2(value,
|
|
8909
|
-
return Math.max(
|
|
9411
|
+
function clamp2(value, minimum2, maximum) {
|
|
9412
|
+
return Math.max(minimum2, Math.min(maximum, value));
|
|
8910
9413
|
}
|
|
8911
9414
|
function boundedInlineByTokens(value, maxTokens) {
|
|
8912
9415
|
const normalized = value.replace(/[\r\n\t]+/gu, " ").trim();
|
|
@@ -9086,9 +9589,9 @@ function escapeAttribute5(value) {
|
|
|
9086
9589
|
}
|
|
9087
9590
|
|
|
9088
9591
|
// src/agent/reuse-gate.ts
|
|
9089
|
-
import { createHash as
|
|
9592
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
9090
9593
|
import { readFile as readFile14, stat as stat10 } from "node:fs/promises";
|
|
9091
|
-
import { basename as basename8, extname as
|
|
9594
|
+
import { basename as basename8, extname as extname3 } from "node:path";
|
|
9092
9595
|
var MAX_CANDIDATES = 5;
|
|
9093
9596
|
var SKIPPED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9094
9597
|
".md",
|
|
@@ -9153,7 +9656,7 @@ async function evaluateReuseGate(input2) {
|
|
|
9153
9656
|
if (preview.paths.every(isExemptPath)) return { triggered: false };
|
|
9154
9657
|
const targetPaths = preview.paths.slice(0, MAX_CANDIDATES);
|
|
9155
9658
|
const query = [input2.request, ...preview.symbols, ...targetPaths.map((path) => basename8(path))].join(" ").slice(0, 8e3);
|
|
9156
|
-
const queryHash =
|
|
9659
|
+
const queryHash = hash3(query);
|
|
9157
9660
|
let refresh;
|
|
9158
9661
|
try {
|
|
9159
9662
|
refresh = input2.context.flushDirty ? await input2.context.flushDirty() : { status: "current", paths: 0 };
|
|
@@ -9276,7 +9779,7 @@ function isExemptPath(path) {
|
|
|
9276
9779
|
const normalized = path.replaceAll("\\", "/").toLocaleLowerCase();
|
|
9277
9780
|
if (/(^|\/)(test|tests|__tests__|fixtures|fixture|generated|vendor|dist|build)(\/|$)/.test(normalized)) return true;
|
|
9278
9781
|
if (/(\.generated|\.min)\.[^.]+$/.test(normalized)) return true;
|
|
9279
|
-
const extension =
|
|
9782
|
+
const extension = extname3(normalized);
|
|
9280
9783
|
if (SKIPPED_EXTENSIONS.has(extension)) return true;
|
|
9281
9784
|
return !SOURCE_EXTENSIONS.has(extension);
|
|
9282
9785
|
}
|
|
@@ -9294,13 +9797,192 @@ function unresolvedReceipt(input2, queryHash, targetPaths, trigger, detail) {
|
|
|
9294
9797
|
warningOnly: true
|
|
9295
9798
|
};
|
|
9296
9799
|
}
|
|
9297
|
-
function
|
|
9298
|
-
return
|
|
9800
|
+
function hash3(value) {
|
|
9801
|
+
return createHash13("sha256").update(value).digest("hex");
|
|
9299
9802
|
}
|
|
9300
9803
|
function roundScore(value) {
|
|
9301
9804
|
return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : 0;
|
|
9302
9805
|
}
|
|
9303
9806
|
|
|
9807
|
+
// src/agent/duplication-audit.ts
|
|
9808
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
9809
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
9810
|
+
var NEAR_CLONE_THRESHOLD = 0.55;
|
|
9811
|
+
var MAX_MATCHES = 8;
|
|
9812
|
+
async function auditChangedFunctions(input2) {
|
|
9813
|
+
const auditableFiles = input2.changedFiles.filter(supportsFunctionFingerprintPath);
|
|
9814
|
+
if (!auditableFiles.length) return void 0;
|
|
9815
|
+
const reports = [];
|
|
9816
|
+
let skippedSmallFunctions = 0;
|
|
9817
|
+
try {
|
|
9818
|
+
for (const path of auditableFiles) {
|
|
9819
|
+
let content;
|
|
9820
|
+
try {
|
|
9821
|
+
content = await readFile15(path, "utf8");
|
|
9822
|
+
} catch (error) {
|
|
9823
|
+
if (error.code === "ENOENT") continue;
|
|
9824
|
+
throw error;
|
|
9825
|
+
}
|
|
9826
|
+
const report = extractFunctionFingerprintReport(path, content);
|
|
9827
|
+
skippedSmallFunctions += report.skippedSmallFunctions;
|
|
9828
|
+
reports.push(report);
|
|
9829
|
+
}
|
|
9830
|
+
} catch {
|
|
9831
|
+
return unresolved(input2.changeSequence, input2.baseline?.generation);
|
|
9832
|
+
}
|
|
9833
|
+
const currentFunctions = reports.flatMap((report) => report.functions.map(withoutTokens));
|
|
9834
|
+
if (!currentFunctions.length) {
|
|
9835
|
+
return auditableFiles.some((path) => input2.recheckPaths?.has(path)) && input2.baseline ? clear(input2.baseline.generation, input2.changeSequence, skippedSmallFunctions) : void 0;
|
|
9836
|
+
}
|
|
9837
|
+
if (!input2.baseline) return unresolved(input2.changeSequence);
|
|
9838
|
+
const lookup = createBaselineLookup(input2.baseline.functions);
|
|
9839
|
+
const previousByCurrent = matchCurrentFunctions(input2.baseline.functions, currentFunctions);
|
|
9840
|
+
const added = currentFunctions.flatMap((current) => {
|
|
9841
|
+
const previous = previousByCurrent.get(locationIdentity(current));
|
|
9842
|
+
const recheck = input2.recheckFunctions?.has(identity(current)) || previous !== void 0 && input2.recheckFunctions?.has(identity(previous));
|
|
9843
|
+
return !previous || current.tokenCount >= previous.tokenCount * 1.5 || recheck ? [{ current, previous }] : [];
|
|
9844
|
+
});
|
|
9845
|
+
if (!added.length) return void 0;
|
|
9846
|
+
const matches = [];
|
|
9847
|
+
for (const { current: item, previous } of added) {
|
|
9848
|
+
let best;
|
|
9849
|
+
for (const candidate of findCandidateFunctions(lookup, item)) {
|
|
9850
|
+
if (previous && locationIdentity(candidate) === locationIdentity(previous)) continue;
|
|
9851
|
+
const similarity = fingerprintSimilarity(item, candidate);
|
|
9852
|
+
if (similarity < NEAR_CLONE_THRESHOLD || best && best.similarity >= similarity) continue;
|
|
9853
|
+
best = { candidate, similarity };
|
|
9854
|
+
}
|
|
9855
|
+
if (!best) continue;
|
|
9856
|
+
matches.push({
|
|
9857
|
+
matchId: matchId(input2.baseline.generation, input2.changeSequence, item, best.candidate, best.similarity),
|
|
9858
|
+
changedPath: item.path,
|
|
9859
|
+
changedSymbol: item.symbol,
|
|
9860
|
+
candidatePath: best.candidate.path,
|
|
9861
|
+
candidateSymbol: best.candidate.symbol,
|
|
9862
|
+
kind: best.similarity === 1 ? "type-1-or-2" : "type-3",
|
|
9863
|
+
similarity: round(best.similarity)
|
|
9864
|
+
});
|
|
9865
|
+
}
|
|
9866
|
+
matches.sort((left, right) => right.similarity - left.similarity || left.changedPath.localeCompare(right.changedPath));
|
|
9867
|
+
const bounded = matches.slice(0, MAX_MATCHES);
|
|
9868
|
+
return {
|
|
9869
|
+
baselineGeneration: input2.baseline.generation,
|
|
9870
|
+
changeSequence: input2.changeSequence,
|
|
9871
|
+
status: bounded.length ? "warning" : "clear",
|
|
9872
|
+
warningOnly: true,
|
|
9873
|
+
checkedFunctions: added.length,
|
|
9874
|
+
skippedSmallFunctions,
|
|
9875
|
+
matches: bounded,
|
|
9876
|
+
rationale: bounded.length ? `${bounded.length} deterministic duplicate candidate${bounded.length === 1 ? "" : "s"} found; review for reuse.` : "No deterministic duplicate candidate exceeded the warning threshold."
|
|
9877
|
+
};
|
|
9878
|
+
}
|
|
9879
|
+
function clear(baselineGeneration, changeSequence, skippedSmallFunctions) {
|
|
9880
|
+
return {
|
|
9881
|
+
baselineGeneration,
|
|
9882
|
+
changeSequence,
|
|
9883
|
+
status: "clear",
|
|
9884
|
+
warningOnly: true,
|
|
9885
|
+
checkedFunctions: 0,
|
|
9886
|
+
skippedSmallFunctions,
|
|
9887
|
+
matches: [],
|
|
9888
|
+
rationale: "No active deterministic duplicate candidate remains on the rechecked path."
|
|
9889
|
+
};
|
|
9890
|
+
}
|
|
9891
|
+
function withoutTokens(value) {
|
|
9892
|
+
const { normalizedTokens: _tokens, ...fingerprint } = value;
|
|
9893
|
+
return fingerprint;
|
|
9894
|
+
}
|
|
9895
|
+
function identity(value) {
|
|
9896
|
+
return `${value.path}\0${value.symbol}`;
|
|
9897
|
+
}
|
|
9898
|
+
function locationIdentity(value) {
|
|
9899
|
+
return `${identity(value)}\0${value.startLine}\0${value.endLine}`;
|
|
9900
|
+
}
|
|
9901
|
+
function createBaselineLookup(functions) {
|
|
9902
|
+
const lookup = {
|
|
9903
|
+
byIdentity: /* @__PURE__ */ new Map(),
|
|
9904
|
+
byPath: /* @__PURE__ */ new Map(),
|
|
9905
|
+
byExactHash: /* @__PURE__ */ new Map(),
|
|
9906
|
+
byFingerprint: /* @__PURE__ */ new Map()
|
|
9907
|
+
};
|
|
9908
|
+
for (const item of functions) {
|
|
9909
|
+
appendLookup(lookup.byIdentity, identity(item), item);
|
|
9910
|
+
appendLookup(lookup.byPath, item.path, item);
|
|
9911
|
+
appendLookup(lookup.byExactHash, item.exactHash, item);
|
|
9912
|
+
for (const fingerprint of new Set(item.fingerprints)) {
|
|
9913
|
+
appendLookup(lookup.byFingerprint, fingerprint, item);
|
|
9914
|
+
}
|
|
9915
|
+
}
|
|
9916
|
+
return lookup;
|
|
9917
|
+
}
|
|
9918
|
+
function appendLookup(lookup, key, item) {
|
|
9919
|
+
const values = lookup.get(key);
|
|
9920
|
+
if (values) values.push(item);
|
|
9921
|
+
else lookup.set(key, [item]);
|
|
9922
|
+
}
|
|
9923
|
+
function findCandidateFunctions(lookup, current) {
|
|
9924
|
+
const exact = lookup.byExactHash.get(current.exactHash);
|
|
9925
|
+
if (exact?.length) return exact;
|
|
9926
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
9927
|
+
for (const fingerprint of new Set(current.fingerprints)) {
|
|
9928
|
+
for (const candidate of lookup.byFingerprint.get(fingerprint) ?? []) {
|
|
9929
|
+
candidates.set(locationIdentity(candidate), candidate);
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
return [...candidates.values()];
|
|
9933
|
+
}
|
|
9934
|
+
function matchCurrentFunctions(baseline, current) {
|
|
9935
|
+
const matches = /* @__PURE__ */ new Map();
|
|
9936
|
+
const available = new Map(baseline.map((item) => [locationIdentity(item), item]));
|
|
9937
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item) && linesOverlap(candidate, item)), false);
|
|
9938
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item)), true);
|
|
9939
|
+
assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => candidate.path === item.path && linesOverlap(candidate, item)), true);
|
|
9940
|
+
return matches;
|
|
9941
|
+
}
|
|
9942
|
+
function assignMatches(current, matches, available, candidatesFor, requireSimilarity) {
|
|
9943
|
+
for (const item of current) {
|
|
9944
|
+
const currentId = locationIdentity(item);
|
|
9945
|
+
if (matches.has(currentId)) continue;
|
|
9946
|
+
let best;
|
|
9947
|
+
for (const candidate of candidatesFor(item)) {
|
|
9948
|
+
const similarity = fingerprintSimilarity(candidate, item);
|
|
9949
|
+
if (!best || similarity > best.similarity) best = { candidate, similarity };
|
|
9950
|
+
}
|
|
9951
|
+
if (!best || requireSimilarity && best.similarity < NEAR_CLONE_THRESHOLD) continue;
|
|
9952
|
+
matches.set(currentId, best.candidate);
|
|
9953
|
+
available.delete(locationIdentity(best.candidate));
|
|
9954
|
+
}
|
|
9955
|
+
}
|
|
9956
|
+
function linesOverlap(left, right) {
|
|
9957
|
+
return Math.max(left.startLine, right.startLine) <= Math.min(left.endLine, right.endLine);
|
|
9958
|
+
}
|
|
9959
|
+
function unresolved(changeSequence, generation = "unavailable") {
|
|
9960
|
+
return {
|
|
9961
|
+
baselineGeneration: generation,
|
|
9962
|
+
changeSequence,
|
|
9963
|
+
status: "unresolved",
|
|
9964
|
+
warningOnly: true,
|
|
9965
|
+
checkedFunctions: 0,
|
|
9966
|
+
skippedSmallFunctions: 0,
|
|
9967
|
+
matches: [],
|
|
9968
|
+
rationale: "Duplication evidence is incomplete; the write remains allowed in warning-only mode."
|
|
9969
|
+
};
|
|
9970
|
+
}
|
|
9971
|
+
function round(value) {
|
|
9972
|
+
return Math.round(value * 1e3) / 1e3;
|
|
9973
|
+
}
|
|
9974
|
+
function matchId(generation, changeSequence, changed, candidate, similarity) {
|
|
9975
|
+
return createHash14("sha256").update([
|
|
9976
|
+
generation,
|
|
9977
|
+
String(changeSequence),
|
|
9978
|
+
changed.path,
|
|
9979
|
+
changed.symbol,
|
|
9980
|
+
candidate.path,
|
|
9981
|
+
candidate.symbol,
|
|
9982
|
+
similarity === 1 ? "type-1-or-2" : "type-3"
|
|
9983
|
+
].join("\0")).digest("hex").slice(0, 24);
|
|
9984
|
+
}
|
|
9985
|
+
|
|
9304
9986
|
// src/agent/runner.ts
|
|
9305
9987
|
var AgentRunner = class {
|
|
9306
9988
|
config;
|
|
@@ -9371,6 +10053,7 @@ var AgentRunner = class {
|
|
|
9371
10053
|
await options.onEvent?.(event);
|
|
9372
10054
|
};
|
|
9373
10055
|
const changeSequenceAtStart = this.changeSequence;
|
|
10056
|
+
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9374
10057
|
const runChangedFiles = /* @__PURE__ */ new Set();
|
|
9375
10058
|
const verificationEvidence = [];
|
|
9376
10059
|
const toolRecovery = new ToolRecoveryController();
|
|
@@ -9400,7 +10083,11 @@ var AgentRunner = class {
|
|
|
9400
10083
|
this.changeSequence,
|
|
9401
10084
|
mutationTracking,
|
|
9402
10085
|
activeRunContract,
|
|
9403
|
-
this.session.audit ?? []
|
|
10086
|
+
this.session.audit ?? [],
|
|
10087
|
+
hasDuplicationActivity(this.session.audit ?? [], runStartedAt) ? buildDuplicationCompletion(
|
|
10088
|
+
this.session.audit ?? [],
|
|
10089
|
+
this.session.duplicationSuppressions ?? []
|
|
10090
|
+
) : void 0
|
|
9404
10091
|
);
|
|
9405
10092
|
if (completion.acceptance && activeRunContract && activeRunContract.state !== "draft") {
|
|
9406
10093
|
activeRunContract.state = completion.acceptance.state;
|
|
@@ -9526,7 +10213,11 @@ var AgentRunner = class {
|
|
|
9526
10213
|
this.tools,
|
|
9527
10214
|
options.askMode === true,
|
|
9528
10215
|
contractEnabled,
|
|
9529
|
-
this.hasReadableToolArtifact()
|
|
10216
|
+
this.hasReadableToolArtifact(),
|
|
10217
|
+
activeDuplicationMatchIds(
|
|
10218
|
+
this.session.audit ?? [],
|
|
10219
|
+
this.session.duplicationSuppressions ?? []
|
|
10220
|
+
).size > 0
|
|
9530
10221
|
);
|
|
9531
10222
|
const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
|
|
9532
10223
|
if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
|
|
@@ -9811,11 +10502,14 @@ ${input2}`
|
|
|
9811
10502
|
contextEngine: this.contextEngine,
|
|
9812
10503
|
toolArtifactStore: this.toolArtifactStore,
|
|
9813
10504
|
emit,
|
|
9814
|
-
...options.signal ? { signal: options.signal } : {}
|
|
10505
|
+
...options.signal ? { signal: options.signal } : {},
|
|
10506
|
+
toolCallId: call.id
|
|
9815
10507
|
};
|
|
9816
10508
|
try {
|
|
9817
10509
|
let reuseReceipt;
|
|
9818
10510
|
let reuseWarning;
|
|
10511
|
+
let duplicationBaseline;
|
|
10512
|
+
const duplicationAuditEnabled = categories.includes("write") && (call.name === "write_file" || call.name === "apply_patch") && Boolean(this.contextEngine.functionFingerprints);
|
|
9819
10513
|
if (categories.includes("write") && this.activeReuseGate && !this.activeReuseGate.attempted && (call.name === "write_file" || call.name === "apply_patch")) {
|
|
9820
10514
|
this.activeReuseGate.attempted = true;
|
|
9821
10515
|
try {
|
|
@@ -9835,6 +10529,13 @@ ${input2}`
|
|
|
9835
10529
|
reuseWarning = "Reuse check (warning-only) was inconclusive.";
|
|
9836
10530
|
}
|
|
9837
10531
|
}
|
|
10532
|
+
if (duplicationAuditEnabled) {
|
|
10533
|
+
try {
|
|
10534
|
+
duplicationBaseline = await this.contextEngine.functionFingerprints?.();
|
|
10535
|
+
} catch {
|
|
10536
|
+
duplicationBaseline = void 0;
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
9838
10539
|
let checkpointId;
|
|
9839
10540
|
if (this.config.agent.checkpointBeforeWrite && categories.includes("write") && tool.affectedPaths) {
|
|
9840
10541
|
const paths = await tool.affectedPaths(call.arguments, executionContext);
|
|
@@ -9852,6 +10553,22 @@ ${input2}`
|
|
|
9852
10553
|
throwIfAborted(options.signal);
|
|
9853
10554
|
const execution = await tool.execute(call.arguments, toolExecutionContext);
|
|
9854
10555
|
const changedFiles = await this.acceptChangedFiles(execution.changedFiles ?? []);
|
|
10556
|
+
const activeDuplicateFunctions = new Set(
|
|
10557
|
+
buildDuplicationCompletion(
|
|
10558
|
+
this.session.audit ?? [],
|
|
10559
|
+
[]
|
|
10560
|
+
)?.matches.map((match) => `${match.changedPath}\0${match.changedSymbol}`) ?? []
|
|
10561
|
+
);
|
|
10562
|
+
const activeDuplicatePaths = new Set(
|
|
10563
|
+
buildDuplicationCompletion(this.session.audit ?? [], [])?.matches.map((match) => match.changedPath) ?? []
|
|
10564
|
+
);
|
|
10565
|
+
const duplicationAudit = duplicationAuditEnabled ? await auditChangedFunctions({
|
|
10566
|
+
...duplicationBaseline ? { baseline: duplicationBaseline } : {},
|
|
10567
|
+
changedFiles,
|
|
10568
|
+
changeSequence: this.changeSequence,
|
|
10569
|
+
recheckFunctions: activeDuplicateFunctions,
|
|
10570
|
+
recheckPaths: activeDuplicatePaths
|
|
10571
|
+
}) : void 0;
|
|
9855
10572
|
const tasksBefore = JSON.stringify(this.session.tasks);
|
|
9856
10573
|
let afterHookError;
|
|
9857
10574
|
let afterHooks = [];
|
|
@@ -9870,11 +10587,17 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
|
|
|
9870
10587
|
if (reuseWarning) completeContent = `${reuseWarning}
|
|
9871
10588
|
|
|
9872
10589
|
${completeContent}`;
|
|
10590
|
+
if (duplicationAudit?.status === "warning" || duplicationAudit?.status === "unresolved") {
|
|
10591
|
+
completeContent = `Duplication audit (warning-only): ${duplicationAudit.rationale}
|
|
10592
|
+
|
|
10593
|
+
${completeContent}`;
|
|
10594
|
+
}
|
|
9873
10595
|
const metadata = {
|
|
9874
10596
|
...execution.metadata ?? {},
|
|
9875
10597
|
...changedFiles.length ? { changedFiles } : {},
|
|
9876
10598
|
...checkpointId ? { checkpointId } : {},
|
|
9877
10599
|
...reuseReceipt ? { reuseReceipt } : {},
|
|
10600
|
+
...duplicationAudit ? { duplicationAudit } : {},
|
|
9878
10601
|
...beforeHooks.length || afterHooks.length ? { hooks: { before: beforeHooks.length, after: afterHooks.length } } : {},
|
|
9879
10602
|
...afterHookError ? { toolSucceeded: true, hookError: afterHookError.message } : {}
|
|
9880
10603
|
};
|
|
@@ -10324,9 +11047,9 @@ ${content}` : content,
|
|
|
10324
11047
|
...failure ? { metadata: { failure } } : {}
|
|
10325
11048
|
};
|
|
10326
11049
|
}
|
|
10327
|
-
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable) {
|
|
11050
|
+
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable) {
|
|
10328
11051
|
return tools.definitions().filter(
|
|
10329
|
-
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact")
|
|
11052
|
+
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
|
|
10330
11053
|
);
|
|
10331
11054
|
}
|
|
10332
11055
|
function formatToolError(error) {
|
|
@@ -10361,7 +11084,7 @@ async function safeEmit(emit, event) {
|
|
|
10361
11084
|
}
|
|
10362
11085
|
|
|
10363
11086
|
// src/agent/profiles.ts
|
|
10364
|
-
import { lstat as lstat17, readFile as
|
|
11087
|
+
import { lstat as lstat17, readFile as readFile16, readdir as readdir6 } from "node:fs/promises";
|
|
10365
11088
|
import { homedir as homedir3 } from "node:os";
|
|
10366
11089
|
import { basename as basename9, join as join15 } from "node:path";
|
|
10367
11090
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -10489,7 +11212,7 @@ async function readProfile(path, source) {
|
|
|
10489
11212
|
try {
|
|
10490
11213
|
const info = await lstat17(path);
|
|
10491
11214
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 1e5) return void 0;
|
|
10492
|
-
const raw = await
|
|
11215
|
+
const raw = await readFile16(path, "utf8");
|
|
10493
11216
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
10494
11217
|
const frontmatter = match ? parseYaml2(match[1] ?? "") : {};
|
|
10495
11218
|
const prompt = (match ? raw.slice(match[0].length) : raw).trim();
|
|
@@ -10518,7 +11241,7 @@ function integer(value, fallback, min, max) {
|
|
|
10518
11241
|
// src/agent/delegation.ts
|
|
10519
11242
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
10520
11243
|
import { join as join18 } from "node:path";
|
|
10521
|
-
import { z as
|
|
11244
|
+
import { z as z19 } from "zod";
|
|
10522
11245
|
|
|
10523
11246
|
// src/agent/external-runtime.ts
|
|
10524
11247
|
async function runExternalAgent(request) {
|
|
@@ -10645,86 +11368,86 @@ function numeric(...values) {
|
|
|
10645
11368
|
}
|
|
10646
11369
|
|
|
10647
11370
|
// src/agent/team-store.ts
|
|
10648
|
-
import { createHash as
|
|
10649
|
-
import { lstat as lstat18, readFile as
|
|
11371
|
+
import { createHash as createHash15, randomUUID as randomUUID13 } from "node:crypto";
|
|
11372
|
+
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
10650
11373
|
import { join as join16, resolve as resolve16 } from "node:path";
|
|
10651
|
-
import { z as
|
|
10652
|
-
var runIdSchema =
|
|
10653
|
-
var hashSchema =
|
|
10654
|
-
var artifactSchema2 =
|
|
11374
|
+
import { z as z18 } from "zod";
|
|
11375
|
+
var runIdSchema = z18.string().uuid();
|
|
11376
|
+
var hashSchema = z18.string().regex(/^[a-f0-9]{64}$/u);
|
|
11377
|
+
var artifactSchema2 = z18.object({
|
|
10655
11378
|
sha256: hashSchema,
|
|
10656
|
-
bytes:
|
|
11379
|
+
bytes: z18.number().int().nonnegative().max(5e5)
|
|
10657
11380
|
}).strict();
|
|
10658
|
-
var phaseSchema =
|
|
10659
|
-
var agentRecordSchema =
|
|
10660
|
-
id:
|
|
10661
|
-
profile:
|
|
10662
|
-
provider:
|
|
10663
|
-
model:
|
|
11381
|
+
var phaseSchema = z18.enum(["work", "review", "revision", "write"]);
|
|
11382
|
+
var agentRecordSchema = z18.object({
|
|
11383
|
+
id: z18.string().uuid(),
|
|
11384
|
+
profile: z18.string(),
|
|
11385
|
+
provider: z18.string(),
|
|
11386
|
+
model: z18.string(),
|
|
10664
11387
|
phase: phaseSchema,
|
|
10665
|
-
ok:
|
|
10666
|
-
createdAt:
|
|
10667
|
-
startedAt:
|
|
10668
|
-
endedAt:
|
|
10669
|
-
durationMs:
|
|
10670
|
-
toolCalls:
|
|
10671
|
-
usage:
|
|
10672
|
-
inputTokens:
|
|
10673
|
-
outputTokens:
|
|
11388
|
+
ok: z18.boolean(),
|
|
11389
|
+
createdAt: z18.string(),
|
|
11390
|
+
startedAt: z18.string().optional(),
|
|
11391
|
+
endedAt: z18.string().optional(),
|
|
11392
|
+
durationMs: z18.number().int().nonnegative().optional(),
|
|
11393
|
+
toolCalls: z18.number().int().nonnegative().optional(),
|
|
11394
|
+
usage: z18.object({
|
|
11395
|
+
inputTokens: z18.number().int().nonnegative(),
|
|
11396
|
+
outputTokens: z18.number().int().nonnegative()
|
|
10674
11397
|
}).strict().optional(),
|
|
10675
11398
|
report: artifactSchema2
|
|
10676
11399
|
}).strict();
|
|
10677
|
-
var messageRecordSchema =
|
|
10678
|
-
id:
|
|
10679
|
-
from:
|
|
10680
|
-
to:
|
|
10681
|
-
createdAt:
|
|
11400
|
+
var messageRecordSchema = z18.object({
|
|
11401
|
+
id: z18.string().uuid(),
|
|
11402
|
+
from: z18.string(),
|
|
11403
|
+
to: z18.string(),
|
|
11404
|
+
createdAt: z18.string(),
|
|
10682
11405
|
content: artifactSchema2
|
|
10683
11406
|
}).strict();
|
|
10684
|
-
var writerIntegrationSchema =
|
|
10685
|
-
status:
|
|
10686
|
-
checkedAt:
|
|
10687
|
-
detail:
|
|
10688
|
-
checkpoint:
|
|
10689
|
-
sessionId:
|
|
10690
|
-
checkpointId:
|
|
11407
|
+
var writerIntegrationSchema = z18.object({
|
|
11408
|
+
status: z18.enum(["ready", "conflict", "integrated"]),
|
|
11409
|
+
checkedAt: z18.string(),
|
|
11410
|
+
detail: z18.string().max(2e4),
|
|
11411
|
+
checkpoint: z18.object({
|
|
11412
|
+
sessionId: z18.string(),
|
|
11413
|
+
checkpointId: z18.string()
|
|
10691
11414
|
}).strict().optional(),
|
|
10692
|
-
integratedAt:
|
|
11415
|
+
integratedAt: z18.string().optional()
|
|
10693
11416
|
}).strict();
|
|
10694
|
-
var writerLaneSchema =
|
|
10695
|
-
profile:
|
|
10696
|
-
reviewer:
|
|
10697
|
-
baseCommit:
|
|
10698
|
-
outcome:
|
|
11417
|
+
var writerLaneSchema = z18.object({
|
|
11418
|
+
profile: z18.string(),
|
|
11419
|
+
reviewer: z18.string(),
|
|
11420
|
+
baseCommit: z18.string().regex(/^[a-f0-9]{40,64}$/u),
|
|
11421
|
+
outcome: z18.enum(["accepted", "rejected", "failed", "cancelled"]),
|
|
10699
11422
|
patch: artifactSchema2,
|
|
10700
|
-
files:
|
|
10701
|
-
worktreeCleaned:
|
|
11423
|
+
files: z18.array(z18.string().min(1).max(4e3)).max(2e3),
|
|
11424
|
+
worktreeCleaned: z18.boolean(),
|
|
10702
11425
|
review: artifactSchema2.optional(),
|
|
10703
11426
|
integration: writerIntegrationSchema.optional()
|
|
10704
11427
|
}).strict();
|
|
10705
11428
|
var manifestFields = {
|
|
10706
11429
|
id: runIdSchema,
|
|
10707
|
-
workspace:
|
|
10708
|
-
objective:
|
|
10709
|
-
reviewer:
|
|
10710
|
-
createdAt:
|
|
10711
|
-
updatedAt:
|
|
10712
|
-
status:
|
|
10713
|
-
maxReviewRounds:
|
|
10714
|
-
reviewRounds:
|
|
10715
|
-
agents:
|
|
10716
|
-
messages:
|
|
11430
|
+
workspace: z18.string(),
|
|
11431
|
+
objective: z18.string().max(3e4),
|
|
11432
|
+
reviewer: z18.string(),
|
|
11433
|
+
createdAt: z18.string(),
|
|
11434
|
+
updatedAt: z18.string(),
|
|
11435
|
+
status: z18.enum(["running", "accepted", "rejected", "failed"]),
|
|
11436
|
+
maxReviewRounds: z18.number().int().min(0).max(3),
|
|
11437
|
+
reviewRounds: z18.number().int().min(0).max(3),
|
|
11438
|
+
agents: z18.array(agentRecordSchema).max(256),
|
|
11439
|
+
messages: z18.array(messageRecordSchema).max(512)
|
|
10717
11440
|
};
|
|
10718
|
-
var manifestV1Schema =
|
|
10719
|
-
version:
|
|
11441
|
+
var manifestV1Schema = z18.object({
|
|
11442
|
+
version: z18.literal(1),
|
|
10720
11443
|
...manifestFields
|
|
10721
11444
|
}).strict();
|
|
10722
|
-
var manifestV2Schema =
|
|
10723
|
-
version:
|
|
11445
|
+
var manifestV2Schema = z18.object({
|
|
11446
|
+
version: z18.literal(2),
|
|
10724
11447
|
...manifestFields,
|
|
10725
11448
|
writer: writerLaneSchema.optional()
|
|
10726
11449
|
}).strict();
|
|
10727
|
-
var manifestSchema2 =
|
|
11450
|
+
var manifestSchema2 = z18.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
|
|
10728
11451
|
var TeamRunStore = class {
|
|
10729
11452
|
workspace;
|
|
10730
11453
|
directory;
|
|
@@ -10819,7 +11542,7 @@ var TeamRunStore = class {
|
|
|
10819
11542
|
await this.assertRunDirectory(runId);
|
|
10820
11543
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
10821
11544
|
await this.assertRegularFile(path);
|
|
10822
|
-
const manifest = manifestSchema2.parse(JSON.parse(await
|
|
11545
|
+
const manifest = manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
10823
11546
|
if (manifest.id !== runId || resolve16(manifest.workspace) !== this.workspace) {
|
|
10824
11547
|
throw new Error("Team run manifest identity does not match its location.");
|
|
10825
11548
|
}
|
|
@@ -10834,7 +11557,7 @@ var TeamRunStore = class {
|
|
|
10834
11557
|
}
|
|
10835
11558
|
async readArtifact(runId, artifact) {
|
|
10836
11559
|
await this.verifyArtifact(runId, artifact);
|
|
10837
|
-
return
|
|
11560
|
+
return readFile17(this.artifactPath(runId, artifact.sha256), "utf8");
|
|
10838
11561
|
}
|
|
10839
11562
|
async list() {
|
|
10840
11563
|
await this.writes;
|
|
@@ -10890,7 +11613,7 @@ var TeamRunStore = class {
|
|
|
10890
11613
|
await this.assertRunDirectory(runId);
|
|
10891
11614
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
10892
11615
|
await this.assertRegularFile(path);
|
|
10893
|
-
return manifestSchema2.parse(JSON.parse(await
|
|
11616
|
+
return manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
10894
11617
|
}
|
|
10895
11618
|
async writeManifest(manifest) {
|
|
10896
11619
|
const directory = this.runDirectory(manifest.id);
|
|
@@ -10903,7 +11626,7 @@ var TeamRunStore = class {
|
|
|
10903
11626
|
async writeArtifact(runId, content, truncate = true) {
|
|
10904
11627
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
10905
11628
|
const bytes = Buffer.byteLength(data);
|
|
10906
|
-
const sha256 =
|
|
11629
|
+
const sha256 = createHash15("sha256").update(data).digest("hex");
|
|
10907
11630
|
const directory = join16(this.runDirectory(runId), "blobs");
|
|
10908
11631
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
10909
11632
|
requireActiveNamespace: this.managedDirectory
|
|
@@ -10911,8 +11634,8 @@ var TeamRunStore = class {
|
|
|
10911
11634
|
const path = join16(directory, `${sha256}.txt`);
|
|
10912
11635
|
try {
|
|
10913
11636
|
await this.assertRegularFile(path);
|
|
10914
|
-
const existing = await
|
|
10915
|
-
if (
|
|
11637
|
+
const existing = await readFile17(path);
|
|
11638
|
+
if (createHash15("sha256").update(existing).digest("hex") !== sha256) {
|
|
10916
11639
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
10917
11640
|
}
|
|
10918
11641
|
} catch (error) {
|
|
@@ -10932,9 +11655,9 @@ var TeamRunStore = class {
|
|
|
10932
11655
|
hashSchema.parse(artifact.sha256);
|
|
10933
11656
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
10934
11657
|
await this.assertRegularFile(path);
|
|
10935
|
-
const data = await
|
|
10936
|
-
const
|
|
10937
|
-
if (
|
|
11658
|
+
const data = await readFile17(path);
|
|
11659
|
+
const hash4 = createHash15("sha256").update(data).digest("hex");
|
|
11660
|
+
if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
|
|
10938
11661
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
10939
11662
|
}
|
|
10940
11663
|
}
|
|
@@ -10996,7 +11719,7 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
10996
11719
|
}
|
|
10997
11720
|
|
|
10998
11721
|
// src/agent/writer-lane.ts
|
|
10999
|
-
import { createHash as
|
|
11722
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
11000
11723
|
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
11001
11724
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11002
11725
|
import { isAbsolute as isAbsolute6, join as join17, resolve as resolve17 } from "node:path";
|
|
@@ -11094,7 +11817,7 @@ var WriterLane = class {
|
|
|
11094
11817
|
return {
|
|
11095
11818
|
baseCommit,
|
|
11096
11819
|
patch,
|
|
11097
|
-
patchSha256:
|
|
11820
|
+
patchSha256: createHash16("sha256").update(patch).digest("hex"),
|
|
11098
11821
|
files,
|
|
11099
11822
|
worktreeCleaned,
|
|
11100
11823
|
value
|
|
@@ -11324,14 +12047,14 @@ async function pathExists2(path) {
|
|
|
11324
12047
|
}
|
|
11325
12048
|
|
|
11326
12049
|
// src/agent/delegation.ts
|
|
11327
|
-
var writerRunInputSchema =
|
|
11328
|
-
task:
|
|
11329
|
-
profile:
|
|
11330
|
-
reviewer:
|
|
12050
|
+
var writerRunInputSchema = z19.object({
|
|
12051
|
+
task: z19.string().min(1).max(2e4),
|
|
12052
|
+
profile: z19.string().max(64).optional(),
|
|
12053
|
+
reviewer: z19.string().max(64).optional()
|
|
11331
12054
|
}).strict();
|
|
11332
|
-
var writerIntegrateInputSchema =
|
|
11333
|
-
run_id:
|
|
11334
|
-
patch_sha256:
|
|
12055
|
+
var writerIntegrateInputSchema = z19.object({
|
|
12056
|
+
run_id: z19.string().uuid(),
|
|
12057
|
+
patch_sha256: z19.string().regex(/^[a-f0-9]{64}$/u)
|
|
11335
12058
|
}).strict();
|
|
11336
12059
|
var DelegationManager = class {
|
|
11337
12060
|
constructor(options) {
|
|
@@ -11392,10 +12115,10 @@ var DelegationManager = class {
|
|
|
11392
12115
|
},
|
|
11393
12116
|
async execute(arguments_, context) {
|
|
11394
12117
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent delegation is disabled." };
|
|
11395
|
-
const input2 =
|
|
11396
|
-
tasks:
|
|
11397
|
-
profile:
|
|
11398
|
-
task:
|
|
12118
|
+
const input2 = z19.object({
|
|
12119
|
+
tasks: z19.array(z19.object({
|
|
12120
|
+
profile: z19.string().max(64).optional(),
|
|
12121
|
+
task: z19.string().min(1).max(2e4)
|
|
11399
12122
|
})).min(1).max(manager.team.maxDelegations)
|
|
11400
12123
|
}).parse(arguments_);
|
|
11401
12124
|
const tasks = input2.tasks.map((task) => ({
|
|
@@ -11441,13 +12164,13 @@ var DelegationManager = class {
|
|
|
11441
12164
|
},
|
|
11442
12165
|
async execute(arguments_, context) {
|
|
11443
12166
|
if (!manager.team.enabled) return { ok: false, content: "Multi-agent teams are disabled." };
|
|
11444
|
-
const input2 =
|
|
11445
|
-
objective:
|
|
11446
|
-
tasks:
|
|
11447
|
-
profile:
|
|
11448
|
-
task:
|
|
12167
|
+
const input2 = z19.object({
|
|
12168
|
+
objective: z19.string().min(1).max(3e4),
|
|
12169
|
+
tasks: z19.array(z19.object({
|
|
12170
|
+
profile: z19.string().max(64).optional(),
|
|
12171
|
+
task: z19.string().min(1).max(2e4)
|
|
11449
12172
|
})).min(1).max(manager.team.maxDelegations),
|
|
11450
|
-
reviewer:
|
|
12173
|
+
reviewer: z19.string().max(64).optional()
|
|
11451
12174
|
}).parse(arguments_);
|
|
11452
12175
|
const tasks = input2.tasks.map((task) => ({
|
|
11453
12176
|
profile: task.profile ?? manager.team.defaultProfile,
|
|
@@ -12811,10 +13534,10 @@ function supportsNodeVersion(version) {
|
|
|
12811
13534
|
const match = version.trim().replace(/^v/u, "").match(/^(\d+)\.(\d+)\.(\d+)/u);
|
|
12812
13535
|
if (!match) return false;
|
|
12813
13536
|
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) < (
|
|
13537
|
+
const minimum2 = MINIMUM_NODE_VERSION.split(".").map(Number);
|
|
13538
|
+
for (let index = 0; index < minimum2.length; index += 1) {
|
|
13539
|
+
if ((current[index] ?? 0) > (minimum2[index] ?? 0)) return true;
|
|
13540
|
+
if ((current[index] ?? 0) < (minimum2[index] ?? 0)) return false;
|
|
12818
13541
|
}
|
|
12819
13542
|
return true;
|
|
12820
13543
|
}
|
|
@@ -13095,22 +13818,24 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
13095
13818
|
if (!completion || completion.status === "no_changes") return;
|
|
13096
13819
|
const checks = completion.checks.map((check) => check.command).join(", ");
|
|
13097
13820
|
const suffix = checks ? ` ${this.glyphs.separator} ${checks}` : "";
|
|
13821
|
+
const duplication = completion.duplication;
|
|
13822
|
+
const duplicateSuffix = duplication ? ` ${this.glyphs.separator} duplication ${duplication.status} (${duplication.warningCount} warning, ${duplication.unresolvedCount} incomplete, ${duplication.suppressedCount} suppressed)` : "";
|
|
13098
13823
|
if (completion.status === "verified") {
|
|
13099
13824
|
process.stderr.write(this.paint.green(
|
|
13100
|
-
`${this.glyphs.success} verified ${this.glyphs.separator} ${completion.detail}${suffix}
|
|
13825
|
+
`${this.glyphs.success} verified ${this.glyphs.separator} ${completion.detail}${suffix}${duplicateSuffix}
|
|
13101
13826
|
`
|
|
13102
13827
|
));
|
|
13103
13828
|
return;
|
|
13104
13829
|
}
|
|
13105
13830
|
if (completion.status === "verification_failed") {
|
|
13106
13831
|
process.stderr.write(this.paint.red(
|
|
13107
|
-
`${this.glyphs.error} verification failed ${this.glyphs.separator} ${completion.detail}${suffix}
|
|
13832
|
+
`${this.glyphs.error} verification failed ${this.glyphs.separator} ${completion.detail}${suffix}${duplicateSuffix}
|
|
13108
13833
|
`
|
|
13109
13834
|
));
|
|
13110
13835
|
return;
|
|
13111
13836
|
}
|
|
13112
13837
|
process.stderr.write(this.paint.yellow(
|
|
13113
|
-
`${this.glyphs.warning} unverified ${this.glyphs.separator} ${completion.detail}
|
|
13838
|
+
`${this.glyphs.warning} unverified ${this.glyphs.separator} ${completion.detail}${duplicateSuffix}
|
|
13114
13839
|
`
|
|
13115
13840
|
));
|
|
13116
13841
|
}
|
|
@@ -13454,7 +14179,7 @@ function graphemes(value) {
|
|
|
13454
14179
|
}
|
|
13455
14180
|
|
|
13456
14181
|
// src/ui/theme.ts
|
|
13457
|
-
import { lstat as lstat20, readdir as readdir8, readFile as
|
|
14182
|
+
import { lstat as lstat20, readdir as readdir8, readFile as readFile18 } from "node:fs/promises";
|
|
13458
14183
|
import { basename as basename10, join as join19, resolve as resolve19 } from "node:path";
|
|
13459
14184
|
import React, { createContext, useContext } from "react";
|
|
13460
14185
|
function defineTheme(seed) {
|
|
@@ -13595,7 +14320,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
13595
14320
|
if (!info.isFile() || info.isSymbolicLink() || info.size > 64e3) {
|
|
13596
14321
|
throw new Error("must be a regular JSON file smaller than 64 KB");
|
|
13597
14322
|
}
|
|
13598
|
-
const parsed = JSON.parse(await
|
|
14323
|
+
const parsed = JSON.parse(await readFile18(path, "utf8"));
|
|
13599
14324
|
const name = themeName(parsed, basename10(entry, ".json"));
|
|
13600
14325
|
if (builtInThemeNames.has(name)) throw new Error(`cannot replace built-in theme \`${name}\``);
|
|
13601
14326
|
themes[name] = defineTheme(themeSeed(parsed, name));
|
|
@@ -13816,10 +14541,10 @@ function Header({ config, askMode, planMode = false, width = 80, glyphMode = "au
|
|
|
13816
14541
|
const separator = ` ${glyphs.separator} `;
|
|
13817
14542
|
const model = sanitizeInlineTerminalText(`${config.model.provider}/${config.model.model}`);
|
|
13818
14543
|
const repository = sanitizeInlineTerminalText(basename11(root) || root);
|
|
13819
|
-
const
|
|
14544
|
+
const minimum2 = `${brand} ${modeLabel}`;
|
|
13820
14545
|
const withRepository = `${brand}${separator}${repository}${separator}${modeLabel}`;
|
|
13821
14546
|
const showRepository = terminalWidth >= 32 && displayWidth(withRepository) <= terminalWidth;
|
|
13822
|
-
const leftWidth = displayWidth(showRepository ? withRepository :
|
|
14547
|
+
const leftWidth = displayWidth(showRepository ? withRepository : minimum2);
|
|
13823
14548
|
const modelSpace = terminalWidth - leftWidth - 2;
|
|
13824
14549
|
const showModel = terminalWidth >= 72 && modelSpace >= 12;
|
|
13825
14550
|
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, height: 1, overflowY: "hidden", children: [
|
|
@@ -14837,7 +15562,7 @@ function safeWidth(width) {
|
|
|
14837
15562
|
}
|
|
14838
15563
|
|
|
14839
15564
|
// src/utils/update-check.ts
|
|
14840
|
-
import { mkdir as mkdir9, readFile as
|
|
15565
|
+
import { mkdir as mkdir9, readFile as readFile19, writeFile as writeFile2 } from "node:fs/promises";
|
|
14841
15566
|
import { join as join20 } from "node:path";
|
|
14842
15567
|
import stripAnsi3 from "strip-ansi";
|
|
14843
15568
|
var PACKAGE_NAME = "@skein-code/cli";
|
|
@@ -14935,7 +15660,7 @@ function noticeIfNewer(latest, current, highlights) {
|
|
|
14935
15660
|
}
|
|
14936
15661
|
async function readUpdateCache(env = process.env) {
|
|
14937
15662
|
try {
|
|
14938
|
-
const raw = await
|
|
15663
|
+
const raw = await readFile19(updateCachePath(env), "utf8");
|
|
14939
15664
|
const parsed = JSON.parse(raw);
|
|
14940
15665
|
if (typeof parsed?.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && parsed.checkedAt >= 0 && (parsed.latest === null || typeof parsed.latest === "string" && parseVersion(parsed.latest))) {
|
|
14941
15666
|
const latest = typeof parsed.latest === "string" ? parsed.latest : null;
|
|
@@ -15535,9 +16260,9 @@ function uniqueNewestFirst(history) {
|
|
|
15535
16260
|
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
15536
16261
|
const entry = history[index];
|
|
15537
16262
|
if (!entry.trim()) continue;
|
|
15538
|
-
const
|
|
15539
|
-
if (seen.has(
|
|
15540
|
-
seen.add(
|
|
16263
|
+
const identity2 = entry.normalize("NFC");
|
|
16264
|
+
if (seen.has(identity2)) continue;
|
|
16265
|
+
seen.add(identity2);
|
|
15541
16266
|
entries.push(entry);
|
|
15542
16267
|
}
|
|
15543
16268
|
return entries;
|
|
@@ -15722,6 +16447,18 @@ function toolMetaSummary(metadata) {
|
|
|
15722
16447
|
parts.push(`reuse ${decision}${status === "unresolved" ? " (incomplete)" : " (warning)"}`);
|
|
15723
16448
|
}
|
|
15724
16449
|
}
|
|
16450
|
+
const duplication = metadata.duplicationAudit;
|
|
16451
|
+
if (duplication && typeof duplication === "object") {
|
|
16452
|
+
const status = duplication.status;
|
|
16453
|
+
const matches = Number(duplication.matches?.length ?? 0);
|
|
16454
|
+
if (status === "warning") parts.push(`duplicates ${matches} (warning)`);
|
|
16455
|
+
else if (status === "unresolved") parts.push("duplicates incomplete");
|
|
16456
|
+
}
|
|
16457
|
+
const suppression = metadata.duplicationSuppression;
|
|
16458
|
+
if (suppression && typeof suppression === "object") {
|
|
16459
|
+
const matchId2 = suppression.matchId;
|
|
16460
|
+
if (typeof matchId2 === "string") parts.push(`duplicate ${matchId2.slice(0, 8)} suppressed`);
|
|
16461
|
+
}
|
|
15725
16462
|
const hooks = metadata.hooks;
|
|
15726
16463
|
if (hooks && typeof hooks === "object") {
|
|
15727
16464
|
const before = Number(hooks.before ?? 0);
|
|
@@ -16181,12 +16918,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
16181
16918
|
refreshSession();
|
|
16182
16919
|
if (event.completion && event.completion.status !== "no_changes") {
|
|
16183
16920
|
const checks = event.completion.checks.map((check) => check.command).join(` ${separator} `);
|
|
16921
|
+
const duplication = event.completion.duplication;
|
|
16922
|
+
const duplicateDetail = duplication ? `${separator} duplication ${duplication.status} (${duplication.warningCount} warning, ${duplication.unresolvedCount} incomplete, ${duplication.suppressedCount} suppressed)` : "";
|
|
16184
16923
|
append({
|
|
16185
16924
|
id: nextId(),
|
|
16186
16925
|
kind: "notice",
|
|
16187
16926
|
wrapWidth: contentWidth,
|
|
16188
16927
|
tone: event.completion.status === "verified" ? "success" : event.completion.status === "unverified" ? "warning" : "error",
|
|
16189
|
-
text: event.completion.status === "verified" ? `Verified${separator}${event.completion.detail}${checks ? `${separator}${checks}` : ""}` : event.completion.status === "verification_failed" ? `Verification failed${separator}${event.completion.detail}${checks ? `${separator}${checks}` : ""}` : `Unverified${separator}${event.completion.detail}`
|
|
16928
|
+
text: event.completion.status === "verified" ? `Verified${separator}${event.completion.detail}${checks ? `${separator}${checks}` : ""}${duplicateDetail}` : event.completion.status === "verification_failed" ? `Verification failed${separator}${event.completion.detail}${checks ? `${separator}${checks}` : ""}${duplicateDetail}` : `Unverified${separator}${event.completion.detail}${duplicateDetail}`
|
|
16190
16929
|
});
|
|
16191
16930
|
}
|
|
16192
16931
|
if (event.reason !== "completed" && event.reason !== "unverified" && event.reason !== "verification_failed") {
|
|
@@ -18208,7 +18947,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
18208
18947
|
import stripAnsi5 from "strip-ansi";
|
|
18209
18948
|
|
|
18210
18949
|
// src/mcp/tool.ts
|
|
18211
|
-
import { createHash as
|
|
18950
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
18212
18951
|
import stripAnsi4 from "strip-ansi";
|
|
18213
18952
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
18214
18953
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
@@ -18216,7 +18955,7 @@ var MAX_RESULT_BYTES = 5 * 1024 * 1024;
|
|
|
18216
18955
|
var MAX_SCHEMA_BYTES = 1e5;
|
|
18217
18956
|
function createMcpToolAdapter(options) {
|
|
18218
18957
|
const { remoteTool } = options;
|
|
18219
|
-
const
|
|
18958
|
+
const inputSchema13 = copyInputSchema(remoteTool.inputSchema);
|
|
18220
18959
|
return {
|
|
18221
18960
|
definition: {
|
|
18222
18961
|
name: options.exposedName,
|
|
@@ -18224,7 +18963,7 @@ function createMcpToolAdapter(options) {
|
|
|
18224
18963
|
// MCP servers are an external trust boundary. Read-only annotations are
|
|
18225
18964
|
// hints from that server and must not lower the local permission level.
|
|
18226
18965
|
category: "network",
|
|
18227
|
-
inputSchema:
|
|
18966
|
+
inputSchema: inputSchema13
|
|
18228
18967
|
},
|
|
18229
18968
|
permissionCategories: () => ["network"],
|
|
18230
18969
|
async execute(arguments_, context) {
|
|
@@ -18372,13 +19111,13 @@ function normalizeToolSegment(value, fallback) {
|
|
|
18372
19111
|
if (!normalized) return fallback;
|
|
18373
19112
|
return /^[a-z]/.test(normalized) ? normalized : `${fallback}_${normalized}`;
|
|
18374
19113
|
}
|
|
18375
|
-
function fitToolName(name,
|
|
19114
|
+
function fitToolName(name, identity2) {
|
|
18376
19115
|
if (name.length <= 64) return name;
|
|
18377
|
-
const suffix = `_${shortHash(
|
|
19116
|
+
const suffix = `_${shortHash(identity2)}`;
|
|
18378
19117
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
18379
19118
|
}
|
|
18380
19119
|
function shortHash(value) {
|
|
18381
|
-
return
|
|
19120
|
+
return createHash17("sha256").update(value).digest("hex").slice(0, 8);
|
|
18382
19121
|
}
|
|
18383
19122
|
function sanitizeOutputText(value) {
|
|
18384
19123
|
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
@@ -18874,12 +19613,12 @@ var McpManager = class {
|
|
|
18874
19613
|
);
|
|
18875
19614
|
for (const remoteTool of remoteTools) {
|
|
18876
19615
|
let exposedName = makeMcpToolName(namespace, remoteTool.name);
|
|
18877
|
-
const
|
|
18878
|
-
if (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName,
|
|
19616
|
+
const identity2 = `${serverName}\0${remoteTool.name}`;
|
|
19617
|
+
if (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName, identity2)) {
|
|
18879
19618
|
exposedName = disambiguateMcpToolName(exposedName, serverName, remoteTool.name);
|
|
18880
19619
|
}
|
|
18881
19620
|
let collision = 0;
|
|
18882
|
-
while (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName,
|
|
19621
|
+
while (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName, identity2)) {
|
|
18883
19622
|
collision += 1;
|
|
18884
19623
|
exposedName = disambiguateMcpToolName(
|
|
18885
19624
|
`${makeMcpToolName(namespace, remoteTool.name)}_${collision}`,
|
|
@@ -18895,7 +19634,7 @@ var McpManager = class {
|
|
|
18895
19634
|
}
|
|
18896
19635
|
return active.client.callTool(params, void 0, options);
|
|
18897
19636
|
};
|
|
18898
|
-
let adapter = this.stableAdapters.get(
|
|
19637
|
+
let adapter = this.stableAdapters.get(identity2);
|
|
18899
19638
|
if (!adapter) {
|
|
18900
19639
|
adapter = createMcpToolAdapter({
|
|
18901
19640
|
serverName,
|
|
@@ -18904,17 +19643,17 @@ var McpManager = class {
|
|
|
18904
19643
|
timeoutMs,
|
|
18905
19644
|
callTool
|
|
18906
19645
|
});
|
|
18907
|
-
this.stableAdapters.set(
|
|
19646
|
+
this.stableAdapters.set(identity2, adapter);
|
|
18908
19647
|
}
|
|
18909
19648
|
result.set(exposedName, adapter);
|
|
18910
19649
|
seen.add(exposedName);
|
|
18911
|
-
this.toolOwners.set(exposedName,
|
|
19650
|
+
this.toolOwners.set(exposedName, identity2);
|
|
18912
19651
|
}
|
|
18913
19652
|
return result;
|
|
18914
19653
|
}
|
|
18915
|
-
isToolNameOwnedByAnother(toolName,
|
|
19654
|
+
isToolNameOwnedByAnother(toolName, identity2) {
|
|
18916
19655
|
const owner = this.toolOwners.get(toolName);
|
|
18917
|
-
return owner !== void 0 && owner !==
|
|
19656
|
+
return owner !== void 0 && owner !== identity2;
|
|
18918
19657
|
}
|
|
18919
19658
|
handleUnexpectedClose(name) {
|
|
18920
19659
|
const connection = this.connections.get(name);
|
|
@@ -19008,9 +19747,9 @@ async function closeTransportQuietly(transport) {
|
|
|
19008
19747
|
}
|
|
19009
19748
|
|
|
19010
19749
|
// src/memory/tools.ts
|
|
19011
|
-
import { z as
|
|
19012
|
-
var scopeSchema =
|
|
19013
|
-
var kindSchema =
|
|
19750
|
+
import { z as z20 } from "zod";
|
|
19751
|
+
var scopeSchema = z20.enum(["user", "workspace", "session", "agent"]);
|
|
19752
|
+
var kindSchema = z20.enum(["semantic", "episodic", "procedural"]);
|
|
19014
19753
|
function createMemoryTools(store) {
|
|
19015
19754
|
return [
|
|
19016
19755
|
{
|
|
@@ -19025,10 +19764,10 @@ function createMemoryTools(store) {
|
|
|
19025
19764
|
}, ["query"])
|
|
19026
19765
|
},
|
|
19027
19766
|
async execute(arguments_, context) {
|
|
19028
|
-
const input2 =
|
|
19029
|
-
query:
|
|
19767
|
+
const input2 = z20.object({
|
|
19768
|
+
query: z20.string().max(4e3),
|
|
19030
19769
|
scope: scopeSchema.optional(),
|
|
19031
|
-
limit:
|
|
19770
|
+
limit: z20.number().int().min(1).max(20).optional()
|
|
19032
19771
|
}).parse(arguments_);
|
|
19033
19772
|
const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
|
|
19034
19773
|
const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
|
|
@@ -19060,17 +19799,17 @@ ${record.content}`
|
|
|
19060
19799
|
}, ["content", "rationale"])
|
|
19061
19800
|
},
|
|
19062
19801
|
async execute(arguments_, context) {
|
|
19063
|
-
const input2 =
|
|
19064
|
-
content:
|
|
19065
|
-
rationale:
|
|
19802
|
+
const input2 = z20.object({
|
|
19803
|
+
content: z20.string().min(1).max(12e3),
|
|
19804
|
+
rationale: z20.string().min(1).max(1e3),
|
|
19066
19805
|
scope: scopeSchema.optional(),
|
|
19067
19806
|
kind: kindSchema.optional(),
|
|
19068
|
-
tags:
|
|
19069
|
-
importance:
|
|
19070
|
-
confidence:
|
|
19071
|
-
agent:
|
|
19072
|
-
revision:
|
|
19073
|
-
conflictKey:
|
|
19807
|
+
tags: z20.array(z20.string()).max(24).optional(),
|
|
19808
|
+
importance: z20.number().min(0).max(1).optional(),
|
|
19809
|
+
confidence: z20.number().min(0).max(1).optional(),
|
|
19810
|
+
agent: z20.string().max(64).optional(),
|
|
19811
|
+
revision: z20.string().max(240).optional(),
|
|
19812
|
+
conflictKey: z20.string().max(240).optional()
|
|
19074
19813
|
}).strict().parse(arguments_);
|
|
19075
19814
|
const scope = input2.scope ?? "workspace";
|
|
19076
19815
|
const candidate = store.propose({
|
|
@@ -19108,9 +19847,9 @@ ${record.content}`
|
|
|
19108
19847
|
}, ["id"])
|
|
19109
19848
|
},
|
|
19110
19849
|
async execute(arguments_) {
|
|
19111
|
-
const input2 =
|
|
19112
|
-
id:
|
|
19113
|
-
permanent:
|
|
19850
|
+
const input2 = z20.object({
|
|
19851
|
+
id: z20.string().uuid(),
|
|
19852
|
+
permanent: z20.boolean().optional()
|
|
19114
19853
|
}).parse(arguments_);
|
|
19115
19854
|
const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
|
|
19116
19855
|
return {
|
|
@@ -19137,7 +19876,7 @@ function scopeKey(scope, context) {
|
|
|
19137
19876
|
}
|
|
19138
19877
|
|
|
19139
19878
|
// src/skills/catalog.ts
|
|
19140
|
-
import { lstat as lstat21, readFile as
|
|
19879
|
+
import { lstat as lstat21, readFile as readFile20, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
19141
19880
|
import { homedir as homedir4 } from "node:os";
|
|
19142
19881
|
import { basename as basename12, join as join21, resolve as resolve21 } from "node:path";
|
|
19143
19882
|
import { parse as parseYaml3 } from "yaml";
|
|
@@ -19269,7 +20008,7 @@ async function safeRead(path, maxBytes) {
|
|
|
19269
20008
|
const resolvedParent = await realpath9(parent);
|
|
19270
20009
|
const resolvedPath = await realpath9(path);
|
|
19271
20010
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
19272
|
-
return await
|
|
20011
|
+
return await readFile20(path, "utf8");
|
|
19273
20012
|
} catch {
|
|
19274
20013
|
return void 0;
|
|
19275
20014
|
}
|
|
@@ -19314,7 +20053,7 @@ function escapeAttribute6(value) {
|
|
|
19314
20053
|
}
|
|
19315
20054
|
|
|
19316
20055
|
// src/workflows/catalog.ts
|
|
19317
|
-
import { z as
|
|
20056
|
+
import { z as z21 } from "zod";
|
|
19318
20057
|
var builtInWorkflows = [
|
|
19319
20058
|
{
|
|
19320
20059
|
name: "implement",
|
|
@@ -19405,7 +20144,7 @@ function createWorkflowTool(catalog) {
|
|
|
19405
20144
|
}, ["name", "task"])
|
|
19406
20145
|
},
|
|
19407
20146
|
async execute(arguments_) {
|
|
19408
|
-
const input2 =
|
|
20147
|
+
const input2 = z21.object({ name: z21.string(), task: z21.string().min(1).max(2e4) }).parse(arguments_);
|
|
19409
20148
|
const workflow = catalog.get(input2.name);
|
|
19410
20149
|
if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
|
|
19411
20150
|
return {
|