@skein-code/cli 0.3.15 → 0.3.17

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.15",
223
+ version: "0.3.17",
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
+ "Calibrated Type-1/2 duplication matches now block completion until reuse or exact audited suppression",
240
+ "Type-3 duplication remains warning-only and Type-4 semantic equivalence remains explicitly unsupported",
241
+ "Duplication benchmark fixtures report deterministic recall, precision, false-positive rate, and latency",
242
+ "Duplication warnings now aggregate into the single completion receipt without changing verified status",
243
+ "Exact match ids support bounded, reason-coded suppression with audit and credential/code-block safeguards",
244
+ "Duplication audit tools are disclosed only when active warnings exist and repaired paths clear stale matches",
239
245
  "Warning-only TS/JS duplication audit compares new functions with the pre-write index generation",
240
246
  "Deterministic Type-1/2 and Type-3 receipts retain only hashes, locations, scores, and bounded counts",
241
247
  "Ordinary edits, renames, moves, deletions, small functions, tests, generated paths, and failed writes stay quiet",
@@ -271,6 +277,7 @@ var package_default = {
271
277
  "pretest:pty": "npm run build",
272
278
  "test:pty": "sh test/pty/run-visual.sh",
273
279
  "benchmark:context": "tsx scripts/benchmark-local-index.ts",
280
+ "benchmark:duplication": "tsx scripts/benchmark-duplication.ts",
274
281
  "verify:package": "node scripts/verify-package.mjs",
275
282
  "release:verify": "npm run check && npm run verify:package --",
276
283
  typecheck: "tsc --noEmit",
@@ -5671,25 +5678,35 @@ var reuseReceiptSchema = z5.object({
5671
5678
  indexGeneration: z5.string().optional(),
5672
5679
  changeSequence: z5.number().int().nonnegative(),
5673
5680
  status: z5.enum(["warning", "skipped", "unresolved"]),
5674
- warningOnly: z5.literal(true)
5681
+ warningOnly: z5.boolean()
5682
+ }).strict();
5683
+ var duplicationMatchSchema = z5.object({
5684
+ matchId: z5.string().regex(/^[a-f0-9]{24}$/u).optional(),
5685
+ changedPath: z5.string(),
5686
+ changedSymbol: z5.string(),
5687
+ candidatePath: z5.string(),
5688
+ candidateSymbol: z5.string(),
5689
+ kind: z5.enum(["type-1-or-2", "type-3"]),
5690
+ similarity: z5.number().min(0).max(1)
5675
5691
  }).strict();
5676
5692
  var duplicationAuditSchema = z5.object({
5677
5693
  baselineGeneration: z5.string().min(1),
5678
5694
  changeSequence: z5.number().int().nonnegative(),
5679
5695
  status: z5.enum(["clear", "warning", "unresolved"]),
5680
- warningOnly: z5.literal(true),
5696
+ warningOnly: z5.boolean(),
5697
+ enforcement: z5.enum(["warning", "blocking"]).optional(),
5681
5698
  checkedFunctions: z5.number().int().nonnegative(),
5682
5699
  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),
5700
+ matches: z5.array(duplicationMatchSchema).max(8),
5691
5701
  rationale: z5.string().max(500)
5692
5702
  }).strict();
5703
+ var duplicationSuppressionSchema = z5.object({
5704
+ matchId: z5.string().regex(/^[a-f0-9]{24}$/u),
5705
+ reasonCode: z5.enum(["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"]),
5706
+ reason: z5.string().min(12).max(240),
5707
+ createdAt: z5.string().datetime(),
5708
+ toolCallId: z5.string().min(1).max(512)
5709
+ }).strict();
5693
5710
  var auditMetadataSchema = z5.record(z5.string(), z5.unknown()).superRefine((metadata, ctx) => {
5694
5711
  const receipt = metadata.reuseReceipt;
5695
5712
  if (receipt !== void 0 && !reuseReceiptSchema.safeParse(receipt).success) {
@@ -5699,6 +5716,14 @@ var auditMetadataSchema = z5.record(z5.string(), z5.unknown()).superRefine((meta
5699
5716
  if (duplication !== void 0 && !duplicationAuditSchema.safeParse(duplication).success) {
5700
5717
  ctx.addIssue({ code: "custom", message: "Invalid duplication audit receipt" });
5701
5718
  }
5719
+ const suppression = metadata.duplicationSuppression;
5720
+ if (suppression !== void 0 && !duplicationSuppressionSchema.safeParse(suppression).success) {
5721
+ ctx.addIssue({ code: "custom", message: "Invalid duplication suppression receipt" });
5722
+ }
5723
+ const activeMatches = metadata.activeDuplicationMatches;
5724
+ if (activeMatches !== void 0 && !z5.array(z5.string().regex(/^[a-f0-9]{24}$/u)).max(8).safeParse(activeMatches).success) {
5725
+ ctx.addIssue({ code: "custom", message: "Invalid active duplication matches" });
5726
+ }
5702
5727
  });
5703
5728
  var auditSchema = z5.object({
5704
5729
  id: z5.string(),
@@ -5738,6 +5763,16 @@ var lastRunSchema = z5.object({
5738
5763
  checks: z5.array(verificationEvidenceSchema),
5739
5764
  detail: z5.string(),
5740
5765
  mutationTracking: z5.enum(["complete", "unknown"]).optional(),
5766
+ duplication: z5.object({
5767
+ enforcement: z5.enum(["warning", "blocking"]),
5768
+ status: z5.enum(["clear", "warning", "unresolved", "suppressed"]),
5769
+ warningCount: z5.number().int().nonnegative(),
5770
+ unresolvedCount: z5.number().int().nonnegative(),
5771
+ suppressedCount: z5.number().int().nonnegative(),
5772
+ matches: z5.array(duplicationMatchSchema.extend({
5773
+ matchId: z5.string().regex(/^[a-f0-9]{24}$/u)
5774
+ })).max(8)
5775
+ }).strict().optional(),
5741
5776
  acceptance: z5.object({
5742
5777
  state: z5.enum(["draft", "active", "satisfied", "blocked"]),
5743
5778
  total: z5.number().int().nonnegative(),
@@ -5803,6 +5838,7 @@ var sessionSchema = z5.object({
5803
5838
  contextSources: z5.array(contextSourceSchema).max(64).optional(),
5804
5839
  toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
5805
5840
  taskContract: taskContractSchema.optional(),
5841
+ duplicationSuppressions: z5.array(duplicationSuppressionSchema).max(64).optional(),
5806
5842
  lastRun: lastRunSchema.optional(),
5807
5843
  usage: z5.object({
5808
5844
  inputTokens: z5.number().nonnegative(),
@@ -8332,7 +8368,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
8332
8368
  commandKey: createHash11("sha256").update(normalized).digest("hex")
8333
8369
  };
8334
8370
  }
8335
- function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
8371
+ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = [], duplication) {
8336
8372
  const files = [...new Set(changedFiles)];
8337
8373
  const acceptance = taskContract ? buildAcceptance(taskContract, audit, evidence, currentChangeSequence) : void 0;
8338
8374
  if (mutationTracking === "unknown") {
@@ -8342,6 +8378,7 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8342
8378
  checks: [],
8343
8379
  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.",
8344
8380
  mutationTracking,
8381
+ ...duplication ? { duplication } : {},
8345
8382
  ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
8346
8383
  };
8347
8384
  }
@@ -8352,7 +8389,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8352
8389
  changedFiles: [],
8353
8390
  checks: [],
8354
8391
  detail: acceptanceDetail(acceptance),
8355
- acceptance: acceptanceForCompletion(acceptance, "unverified")
8392
+ acceptance: acceptanceForCompletion(acceptance, "unverified"),
8393
+ ...duplication ? { duplication } : {}
8356
8394
  };
8357
8395
  }
8358
8396
  return {
@@ -8360,7 +8398,18 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8360
8398
  changedFiles: [],
8361
8399
  checks: [],
8362
8400
  detail: "No workspace files changed in this run.",
8363
- ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "no_changes") } : {}
8401
+ ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "no_changes") } : {},
8402
+ ...duplication ? { duplication } : {}
8403
+ };
8404
+ }
8405
+ if (duplication?.enforcement === "blocking" && duplication.warningCount > 0) {
8406
+ return {
8407
+ status: "unverified",
8408
+ changedFiles: files,
8409
+ checks: [],
8410
+ detail: "A high-confidence duplicate implementation requires reuse, extension, or an exact audited suppression before completion.",
8411
+ ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {},
8412
+ duplication
8364
8413
  };
8365
8414
  }
8366
8415
  const latestByCommand = /* @__PURE__ */ new Map();
@@ -8376,7 +8425,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8376
8425
  changedFiles: files,
8377
8426
  checks,
8378
8427
  detail: `No successful verification was recorded after the last change to ${fileCount(files.length)}.`,
8379
- ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {}
8428
+ ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "unverified") } : {},
8429
+ ...duplication ? { duplication } : {}
8380
8430
  };
