agentwheel 0.20.9 → 0.20.10

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/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  MergeAdoptionMismatchError,
4
4
  applyCombinedInstallPlan,
5
5
  assertExactMergeContribution,
6
+ assertMergedSourceContribution,
6
7
  assertOperationContained,
7
8
  assertSafeInstallName,
8
9
  canonicalGraphLockJson,
@@ -36,7 +37,7 @@ import {
36
37
  uninstall,
37
38
  withManifestRevision,
38
39
  writeInstallManifest
39
- } from "./chunk-E7Z2YB2C.js";
40
+ } from "./chunk-P2JK34W7.js";
40
41
  import {
41
42
  CURRENT_WORKSPACE_SCHEMA_VERSION,
42
43
  GovernedMutation,
@@ -97,7 +98,7 @@ import {
97
98
  workspaceSelectionImportSchema,
98
99
  writeApplyJournal,
99
100
  writeWorkspaceConfig
100
- } from "./chunk-SHPNLNBR.js";
101
+ } from "./chunk-J7DKPDS7.js";
101
102
  import {
102
103
  inferSourceDriverName
103
104
  } from "./chunk-EMDIG24I.js";
@@ -112,11 +113,11 @@ import {
112
113
  } from "./chunk-7VPI5J5Y.js";
113
114
 
114
115
  // src/cli/index.ts
115
- import { createHash as createHash14 } from "crypto";
116
+ import { createHash as createHash15 } from "crypto";
116
117
  import { existsSync } from "fs";
117
- import { mkdir as mkdir16, rm as rm10, writeFile as writeFile13 } from "fs/promises";
118
- import { homedir as homedir12 } from "os";
119
- import { dirname as dirname22, join as join45, resolve as resolve25 } from "path";
118
+ import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm10, writeFile as writeFile13 } from "fs/promises";
119
+ import { homedir as homedir12, tmpdir as tmpdir5 } from "os";
120
+ import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute4, join as join46, relative as relative10, resolve as resolve26 } from "path";
120
121
  import { fileURLToPath as fileURLToPath3 } from "url";
121
122
  import { Command } from "commander";
122
123
 
@@ -3112,14 +3113,14 @@ async function writeMermaidAsset(response) {
3112
3113
  "content-type": "application/javascript; charset=utf-8",
3113
3114
  "cache-control": "public, max-age=3600"
3114
3115
  });
