@skein-code/cli 0.3.14 → 0.3.15

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