8381
8431
  }
8382
8432
  const failures = checks.filter((check) => !check.ok);
@@ -8386,7 +8436,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8386
8436
  changedFiles: files,
8387
8437
  checks,
8388
8438
  detail: `${failures.length} of ${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} failed.`,
8389
- ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verification_failed") } : {}
8439
+ ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verification_failed") } : {},
8440
+ ...duplication ? { duplication } : {}
8390
8441
  };
8391
8442
  }
8392
8443
  if (acceptance && acceptanceUnresolved(acceptance)) {
@@ -8395,7 +8446,8 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8395
8446
  changedFiles: files,
8396
8447
  checks,
8397
8448
  detail: acceptanceDetail(acceptance),
8398
- acceptance: acceptanceForCompletion(acceptance, "unverified")
8449
+ acceptance: acceptanceForCompletion(acceptance, "unverified"),
8450
+ ...duplication ? { duplication } : {}
8399
8451
  };
8400
8452
  }
8401
8453
  return {
@@ -8403,10 +8455,18 @@ function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutat
8403
8455
  changedFiles: files,
8404
8456
  checks,
8405
8457
  detail: `${checks.length} current verification ${checks.length === 1 ? "check" : "checks"} passed for ${fileCount(files.length)}.`,
8406
- ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verified") } : {}
8458
+ ...acceptance ? { acceptance: acceptanceForCompletion(acceptance, "verified") } : {},
8459
+ ...duplication ? { duplication } : {}
8407
8460
  };
8408
8461
  }
