@adhdev/daemon-core 0.9.82-rc.323 → 0.9.82-rc.324

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.
@@ -87,6 +87,43 @@ type MeshRefineSubmoduleConflictHint = {
87
87
  nextSteps: string[];
88
88
  };
89
89
  export declare function runMeshRefinePatchEquivalenceGate(repoRoot: string, baseHead: string, branchHead: string): Promise<MeshRefinePatchEquivalenceSummary>;
90
+ export type MeshWorktreePatchContainmentSummary = {
91
+ /** True only when merging worktreeHead into ref introduces no new patch. */
92
+ contained: boolean;
93
+ ref: string;
94
+ worktreeHead: string;
95
+ mergeBase?: string;
96
+ mergedTree?: string;
97
+ /** patch-id of (ref -> synthesized merge tree); empty string when nothing new is added. */
98
+ residualPatchId?: string;
99
+ durationMs: number;
100
+ /** Set when the check could not run (treated conservatively as NOT contained). */
101
+ error?: string;
102
+ };
103
+ /**
104
+ * Patch-equivalence containment check for the worktree force-cleanup convergence
105
+ * guard. Answers a narrower question than {@link runMeshRefinePatchEquivalenceGate}:
106
+ * "are the worktree branch's changes ALREADY present in `ref` (e.g. origin/main),
107
+ * even though the worktree HEAD's commit SHA is not an ancestor of ref?"
108
+ *
109
+ * This is the cherry-pick / squash / rebase case: the same content landed on the
110
+ * default ref under a different commit SHA, so `merge-base --is-ancestor` (the
111
+ * primary cleanup guard) reports the worktree as un-converged and refuses to
112
+ * remove it. Refinery already accepts patch-equivalent landings via merge-tree +
113
+ * patch-id; this brings the same notion of "convergence" to the cleanup guard.
114
+ *
115
+ * Mechanism: synthesize the merge of `worktreeHead` into `ref` (reusing the same
116
+ * trivial-gitlink-fast-forward handling as the refine gate) and compute the
117
+ * patch-id of (ref -> mergedTree). If that residual diff is EMPTY, merging the
118
+ * worktree adds nothing new on top of ref — its changes are already present there
119
+ * and the worktree is safe to remove. A non-empty residual means the worktree
120
+ * still carries content not in ref, so it is NOT contained and must stay blocked.
121
+ *
122
+ * Conservative by construction: any merge-tree / patch-id failure, a genuine
123
+ * (non-trivial) submodule conflict, or any thrown error yields `contained: false`
124
+ * so an exception can never widen the cleanup allow-list.
125
+ */
126
+ export declare function checkWorktreeChangesPatchEquivalentInRef(repoRoot: string, ref: string, worktreeHead: string): Promise<MeshWorktreePatchContainmentSummary>;
90
127
  /**
91
128
  * No-op guard: detect a "silent no-op" merge before the Refinery merge runs.
92
129
  *
package/dist/index.js CHANGED
@@ -313,10 +313,10 @@ function readInjected(value) {
313
313
  }
314
314
  function getDaemonBuildInfo() {
315
315
  if (cached) return cached;
316
- const commit = readInjected(true ? "c19690a16292dbcb1ed5ff5458b94596fce46e38" : void 0) ?? "unknown";
317
- const commitShort = readInjected(true ? "c19690a1" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
318
- const version = readInjected(true ? "0.9.82-rc.323" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
319
- const builtAt = readInjected(true ? "2026-06-19T06:24:49.522Z" : void 0);
316
+ const commit = readInjected(true ? "a0aeebdac30d9abea5b46e3f2618f864bbff19d0" : void 0) ?? "unknown";
317
+ const commitShort = readInjected(true ? "a0aeebda" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
318
+ const version = readInjected(true ? "0.9.82-rc.324" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
319
+ const builtAt = readInjected(true ? "2026-06-19T07:37:54.347Z" : void 0);
320
320
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
321
321
  return cached;
322
322
  }
@@ -2947,26 +2947,113 @@ __export(mesh_work_queue_exports, {
2947
2947
  updateTaskStatus: () => updateTaskStatus,
2948
2948
  validateMeshTaskModeRequest: () => validateMeshTaskModeRequest
2949
2949
  });
2950
+ function hasNegationBefore(text, matchIndex) {
2951
+ const before = text.slice(0, matchIndex);
2952
+ const clauseStart = Math.max(
2953
+ before.lastIndexOf("\n"),
2954
+ before.lastIndexOf(". "),
2955
+ before.lastIndexOf("! "),
2956
+ before.lastIndexOf("? "),
2957
+ before.lastIndexOf(";")
2958
+ );
2959
+ const clause = before.slice(clauseStart + 1);
2960
+ const lower = clause.toLowerCase();
2961
+ const tokens = clause.split(/\s+/).filter(Boolean);
2962
+ const windowTokens = tokens.slice(Math.max(0, tokens.length - NEGATION_WINDOW_TOKENS));
2963
+ const windowText = windowTokens.join(" ").toLowerCase();
2964
+ for (const cue of NEGATION_CUES) {
2965
+ const c = cue.toLowerCase();
2966
+ if (/[^\x00-\x7f]/.test(c)) {
2967
+ if (lower.includes(c)) return true;
2968
+ } else if (windowText.includes(c)) {
2969
+ return true;
2970
+ }
2971
+ }
2972
+ return false;
2973
+ }
2974
+ function hasTrailingNegation(text, matchEnd) {
2975
+ const after = text.slice(matchEnd);
2976
+ const clauseEnd = (() => {
2977
+ const stops = [after.indexOf("\n"), after.indexOf(". "), after.indexOf("; ")].filter((i) => i >= 0);
2978
+ return stops.length ? Math.min(...stops) : after.length;
2979
+ })();
2980
+ const clause = after.slice(0, clauseEnd).toLowerCase();
2981
+ for (const cue of NEGATION_CUES) {
2982
+ const c = cue.toLowerCase();
2983
+ if (/[^\x00-\x7f]/.test(c) && clause.includes(c)) return true;
2984
+ }
2985
+ return false;
2986
+ }
2987
+ function isMutationKeywordInCommandContext(text, matchStart, matchEnd) {
2988
+ if (isInsideBackticksOrFence(text, matchStart, matchEnd)) return true;
2989
+ const lineStart = text.lastIndexOf("\n", matchStart - 1) + 1;
2990
+ const linePrefix = text.slice(lineStart, matchStart);
2991
+ if (/^\s*[$>]\s/.test(text.slice(lineStart))) return true;
2992
+ if (/^\s*$/.test(linePrefix)) return true;
2993
+ if (/(?:&&|\|\||\||;)\s*$/.test(linePrefix)) return true;
2994
+ if (/(?:^|[\s,])(?:then|run|first)\s+$/i.test(linePrefix)) return true;
2995
+ if (/,\s*$/.test(linePrefix)) return true;
2996
+ return false;
2997
+ }
2998
+ function isInsideBackticksOrFence(text, matchStart, matchEnd) {
2999
+ const fenceRe = /```/g;
3000
+ let fenceCount = 0;
3001
+ let m;
3002
+ while ((m = fenceRe.exec(text)) !== null) {
3003
+ if (m.index >= matchStart) break;
3004
+ fenceCount++;
3005
+ }
3006
+ if (fenceCount % 2 === 1) return true;
3007
+ let inlineCount = 0;
3008
+ for (let i = 0; i < matchStart; i++) {
3009
+ if (text[i] === "`") {
3010
+ if (text[i + 1] === "`" && text[i + 2] === "`") {
3011
+ i += 2;
3012
+ continue;
3013
+ }
3014
+ inlineCount++;
3015
+ }
3016
+ }
3017
+ return inlineCount % 2 === 1;
3018
+ }
3019
+ function isRealMutationMatch(text, matchStart, matchEnd) {
3020
+ if (hasNegationBefore(text, matchStart)) return false;
3021
+ if (hasTrailingNegation(text, matchEnd)) return false;
3022
+ return isMutationKeywordInCommandContext(text, matchStart, matchEnd);
3023
+ }
3024
+ function patternHasRealMutation(pattern, text) {
3025
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
3026
+ let match;
3027
+ while ((match = re.exec(text)) !== null) {
3028
+ if (isRealMutationMatch(text, match.index, match.index + match[0].length)) return true;
3029
+ if (match.index === re.lastIndex) re.lastIndex++;
3030
+ }
3031
+ return false;
3032
+ }
2950
3033
  function detectGitMutation(message) {
2951
3034
  const re = /\bgit\s+([a-z][a-z0-9-]*)/gi;
2952
3035
  let match;
2953
3036
  while ((match = re.exec(message)) !== null) {
2954
3037
  const sub = match[1].toLowerCase();
2955
- if (GIT_MUTATION_SUBCOMMANDS.has(sub)) return true;
3038
+ const isReal = () => isRealMutationMatch(message, match.index, match.index + match[0].length);
3039
+ if (GIT_MUTATION_SUBCOMMANDS.has(sub)) {
3040
+ if (isReal()) return true;
3041
+ continue;
3042
+ }
2956
3043
  if (sub === "stash") {
2957
3044
  const after = message.slice(re.lastIndex).match(/^\s+([a-z][a-z0-9-]*)/i);
2958
3045
  const next = after ? after[1].toLowerCase() : "";
2959
- if (!GIT_STASH_READONLY_SUBCOMMANDS.has(next)) return true;
3046
+ if (!GIT_STASH_READONLY_SUBCOMMANDS.has(next) && isReal()) return true;
2960
3047
  } else if (sub === "checkout") {
2961
- return true;
3048
+ if (isReal()) return true;
2962
3049
  } else if (sub === "submodule") {
2963
3050
  const after = message.slice(re.lastIndex).match(/^\s+([a-z][a-z0-9-]*)/i);
2964
3051
  const next = after ? after[1].toLowerCase() : "";
2965
- if (next === "update" || next === "add" || next === "sync" || next === "deinit") return true;
3052
+ if ((next === "update" || next === "add" || next === "sync" || next === "deinit") && isReal()) return true;
2966
3053
  } else if (sub === "worktree") {
2967
3054
  const after = message.slice(re.lastIndex).match(/^\s+([a-z][a-z0-9-]*)/i);
2968
3055
  const next = after ? after[1].toLowerCase() : "";
2969
- if (next === "add" || next === "remove" || next === "move" || next === "prune") return true;
3056
+ if ((next === "add" || next === "remove" || next === "move" || next === "prune") && isReal()) return true;
2970
3057
  }
2971
3058
  }
2972
3059
  return false;
@@ -2985,7 +3072,7 @@ function validateMeshTaskModeRequest(mode, message) {
2985
3072
  return { valid: true, taskMode, violations: [] };
2986
3073
  }
2987
3074
  const text = message || "";
2988
- const violations = LIVE_DEBUG_READONLY_FORBIDDEN.filter((rule) => rule.pattern.test(text)).map((rule) => rule.label);
3075
+ const violations = LIVE_DEBUG_READONLY_FORBIDDEN.filter((rule) => patternHasRealMutation(rule.pattern, text)).map((rule) => rule.label);
2989
3076
  if (detectGitMutation(text)) {
2990
3077
  violations.push("git_mutation");
2991
3078
  }
@@ -3015,13 +3102,21 @@ function firstProviderPriority(policy) {
3015
3102
  if (!Array.isArray(raw)) return void 0;
3016
3103
  return raw.find((type) => typeof type === "string" && type.trim())?.trim();
3017
3104
  }
3105
+ function readNodeOverride(node, key) {
3106
+ const overrides = node?.userOverrides;
3107
+ if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) return null;
3108
+ const value = overrides[key];
3109
+ return typeof value === "string" && value.trim() ? value.trim() : null;
3110
+ }
3018
3111
  function buildMeshNodeCapabilityTags(node, providerType) {
3019
3112
  const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
3020
3113
  const worktreeBranch = typeof node?.worktreeBranch === "string" && node.worktreeBranch.trim() ? node.worktreeBranch.trim() : null;
3114
+ const os30 = readNodeOverride(node, "platform") ?? process.platform;
3115
+ const arch2 = readNodeOverride(node, "arch") ?? process.arch;
3021
3116
  return normalizeMeshCapabilityTags([
3022
3117
  ...Array.isArray(node?.capabilities) ? node.capabilities : [],
3023
- `os=${process.platform}`,
3024
- `arch=${process.arch}`,
3118
+ `os=${os30}`,
3119
+ `arch=${arch2}`,
3025
3120
  ...provider ? [`provider=${provider}`] : [],
3026
3121
  // Worktree nodes automatically expose a "worktree=<branch>" tag so that
3027
3122
  // mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
@@ -3378,7 +3473,7 @@ function recordMeshToolCall(opts) {
3378
3473
  return { rateLimitExceeded: false, callsInWindow: 0, advisory: null };
3379
3474
  }
3380
3475
  }
3381
- var import_crypto5, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, LIVE_DEBUG_READONLY_FORBIDDEN, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS;
3476
+ var import_crypto5, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS;
3382
3477
  var init_mesh_work_queue = __esm({
3383
3478
  "src/mesh/mesh-work-queue.ts"() {
3384
3479
  "use strict";
@@ -3398,6 +3493,23 @@ var init_mesh_work_queue = __esm({
3398
3493
  { label: "package_install", pattern: /\b(npm\s+(?:install|i|add|link|uninstall|remove)|yarn\s+(?:add|remove|link)|pnpm\s+(?:add|remove|link)|pip\s+install|brew\s+install|apt\s+install|cargo\s+install)\b/i },
3399
3494
  { label: "container_mutation", pattern: /\b(docker\s+(?:build|run|exec|push|tag|rmi|rm|create|start|stop|kill)|kubectl\s+(?:apply|delete|patch|replace|create|scale))\b/i }
3400
3495
  ];
3496
+ NEGATION_CUES = [
3497
+ "don't",
3498
+ "do not",
3499
+ "never",
3500
+ "avoid",
3501
+ "without",
3502
+ "no longer",
3503
+ "not",
3504
+ "forbidden",
3505
+ "\uD558\uC9C0 \uB9C8",
3506
+ "\uD558\uC9C0 \uB9C8\uC138\uC694",
3507
+ "\uB9D0 \uAC83",
3508
+ "\uAE08\uC9C0",
3509
+ "\uC5C6\uC74C",
3510
+ "\uC54A"
3511
+ ];
3512
+ NEGATION_WINDOW_TOKENS = 6;
3401
3513
  GIT_MUTATION_SUBCOMMANDS = /* @__PURE__ */ new Set([
3402
3514
  "add",
3403
3515
  "commit",
@@ -43869,6 +43981,70 @@ ${e?.stderr || ""}`
43869
43981
  };
43870
43982
  }
43871
43983
  }
43984
+ async function checkWorktreeChangesPatchEquivalentInRef(repoRoot, ref, worktreeHead) {
43985
+ const startedAt = Date.now();
43986
+ try {
43987
+ const { execFileSync: execFileSync7 } = await import("child_process");
43988
+ const git = (gitArgs) => execFileSync7("git", gitArgs, {
43989
+ cwd: repoRoot,
43990
+ encoding: "utf8",
43991
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
43992
+ });
43993
+ const mergeBase = git(["merge-base", ref, worktreeHead]).trim();
43994
+ let mergedTree = "";
43995
+ try {
43996
+ mergedTree = git(["merge-tree", "--write-tree", ref, worktreeHead]).trim().split(/\s+/)[0] || "";
43997
+ } catch (mergeTreeErr) {
43998
+ const output = `${mergeTreeErr?.message || ""}
43999
+ ${mergeTreeErr?.stdout || ""}
44000
+ ${mergeTreeErr?.stderr || ""}`;
44001
+ const isSubmoduleConflict = /(submodule|160000)/i.test(output) || /Recursive merging with submodules/i.test(output);
44002
+ if (!isSubmoduleConflict) throw mergeTreeErr;
44003
+ const evaluation = evaluateGitlinkTrivialFastForward(repoRoot, ref, worktreeHead);
44004
+ if (!evaluation.trivial) {
44005
+ return {
44006
+ contained: false,
44007
+ ref,
44008
+ worktreeHead,
44009
+ mergeBase: mergeBase || void 0,
44010
+ durationMs: Date.now() - startedAt,
44011
+ error: `merge-tree submodule conflict is not a trivial fast-forward: ${evaluation.reason || "unknown"}`
44012
+ };
44013
+ }
44014
+ mergedTree = synthesizeTrivialFastForwardMergeTree(repoRoot, ref, worktreeHead, evaluation.gitlinks) || "";
44015
+ }
44016
+ if (!mergedTree) {
44017
+ return {
44018
+ contained: false,
44019
+ ref,
44020
+ worktreeHead,
44021
+ mergeBase: mergeBase || void 0,
44022
+ durationMs: Date.now() - startedAt,
44023
+ error: "could not resolve synthetic merge tree for containment check"
44024
+ };
44025
+ }
44026
+ const ffGitlinkExcludePaths = collectFastForwardGitlinkPaths(repoRoot, ref, worktreeHead);
44027
+ const residualPatchId = await computeGitPatchId(repoRoot, ref, mergedTree, ffGitlinkExcludePaths);
44028
+ const contained = residualPatchId === "";
44029
+ return {
44030
+ contained,
44031
+ ref,
44032
+ worktreeHead,
44033
+ mergeBase: mergeBase || void 0,
44034
+ mergedTree,
44035
+ residualPatchId,
44036
+ durationMs: Date.now() - startedAt
44037
+ };
44038
+ } catch (e) {
44039
+ return {
44040
+ contained: false,
44041
+ ref,
44042
+ worktreeHead,
44043
+ durationMs: Date.now() - startedAt,
44044
+ error: e?.message || String(e)
44045
+ };
44046
+ }
44047
+ }
43872
44048
  async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
43873
44049
  const startedAt = Date.now();
43874
44050
  try {
@@ -44840,13 +45016,19 @@ function buildMemberJoinNode(mesh, args, fallbackDaemonId) {
44840
45016
  const workspace = typeof source?.workspace === "string" && source.workspace.trim() ? source.workspace.trim() : typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : process.cwd();
44841
45017
  if (!workspace) return null;
44842
45018
  const nodeId = typeof source?.id === "string" && source.id.trim() ? source.id.trim() : typeof source?.nodeId === "string" && source.nodeId.trim() ? source.nodeId.trim() : void 0;
45019
+ const baseOverrides = source?.userOverrides && typeof source.userOverrides === "object" && !Array.isArray(source.userOverrides) ? source.userOverrides : {};
45020
+ const userOverrides = {
45021
+ ...baseOverrides,
45022
+ ...typeof baseOverrides.platform === "string" && baseOverrides.platform.trim() ? {} : { platform: process.platform },
45023
+ ...typeof baseOverrides.arch === "string" && baseOverrides.arch.trim() ? {} : { arch: process.arch }
45024
+ };
44843
45025
  return {
44844
45026
  ...nodeId ? { id: nodeId } : {},
44845
45027
  workspace,
44846
45028
  ...typeof source?.repoRoot === "string" && source.repoRoot.trim() ? { repoRoot: source.repoRoot.trim() } : {},
44847
45029
  ...typeof source?.daemonId === "string" && source.daemonId.trim() ? { daemonId: source.daemonId.trim() } : fallbackDaemonId ? { daemonId: fallbackDaemonId } : {},
44848
45030
  ...typeof source?.machineId === "string" && source.machineId.trim() ? { machineId: source.machineId.trim() } : {},
44849
- userOverrides: source?.userOverrides && typeof source.userOverrides === "object" && !Array.isArray(source.userOverrides) ? source.userOverrides : {},
45031
+ userOverrides,
44850
45032
  policy: source?.policy && typeof source.policy === "object" && !Array.isArray(source.policy) ? source.policy : {},
44851
45033
  role: "member"
44852
45034
  };
@@ -45356,6 +45538,7 @@ var DaemonCommandRouter = class {
45356
45538
  candidateRefs.push("origin/main", "origin/master", "main", "master");
45357
45539
  const seen = /* @__PURE__ */ new Set();
45358
45540
  const checkedRefs = [];
45541
+ const resolvedRefCommits = [];
45359
45542
  for (const ref of candidateRefs) {
45360
45543
  if (!ref || seen.has(ref)) continue;
45361
45544
  seen.add(ref);
@@ -45366,12 +45549,24 @@ var DaemonCommandRouter = class {
45366
45549
  continue;
45367
45550
  }
45368
45551
  checkedRefs.push(ref);
45552
+ resolvedRefCommits.push({ ref, commit });
45369
45553
  try {
45370
45554
  await runGit3(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
45371
45555
  return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
45372
45556
  } catch {
45373
45557
  }
45374
45558
  }
45559
+ for (const { ref, commit } of resolvedRefCommits) {
45560
+ let containment;
45561
+ try {
45562
+ containment = await checkWorktreeChangesPatchEquivalentInRef(args.repoRoot, commit, head);
45563
+ } catch {
45564
+ continue;
45565
+ }
45566
+ if (containment.contained) {
45567
+ return { allow: true, status: "patch_equivalent_to_default_ref", source: "git_patch_equivalence", ref };
45568
+ }
45569
+ }
45375
45570
  return {
45376
45571
  allow: false,
45377
45572
  status: metadataStatus || void 0,