3115
- await new Promise((resolve26) => {
3116
+ await new Promise((resolve27) => {
3116
3117
  const stream = createReadStream(assetPath);
3117
3118
  stream.on("error", () => {
3118
3119
  if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
3119
3120
  else response.destroy();
3120
- resolve26();
3121
+ resolve27();
3121
3122
  });
3122
- stream.on("end", resolve26);
3123
+ stream.on("end", resolve27);
3123
3124
  stream.pipe(response);
3124
3125
  });
3125
3126
  }
@@ -3146,14 +3147,14 @@ function escapeHtml2(value) {
3146
3147
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3147
3148
  }
3148
3149
  function listen(server, port, bind) {
3149
- return new Promise((resolve26, reject) => {
3150
+ return new Promise((resolve27, reject) => {
3150
3151
  const onError = (error) => {
3151
3152
  server.off("listening", onListening);
3152
3153
  reject(error);
3153
3154
  };
3154
3155
  const onListening = () => {
3155
3156
  server.off("error", onError);
3156
- resolve26();
3157
+ resolve27();
3157
3158
  };
3158
3159
  server.once("error", onError);
3159
3160
  server.once("listening", onListening);
@@ -3161,7 +3162,7 @@ function listen(server, port, bind) {
3161
3162
  });
3162
3163
  }
3163
3164
  function waitForShutdown(server, beforeClose) {
3164
- return new Promise((resolve26) => {
3165
+ return new Promise((resolve27) => {
3165
3166
  let closing = false;
3166
3167
  const shutdown = () => {
3167
3168
  if (closing) return;
@@ -3169,7 +3170,7 @@ function waitForShutdown(server, beforeClose) {
3169
3170
  process.off("SIGINT", shutdown);
3170
3171
  process.off("SIGTERM", shutdown);
3171
3172
  beforeClose();
3172
- server.close(() => resolve26());
3173
+ server.close(() => resolve27());
3173
3174
  };
3174
3175
  process.once("SIGINT", shutdown);
3175
3176
  process.once("SIGTERM", shutdown);
@@ -3179,7 +3180,7 @@ function waitForShutdown(server, beforeClose) {
3179
3180
  process.off("SIGINT", shutdown);
3180
3181
  process.off("SIGTERM", shutdown);
3181
3182
  beforeClose();
3182
- resolve26();
3183
+ resolve27();
3183
3184
  });
3184
3185
  });
3185
3186
  }
@@ -3732,7 +3733,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
3732
3733
  throw new Error(`ClawHub lookup failed for ${packageName}`);
3733
3734
  }
3734
3735
  async function delay(ms) {
3735
- await new Promise((resolve26) => setTimeout(resolve26, ms));
3736
+ await new Promise((resolve27) => setTimeout(resolve27, ms));
3736
3737
  }
3737
3738
  function parseClawHubSource(source) {
3738
3739
  if (!source.startsWith(sourcePrefix)) {
@@ -3909,7 +3910,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
3909
3910
  if (Date.now() - started > timeoutMs) {
3910
3911
  throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
3911
3912
  }
3912
- await new Promise((resolve26) => setTimeout(resolve26, 50));
3913
+ await new Promise((resolve27) => setTimeout(resolve27, 50));
3913
3914
  }
3914
3915
  }
3915
3916
  try {
@@ -6775,8 +6776,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
6775
6776
  async function withCachePathLock(path, fn) {
6776
6777
  const previous = cacheLocks.get(path) ?? Promise.resolve();
6777
6778
  let release = () => void 0;
6778
- const current = previous.then(() => new Promise((resolve26) => {
6779
- release = resolve26;
6779
+ const current = previous.then(() => new Promise((resolve27) => {
6780
+ release = resolve27;
6780
6781
  }));
6781
6782
  cacheLocks.set(path, current);
6782
6783
  await previous;
@@ -8141,6 +8142,9 @@ async function createGraphSourcePlan(options) {
8141
8142
  if (options.roots.length === 0) {
8142
8143
  throw new Error("At least one source is required for a graph plan.");
8143
8144
  }
8145
+ if (options.freshGraphOnly && options.readOnly !== true) {
8146
+ throw new Error("Fresh graph recovery is read-only and cannot be used for install planning.");
8147
+ }
8144
8148
  const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
8145
8149
  const transport = options.transport ?? localTransport;
8146
8150
  const warnings = [];
@@ -8233,12 +8237,12 @@ async function createGraphSourcePlan(options) {
8233
8237
  });
8234
8238
  return { priorState: priorState2, stableLock: stableLock2, stableSourceLock: stableSourceLock2 };
8235
8239
  };
8236
- const { priorState, stableLock, stableSourceLock } = await readPriorState();
8240
+ const { priorState, stableLock, stableSourceLock } = options.freshGraphOnly ? { priorState: {}, stableLock: void 0, stableSourceLock: void 0 } : await readPriorState();
8237
8241
  const previousLock = priorState.graphLock;
8238
- const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
8242
+ const registryClient = new RegistryClient({ workspaceRoot, cachePath: options.registryCachePath, offline: lockMode, offlineLabel: lockLabel, warn });
8239
8243
  const graph = await resolveDependencyGraph(options.roots, {
8240
8244
  workspaceRoot,
8241
- cacheRoot: join33(workspaceRoot, ".agentwheel", "cache"),
8245
+ cacheRoot: options.cacheRoot ?? join33(workspaceRoot, ".agentwheel", "cache"),
8242
8246
  registryClient,
8243
8247
  noDeps: options.noDeps,
8244
8248
  includeSuggestions: options.includeSuggestions,
@@ -10149,10 +10153,262 @@ function canonicalJson(value) {
10149
10153
  return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
10150
10154
  }
10151
10155
 
10156
+ // src/lifecycle/legacy-recovery-plan.ts
10157
+ import { createHash as createHash10 } from "crypto";
10158
+ import { readdir as readdir10 } from "fs/promises";
10159
+ import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
10160
+ async function inventoryHistoricalLocks(fleetRoot) {
10161
+ const root = join39(fleetRoot, ".agentwheel", "locks");
10162
+ const found = [];
10163
+ async function visit(dir) {
10164
+ for (const entry of await readdir10(dir, { withFileTypes: true }).catch((error) => {
10165
+ if (error.code === "ENOENT") return [];
10166
+ throw error;
10167
+ })) {
10168
+ const path = join39(dir, entry.name);
10169
+ if (entry.isDirectory()) await visit(path);
10170
+ else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) {
10171
+ const lock = await readGraphLock(path);
10172
+ found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
10173
+ }
10174
+ }
10175
+ }
10176
+ await visit(root);
10177
+ return found.sort((a, b) => a.path.localeCompare(b.path));
10178
+ }
10179
+ async function planLegacyRecovery(input) {
10180
+ const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
10181
+ assertCompleteGraphPlan(graphPlan);
10182
+ const binding = {
10183
+ targetLockDir: dirname21(graphPlan.graphLockPath),
10184
+ stableLockPath: graphPlan.graphLockPath,
10185
+ stableStateKey: graphPlan.plan.stateKey,
10186
+ currentFingerprint: graphPlan.targetFingerprint,
10187
+ expectedOwner: workspaceOwnerForRoot(input.workspaceRoot, input.fleetId),
10188
+ installRoot,
10189
+ adapter,
10190
+ installationType,
10191
+ fleetId: input.fleetId,
10192
+ explicitStateKey: input.explicitStateKey,
10193
+ transportKind: transport.kind
10194
+ };
10195
+ const manifests = await listInstallManifests(installRoot, adapter, transport);
10196
+ const paths = /* @__PURE__ */ new Set();
10197
+ const desired = /* @__PURE__ */ new Map();
10198
+ const nodeHashes = new Map(graphPlan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node.sourceHash]));
10199
+ for (const op of graphPlan.plan.operations) {
10200
+ if (!op.desiredHash || op.action === "remove" || op.action === "keep") continue;
10201
+ const path = relative8(installRoot, op.destPath);
10202
+ if (path.startsWith("..") || path === "") throw new Error(`Planner destination escapes target: ${op.destPath}`);
10203
+ paths.add(path);
10204
+ const items = desired.get(path) ?? [];
10205
+ items.push(op);
10206
+ desired.set(path, items);
10207
+ }
10208
+ for (const item of manifests) for (const entry of item.manifest.entries) paths.add(entry.path);
10209
+ const resolutionWarnings = graphPlan.warnings.filter((warning) => !/^skip (?:artifact|dependency) .+ \((?:selected but )?not targeted: runtimes=\[[^\]]+\]\)$/.test(warning));
10210
+ const complete = resolutionWarnings.length === 0;
10211
+ const output = [];
10212
+ for (const path of [...paths].sort()) {
10213
+ const allClaims = manifests.flatMap((item) => item.manifest.entries.filter((entry) => entry.path === path).map((entry) => claimFor(item, entry, historicalLocks, binding)));
10214
+ const claims = allClaims.filter((claim) => claim.scope !== "foreign");
10215
+ const foreignClaims = allClaims.filter((claim) => claim.scope === "foreign");
10216
+ const current = desired.get(path) ?? [];
10217
+ const currentHashes = [...new Set(current.map((item) => item.desiredHash))];
10218
+ const currentSourceHashes = [...new Set(current.map((item) => item.graphNodeId ? nodeHashes.get(item.graphNodeId) : void 0).filter((value) => Boolean(value)))].sort();
10219
+ const currentNodeIds = [...new Set(current.map((item) => item.graphNodeId).filter((value) => Boolean(value)))].sort();
10220
+ const currentSelectors = [...new Set(current.map((item) => item.logicalSelector).filter((value) => Boolean(value)))].sort();
10221
+ const currentOwners = [...new Set(current.flatMap((item) => item.owners ?? []))].sort();
10222
+ const livePath = resolve22(installRoot, path);
10223
+ if (relative8(installRoot, livePath).startsWith("..")) throw new Error(`Manifest path escapes target: ${path}`);
10224
+ const liveHash = await transport.pathExists(livePath) ? await transport.hashPath(livePath) : null;
10225
+ const reasons = [];
10226
+ const currentResults = await Promise.all(current.map((op) => verifyCurrentOperation(op, liveHash, transport)));
10227
+ const currentGraphStatus = current.length === 0 ? "absent" : currentResults.every((result) => result === "exact-match") ? "exact-match" : currentResults.some((result) => result === "unverifiable") ? "unverifiable" : "mismatch";
10228
+ if (currentHashes.length > 1 && current.some((op) => !op.mergeStrategy && op.mode !== "managed-block")) {
10229
+ reasons.push("current graph has divergent hashes for the destination");
10230
+ }
10231
+ if (currentGraphStatus === "mismatch") reasons.push("live contribution differs from current source");
10232
+ if (currentGraphStatus === "unverifiable") reasons.push("current contribution could not be verified");
10233
+ if (current.some((op) => op.action === "conflict" || op.action === "drift")) reasons.push("install planner has a blocking operation");
10234
+ if (!complete) reasons.push("current source graph has resolution warnings; completeness unproven");
10235
+ if (claims.some((claim) => claim.scope === "unbound" || claim.evidence.length)) reasons.push("historical provenance unresolved");
10236
+ if (foreignClaims.length) reasons.push("foreign manifest claim conflicts with destination");
10237
+ if (new Set(claims.map((claim) => `${claim.owner}:${claim.hash}:${claim.sourceHash}:${claim.graphLockDigest ?? ""}`)).size > 1) {
10238
+ reasons.push("competing same-target claims diverge");
10239
+ }
10240
+ if (current.length === 0 && claims.length === 0) reasons.push("no same-target historical claim");
10241
+ if (current.length === 0) {
10242
+ for (const claim of claims) {
10243
+ if (claim.scope !== "same-target") continue;
10244
+ const item = manifests.find((manifest) => manifest.path === claim.manifestPath);
10245
+ const entry = item.manifest.entries.find((candidate) => candidate.path === path && candidate.sourceHash === claim.sourceHash);
10246
+ if (!await verifyHistoricalEntry(entry, livePath, liveHash, transport)) {
10247
+ reasons.push("live contribution differs from same-target historical claim");
10248
+ break;
10249
+ }
10250
+ }
10251
+ }
10252
+ const category = reasons.length ? "unresolved" : currentGraphStatus === "exact-match" ? "current-graph-exact-match" : "retirement-candidate";
10253
+ output.push({
10254
+ path,
10255
+ category,
10256
+ reasons,
10257
+ currentHash: currentHashes.length === 1 ? currentHashes[0] : null,
10258
+ currentHashes: currentHashes.sort(),
10259
+ currentSourceHashes,
10260
+ currentNodeIds,
10261
+ currentSelectors,
10262
+ currentOwners,
10263
+ currentGraphStatus,
10264
+ liveHash,
10265
+ claims,
10266
+ foreignClaims
10267
+ });
10268
+ }
10269
+ const counts = { "current-graph-exact-match": 0, "retirement-candidate": 0, unresolved: 0 };
10270
+ const currentGraphCounts = { "exact-match": 0, mismatch: 0, absent: 0, unverifiable: 0 };
10271
+ for (const path of output) counts[path.category]++;
10272
+ for (const path of output) currentGraphCounts[path.currentGraphStatus]++;
10273
+ return {
10274
+ targetRoot: installRoot,
10275
+ targetKey: targetKey2,
10276
+ adapter,
10277
+ installationType,
10278
+ currentGraphLockDigest: graphPlan.graphLockDigest,
10279
+ roots: graphPlan.graph.roots.map((root) => ({ rootId: root.rootId, source: root.source, graphNodeId: root.graphNodeId, selected: root.selected })),
10280
+ resolutionWarnings,
10281
+ complete,
10282
+ paths: output,
10283
+ counts,
10284
+ currentGraphCounts
10285
+ };
10286
+ }
10287
+ function claimFor(item, entry, locks, binding) {
10288
+ const digest2 = "graphLockDigest" in entry ? entry.graphLockDigest : void 0;
10289
+ const nodeId = "graphNodeId" in entry ? entry.graphNodeId : void 0;
10290
+ const selector = "logicalSelector" in entry ? entry.logicalSelector : void 0;
10291
+ const matches = locks.filter((candidate) => candidate.digest === digest2);
10292
+ const evidence = [];
10293
+ const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
10294
+ const sameRoot = binding.transportKind === "ssh" ? posix3.normalize(item.manifest.targetRoot) === posix3.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
10295
+ const inTargetDir = matches.filter((candidate) => resolve22(dirname21(candidate.path)) === resolve22(binding.targetLockDir));
10296
+ const boundLocks = inTargetDir.filter((candidate) => lockBindsState(candidate, item, binding));
10297
+ if (owner !== binding.expectedOwner) evidence.push("workspace owner differs from selected Fleet target");
10298
+ if (item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType) evidence.push("installation type differs or manifest is legacy v1");
10299
+ if (!sameRoot) evidence.push("manifest target root differs from selected Fleet target");
10300
+ if (item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter)) {
10301
+ evidence.push("manifest state key or adapter differs from filename");
10302
+ }
10303
+ if (!digest2) evidence.push("historical graph-lock digest missing");
10304
+ else if (matches.length === 0) evidence.push("historical graph-lock digest has no matching lock");
10305
+ if (matches.length && !inTargetDir.length) evidence.push("claim belongs to another Fleet target graph");
10306
+ if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
10307
+ if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
10308
+ evidence.push("historical graph-lock selector/node does not cover claim");
10309
+ } else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector && artifact.hash === entry.sourceHash))) {
10310
+ evidence.push("historical graph-lock artifact hash differs from manifest source hash");
10311
+ }
10312
+ const foreign = owner !== binding.expectedOwner || !sameRoot || item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType || item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter) || matches.length > 0 && inTargetDir.length === 0;
10313
+ const scope = foreign ? "foreign" : boundLocks.length && evidence.length === 0 ? "same-target" : "unbound";
10314
+ return {
10315
+ scope,
10316
+ manifestPath: item.path,
10317
+ stateKey: item.stateKey,
10318
+ manifestStateKey: item.manifest.version === 2 ? item.manifest.stateKey : void 0,
10319
+ installationType: item.manifest.version === 2 ? item.manifest.installationType : void 0,
10320
+ targetRoot: item.manifest.targetRoot,
10321
+ owner,
10322
+ hash: entry.hash,
10323
+ sourceHash: entry.sourceHash,
10324
+ graphLockDigest: digest2,
10325
+ graphNodeId: nodeId,
10326
+ logicalSelector: selector,
10327
+ owners: "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"],
10328
+ lockPaths: matches.map((candidate) => candidate.path),
10329
+ evidence
10330
+ };
10331
+ }
10332
+ function lockBindsState(candidate, manifest, binding) {
10333
+ const name = basename20(candidate.path);
10334
+ if (!name.endsWith(".graph-lock.json")) return false;
10335
+ const fingerprint = name.slice(0, -".graph-lock.json".length);
10336
+ if (!/^[a-f0-9]{64}$/u.test(fingerprint)) return false;
10337
+ const stable = resolve22(candidate.path) === resolve22(binding.stableLockPath);
10338
+ if (stable) {
10339
+ return manifest.stateKey === binding.stableStateKey && candidate.lock.canonical.targetFingerprint === binding.currentFingerprint;
10340
+ }
10341
+ return candidate.lock.canonical.targetFingerprint === fingerprint && manifest.stateKey === stateKeyFor(binding.adapter, {
10342
+ installationType: binding.installationType,
10343
+ stateKey: binding.explicitStateKey,
10344
+ targetFingerprint: fingerprint,
10345
+ fleetId: binding.fleetId
10346
+ });
10347
+ }
10348
+ async function verifyCurrentOperation(op, liveHash, transport) {
10349
+ if (liveHash === null || !op.desiredHash) return "mismatch";
10350
+ if (op.semanticPlugin || op.programmaticOperation) return "unverifiable";
10351
+ if (op.mode === "managed-block") {
10352
+ const selector = managedInstructionSelector(op.logicalSelector, op.artifactType, op.artifactName);
10353
+ const state = await readManagedInstructionBlockState(op.destPath, selector, transport);
10354
+ return state.hasBlock && !state.drifted && state.hash === op.desiredHash ? "exact-match" : "mismatch";
10355
+ }
10356
+ if (op.mergeStrategy) {
10357
+ if (!op.sourcePath) return "unverifiable";
10358
+ try {
10359
+ await assertMergedSourceContribution(op.sourcePath, op.mergeStrategy, await transport.readFile(op.destPath));
10360
+ return "exact-match";
10361
+ } catch {
10362
+ return "mismatch";
10363
+ }
10364
+ }
10365
+ return liveHash === op.desiredHash ? "exact-match" : "mismatch";
10366
+ }
10367
+ async function verifyHistoricalEntry(entry, livePath, liveHash, transport) {
10368
+ if (liveHash === null || entry.semanticPlugin) return false;
10369
+ if (entry.mode === "managed-block") {
10370
+ const selector = managedInstructionSelector(
10371
+ "logicalSelector" in entry ? entry.logicalSelector : void 0,
10372
+ entry.artifactType,
10373
+ entry.artifactName
10374
+ );
10375
+ const state = await readManagedInstructionBlockState(livePath, selector, transport);
10376
+ return state.hasBlock && !state.drifted && state.hash === entry.hash;
10377
+ }
10378
+ if (entry.mergeStrategy) {
10379
+ if (!hasMergeRemovalContent(entry.mergeRemoval)) return false;
10380
+ try {
10381
+ assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, await transport.readFile(livePath));
10382
+ return true;
10383
+ } catch {
10384
+ return false;
10385
+ }
10386
+ }
10387
+ return entry.hash === liveHash;
10388
+ }
10389
+ function assertCompleteGraphPlan(plan) {
10390
+ const sourceNodes = plan.graph.nodes.filter((node) => node.selected.length > 0);
10391
+ const lockedNodes = new Map(plan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node]));
10392
+ for (const node of sourceNodes) {
10393
+ const locked = lockedNodes.get(node.id);
10394
+ if (!locked || locked.sourceHash !== node.sourceHash || node.selected.some((selector) => !locked.selected.includes(selector))) {
10395
+ throw new Error(`Current source graph omitted selected node or dependency: ${node.id}`);
10396
+ }
10397
+ }
10398
+ for (const edge of plan.graph.edges) {
10399
+ if (!plan.bundle.graphLock.canonical.edges.some((locked) => locked.from === edge.from && locked.to === edge.to && locked.alias === edge.alias)) {
10400
+ throw new Error(`Current source graph omitted dependency edge: ${edge.from} -> ${edge.to}`);
10401
+ }
10402
+ }
10403
+ }
10404
+ function sha2564(value) {
10405
+ return createHash10("sha256").update(value).digest("hex");
10406
+ }
10407
+
10152
10408
  // src/version/policy.ts