8409
8462
  function completionRecoveryDirective(completion) {
8463
+ if (completion.duplication?.enforcement === "blocking" && completion.duplication.warningCount > 0) {
8464
+ const matches = completion.duplication.matches.filter((match) => match.kind === "type-1-or-2").map((match) => `- ${match.matchId}: ${match.changedPath} ${match.changedSymbol} duplicates ${match.candidatePath} ${match.candidateSymbol}`).join("\n");
8465
+ return `<runtime-completion-gate status="duplication_blocking" authorization="none">
8466
+ High-confidence Type-1/2 duplication was detected. Reuse or extend the existing implementation, remove the duplicate, or call the read-only duplication_audit tool to suppress an exact match with an audited reason. Suppression never waives verification, safety, accessibility, error handling, concurrency, or correctness requirements.
8467
+ ${matches || "- Active duplicate match details are unavailable."}
8468
+ </runtime-completion-gate>`;
8469
+ }
8410
8470
  if (completion.acceptance && acceptanceUnresolved(completion.acceptance)) {
8411
8471
  const unresolved2 = completion.acceptance.unresolved.map((item) => `- [${item.status}] ${item.id}: ${item.description}`).join("\n");
8412
8472
  const failed = completion.checks.filter((check) => !check.ok).map((check) => `- ${check.command} (tool call ${check.toolCallId})`).join("\n");
@@ -8815,6 +8875,140 @@ function render(memory) {
8815
8875
  ].join("\n");
8816
8876
  }
8817
8877
 
8878
+ // src/tools/duplication.ts
8879
+ import { z as z17 } from "zod";
8880
+
8881
+ // src/agent/duplication-state.ts
8882
+ function buildDuplicationCompletion(audit, suppressions = [], runStartedAt) {
8883
+ const active = /* @__PURE__ */ new Map();
8884
+ const unresolved2 = /* @__PURE__ */ new Map();
8885
+ let sawCurrentReceipt = false;
8886
+ for (const event of audit) {
8887
+ if (runStartedAt && event.createdAt < runStartedAt) continue;
8888
+ const receipt = duplicationReceipt(event.metadata?.duplicationAudit);
8889
+ if (!receipt) continue;
8890
+ if (receipt.status === "clear" || receipt.status === "unresolved" || receipt.matches.some((match) => typeof match.matchId === "string")) sawCurrentReceipt = true;
8891
+ const changedPaths2 = changedPathsFor(event, receipt);
8892
+ for (const path of changedPaths2) {
8893
+ for (const [id, match] of active) if (match.changedPath === path) active.delete(id);
8894
+ unresolved2.delete(path);
8895
+ }
8896
+ if (receipt.status === "unresolved") {
8897
+ for (const path of changedPaths2) unresolved2.set(path, receipt.changeSequence);
8898
+ }
8899
+ for (const match of receipt.matches) {
8900
+ if (typeof match.matchId === "string") {
8901
+ active.set(match.matchId, match);
8902
+ }
8903
+ }
8904
+ }
8905
+ if (!active.size && !unresolved2.size && !sawCurrentReceipt) return void 0;
8906
+ const activeIds = new Set(active.keys());
8907
+ const suppressedIds = new Set(suppressions.filter((item) => activeIds.has(item.matchId)).map((item) => item.matchId));
8908
+ const warnings = [...active.values()].filter((match) => !suppressedIds.has(match.matchId));
8909
+ const suppressedCount = active.size - warnings.length;
8910
+ const enforcement = warnings.some((match) => match.kind === "type-1-or-2") ? "blocking" : "warning";
8911
+ return {
8912
+ enforcement,
8913
+ status: unresolved2.size ? "unresolved" : warnings.length ? "warning" : suppressedCount ? "suppressed" : "clear",
8914
+ warningCount: warnings.length,
8915
+ unresolvedCount: unresolved2.size,
8916
+ suppressedCount,
8917
+ matches: warnings.slice(0, 8)
8918
+ };
8919
+ }
8920
+ function activeDuplicationMatchIds(audit, suppressions = []) {
8921
+ const completion = buildDuplicationCompletion(audit, suppressions);
8922
+ return new Set(completion?.matches.map((match) => match.matchId) ?? []);
8923
+ }
8924
+ function pruneDuplicationSuppressions(audit, suppressions = []) {
8925
+ if (!suppressions.length) return [];
8926
+ const active = new Set(buildDuplicationCompletion(audit, [])?.matches.map((match) => match.matchId) ?? []);
8927
+ return suppressions.filter((item) => active.has(item.matchId)).slice(-64);
8928
+ }
8929
+ function findActiveDuplicationMatches(audit, suppressions = []) {
8930
+ return buildDuplicationCompletion(audit, suppressions)?.matches ?? [];
8931
+ }
8932
+ function hasDuplicationActivity(audit, runStartedAt) {
8933
+ return audit.some(
8934
+ (event) => (!runStartedAt || event.createdAt >= runStartedAt) && (duplicationReceipt(event.metadata?.duplicationAudit) !== void 0 || event.metadata?.duplicationSuppression !== void 0 || event.metadata?.activeDuplicationMatches !== void 0)
8935
+ );
8936
+ }
8937
+ function duplicationReceipt(value) {
8938
+ if (!value || typeof value !== "object") return void 0;
8939
+ const receipt = value;
8940
+ if (!Array.isArray(receipt.matches) || typeof receipt.changeSequence !== "number" || !["clear", "warning", "unresolved"].includes(String(receipt.status))) return void 0;
8941
+ return receipt;
8942
+ }
8943
+ function changedPathsFor(event, receipt) {
8944
+ const metadataPaths = event.metadata?.changedFiles;
8945
+ const fromMetadata = Array.isArray(metadataPaths) ? metadataPaths.filter((path) => typeof path === "string") : [];
8946
+ return [.../* @__PURE__ */ new Set([...fromMetadata, ...receipt.matches.map((match) => match.changedPath)])];
8947
+ }
8948
+
8949
+ // src/tools/duplication.ts
8950
+ var inputSchema12 = z17.discriminatedUnion("action", [
8951
+ z17.object({ action: z17.literal("show") }).strict(),
8952
+ z17.object({
8953
+ action: z17.literal("suppress"),
8954
+ match_id: z17.string().regex(/^[a-f0-9]{24}$/u),
8955
+ reason_code: z17.enum(["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"]),
8956
+ reason: z17.string().trim().min(12).max(240)
8957
+ }).strict()
8958
+ ]);
8959
+ var duplicationTool = {
8960
+ definition: {
8961
+ name: "duplication_audit",
8962
+ 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.",
8963
+ category: "read",
8964
+ inputSchema: jsonSchema({
8965
+ action: { type: "string", enum: ["show", "suppress"] },
8966
+ match_id: { type: "string", description: "Exact 24-character match id from the current audit." },
8967
+ reason_code: { type: "string", enum: ["separate-boundary", "protocol-required", "generated-contract", "false-positive", "other"] },
8968
+ reason: { type: "string", description: "Specific 12-240 character explanation; code blocks and credentials are rejected." }
8969
+ }, ["action"])
8970
+ },
8971
+ async execute(arguments_, context) {
8972
+ const input2 = inputSchema12.parse(arguments_);
8973
+ const suppressions = pruneDuplicationSuppressions(
8974
+ context.session.audit ?? [],
8975
+ context.session.duplicationSuppressions ?? []
8976
+ );
8977
+ context.session.duplicationSuppressions = suppressions;
8978
+ const active = activeDuplicationMatchIds(context.session.audit ?? [], suppressions);
8979
+ if (input2.action === "suppress") {
8980
+ if (!active.has(input2.match_id)) {
8981
+ throw new Error(`Unknown, stale, or already suppressed duplication match: ${input2.match_id}`);
8982
+ }
8983
+ suppressions.push({
8984
+ matchId: input2.match_id,
8985
+ reasonCode: input2.reason_code,
8986
+ reason: cleanReason(input2.reason),
8987
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8988
+ toolCallId: context.toolCallId ?? "unavailable"
8989
+ });
8990
+ context.session.duplicationSuppressions = suppressions.slice(-64);
8991
+ return {
8992
+ content: `Suppressed duplication match ${input2.match_id}. This does not waive verification or safety requirements.`,
8993
+ metadata: { duplicationSuppression: context.session.duplicationSuppressions.at(-1) }
8994
+ };
8995
+ }
8996
+ const matches = findActiveDuplicationMatches(context.session.audit ?? [], suppressions);
8997
+ return {
8998
+ content: matches.length ? matches.map(
8999
+ (match) => `- ${match.matchId}: ${match.changedPath}#${match.changedSymbol} -> ${match.candidatePath}#${match.candidateSymbol} (${match.kind}, ${match.similarity.toFixed(3)})`
9000
+ ).join("\n") : "No unsuppressed duplication matches.",
9001
+ metadata: { activeDuplicationMatches: matches.map((match) => match.matchId) }
9002
+ };
9003
+ }
9004
+ };
9005
+ function cleanReason(value) {
9006
+ 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)) {
9007
+ throw new Error("Suppression reasons cannot contain code blocks or credentials.");
9008
+ }
9009
+ return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
9010
+ }
9011
+
8818
9012
  // src/tools/index.ts
8819
9013
  function createDefaultToolRegistry(_options = {}) {
8820
9014
  return new ToolRegistry([
@@ -8828,6 +9022,7 @@ function createDefaultToolRegistry(_options = {}) {
8828
9022
  gitTool,
8829
9023
  taskTool,
8830
9024
  taskContractTool,
9025
+ duplicationTool,
8831
9026
  workingMemoryTool
8832
9027
  ]);
8833
9028
  }
@@ -9633,10 +9828,15 @@ function roundScore(value) {
9633
9828
  }
9634
9829
 
9635
9830
  // src/agent/duplication-audit.ts
9831
+ import { createHash as createHash14 } from "node:crypto";
9636
9832
  import { readFile as readFile15 } from "node:fs/promises";
9637
- var NEAR_CLONE_THRESHOLD = 0.55;
9833
+ var DEFAULT_NEAR_CLONE_THRESHOLD = 0.55;
9638
9834
  var MAX_MATCHES = 8;
9639
9835
  async function auditChangedFunctions(input2) {
9836
+ const nearCloneThreshold = input2.nearCloneThreshold ?? DEFAULT_NEAR_CLONE_THRESHOLD;
9837
+ if (!Number.isFinite(nearCloneThreshold) || nearCloneThreshold < 0 || nearCloneThreshold > 1) {
9838
+ throw new Error("nearCloneThreshold must be a finite number between 0 and 1.");
9839
+ }
9640
9840
  const auditableFiles = input2.changedFiles.filter(supportsFunctionFingerprintPath);
9641
9841
  if (!auditableFiles.length) return void 0;
9642
9842
  const reports = [];
@@ -9658,13 +9858,16 @@ async function auditChangedFunctions(input2) {
9658
9858
  return unresolved(input2.changeSequence, input2.baseline?.generation);
9659
9859
  }
9660
9860
  const currentFunctions = reports.flatMap((report) => report.functions.map(withoutTokens));
9661
- if (!currentFunctions.length) return void 0;
9861
+ if (!currentFunctions.length) {
9862
+ return auditableFiles.some((path) => input2.recheckPaths?.has(path)) && input2.baseline ? clear(input2.baseline.generation, input2.changeSequence, skippedSmallFunctions) : void 0;
9863
+ }
9662
9864
  if (!input2.baseline) return unresolved(input2.changeSequence);
9663
9865
  const lookup = createBaselineLookup(input2.baseline.functions);
9664
- const previousByCurrent = matchCurrentFunctions(input2.baseline.functions, currentFunctions);
9866
+ const previousByCurrent = matchCurrentFunctions(input2.baseline.functions, currentFunctions, nearCloneThreshold);
9665
9867
  const added = currentFunctions.flatMap((current) => {
9666
9868
  const previous = previousByCurrent.get(locationIdentity(current));
9667
- return !previous || current.tokenCount >= previous.tokenCount * 1.5 ? [{ current, previous }] : [];
9869
+ const recheck = input2.recheckFunctions?.has(identity(current)) || previous !== void 0 && input2.recheckFunctions?.has(identity(previous));
9870
+ return !previous || current.tokenCount >= previous.tokenCount * 1.5 || recheck ? [{ current, previous }] : [];
9668
9871
  });
9669
9872
  if (!added.length) return void 0;
9670
9873
  const matches = [];
@@ -9673,11 +9876,12 @@ async function auditChangedFunctions(input2) {
9673
9876
  for (const candidate of findCandidateFunctions(lookup, item)) {
9674
9877
  if (previous && locationIdentity(candidate) === locationIdentity(previous)) continue;
9675
9878
  const similarity = fingerprintSimilarity(item, candidate);
9676
- if (similarity < NEAR_CLONE_THRESHOLD || best && best.similarity >= similarity) continue;
9879
+ if (similarity < nearCloneThreshold || best && best.similarity >= similarity) continue;
9677
9880
  best = { candidate, similarity };
9678
9881
  }
9679
9882
  if (!best) continue;
9680
9883
  matches.push({
9884
+ matchId: matchId(input2.baseline.generation, input2.changeSequence, item, best.candidate, best.similarity),
9681
9885
  changedPath: item.path,
9682
9886
  changedSymbol: item.symbol,
9683
9887
  candidatePath: best.candidate.path,
@@ -9688,17 +9892,32 @@ async function auditChangedFunctions(input2) {
9688
9892
  }
9689
9893
  matches.sort((left, right) => right.similarity - left.similarity || left.changedPath.localeCompare(right.changedPath));
9690
9894
  const bounded = matches.slice(0, MAX_MATCHES);
9895
+ const enforcement = bounded.some((match) => match.kind === "type-1-or-2") ? "blocking" : "warning";
9691
9896
  return {
9692
9897
  baselineGeneration: input2.baseline.generation,
9693
9898
  changeSequence: input2.changeSequence,
9694
9899
  status: bounded.length ? "warning" : "clear",
9695
- warningOnly: true,
9900
+ warningOnly: enforcement === "warning",
9901
+ enforcement,
9696
9902
  checkedFunctions: added.length,
9697
9903
  skippedSmallFunctions,
9698
9904
  matches: bounded,
9699
9905
  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
9906
  };
9701
9907
  }
9908
+ function clear(baselineGeneration, changeSequence, skippedSmallFunctions) {
9909
+ return {
9910
+ baselineGeneration,
9911
+ changeSequence,
9912
+ status: "clear",
9913
+ warningOnly: true,
9914
+ enforcement: "warning",
9915
+ checkedFunctions: 0,
9916
+ skippedSmallFunctions,
9917
+ matches: [],
9918
+ rationale: "No active deterministic duplicate candidate remains on the rechecked path."
9919
+ };
9920
+ }
9702
9921
  function withoutTokens(value) {
9703
9922
  const { normalizedTokens: _tokens, ...fingerprint } = value;
9704
9923
  return fingerprint;
@@ -9742,15 +9961,15 @@ function findCandidateFunctions(lookup, current) {
9742
9961
  }
9743
9962
  return [...candidates.values()];
9744
9963
  }
9745
- function matchCurrentFunctions(baseline, current) {
9964
+ function matchCurrentFunctions(baseline, current, nearCloneThreshold) {
9746
9965
  const matches = /* @__PURE__ */ new Map();
9747
9966
  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);
9967
+ assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item) && linesOverlap(candidate, item)), false, nearCloneThreshold);
9968
+ assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => identity(candidate) === identity(item)), true, nearCloneThreshold);
9969
+ assignMatches(current, matches, available, (item) => [...available.values()].filter((candidate) => candidate.path === item.path && linesOverlap(candidate, item)), true, nearCloneThreshold);
9751
9970
  return matches;
9752
9971
  }