10153
10409
  import { execFile as execFile3 } from "child_process";
10154
10410
  import { readFile as readFile23 } from "fs/promises";
10155
- import { join as join39, resolve as resolve22 } from "path";
10411
+ import { join as join40, resolve as resolve23 } from "path";
10156
10412
  import { promisify as promisify3 } from "util";
10157
10413
  import { parse as parseJsonc } from "jsonc-parser";
10158
10414
  import { z as z5 } from "zod";
@@ -10246,7 +10502,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
10246
10502
  if (tagged.length > 0) return tagged;
10247
10503
  }
10248
10504
  if (driverName === "local") {
10249
- const root = resolve22(workspaceRoot, pkg.source);
10505
+ const root = resolve23(workspaceRoot, pkg.source);
10250
10506
  const manifest2 = await readPackageManifest(root);
10251
10507
  const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
10252
10508
  try {
@@ -10261,7 +10517,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
10261
10517
  }
10262
10518
  const driver = getSourceDriver(driverName);
10263
10519
  const resolved = await driver.resolve(pkg.source, {
10264
- cacheRoot: join39(workspaceRoot, ".agentwheel", "cache"),
10520
+ cacheRoot: join40(workspaceRoot, ".agentwheel", "cache"),
10265
10521
  mode: "tracking",
10266
10522
  ref: pkg.requestedRef
10267
10523
  });
@@ -10355,7 +10611,7 @@ function gitUrlFromSource(source) {
10355
10611
  throw new Error(`Version discovery does not support Git source: ${source}`);
10356
10612
  }
10357
10613
  function versionCachePath(workspaceRoot) {
10358
- return join39(workspaceRoot, ".agentwheel", "cache", "version-index.json");
10614
+ return join40(workspaceRoot, ".agentwheel", "cache", "version-index.json");
10359
10615
  }
10360
10616
  async function readVersionCache(path) {
10361
10617
  if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
@@ -10369,7 +10625,7 @@ async function readVersionCache(path) {
10369
10625
  // src/profile/members.ts
10370
10626
  import { execFile as execFile4 } from "child_process";
10371
10627
  import { readFile as readFile24 } from "fs/promises";
10372
- import { join as join40, resolve as resolve23 } from "path";
10628
+ import { join as join41, resolve as resolve24 } from "path";
10373
10629
  import { promisify as promisify4 } from "util";
10374
10630
  import { z as z7 } from "zod";
10375
10631
 
@@ -10544,7 +10800,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
10544
10800
  let stderr = "";
10545
10801
  try {
10546
10802
  if (member.transport === "local") {
10547
- const workspace = resolve23(parentWorkspace, member.workspace);
10803
+ const workspace = resolve24(parentWorkspace, member.workspace);
10548
10804
  const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
10549
10805
  cwd: workspace,
10550
10806
  env: env2,
@@ -10590,7 +10846,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
10590
10846
  process.execPath,
10591
10847
  [process.argv[1], ...memberCommandArgs(member, args)],
10592
10848
  {
10593
- cwd: resolve23(parentWorkspace, member.workspace),
10849
+ cwd: resolve24(parentWorkspace, member.workspace),
10594
10850
  env: env2,
10595
10851
  maxBuffer: 20 * 1024 * 1024
10596
10852
  }
@@ -10670,7 +10926,7 @@ function memberFailure(member, health, error) {
10670
10926
  };
10671
10927
  }
10672
10928
  function memberCachePath(workspaceRoot, profileName, memberId) {
10673
- return join40(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
10929
+ return join41(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
10674
10930
  }
10675
10931
  async function readMemberCache(path) {
10676
10932
  if (!await pathExists(path)) return void 0;
@@ -10696,7 +10952,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
10696
10952
  }
10697
10953
  }
10698
10954
  function compositeKey(workspaceRoot, profileName) {
10699
- return `${resolve23(workspaceRoot)}#${profileName}`;
10955
+ return `${resolve24(workspaceRoot)}#${profileName}`;
10700
10956
  }
10701
10957
 
10702
10958
  // src/status/repository.ts
@@ -10743,10 +10999,10 @@ function valueAfter(lines, prefix) {
10743
10999
  }
10744
11000
 
10745
11001
  // src/catalogue/client.ts
10746
- import { createHash as createHash10 } from "crypto";
11002
+ import { createHash as createHash11 } from "crypto";
10747
11003
  import { readFile as readFile25, rm as rm8 } from "fs/promises";
10748
11004
  import { homedir as homedir9 } from "os";
10749
- import { join as join41 } from "path";
11005
+ import { join as join42 } from "path";
10750
11006
 
10751
11007
  // src/model/catalogue.ts
10752
11008
  import { z as z8 } from "zod";
@@ -11018,18 +11274,18 @@ var CatalogueClient = class {
11018
11274
  }
11019
11275
  return {
11020
11276
  value: schema.parse(value),
11021
- digest: createHash10("sha256").update(bytes).digest("hex")
11277
+ digest: createHash11("sha256").update(bytes).digest("hex")
11022
11278
  };
11023
11279
  }
11024
11280
  };
11025
11281
  function defaultCatalogueCachePath() {
11026
- return join41(homedir9(), ".agentwheel", "catalogue-cache.json");
11282
+ return join42(homedir9(), ".agentwheel", "catalogue-cache.json");
11027
11283
  }
11028
11284
  function sameSources2(a, b) {
11029
11285
  return a.length === b.length && a.every((source, index) => source === b[index]);
11030
11286
  }
11031
11287
  function catalogueContentHash(enriched, vercel) {
11032
- return createHash10("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11288
+ return createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11033
11289
  }
11034
11290
 
11035
11291
  // src/search/index.ts
@@ -11391,9 +11647,9 @@ function includesToken(normalizedText, token) {
11391
11647
  }
11392
11648
 
11393
11649
  // src/semantic/index.ts
11394
- import { createHash as createHash11 } from "crypto";
11650
+ import { createHash as createHash12 } from "crypto";
11395
11651
  import { homedir as homedir10 } from "os";
11396
- import { join as join42 } from "path";
11652
+ import { join as join43 } from "path";
11397
11653
  import { env, pipeline } from "@huggingface/transformers";
11398
11654
  var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
11399
11655
  var CONTRACT = {
@@ -11481,13 +11737,13 @@ var SemanticSearchClient = class {
11481
11737
  if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
11482
11738
  const bytes = new Uint8Array(await response.arrayBuffer());
11483
11739
  if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
11484
- const digest2 = createHash11("sha256").update(bytes).digest("hex");
11740
+ const digest2 = createHash12("sha256").update(bytes).digest("hex");
11485
11741
  if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
11486
11742
  return bytes;
11487
11743
  }
11488
11744
  };
11489
11745
  async function embedQuery(query) {
11490
- env.cacheDir = join42(homedir10(), ".agentwheel", "semantic-models");
11746
+ env.cacheDir = join43(homedir10(), ".agentwheel", "semantic-models");
11491
11747
  const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
11492
11748
  revision: CONTRACT.model.revision,
11493
11749
  dtype: CONTRACT.model.dtype,
@@ -11571,9 +11827,9 @@ function ensureTrailingSlash(value) {
11571
11827
  }
11572
11828
 
11573
11829
  // src/trial/skill.ts
11574
- import { createHash as createHash12 } from "crypto";
11830
+ import { createHash as createHash13 } from "crypto";
11575
11831
  import { readFile as readFile26, stat as stat13 } from "fs/promises";
11576
- import { join as join43 } from "path";
11832
+ import { join as join44 } from "path";
11577
11833
  import { parse as parseYaml2 } from "yaml";
11578
11834
  var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
11579
11835
  async function createSkillTrial(driver, resolved, selectors) {
@@ -11585,7 +11841,7 @@ async function createSkillTrial(driver, resolved, selectors) {
11585
11841
  throw new Error("Skill trial requires exactly one selected skill. Use --skill <name> or --select skills/<name>.");
11586
11842
  }
11587
11843
  const artifact = skills[0];
11588
- const path = artifact.kind === "dir" ? join43(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
11844
+ const path = artifact.kind === "dir" ? join44(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
11589
11845
  const info = await stat13(path);
11590
11846
  if (info.size > MAX_TRIAL_SKILL_BYTES) {
11591
11847
  throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
@@ -11601,7 +11857,7 @@ async function createSkillTrial(driver, resolved, selectors) {
11601
11857
  skill: {
11602
11858
  name: artifact.name,
11603
11859
  relativePath: artifact.relativePath,
11604
- sha256: createHash12("sha256").update(content).digest("hex"),
11860
+ sha256: createHash13("sha256").update(content).digest("hex"),
11605
11861
  frontmatter,
11606
11862
  content
11607
11863
  }
@@ -11620,10 +11876,10 @@ function readSkillFrontmatter(content, artifact) {
11620
11876
  }
11621
11877
 
11622
11878
  // src/lifecycle/fleet-normalize.ts
11623
- import { createHash as createHash13 } from "crypto";
11624
- import { readFile as readFile27, readdir as readdir10, rm as rm9 } from "fs/promises";
11879
+ import { createHash as createHash14 } from "crypto";
11880
+ import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
11625
11881
  import { homedir as homedir11 } from "os";
11626
- import { dirname as dirname21, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve24 } from "path";
11882
+ import { dirname as dirname22, isAbsolute as isAbsolute3, join as join45, relative as relative9, resolve as resolve25 } from "path";
11627
11883
  async function planFleetNormalization(request) {
11628
11884
  const normalizedRequest = normalizeRequest2(request);
11629
11885
  const destinationScope = await resolveWorkspaceScope({
@@ -11653,7 +11909,7 @@ async function planFleetNormalization(request) {
11653
11909
  `Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
11654
11910
  );
11655
11911
  }
11656
- packages.push({ name, declarationDigest: sha2564(sourceDeclaration) });
11912
+ packages.push({ name, declarationDigest: sha2565(sourceDeclaration) });
11657
11913
  }
11658
11914
  if (!legacySelfNormalization) await assertSourceFleetPostcondition(normalizedRequest, sourceScope, candidates);
11659
11915
  if ((normalizedRequest.profile || normalizedRequest.agent || normalizedRequest.artifacts || normalizedRequest.orphanedOwnerRoots) && !legacySelfNormalization) {
@@ -11684,7 +11940,7 @@ async function planFleetNormalization(request) {
11684
11940
  packages,
11685
11941
  installedState
11686
11942
  };
11687
- return { ...planWithoutDigest, planDigest: sha2564(canonicalJson2(planWithoutDigest)) };
11943
+ return { ...planWithoutDigest, planDigest: sha2565(canonicalJson2(planWithoutDigest)) };
11688
11944
  }
11689
11945
  async function applyFleetNormalization(request, hooks = {}) {
11690
11946
  if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
@@ -11822,7 +12078,7 @@ async function assertNormalizationPostconditions(journal) {
11822
12078
  }
11823
12079
  for (const graphLock of journal.graphLocks) {
11824
12080
  const raw = await readOptionalRecord(graphLock.path);
11825
- const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
12081
+ const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
11826
12082
  if (digest2 !== graphLock.afterDigest) {
11827
12083
  throw new Error(`Fleet normalization graph-lock postcheck failed: ${graphLock.path}`);
11828
12084
  }
@@ -11894,17 +12150,17 @@ function normalizeRequest2(request) {
11894
12150
  ...profile ? { profile } : {},
11895
12151
  ...agent ? { agent } : {},
11896
12152
  ...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
11897
- ...request.globalRoot ? { globalRoot: resolve24(request.globalRoot) } : {}
12153
+ ...request.globalRoot ? { globalRoot: resolve25(request.globalRoot) } : {}
11898
12154
  };
11899
12155
  }
11900
12156
  function normalizeOrphanedOwnerRoot(value) {
11901
12157
  const root = value.trim();
11902
12158
  if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
11903
- return resolve24(root);
12159
+ return resolve25(root);
11904
12160
  }
11905
12161
  async function normalizationRoots(request) {
11906
12162
  const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
11907
- const source = request.from === "user" ? resolve24(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
12163
+ const source = request.from === "user" ? resolve25(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
11908
12164
  return { source, destination: destination.root };
11909
12165
  }
11910
12166
  async function withOrderedLocks(roots, fn) {
@@ -11912,7 +12168,7 @@ async function withOrderedLocks(roots, fn) {
11912
12168
  if (index === roots.length) return fn();
11913
12169
  const root = roots[index];
11914
12170
  return withFilesystemLock(
11915
- join44(root, ".agentwheel", "fleet-normalize.lock"),
12171
+ join45(root, ".agentwheel", "fleet-normalize.lock"),
11916
12172
  5e3,
11917
12173
  () => run(index + 1),
11918
12174
  "fleet normalization"
@@ -12011,10 +12267,10 @@ async function inspectInstalledState(source, destination, packageNames) {
12011
12267
  const destinationGraphAfter = destinationGraph?.lock ?? destinationGraphFromSource(sourceGraph.lock, destinationState.targetFingerprint);
12012
12268
  graphTransfers.push({
12013
12269
  sourceGraphLockPath: sourceGraph.path,
12014
- sourceGraphLockDigest: sha2564(canonicalJson2(sourceGraph.lock)),
12270
+ sourceGraphLockDigest: sha2565(canonicalJson2(sourceGraph.lock)),
12015
12271
  destinationGraphLockPath: destinationState.graphLockPath,
12016
- destinationGraphLockDigest: destinationGraph ? sha2564(canonicalJson2(destinationGraph.lock)) : null,
12017
- destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraphAfter)),
12272
+ destinationGraphLockDigest: destinationGraph ? sha2565(canonicalJson2(destinationGraph.lock)) : null,
12273
+ destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraphAfter)),
12018
12274
  targetKey: sourceGraph.targetKey,
12019
12275
  adapter: destinationState.adapter,
12020
12276
  targetFingerprint: destinationState.targetFingerprint
@@ -12118,7 +12374,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12118
12374
  let normalizedGraphCount = 0;
12119
12375
  for (const graph of graphCandidates) {
12120
12376
  const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
12121
- if (resolve24(destinationState.graphLockPath) === resolve24(graph.path)) {
12377
+ if (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
12122
12378
  const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
12123
12379
  if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
12124
12380
  assertCorrelatedGraphManifest(
@@ -12133,7 +12389,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12133
12389
  }
12134
12390
  }
12135
12391
  const legacyState = await legacyTargetStateForGraph(fleet, graph);
12136
- if (resolve24(legacyState.graphLockPath) !== resolve24(graph.path)) {
12392
+ if (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
12137
12393
  throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
12138
12394
  }
12139
12395
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
@@ -12241,7 +12497,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12241
12497
  )
12242
12498
  });
12243
12499
  if (!selectedArtifacts) {
12244
- const fullDigest = sha2564(canonicalJson2(graph.lock));
12500
+ const fullDigest = sha2565(canonicalJson2(graph.lock));
12245
12501
  const existingDestinationGraph = graphCandidates.find((candidate) => candidate.path === destinationState.graphLockPath);
12246
12502
  if (existingDestinationGraph) assertGraphIsSubset(graph, existingDestinationGraph);
12247
12503
  const destinationGraph = destinationGraphFromSource(graph.lock, destinationState.targetFingerprint);
@@ -12249,8 +12505,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12249
12505
  sourceGraphLockPath: graph.path,
12250
12506
  sourceGraphLockDigest: fullDigest,
12251
12507
  destinationGraphLockPath: destinationState.graphLockPath,
12252
- destinationGraphLockDigest: existingDestinationGraph ? sha2564(canonicalJson2(existingDestinationGraph.lock)) : null,
12253
- destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraph)),
12508
+ destinationGraphLockDigest: existingDestinationGraph ? sha2565(canonicalJson2(existingDestinationGraph.lock)) : null,
12509
+ destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraph)),
12254
12510
  targetKey: graph.targetKey,
12255
12511
  adapter: graph.adapter,
12256
12512
  targetFingerprint: destinationState.targetFingerprint
@@ -12308,7 +12564,7 @@ function targetKeysForAgent(scope, name) {
12308
12564
  async function runtimeRoots(scope) {
12309
12565
  const roots = /* @__PURE__ */ new Set([scope.root]);
12310
12566
  if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
12311
- roots.add(resolve24(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
12567
+ roots.add(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
12312
12568
  }
12313
12569
  for (const [name, agent] of Object.entries(scope.config.agents)) {
12314
12570
  if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
@@ -12326,17 +12582,17 @@ async function runtimeRoots(scope) {
12326
12582
  async function collectManifests(roots) {
12327
12583
  const found = /* @__PURE__ */ new Map();
12328
12584
  for (const root of roots) {
12329
- const dir = join44(root, ".agentwheel");
12585
+ const dir = join45(root, ".agentwheel");
12330
12586
  let names;
12331
12587
  try {
12332
- names = await readdir10(dir);
12588
+ names = await readdir11(dir);
12333
12589
  } catch (error) {
12334
12590
  if (isMissing(error)) continue;
12335
12591
  throw error;
12336
12592
  }
12337
12593
  for (const name of names.sort((a, b) => a.localeCompare(b))) {
12338
12594
  if (!name.endsWith(".install-manifest.json")) continue;
12339
- const path = join44(dir, name);
12595
+ const path = join45(dir, name);
12340
12596
  const raw = JSON.parse(await readFile27(path, "utf8"));
12341
12597
  const parsed = installManifestSchema.parse(raw);
12342
12598
  if (parsed.version !== 2) {
@@ -12345,14 +12601,14 @@ async function collectManifests(roots) {
12345
12601
  }
12346
12602
  continue;
12347
12603
  }
12348
- if (resolve24(parsed.targetRoot) !== resolve24(root)) {
12604
+ if (resolve25(parsed.targetRoot) !== resolve25(root)) {
12349
12605
  throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
12350
12606
  }
12351
12607
  const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
12352
12608
  installationType: parsed.installationType,
12353
12609
  stateKey: parsed.stateKey
12354
12610
  });
12355
- if (resolve24(expectedPath) !== resolve24(path)) {
12611
+ if (resolve25(expectedPath) !== resolve25(path)) {
12356
12612
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12357
12613
  }
12358
12614
  found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12373,7 +12629,7 @@ async function collectManifestPaths(paths) {
12373
12629
  installationType: parsed.installationType,
12374
12630
  stateKey: parsed.stateKey
12375
12631
  });
12376
- if (resolve24(expectedPath) !== resolve24(path)) {
12632
+ if (resolve25(expectedPath) !== resolve25(path)) {
12377
12633
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12378
12634
  }
12379
12635
  found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12392,14 +12648,14 @@ function assertNoForeignSamePathOwnership(manifests, renderedPaths, allowedOwner
12392
12648
  }
12393
12649
  }
12394
12650
  async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include) {
12395
- const root = join44(workspaceRoot, ".agentwheel", "locks");
12651
+ const root = join45(workspaceRoot, ".agentwheel", "locks");
12396
12652
  if (!await pathExists(root)) return [];
12397
12653
  const results = [];
12398
12654
  for (const path of await listFiles(root)) {
12399
12655
  if (!path.endsWith(".graph-lock.json")) continue;
12400
12656
  const lock = await readGraphLock(path);
12401
12657
  if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
12402
- const parts = relative8(root, path).split(/[\\/]/);
12658
+ const parts = relative9(root, path).split(/[\\/]/);
12403
12659
  if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
12404
12660
  const [targetKey2, adapter] = parts;
12405
12661
  if (targetKeys && !targetKeys.has(targetKey2)) continue;
@@ -12416,7 +12672,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
12416
12672
  const projection = relevantGraphProjection(lock, selected);
12417
12673
  results.push({
12418
12674
  ...candidate,
12419
- digest: sha2564(canonicalJson2(projection)),
12675
+ digest: sha2565(canonicalJson2(projection)),
12420
12676
  artifactIdentities: lock.canonical.artifacts.filter((artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(lock, owner, selected))).map(graphArtifactIdentity).sort()
12421
12677
  });
12422
12678
  }
@@ -12441,7 +12697,7 @@ async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
12441
12697
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
12442
12698
  if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
12443
12699
  const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
12444
- if (resolve24(stableState.graphLockPath) !== resolve24(graph.path)) return false;
12700
+ if (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
12445
12701
  const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
12446
12702
  return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
12447
12703
  }
@@ -12462,7 +12718,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
12462
12718
  baseDir: scope.root
12463
12719
  });
12464
12720
  const targetRoot = resolveConfigPath(agent.root, scope.root);
12465
- const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
12721
+ const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
12466
12722
  const stateKey = stateKeyFor(adapter.name, {
12467
12723
  installationType: agent.installationType,
12468
12724
  stateKey: agent.stateKey,
@@ -12530,7 +12786,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12530
12786
  ssh: void 0,
12531
12787
  ...agent?.stateKey ? { stateKey: agent.stateKey } : {}
12532
12788
  };
12533
- const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
12789
+ const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
12534
12790
  const targetIdentity = resolveTargetStateIdentity({
12535
12791
  targetFingerprintParts: fingerprintParts,
12536
12792
  workspaceRoot: scope.root,
@@ -12562,16 +12818,16 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12562
12818
  async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
12563
12819
  const stable = await targetStateForGraph(scope, graph, identityFleetId);
12564
12820
  let state;
12565
- if (resolve24(graph.path) === resolve24(stable.graphLockPath)) {
12821
+ if (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
12566
12822
  state = stable;
12567
12823
  } else {
12568
12824
  const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
12569
- if (resolve24(graph.path) !== resolve24(released.graphLockPath)) {
12825
+ if (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
12570
12826
  throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
12571
12827
  }
12572
12828
  state = released;
12573
12829
  }
12574
- const manifest = manifests.find((candidate) => resolve24(candidate.path) === resolve24(state.manifestPath));
12830
+ const manifest = manifests.find((candidate) => resolve25(candidate.path) === resolve25(state.manifestPath));
12575
12831
  if (!manifest) {
12576
12832
  throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
12577
12833
  }
@@ -12595,7 +12851,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
12595
12851
  ...current,
12596
12852
  stateKey,
12597
12853
  targetFingerprint,
12598
- graphLockPath: join44(dirname21(graph.path), `${targetFingerprint}.graph-lock.json`),
12854
+ graphLockPath: join45(dirname22(graph.path), `${targetFingerprint}.graph-lock.json`),
12599
12855
  manifestPath: installManifestPath(current.installRoot, current.adapter, {
12600
12856
  installationType: current.installationType,
12601
12857
  stateKey
@@ -12603,7 +12859,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
12603
12859
  };
12604
12860
  }
12605
12861
  function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
12606
- if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve24(manifest.manifest.targetRoot) !== resolve24(state.installRoot)) {
12862
+ if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve25(manifest.manifest.targetRoot) !== resolve25(state.installRoot)) {
12607
12863
  throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
12608
12864
  }
12609
12865
  const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
@@ -12611,7 +12867,7 @@ function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected)
12611
12867
  throw new Error(`Graph lock is not covered by selected install-manifest contributions: ${graph.path}`);
12612
12868
  }
12613
12869
  const graphArtifacts = new Set(graph.artifactIdentities);
12614
- const graphLockDigest = sha2564(canonicalJson2(graph.lock));
12870
+ const graphLockDigest = sha2565(canonicalJson2(graph.lock));
12615
12871
  for (const entry of entries) {
12616
12872
  if (!owners.has(entry.workspaceOwner)) {
12617
12873
  throw new Error(`Graph lock manifest owner mismatch at ${manifest.path}: ${entry.workspaceOwner}.`);
@@ -12660,7 +12916,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
12660
12916
  function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
12661
12917
  if (existing) {
12662
12918
  const manifest2 = existing.manifest;
12663
- if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve24(manifest2.targetRoot) !== state.installRoot) {
12919
+ if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve25(manifest2.targetRoot) !== state.installRoot) {
12664
12920
  throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
12665
12921
  }
12666
12922
  }
@@ -12720,7 +12976,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
12720
12976
  const content = await readFile27(path, "utf8");
12721
12977
  if (hasMergeRemovalContent(entry.mergeRemoval)) {
12722
12978
  assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
12723
- } else if (resolve24(sourcePath) === resolve24(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
12979
+ } else if (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
12724
12980
  assertExactMergeContribution({}, entry.mergeStrategy, content);
12725
12981
  } else {
12726
12982
  throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
@@ -12731,8 +12987,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
12731
12987
  await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
12732
12988
  }
12733
12989
  function renderedEntryPathForRoot(root, entryPath) {
12734
- const normalizedRoot = resolve24(root);
12735
- const candidate = resolve24(normalizedRoot, entryPath);
12990
+ const normalizedRoot = resolve25(root);
12991
+ const candidate = resolve25(normalizedRoot, entryPath);
12736
12992
  if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
12737
12993
  throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
12738
12994
  }
@@ -12823,8 +13079,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
12823
13079
  return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
12824
13080
  }
12825
13081
  function renderedEntryPath(manifest, entry) {
12826
- const root = resolve24(manifest.targetRoot);
12827
- const candidate = resolve24(root, entry.path);
13082
+ const root = resolve25(manifest.targetRoot);
13083
+ const candidate = resolve25(root, entry.path);
12828
13084
  if (candidate !== root && !candidate.startsWith(`${root}/`)) {
12829
13085
  throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
12830
13086
  }
@@ -12979,7 +13235,7 @@ async function buildJournalGraphLockStates(plan) {
12979
13235
  for (const transfer of plan.installedState.graphTransfers) {
12980
13236
  const sourceBefore = JSON.parse(await readFile27(transfer.sourceGraphLockPath, "utf8"));
12981
13237
  const sourceLock = await readGraphLock(transfer.sourceGraphLockPath);
12982
- const sourceDigest = sha2564(canonicalJson2(sourceLock));
13238
+ const sourceDigest = sha2565(canonicalJson2(sourceLock));
12983
13239
  if (sourceDigest !== transfer.sourceGraphLockDigest) {
12984
13240
  throw new Error(`Source graph lock changed after planning: ${transfer.sourceGraphLockPath}`);
12985
13241
  }
@@ -12997,9 +13253,9 @@ async function buildJournalGraphLockStates(plan) {
12997
13253
  continue;
12998
13254
  }
12999
13255
  const destinationBefore = await readOptionalRecord(transfer.destinationGraphLockPath);
13000
- const destinationBeforeDigest = destinationBefore ? sha2564(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
13256
+ const destinationBeforeDigest = destinationBefore ? sha2565(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
13001
13257
  const destinationAfter = destinationBefore ? await readGraphLock(transfer.destinationGraphLockPath) : destinationGraphFromSource(sourceLock, transfer.targetFingerprint);
13002
- const destinationAfterDigest = sha2564(canonicalJson2(destinationAfter));
13258
+ const destinationAfterDigest = sha2565(canonicalJson2(destinationAfter));
13003
13259
  if (destinationBeforeDigest !== transfer.destinationGraphLockDigest || destinationAfterDigest !== transfer.destinationGraphLockAfterDigest) {
13004
13260
  throw new Error(`Destination graph lock changed after planning: ${transfer.destinationGraphLockPath}`);
13005
13261
  }
@@ -13109,7 +13365,7 @@ async function assertJournalRecoverable(journal) {
13109
13365
  }
13110
13366
  for (const graphLock of journal.graphLocks) {
13111
13367
  const raw = await readOptionalRecord(graphLock.path);
13112
- const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
13368
+ const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
13113
13369
  if (digest2 !== graphLock.beforeDigest && digest2 !== graphLock.afterDigest) {
13114
13370
  throw new Error(`Cannot recover fleet normalization because graph lock changed outside the transaction: ${graphLock.path}`);
13115
13371
  }
@@ -13162,10 +13418,10 @@ function packageMap(config) {
13162
13418
  return result;
13163
13419
  }
13164
13420
  function configRevision(config) {
13165
- return sha2564(canonicalJson2(config));
13421
+ return sha2565(canonicalJson2(config));
13166
13422
  }
13167
13423
  function normalizationJournalPath(sourceRoot, destinationFleet) {
13168
- return join44(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
13424
+ return join45(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
13169
13425
  }
13170
13426
  function canonicalJson2(value) {
13171
13427
  return JSON.stringify(sortValue(value));
@@ -13177,8 +13433,8 @@ function sortValue(value) {
13177
13433
  }
13178
13434
  return value;
13179
13435
  }
13180
- function sha2564(value) {
13181
- return createHash13("sha256").update(value).digest("hex");
13436
+ function sha2565(value) {
13437
+ return createHash14("sha256").update(value).digest("hex");
13182
13438
  }
13183
13439
  function assertDigest(value) {
13184
13440
  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
@@ -13216,6 +13472,7 @@ var commandGovernance = {
13216
13472
  "package migrate": "governed-always",
13217
13473
  remember: "governed-always",
13218
13474
  "ownership handoff": "governed-unless-dry-run",
13475
+ "ownership recovery-plan": "incidental-cache",
13219
13476
  "ownership retire-stale": "governed-apply",
13220
13477
  "mcp retire": "governed-apply",
13221
13478
  eject: "governed-always",
@@ -13322,7 +13579,7 @@ fleetCommand.command("normalize").description("plan or apply duplicate desired-s
13322
13579
  });
13323
13580
  cacheCommand.command("prune").description("remove old Git source snapshots while preserving locked commits").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit workspace root").option("--keep <count>", "newest snapshots to retain per source", parsePositiveInteger, 3).option("--apply", "delete the selected snapshots; without this flag only preview", false).action(async (options) => {
13324
13581
  const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
13325
- const cacheRoot = join45(targetRoot, ".agentwheel", "cache");
13582
+ const cacheRoot = join46(targetRoot, ".agentwheel", "cache");
13326
13583
  const result = await pruneGitCache(cacheRoot, { keepSnapshots: options.keep, dryRun: !options.apply });
13327
13584
  const verb = options.apply ? "Removed" : "Would remove";
13328
13585
  for (const path of result.removedPaths) console.log(`${verb} ${path}`);
@@ -13340,7 +13597,7 @@ program.command("list").description("list artifacts exposed by a package source"
13340
13597
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13341
13598
  const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
13342
13599
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
13343
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join45(targetRoot, ".agentwheel", "cache") }))));
13600
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
13344
13601
  try {
13345
13602
  const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
13346
13603
  for (const artifact of artifacts) {
@@ -13409,7 +13666,7 @@ program.command("try").description("read and validate one skill for the current
13409
13666
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13410
13667
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
13411
13668
  const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, {
13412
- cacheRoot: join45(targetRoot, ".agentwheel", "cache")
13669
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache")
13413
13670
  }))));
13414
13671
  try {
13415
13672
  const trial = await createSkillTrial(driver, resolved, selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry));
@@ -13431,7 +13688,7 @@ program.command("scan").description("scan a package source for validation findin
13431
13688
  const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
13432
13689
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13433
13690
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
13434
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join45(targetRoot, ".agentwheel", "cache") }))));
13691
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
13435
13692
  try {
13436
13693
  const result = await driver.scan(resolved);
13437
13694
  if (result.findings.length === 0) {
@@ -13636,7 +13893,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
13636
13893
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
13637
13894
  if (options.toFleet) {
13638
13895
  const fleet = await showRegisteredFleet(options.toFleet);
13639
- if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
13896
+ if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
13640
13897
  throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
13641
13898
  }
13642
13899
  }
@@ -13662,11 +13919,85 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
13662
13919
  console.log(`Manifest revision: ${result.manifestRevision}`);
13663
13920
  console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
13664
13921
  });
13922
+ ownershipCommand.command("recovery-plan").description("write a read-only per-path legacy ownership evidence report").requiredOption("--fleet <id>", "registered Fleet containing the current source graph").requiredOption("--report <path>", "new private JSON report path").option("--agent <name>", "one named Fleet target").option("--profile <name>", "Fleet profile resolving to one target").action(async (options) => {
13923
+ const targets = await resolveCliTargets({ fleet: options.fleet, agent: options.agent, profile: options.profile });
13924
+ if (targets.length !== 1) throw new Error(`Recovery planning requires one Fleet target, found ${targets.length}.`);
13925
+ const target = targets[0];
13926
+ if (target.fleetId !== options.fleet) throw new Error("Resolved target does not belong to the selected Fleet.");
13927
+ const adapter = await resolveAdapterForTarget(target, {});
13928
+ const transport = transportForTarget(target);
13929
+ const requestedReport = normalizeCliPath(options.report);
13930
+ const reportPath = join46(await realpath2(dirname23(requestedReport)), basename21(requestedReport));
13931
+ const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
13932
+ if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute4(reportInsideTarget)) {
13933
+ throw new Error("Recovery report must be outside the runtime target root.");
13934
+ }
13935
+ const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
13936
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
13937
+ if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
13938
+ const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
13939
+ const priorTmpdir = process.env.TMPDIR;
13940
+ try {
13941
+ process.env.TMPDIR = stage;
13942
+ const graphPlan = await createGraphSourcePlan({
13943
+ roots: config.packages.map((pkg) => ({
13944
+ rootId: pkg.name,
13945
+ source: pkg.source,
13946
+ mode: pkg.mode,
13947
+ version: pkg.version,
13948
+ ref: pkg.requestedRef,
13949
+ select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
13950
+ selection: pkg.selection,
13951
+ aliases: pkg.aliases,
13952
+ overrides: pkg.overrides,
13953
+ includeSuggestions: pkg.withSuggestions,
13954
+ suggestionAliases: pkg.suggestions,
13955
+ useLock: false
13956
+ })),
13957
+ targetRoot: target.targetRoot,
13958
+ workspaceRoot: target.workspaceRoot,
13959
+ fleetId: target.fleetId,
13960
+ adapter,
13961
+ transport,
13962
+ targetKey: targetKeyForTarget(target, adapter.name),
13963
+ targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
13964
+ installationType,
13965
+ stateKey: target.stateKey,
13966
+ readOnly: true,
13967
+ yes: true,
13968
+ freshGraphOnly: true,
13969
+ deferForeignStateCheck: true,
13970
+ cacheRoot: join46(stage, "source-cache"),
13971
+ registryCachePath: join46(stage, "registry.json")
13972
+ });
13973
+ const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
13974
+ const report = await planLegacyRecovery({
13975
+ graphPlan,
13976
+ installRoot,
13977
+ workspaceRoot: target.workspaceRoot,
13978
+ fleetId: target.fleetId,
13979
+ explicitStateKey: target.stateKey,
13980
+ targetKey: targetKeyForTarget(target, adapter.name),
13981
+ adapter: adapter.name,
13982
+ installationType,
13983
+ historicalLocks: await inventoryHistoricalLocks(target.workspaceRoot),
13984
+ transport
13985
+ });
13986
+ await writeFile13(reportPath, `${JSON.stringify(report, null, 2)}
13987
+ `, { mode: 384, flag: "wx" });
13988
+ console.log(`Recovery report: ${reportPath}`);
13989
+ console.log(JSON.stringify(report.counts));
13990
+ } finally {
13991
+ if (priorTmpdir === void 0) delete process.env.TMPDIR;
13992
+ else process.env.TMPDIR = priorTmpdir;
13993
+ await rm10(stage, { recursive: true, force: true });
13994
+ }
13995
+ });
13665
13996
  ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
13666
13997
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
13667
13998
  const fleet = await showRegisteredFleet(options.fleet);
13668
13999
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
13669
- if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
14000
+ if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
13670
14001
  throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
13671
14002
  }
13672
14003
  const targets = await resolveCliTargets(normalizedOptions);
@@ -13674,7 +14005,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
13674
14005
  throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
13675
14006
  }
13676
14007
  const target = targets[0];
13677
- if (target.fleetId !== options.fleet || resolve25(target.workspaceRoot) !== resolve25(fleet.root)) {
14008
+ if (target.fleetId !== options.fleet || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
13678
14009
  throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
13679
14010
  }
13680
14011
  const state = await resolveCliTargetState(target, normalizedOptions);
@@ -13832,7 +14163,7 @@ journalCommand.command("list").description("show pending apply journals for reso
13832
14163
  if (!journal) continue;
13833
14164
  pending += 1;
13834
14165
  console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
13835
- console.log(` journal: ${join45(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
14166
+ console.log(` journal: ${join46(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
13836
14167
  console.log(` stateKey: ${state.state.stateKey}`);
13837
14168
  console.log(` createdAt: ${journal.createdAt}`);
13838
14169
  console.log(` updatedAt: ${journal.updatedAt}`);
@@ -14168,14 +14499,14 @@ async function packageEntryFromSource(source, targetRoot, options) {
14168
14499
  );
14169
14500
  }
14170
14501
  const bundle = options.deferGraphRendering ? await stageSourceRaw(driver, resolvedSource, {
14171
- cacheRoot: join45(targetRoot, ".agentwheel", "cache"),
14502
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
14172
14503
  mode: options.mode,
14173
14504
  ref: initialVersion?.ref,
14174
14505
  frozenLock: lockMode
14175
14506
  }) : await stageSource(driver, resolvedSource, {
14176
14507
  workspaceRoot: targetRoot,
14177
14508
  adapter,
14178
- cacheRoot: join45(targetRoot, ".agentwheel", "cache"),
14509
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
14179
14510
  mode: options.mode,
14180
14511
  ref: initialVersion?.ref,
14181
14512
  frozenLock: lockMode,
@@ -14756,7 +15087,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
14756
15087
  selectedPreviousNodeIds,
14757
15088
  selectedRootIds
14758
15089
  );
14759
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15090
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14760
15091
  return {
14761
15092
  ...result,
14762
15093
  bundle: { ...result.bundle, graphLock },
@@ -14882,7 +15213,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
14882
15213
  function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
14883
15214
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
14884
15215
  const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
14885
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15216
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14886
15217
  return {
14887
15218
  ...scoped,
14888
15219
  bundle: { ...scoped.bundle, graphLock },
@@ -14893,7 +15224,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
14893
15224
  function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
14894
15225
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
14895
15226
  const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
14896
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15227
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14897
15228
  return {
14898
15229
  ...scoped,
14899
15230
  bundle: { ...scoped.bundle, graphLock },
@@ -15188,7 +15519,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
15188
15519
  selectedPreviousNodeIds,
15189
15520
  /* @__PURE__ */ new Set([rootId])
15190
15521
  );
15191
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15522
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15192
15523
  return {
15193
15524
  ...scoped,
15194
15525
  bundle: { ...scoped.bundle, graphLock },
@@ -15260,7 +15591,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
15260
15591
  artifactType: entry.artifactType,
15261
15592
  artifactName: entry.artifactName,
15262
15593
  kind: entry.kind,
15263
- destPath: operation?.destPath ?? join45(targetRoot, entry.path),
15594
+ destPath: operation?.destPath ?? join46(targetRoot, entry.path),
15264
15595
  relativeDestPath: entry.path,
15265
15596
  desiredHash: entry.sourceHash,
15266
15597
  currentHash: operation?.currentHash ?? entry.hash,
@@ -15526,7 +15857,7 @@ async function resolveCliTargetState(target, options) {
15526
15857
  migration: prior.migration,
15527
15858
  targetStateFilePreconditions: {
15528
15859
  graphLockPath: stableGraphLockPath,
15529
- graphLockRevision: stableLock ? createHash14("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
15860
+ graphLockRevision: stableLock ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
15530
15861
  sourceLockRevision: computeSourceLockRevision(stableSourceLock)
15531
15862
  }
15532
15863
  };
@@ -15549,7 +15880,7 @@ async function collectTargetStatus(target, options) {
15549
15880
  const pending = await collectPendingInstallWork(target, options);
15550
15881
  const foreignObservations = pending.foreignStateObservations;
15551
15882
  const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
15552
- const observedManifestRevision = foreignObservations.length > 0 ? createHash14("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
15883
+ const observedManifestRevision = foreignObservations.length > 0 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
15553
15884
  observation.fileName,
15554
15885
  observation.manifestRevision
15555
15886
  ]).sort())).digest("hex") : null;
@@ -15796,7 +16127,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
15796
16127
  `Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
15797
16128
  );
15798
16129
  }
15799
- const memberRoot = resolve25(workspaceRoot, member.workspace);
16130
+ const memberRoot = resolve26(workspaceRoot, member.workspace);
15800
16131
  const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
15801
16132
  if (memberPolicy?.revisioning.mode === "commit-after-verify") {
15802
16133
  throw new Error(
@@ -16032,12 +16363,12 @@ async function printDoctor(target, options) {
16032
16363
  const requestedSkills = doctorSkillRequests(target, options);
16033
16364
  const skills = [];
16034
16365
  for (const request of requestedSkills) {
16035
- const skillPath = join45(state.installRoot, targetMapping.dest, request.name);
16366
+ const skillPath = join46(state.installRoot, targetMapping.dest, request.name);
16036
16367
  const exists = await pathExists(skillPath);
16037
16368
  const manifestEntry = manifest?.entries.find((entry) => {
16038
16369
  if (entry.artifactType !== "skills") return false;
16039
16370
  const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
16040
- return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join45(targetMapping.dest, request.name);
16371
+ return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join46(targetMapping.dest, request.name);
16041
16372
  });
16042
16373
  const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
16043
16374
  skills.push({
@@ -16117,7 +16448,7 @@ function doctorSkillLabel(name) {
16117
16448
  return `${name} skill`;
16118
16449
  }
16119
16450
  function isSyncwheelWorkspace(targetRoot) {
16120
- return existsSync(join45(targetRoot, ".syncwheel", "manifest.json"));
16451
+ return existsSync(join46(targetRoot, ".syncwheel", "manifest.json"));
16121
16452
  }
16122
16453
  function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
16123
16454
  const args = [
@@ -16280,14 +16611,14 @@ function looksLikeSourceSpecifier(value) {
16280
16611
  }
16281
16612
  function normalizeCliPath(value) {
16282
16613
  if (value === "~") return homedir12();
16283
- if (value.startsWith("~/")) return resolve25(homedir12(), value.slice(2));
16284
- return resolve25(value);
16614
+ if (value.startsWith("~/")) return resolve26(homedir12(), value.slice(2));
16615
+ return resolve26(value);
16285
16616
  }
16286
16617
  function shellQuoteArgument(value) {
16287
16618
  return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
16288
16619
  }
16289
16620
  function isHomePath(path) {
16290
- return resolve25(path) === resolve25(homedir12());
16621
+ return resolve26(path) === resolve26(homedir12());
16291
16622
  }
16292
16623
  function adapterListFromOption(adapter) {
16293
16624
  if (!adapter) return [];
@@ -16342,11 +16673,11 @@ function filterUninstallPlanBySelection(plan, selected) {
16342
16673
  };
16343
16674
  }
16344
16675
  async function initPackage(root) {
16345
- await mkdir16(join45(root, "instructions"), { recursive: true });
16346
- await mkdir16(join45(root, "rules"), { recursive: true });
16347
- await mkdir16(join45(root, "skills"), { recursive: true });
16348
- const manifestPath = join45(root, "openpack.json");
16349
- const instructionsPath = join45(root, "instructions", "AGENTS.md");
16676
+ await mkdir16(join46(root, "instructions"), { recursive: true });
16677
+ await mkdir16(join46(root, "rules"), { recursive: true });
16678
+ await mkdir16(join46(root, "skills"), { recursive: true });
16679
+ const manifestPath = join46(root, "openpack.json");
16680
+ const instructionsPath = join46(root, "instructions", "AGENTS.md");
16350
16681
  const manifest = {
16351
16682
  schemaVersion: CURRENT_OPENPACK_SCHEMA_VERSION,
16352
16683
  name: "example/agentwheel-package",
@@ -16364,7 +16695,7 @@ async function initPackage(root) {
16364
16695
  await writeFile13(instructionsPath, "# Agent Instructions\n", "utf8");
16365
16696
  }
16366
16697
  async function defaultBootstrapPackage(_root) {
16367
- const packageRoot = await findAgentwheelPackageRoot(dirname22(fileURLToPath3(import.meta.url)));
16698
+ const packageRoot = await findAgentwheelPackageRoot(dirname23(fileURLToPath3(import.meta.url)));
16368
16699
  if (!packageRoot) return void 0;
16369
16700
  return {
16370
16701
  name: "agentwheel",
@@ -16408,10 +16739,10 @@ function withFleetExample(config) {
16408
16739
  };
16409
16740
  }
16410
16741
  async function findAgentwheelPackageRoot(start) {
16411
- let current = resolve25(start);
16742
+ let current = resolve26(start);
16412
16743
  while (true) {
16413
16744
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
16414
- const parent = dirname22(current);
16745
+ const parent = dirname23(current);
16415
16746
  if (parent === current) return void 0;
16416
16747
  current = parent;
16417
16748
  }
@@ -16536,14 +16867,14 @@ async function governedCommandContext(command, options) {
16536
16867
  const kind = String(command.args[0] ?? "workspace");
16537
16868
  return {
16538
16869
  workspaceRoot: workspaceRoot2,
16539
- anticipatedPaths: kind === "package" ? [join45(targetRoot, "openpack.json"), join45(targetRoot, "instructions", "AGENTS.md")] : [workspaceConfigPath(targetRoot)]
16870
+ anticipatedPaths: kind === "package" ? [join46(targetRoot, "openpack.json"), join46(targetRoot, "instructions", "AGENTS.md")] : [workspaceConfigPath(targetRoot)]
16540
16871
  };
16541
16872
  }
16542
16873
  if (path === "package migrate") {
16543
16874
  const packageRoot = normalizeTargetRoot(String(command.args[0] ?? "."));
16544
16875
  return {
16545
16876
  workspaceRoot: await findExistingWorkspaceRoot(packageRoot) ?? packageRoot,
16546
- anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) => join45(packageRoot, name))
16877
+ anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) => join46(packageRoot, name))
16547
16878
  };
16548
16879
  }
16549
16880
  const source = typeof command.args[0] === "string" ? command.args[0] : void 0;
@@ -16555,7 +16886,7 @@ async function governedCommandContext(command, options) {
16555
16886
  const anticipatedPaths = [];
16556
16887
  if (path === "add") anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
16557
16888
  if (path === "remember" && options.runtime) {
16558
- anticipatedPaths.push(join45(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
16889
+ anticipatedPaths.push(join46(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
16559
16890
  }
16560
16891
  if (path === "uninstall" && source) anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
16561
16892
  return {