9753
- function assignMatches(current, matches, available, candidatesFor, requireSimilarity) {
9972
+ function assignMatches(current, matches, available, candidatesFor, requireSimilarity, nearCloneThreshold) {
9754
9973
  for (const item of current) {
9755
9974
  const currentId = locationIdentity(item);
9756
9975
  if (matches.has(currentId)) continue;
@@ -9759,7 +9978,7 @@ function assignMatches(current, matches, available, candidatesFor, requireSimila
9759
9978
  const similarity = fingerprintSimilarity(candidate, item);
9760
9979
  if (!best || similarity > best.similarity) best = { candidate, similarity };
9761
9980
  }
9762
- if (!best || requireSimilarity && best.similarity < NEAR_CLONE_THRESHOLD) continue;
9981
+ if (!best || requireSimilarity && best.similarity < nearCloneThreshold) continue;
9763
9982
  matches.set(currentId, best.candidate);
9764
9983
  available.delete(locationIdentity(best.candidate));
9765
9984
  }
@@ -9773,6 +9992,7 @@ function unresolved(changeSequence, generation = "unavailable") {
9773
9992
  changeSequence,
9774
9993
  status: "unresolved",
9775
9994
  warningOnly: true,
9995
+ enforcement: "warning",
9776
9996
  checkedFunctions: 0,
9777
9997
  skippedSmallFunctions: 0,
9778
9998
  matches: [],
@@ -9782,6 +10002,17 @@ function unresolved(changeSequence, generation = "unavailable") {
9782
10002
  function round(value) {
9783
10003
  return Math.round(value * 1e3) / 1e3;
9784
10004
  }
10005
+ function matchId(generation, changeSequence, changed, candidate, similarity) {
10006
+ return createHash14("sha256").update([
10007
+ generation,
10008
+ String(changeSequence),
10009
+ changed.path,
10010
+ changed.symbol,
10011
+ candidate.path,
10012
+ candidate.symbol,
10013
+ similarity === 1 ? "type-1-or-2" : "type-3"
10014
+ ].join("\0")).digest("hex").slice(0, 24);
10015
+ }
9785
10016
 
9786
10017
  // src/agent/runner.ts
9787
10018
  var AgentRunner = class {
@@ -9853,6 +10084,7 @@ var AgentRunner = class {
9853
10084
  await options.onEvent?.(event);
9854
10085
  };
9855
10086
  const changeSequenceAtStart = this.changeSequence;
10087
+ const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
9856
10088
  const runChangedFiles = /* @__PURE__ */ new Set();
9857
10089
  const verificationEvidence = [];
9858
10090
  const toolRecovery = new ToolRecoveryController();
@@ -9882,7 +10114,11 @@ var AgentRunner = class {
9882
10114
  this.changeSequence,
9883
10115
  mutationTracking,
9884
10116
  activeRunContract,
9885
- this.session.audit ?? []
10117
+ this.session.audit ?? [],
10118
+ hasDuplicationActivity(this.session.audit ?? [], runStartedAt) ? buildDuplicationCompletion(
10119
+ this.session.audit ?? [],
10120
+ this.session.duplicationSuppressions ?? []
10121
+ ) : void 0
9886
10122
  );
9887
10123
  if (completion.acceptance && activeRunContract && activeRunContract.state !== "draft") {
9888
10124
  activeRunContract.state = completion.acceptance.state;
@@ -10008,7 +10244,11 @@ var AgentRunner = class {
10008
10244
  this.tools,
10009
10245
  options.askMode === true,
10010
10246
  contractEnabled,
10011
- this.hasReadableToolArtifact()
10247
+ this.hasReadableToolArtifact(),
10248
+ activeDuplicationMatchIds(
10249
+ this.session.audit ?? [],
10250
+ this.session.duplicationSuppressions ?? []
10251
+ ).size > 0
10012
10252
  );
10013
10253
  const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
10014
10254
  if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
@@ -10293,7 +10533,8 @@ ${input2}`
10293
10533
  contextEngine: this.contextEngine,
10294
10534
  toolArtifactStore: this.toolArtifactStore,
10295
10535
  emit,
10296
- ...options.signal ? { signal: options.signal } : {}
10536
+ ...options.signal ? { signal: options.signal } : {},
10537
+ toolCallId: call.id
10297
10538
  };
10298
10539
  try {
10299
10540
  let reuseReceipt;
@@ -10343,10 +10584,21 @@ ${input2}`
10343
10584
  throwIfAborted(options.signal);
10344
10585
  const execution = await tool.execute(call.arguments, toolExecutionContext);
10345
10586
  const changedFiles = await this.acceptChangedFiles(execution.changedFiles ?? []);
10587
+ const activeDuplicateFunctions = new Set(
10588
+ buildDuplicationCompletion(
10589
+ this.session.audit ?? [],
10590
+ []
10591
+ )?.matches.map((match) => `${match.changedPath}\0${match.changedSymbol}`) ?? []
10592
+ );
10593
+ const activeDuplicatePaths = new Set(
10594
+ buildDuplicationCompletion(this.session.audit ?? [], [])?.matches.map((match) => match.changedPath) ?? []
10595
+ );
10346
10596
  const duplicationAudit = duplicationAuditEnabled ? await auditChangedFunctions({
10347
10597
  ...duplicationBaseline ? { baseline: duplicationBaseline } : {},
10348
10598
  changedFiles,
10349
- changeSequence: this.changeSequence
10599
+ changeSequence: this.changeSequence,
10600
+ recheckFunctions: activeDuplicateFunctions,
10601
+ recheckPaths: activeDuplicatePaths
10350
10602
  }) : void 0;
10351
10603
  const tasksBefore = JSON.stringify(this.session.tasks);
10352
10604
  let afterHookError;
@@ -10367,7 +10619,8 @@ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : executio
10367
10619
 
10368
10620
  ${completeContent}`;
10369
10621
  if (duplicationAudit?.status === "warning" || duplicationAudit?.status === "unresolved") {
10370
- completeContent = `Duplication audit (warning-only): ${duplicationAudit.rationale}
10622
+ const enforcement = duplicationAudit.enforcement === "blocking" ? "completion-blocking Type-1/2" : "warning-only";
10623
+ completeContent = `Duplication audit (${enforcement}): ${duplicationAudit.rationale}
10371
10624
 
10372
10625
  ${completeContent}`;
10373
10626
  }
@@ -10826,9 +11079,9 @@ ${content}` : content,
10826
11079
  ...failure ? { metadata: { failure } } : {}
10827
11080
  };
10828
11081
  }
10829
- function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable) {
11082
+ function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable) {
10830
11083
  return tools.definitions().filter(
10831
- (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact")
11084
+ (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
10832
11085
  );
10833
11086
  }
10834
11087
  function formatToolError(error) {
@@ -11020,7 +11273,7 @@ function integer(value, fallback, min, max) {
11020
11273
  // src/agent/delegation.ts
11021
11274
  import { randomUUID as randomUUID14 } from "node:crypto";
11022
11275
  import { join as join18 } from "node:path";
11023
- import { z as z18 } from "zod";
11276
+ import { z as z19 } from "zod";
11024
11277
 
11025
11278
  // src/agent/external-runtime.ts
11026
11279
  async function runExternalAgent(request) {
@@ -11147,86 +11400,86 @@ function numeric(...values) {
11147
11400
  }
11148
11401
 
11149
11402
  // src/agent/team-store.ts
11150
- import { createHash as createHash14, randomUUID as randomUUID13 } from "node:crypto";
11403
+ import { createHash as createHash15, randomUUID as randomUUID13 } from "node:crypto";
11151
11404
  import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
11152
11405
  import { join as join16, resolve as resolve16 } from "node:path";
11153
- import { z as z17 } from "zod";
11154
- var runIdSchema = z17.string().uuid();
11155
- var hashSchema = z17.string().regex(/^[a-f0-9]{64}$/u);
11156
- var artifactSchema2 = z17.object({
11406
+ import { z as z18 } from "zod";
11407
+ var runIdSchema = z18.string().uuid();
11408
+ var hashSchema = z18.string().regex(/^[a-f0-9]{64}$/u);
11409
+ var artifactSchema2 = z18.object({
11157
11410
  sha256: hashSchema,
11158
- bytes: z17.number().int().nonnegative().max(5e5)
11411
+ bytes: z18.number().int().nonnegative().max(5e5)
11159
11412
  }).strict();
11160
- var phaseSchema = z17.enum(["work", "review", "revision", "write"]);
11161
- var agentRecordSchema = z17.object({
11162
- id: z17.string().uuid(),
11163
- profile: z17.string(),
11164
- provider: z17.string(),
11165
- model: z17.string(),
11413
+ var phaseSchema = z18.enum(["work", "review", "revision", "write"]);
11414
+ var agentRecordSchema = z18.object({
11415
+ id: z18.string().uuid(),
11416
+ profile: z18.string(),
11417
+ provider: z18.string(),
11418
+ model: z18.string(),
11166
11419
  phase: phaseSchema,
11167
- ok: z17.boolean(),
11168
- createdAt: z17.string(),
11169
- startedAt: z17.string().optional(),
11170
- endedAt: z17.string().optional(),
11171
- durationMs: z17.number().int().nonnegative().optional(),
11172
- toolCalls: z17.number().int().nonnegative().optional(),
11173
- usage: z17.object({
11174
- inputTokens: z17.number().int().nonnegative(),
11175
- outputTokens: z17.number().int().nonnegative()
11420
+ ok: z18.boolean(),
11421
+ createdAt: z18.string(),
11422
+ startedAt: z18.string().optional(),
11423
+ endedAt: z18.string().optional(),
11424
+ durationMs: z18.number().int().nonnegative().optional(),
11425
+ toolCalls: z18.number().int().nonnegative().optional(),
11426
+ usage: z18.object({
11427
+ inputTokens: z18.number().int().nonnegative(),
11428
+ outputTokens: z18.number().int().nonnegative()
11176
11429
  }).strict().optional(),
11177
11430
  report: artifactSchema2
11178
11431
  }).strict();
11179
- var messageRecordSchema = z17.object({
11180
- id: z17.string().uuid(),
11181
- from: z17.string(),
11182
- to: z17.string(),
11183
- createdAt: z17.string(),
11432
+ var messageRecordSchema = z18.object({
11433
+ id: z18.string().uuid(),
11434
+ from: z18.string(),
11435
+ to: z18.string(),
11436
+ createdAt: z18.string(),
11184
11437
  content: artifactSchema2
11185
11438
  }).strict();
11186
- var writerIntegrationSchema = z17.object({
11187
- status: z17.enum(["ready", "conflict", "integrated"]),
11188
- checkedAt: z17.string(),
11189
- detail: z17.string().max(2e4),
11190
- checkpoint: z17.object({
11191
- sessionId: z17.string(),
11192
- checkpointId: z17.string()
11439
+ var writerIntegrationSchema = z18.object({
11440
+ status: z18.enum(["ready", "conflict", "integrated"]),
11441
+ checkedAt: z18.string(),
11442
+ detail: z18.string().max(2e4),
11443
+ checkpoint: z18.object({
11444
+ sessionId: z18.string(),
11445
+ checkpointId: z18.string()
11193
11446
  }).strict().optional(),
11194
- integratedAt: z17.string().optional()
11447
+ integratedAt: z18.string().optional()
11195
11448
  }).strict();
11196
- var writerLaneSchema = z17.object({
11197
- profile: z17.string(),
11198
- reviewer: z17.string(),
11199
- baseCommit: z17.string().regex(/^[a-f0-9]{40,64}$/u),
11200
- outcome: z17.enum(["accepted", "rejected", "failed", "cancelled"]),
11449
+ var writerLaneSchema = z18.object({
11450
+ profile: z18.string(),
11451
+ reviewer: z18.string(),
11452
+ baseCommit: z18.string().regex(/^[a-f0-9]{40,64}$/u),
11453
+ outcome: z18.enum(["accepted", "rejected", "failed", "cancelled"]),
11201
11454
  patch: artifactSchema2,
11202
- files: z17.array(z17.string().min(1).max(4e3)).max(2e3),
11203
- worktreeCleaned: z17.boolean(),
11455
+ files: z18.array(z18.string().min(1).max(4e3)).max(2e3),
11456
+ worktreeCleaned: z18.boolean(),
11204
11457
  review: artifactSchema2.optional(),
11205
11458
  integration: writerIntegrationSchema.optional()
11206
11459
  }).strict();
11207
11460
  var manifestFields = {
11208
11461
  id: runIdSchema,
11209
- workspace: z17.string(),
11210
- objective: z17.string().max(3e4),
11211
- reviewer: z17.string(),
11212
- createdAt: z17.string(),
11213
- updatedAt: z17.string(),
11214
- status: z17.enum(["running", "accepted", "rejected", "failed"]),
11215
- maxReviewRounds: z17.number().int().min(0).max(3),
11216
- reviewRounds: z17.number().int().min(0).max(3),
11217
- agents: z17.array(agentRecordSchema).max(256),
11218
- messages: z17.array(messageRecordSchema).max(512)
11462
+ workspace: z18.string(),
11463
+ objective: z18.string().max(3e4),
11464
+ reviewer: z18.string(),
11465
+ createdAt: z18.string(),
11466
+ updatedAt: z18.string(),
11467
+ status: z18.enum(["running", "accepted", "rejected", "failed"]),
11468
+ maxReviewRounds: z18.number().int().min(0).max(3),
11469
+ reviewRounds: z18.number().int().min(0).max(3),
11470
+ agents: z18.array(agentRecordSchema).max(256),
11471
+ messages: z18.array(messageRecordSchema).max(512)
11219
11472
  };
11220
- var manifestV1Schema = z17.object({
11221
- version: z17.literal(1),
11473
+ var manifestV1Schema = z18.object({
11474
+ version: z18.literal(1),
11222
11475
  ...manifestFields
11223
11476
  }).strict();
11224
- var manifestV2Schema = z17.object({
11225
- version: z17.literal(2),
11477
+ var manifestV2Schema = z18.object({
11478
+ version: z18.literal(2),
11226
11479
  ...manifestFields,
11227
11480
  writer: writerLaneSchema.optional()
11228
11481
  }).strict();
11229
- var manifestSchema2 = z17.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
11482
+ var manifestSchema2 = z18.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
11230
11483
  var TeamRunStore = class {
11231
11484
  workspace;
11232
11485
  directory;
@@ -11405,7 +11658,7 @@ var TeamRunStore = class {
11405
11658
  async writeArtifact(runId, content, truncate = true) {
11406
11659
  const data = boundedArtifactText(content, 5e5, truncate);
11407
11660
  const bytes = Buffer.byteLength(data);
11408
- const sha256 = createHash14("sha256").update(data).digest("hex");
11661
+ const sha256 = createHash15("sha256").update(data).digest("hex");
11409
11662
  const directory = join16(this.runDirectory(runId), "blobs");
11410
11663
  await ensureWorkspaceStorageDirectory(this.workspace, directory, {
11411
11664
  requireActiveNamespace: this.managedDirectory
@@ -11414,7 +11667,7 @@ var TeamRunStore = class {
11414
11667
  try {
11415
11668
  await this.assertRegularFile(path);
11416
11669
  const existing = await readFile17(path);
11417
- if (createHash14("sha256").update(existing).digest("hex") !== sha256) {
11670
+ if (createHash15("sha256").update(existing).digest("hex") !== sha256) {
11418
11671
  throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
11419
11672
  }
11420
11673
  } catch (error) {
@@ -11435,7 +11688,7 @@ var TeamRunStore = class {
11435
11688
  const path = this.artifactPath(runId, artifact.sha256);
11436
11689
  await this.assertRegularFile(path);
11437
11690
  const data = await readFile17(path);
11438
- const hash4 = createHash14("sha256").update(data).digest("hex");
11691
+ const hash4 = createHash15("sha256").update(data).digest("hex");
11439
11692
  if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
11440
11693
  throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
11441
11694
  }
@@ -11498,7 +11751,7 @@ function resolveAgentModelRoute(team, parent, profile) {
11498
11751
  }
11499
11752
 
11500
11753
  // src/agent/writer-lane.ts
11501
- import { createHash as createHash15 } from "node:crypto";
11754
+ import { createHash as createHash16 } from "node:crypto";
11502
11755
  import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
11503
11756
  import { tmpdir as tmpdir2 } from "node:os";
11504
11757
  import { isAbsolute as isAbsolute6, join as join17, resolve as resolve17 } from "node:path";
@@ -11596,7 +11849,7 @@ var WriterLane = class {
11596
11849
  return {
11597
11850
  baseCommit,
11598
11851
  patch,
11599
- patchSha256: createHash15("sha256").update(patch).digest("hex"),
11852
+ patchSha256: createHash16("sha256").update(patch).digest("hex"),
11600
11853
  files,
11601
11854
  worktreeCleaned,
11602
11855
  value
@@ -11826,14 +12079,14 @@ async function pathExists2(path) {
11826
12079
  }
11827
12080
 
11828
12081
  // src/agent/delegation.ts
11829
- var writerRunInputSchema = z18.object({
11830
- task: z18.string().min(1).max(2e4),
11831
- profile: z18.string().max(64).optional(),
11832
- reviewer: z18.string().max(64).optional()
12082
+ var writerRunInputSchema = z19.object({
12083
+ task: z19.string().min(1).max(2e4),
12084
+ profile: z19.string().max(64).optional(),
12085
+ reviewer: z19.string().max(64).optional()
11833
12086
  }).strict();
11834
- var writerIntegrateInputSchema = z18.object({
11835
- run_id: z18.string().uuid(),
11836
- patch_sha256: z18.string().regex(/^[a-f0-9]{64}$/u)
12087
+ var writerIntegrateInputSchema = z19.object({
12088
+ run_id: z19.string().uuid(),
12089
+ patch_sha256: z19.string().regex(/^[a-f0-9]{64}$/u)
11837
12090
  }).strict();
11838
12091
  var DelegationManager = class {
11839
12092
  constructor(options) {
@@ -11894,10 +12147,10 @@ var DelegationManager = class {
11894
12147
  },
11895
12148
  async execute(arguments_, context) {
11896
12149
  if (!manager.team.enabled) return { ok: false, content: "Multi-agent delegation is disabled." };
11897
- const input2 = z18.object({
11898
- tasks: z18.array(z18.object({
11899
- profile: z18.string().max(64).optional(),
11900
- task: z18.string().min(1).max(2e4)
12150
+ const input2 = z19.object({
12151
+ tasks: z19.array(z19.object({
12152
+ profile: z19.string().max(64).optional(),
12153
+ task: z19.string().min(1).max(2e4)
11901
12154
  })).min(1).max(manager.team.maxDelegations)
11902
12155
  }).parse(arguments_);
11903
12156
  const tasks = input2.tasks.map((task) => ({
@@ -11943,13 +12196,13 @@ var DelegationManager = class {
11943
12196
  },
11944
12197
  async execute(arguments_, context) {
11945
12198
  if (!manager.team.enabled) return { ok: false, content: "Multi-agent teams are disabled." };
11946
- const input2 = z18.object({
11947
- objective: z18.string().min(1).max(3e4),
11948
- tasks: z18.array(z18.object({
11949
- profile: z18.string().max(64).optional(),
11950
- task: z18.string().min(1).max(2e4)
12199
+ const input2 = z19.object({
12200
+ objective: z19.string().min(1).max(3e4),
12201
+ tasks: z19.array(z19.object({
12202
+ profile: z19.string().max(64).optional(),
12203
+ task: z19.string().min(1).max(2e4)
11951
12204
  })).min(1).max(manager.team.maxDelegations),
11952
- reviewer: z18.string().max(64).optional()
12205
+ reviewer: z19.string().max(64).optional()
11953
12206
  }).parse(arguments_);
11954
12207
  const tasks = input2.tasks.map((task) => ({
11955
12208
  profile: task.profile ?? manager.team.defaultProfile,
@@ -13597,22 +13850,24 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
13597
13850
  if (!completion || completion.status === "no_changes") return;
13598
13851
  const checks = completion.checks.map((check) => check.command).join(", ");
13599
13852
  const suffix = checks ? ` ${this.glyphs.separator} ${checks}` : "";
13853
+ const duplication = completion.duplication;
13854
+ const duplicateSuffix = duplication ? ` ${this.glyphs.separator} duplication ${duplication.status} (${duplication.warningCount} warning, ${duplication.unresolvedCount} incomplete, ${duplication.suppressedCount} suppressed)` : "";
13600
13855
  if (completion.status === "verified") {
13601
13856
  process.stderr.write(this.paint.green(
13602
- `${this.glyphs.success} verified ${this.glyphs.separator} ${completion.detail}${suffix}
13857
+ `${this.glyphs.success} verified ${this.glyphs.separator} ${completion.detail}${suffix}${duplicateSuffix}
13603
13858
  `
13604
13859
  ));
13605
13860
  return;
13606
13861
  }
13607
13862
  if (completion.status === "verification_failed") {
13608
13863
  process.stderr.write(this.paint.red(
13609
- `${this.glyphs.error} verification failed ${this.glyphs.separator} ${completion.detail}${suffix}
13864
+ `${this.glyphs.error} verification failed ${this.glyphs.separator} ${completion.detail}${suffix}${duplicateSuffix}
13610
13865
  `
13611
13866
  ));
13612
13867
  return;
13613
13868
  }
13614
13869
  process.stderr.write(this.paint.yellow(
13615
- `${this.glyphs.warning} unverified ${this.glyphs.separator} ${completion.detail}
13870
+ `${this.glyphs.warning} unverified ${this.glyphs.separator} ${completion.detail}${duplicateSuffix}
13616
13871
  `
13617
13872
  ));
13618
13873
  }
@@ -16231,6 +16486,11 @@ function toolMetaSummary(metadata) {
16231
16486
  if (status === "warning") parts.push(`duplicates ${matches} (warning)`);
16232
16487
  else if (status === "unresolved") parts.push("duplicates incomplete");
16233
16488
  }
16489
+ const suppression = metadata.duplicationSuppression;
16490
+ if (suppression && typeof suppression === "object") {
16491
+ const matchId2 = suppression.matchId;
16492
+ if (typeof matchId2 === "string") parts.push(`duplicate ${matchId2.slice(0, 8)} suppressed`);
16493
+ }
16234
16494
  const hooks = metadata.hooks;
16235
16495
  if (hooks && typeof hooks === "object") {
16236
16496
  const before = Number(hooks.before ?? 0);
@@ -16690,12 +16950,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
16690
16950
  refreshSession();
16691
16951
  if (event.completion && event.completion.status !== "no_changes") {
16692
16952
  const checks = event.completion.checks.map((check) => check.command).join(` ${separator} `);
16953
+ const duplication = event.completion.duplication;
16954
+ const duplicateDetail = duplication ? `${separator} duplication ${duplication.status} (${duplication.warningCount} warning, ${duplication.unresolvedCount} incomplete, ${duplication.suppressedCount} suppressed)` : "";
16693
16955
  append({
16694
16956
  id: nextId(),
16695
16957
  kind: "notice",
16696
16958
  wrapWidth: contentWidth,
16697
16959
  tone: event.completion.status === "verified" ? "success" : event.completion.status === "unverified" ? "warning" : "error",
16698
- 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}`
16960
+ 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}`
16699
16961
  });
16700
16962
  }
16701
16963
  if (event.reason !== "completed" && event.reason !== "unverified" && event.reason !== "verification_failed") {
@@ -18717,7 +18979,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
18717
18979
  import stripAnsi5 from "strip-ansi";
18718
18980
 
18719
18981
  // src/mcp/tool.ts
18720
- import { createHash as createHash16 } from "node:crypto";
18982
+ import { createHash as createHash17 } from "node:crypto";
18721
18983
  import stripAnsi4 from "strip-ansi";
18722
18984
  var MAX_ARGUMENT_BYTES = 256e3;
18723
18985
  var MAX_DESCRIPTION_LENGTH = 4e3;
@@ -18725,7 +18987,7 @@ var MAX_RESULT_BYTES = 5 * 1024 * 1024;
18725
18987
  var MAX_SCHEMA_BYTES = 1e5;
18726
18988
  function createMcpToolAdapter(options) {
18727
18989
  const { remoteTool } = options;
18728
- const inputSchema12 = copyInputSchema(remoteTool.inputSchema);
18990
+ const inputSchema13 = copyInputSchema(remoteTool.inputSchema);
18729
18991
  return {
18730
18992
  definition: {
18731
18993
  name: options.exposedName,
@@ -18733,7 +18995,7 @@ function createMcpToolAdapter(options) {
18733
18995
  // MCP servers are an external trust boundary. Read-only annotations are
18734
18996
  // hints from that server and must not lower the local permission level.
18735
18997
  category: "network",
18736
- inputSchema: inputSchema12
18998
+ inputSchema: inputSchema13
18737
18999
  },
18738
19000
  permissionCategories: () => ["network"],
18739
19001
  async execute(arguments_, context) {
@@ -18887,7 +19149,7 @@ function fitToolName(name, identity2) {
18887
19149
  return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
18888
19150
  }
18889
19151
  function shortHash(value) {
18890
- return createHash16("sha256").update(value).digest("hex").slice(0, 8);
19152
+ return createHash17("sha256").update(value).digest("hex").slice(0, 8);
18891
19153
  }
18892
19154
  function sanitizeOutputText(value) {
18893
19155
  return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
@@ -19517,9 +19779,9 @@ async function closeTransportQuietly(transport) {
19517
19779
  }
19518
19780
 
19519
19781
  // src/memory/tools.ts
19520
- import { z as z19 } from "zod";
19521
- var scopeSchema = z19.enum(["user", "workspace", "session", "agent"]);
19522
- var kindSchema = z19.enum(["semantic", "episodic", "procedural"]);
19782
+ import { z as z20 } from "zod";
19783
+ var scopeSchema = z20.enum(["user", "workspace", "session", "agent"]);
19784
+ var kindSchema = z20.enum(["semantic", "episodic", "procedural"]);
19523
19785
  function createMemoryTools(store) {
19524
19786
  return [
19525
19787
  {
@@ -19534,10 +19796,10 @@ function createMemoryTools(store) {
19534
19796
  }, ["query"])
19535
19797
  },
19536
19798
  async execute(arguments_, context) {
19537
- const input2 = z19.object({
19538
- query: z19.string().max(4e3),
19799
+ const input2 = z20.object({
19800
+ query: z20.string().max(4e3),
19539
19801
  scope: scopeSchema.optional(),
19540
- limit: z19.number().int().min(1).max(20).optional()
19802
+ limit: z20.number().int().min(1).max(20).optional()
19541
19803
  }).parse(arguments_);
19542
19804
  const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
19543
19805
  const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
@@ -19569,17 +19831,17 @@ ${record.content}`
19569
19831
  }, ["content", "rationale"])
19570
19832
  },
19571
19833
  async execute(arguments_, context) {
19572
- const input2 = z19.object({
19573
- content: z19.string().min(1).max(12e3),
19574
- rationale: z19.string().min(1).max(1e3),
19834
+ const input2 = z20.object({
19835
+ content: z20.string().min(1).max(12e3),
19836
+ rationale: z20.string().min(1).max(1e3),
19575
19837
  scope: scopeSchema.optional(),
19576
19838
  kind: kindSchema.optional(),
19577
- tags: z19.array(z19.string()).max(24).optional(),
19578
- importance: z19.number().min(0).max(1).optional(),
19579
- confidence: z19.number().min(0).max(1).optional(),
19580
- agent: z19.string().max(64).optional(),
19581
- revision: z19.string().max(240).optional(),
19582
- conflictKey: z19.string().max(240).optional()
19839
+ tags: z20.array(z20.string()).max(24).optional(),
19840
+ importance: z20.number().min(0).max(1).optional(),
19841
+ confidence: z20.number().min(0).max(1).optional(),
19842
+ agent: z20.string().max(64).optional(),
19843
+ revision: z20.string().max(240).optional(),
19844
+ conflictKey: z20.string().max(240).optional()
19583
19845
  }).strict().parse(arguments_);
19584
19846
  const scope = input2.scope ?? "workspace";
19585
19847
  const candidate = store.propose({
@@ -19617,9 +19879,9 @@ ${record.content}`
19617
19879
  }, ["id"])
19618
19880
  },
19619
19881
  async execute(arguments_) {
19620
- const input2 = z19.object({
19621
- id: z19.string().uuid(),
19622
- permanent: z19.boolean().optional()
19882
+ const input2 = z20.object({
19883
+ id: z20.string().uuid(),
19884
+ permanent: z20.boolean().optional()
19623
19885
  }).parse(arguments_);
19624
19886
  const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
19625
19887
  return {
@@ -19823,7 +20085,7 @@ function escapeAttribute6(value) {
19823
20085
  }
19824
20086
 
19825
20087
  // src/workflows/catalog.ts
19826
- import { z as z20 } from "zod";
20088
+ import { z as z21 } from "zod";
19827
20089
  var builtInWorkflows = [
19828
20090
  {
19829
20091
  name: "implement",
@@ -19914,7 +20176,7 @@ function createWorkflowTool(catalog) {
19914
20176
  }, ["name", "task"])
19915
20177
  },
19916
20178
  async execute(arguments_) {
19917
- const input2 = z20.object({ name: z20.string(), task: z20.string().min(1).max(2e4) }).parse(arguments_);
20179
+ const input2 = z21.object({ name: z21.string(), task: z21.string().min(1).max(2e4) }).parse(arguments_);
19918
20180
  const workflow = catalog.get(input2.name);
19919
20181
  if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
19920
20182
  return {