agentwheel 0.20.9 → 0.20.11

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-A44XYLUT.js";
40
41
  import {
41
42
  CURRENT_WORKSPACE_SCHEMA_VERSION,
42
43
  GovernedMutation,
@@ -97,10 +98,10 @@ import {
97
98
  workspaceSelectionImportSchema,
98
99
  writeApplyJournal,
99
100
  writeWorkspaceConfig
100
- } from "./chunk-SHPNLNBR.js";
101
+ } from "./chunk-FP26KSBM.js";
101
102
  import {
102
103
  inferSourceDriverName
103
- } from "./chunk-EMDIG24I.js";
104
+ } from "./chunk-U2GRG4UI.js";
104
105
  import {
105
106
  hashPath,
106
107
  isAlreadyExists,
@@ -109,14 +110,14 @@ import {
109
110
  pathExists,
110
111
  withFilesystemLock,
111
112
  writeJsonAtomic
112
- } from "./chunk-7VPI5J5Y.js";
113
+ } from "./chunk-GE7F76AC.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 {
@@ -5788,7 +5789,7 @@ var RegistryClient = class {
5788
5789
  }
5789
5790
  };
5790
5791
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
5791
- const { isExplicitSource } = await import("./identify-D64HZTUT.js");
5792
+ const { isExplicitSource } = await import("./identify-O54QXPQR.js");
5792
5793
  if (await isExplicitSource(source)) return { source };
5793
5794
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
5794
5795
  if (!entry) {
@@ -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 = [];
@@ -8229,16 +8233,18 @@ async function createGraphSourcePlan(options) {
8229
8233
  graphLockPath,
8230
8234
  stableManifest,
8231
8235
  stableLock: stableLock2,
8232
- transport
8236
+ transport,
8237
+ recoverLegacyState: options.recoverLegacyState,
8238
+ warn
8233
8239
  });
8234
8240
  return { priorState: priorState2, stableLock: stableLock2, stableSourceLock: stableSourceLock2 };
8235
8241
  };
8236
- const { priorState, stableLock, stableSourceLock } = await readPriorState();
8242
+ const { priorState, stableLock, stableSourceLock } = options.freshGraphOnly ? { priorState: {}, stableLock: void 0, stableSourceLock: void 0 } : await readPriorState();
8237
8243
  const previousLock = priorState.graphLock;
8238
- const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
8244
+ const registryClient = new RegistryClient({ workspaceRoot, cachePath: options.registryCachePath, offline: lockMode, offlineLabel: lockLabel, warn });
8239
8245
  const graph = await resolveDependencyGraph(options.roots, {
8240
8246
  workspaceRoot,
8241
- cacheRoot: join33(workspaceRoot, ".agentwheel", "cache"),
8247
+ cacheRoot: options.cacheRoot ?? join33(workspaceRoot, ".agentwheel", "cache"),
8242
8248
  registryClient,
8243
8249
  noDeps: options.noDeps,
8244
8250
  includeSuggestions: options.includeSuggestions,
@@ -8491,7 +8497,10 @@ function validateJournalManifestEnvelope(value, options, stateKey, journalPath)
8491
8497
  return manifest;
8492
8498
  }
8493
8499
  async function resolvePriorTargetState(options) {
8494
- const candidates = await discoverLegacyStateCandidates(options);
8500
+ const candidates = await discoverLegacyStateCandidates({
8501
+ ...options,
8502
+ recoverLegacyState: options.recoverLegacyState === true || options.stableManifest !== void 0
8503
+ });
8495
8504
  const correlated = candidates.filter((candidate) => candidate.manifest);
8496
8505
  if (options.stableManifest) {
8497
8506
  for (const candidate of correlated) {
@@ -8559,34 +8568,28 @@ async function discoverLegacyStateCandidates(options) {
8559
8568
  const fingerprint = name.slice(0, -suffix.length);
8560
8569
  if (!/^[a-f0-9]{64}$/u.test(fingerprint)) continue;
8561
8570
  const graphLockPath = join33(directory, name);
8562
- let graphLock;
8563
8571
  try {
8572
+ let graphLock;
8564
8573
  graphLock = await readGraphLock(graphLockPath);
8565
- } catch (error) {
8566
- throw new Error(
8567
- `Legacy graph lock at ${graphLockPath} is invalid or unsupported: ` + (error instanceof Error ? error.message : String(error))
8568
- );
8569
- }
8570
- if (graphLock.canonical.targetFingerprint !== fingerprint) {
8571
- throw new Error(
8572
- `Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
8573
- );
8574
- }
8575
- if (options.transport.kind === "ssh") {
8576
- throw new Error(
8577
- `Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
8578
- );
8579
- }
8580
- const graphLockDigest = digestGraphLock(graphLock);
8581
- const candidateStateKey = stateKeyFor(options.adapter, {
8582
- installationType: options.installationType,
8583
- stateKey: options.explicitStateKey,
8584
- targetFingerprint: fingerprint,
8585
- fleetId: options.fleetId
8586
- });
8587
- let manifest;
8588
- let sourceLock;
8589
- try {
8574
+ if (graphLock.canonical.targetFingerprint !== fingerprint) {
8575
+ throw new Error(
8576
+ `Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
8577
+ );
8578
+ }
8579
+ if (options.transport.kind === "ssh") {
8580
+ throw new Error(
8581
+ `Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
8582
+ );
8583
+ }
8584
+ const graphLockDigest = digestGraphLock(graphLock);
8585
+ const candidateStateKey = stateKeyFor(options.adapter, {
8586
+ installationType: options.installationType,
8587
+ stateKey: options.explicitStateKey,
8588
+ targetFingerprint: fingerprint,
8589
+ fleetId: options.fleetId
8590
+ });
8591
+ let manifest;
8592
+ let sourceLock;
8590
8593
  manifest = await readInstallManifest(options.installRoot, options.adapter, options.transport, {
8591
8594
  installationType: options.installationType,
8592
8595
  stateKey: candidateStateKey
@@ -8595,32 +8598,32 @@ async function discoverLegacyStateCandidates(options) {
8595
8598
  installationType: options.installationType,
8596
8599
  stateKey: candidateStateKey
8597
8600
  });
8598
- } catch (error) {
8599
- throw new Error(
8600
- `Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error))
8601
- );
8602
- }
8603
- if (!manifest) {
8604
- if (sourceLock) {
8605
- throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
8601
+ if (!manifest) {
8602
+ if (sourceLock) {
8603
+ throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
8604
+ }
8605
+ candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
8606
+ continue;
8606
8607
  }
8607
- candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
8608
- continue;
8608
+ const validated = validateLegacyManifestCandidate(manifest, {
8609
+ ...options,
8610
+ candidateStateKey,
8611
+ graphLockDigest,
8612
+ graphLockPath
8613
+ });
8614
+ candidates.push({
8615
+ stateKey: candidateStateKey,
8616
+ graphLockPath,
8617
+ graphLockDigest,
8618
+ graphLock,
8619
+ manifest: validated,
8620
+ sourceLock
8621
+ });
8622
+ } catch (error) {
8623
+ const message = `Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error));
8624
+ if (!options.recoverLegacyState) throw new Error(message);
8625
+ options.warn?.(`${message} Preserving it outside the recovered stable state.`);
8609
8626
  }
8610
- const validated = validateLegacyManifestCandidate(manifest, {
8611
- ...options,
8612
- candidateStateKey,
8613
- graphLockDigest,
8614
- graphLockPath
8615
- });
8616
- candidates.push({
8617
- stateKey: candidateStateKey,
8618
- graphLockPath,
8619
- graphLockDigest,
8620
- graphLock,
8621
- manifest: validated,
8622
- sourceLock
8623
- });
8624
8627
  }
8625
8628
  return candidates;
8626
8629
  }
@@ -10149,10 +10152,262 @@ function canonicalJson(value) {
10149
10152
  return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
10150
10153
  }
10151
10154
 
10155
+ // src/lifecycle/legacy-recovery-plan.ts
10156
+ import { createHash as createHash10 } from "crypto";
10157
+ import { readdir as readdir10 } from "fs/promises";
10158
+ import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
10159
+ async function inventoryHistoricalLocks(fleetRoot) {
10160
+ const root = join39(fleetRoot, ".agentwheel", "locks");
10161
+ const found = [];
10162
+ async function visit(dir) {
10163
+ for (const entry of await readdir10(dir, { withFileTypes: true }).catch((error) => {
10164
+ if (error.code === "ENOENT") return [];
10165
+ throw error;
10166
+ })) {
10167
+ const path = join39(dir, entry.name);
10168
+ if (entry.isDirectory()) await visit(path);
10169
+ else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) {
10170
+ const lock = await readGraphLock(path);
10171
+ found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
10172
+ }
10173
+ }
10174
+ }
10175
+ await visit(root);
10176
+ return found.sort((a, b) => a.path.localeCompare(b.path));
10177
+ }
10178
+ async function planLegacyRecovery(input) {
10179
+ const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
10180
+ assertCompleteGraphPlan(graphPlan);
10181
+ const binding = {
10182
+ targetLockDir: dirname21(graphPlan.graphLockPath),
10183
+ stableLockPath: graphPlan.graphLockPath,
10184
+ stableStateKey: graphPlan.plan.stateKey,
10185
+ currentFingerprint: graphPlan.targetFingerprint,
10186
+ expectedOwner: workspaceOwnerForRoot(input.workspaceRoot, input.fleetId),
10187
+ installRoot,
10188
+ adapter,
10189
+ installationType,
10190
+ fleetId: input.fleetId,
10191
+ explicitStateKey: input.explicitStateKey,
10192
+ transportKind: transport.kind
10193
+ };
10194
+ const manifests = await listInstallManifests(installRoot, adapter, transport);
10195
+ const paths = /* @__PURE__ */ new Set();
10196
+ const desired = /* @__PURE__ */ new Map();
10197
+ const nodeHashes = new Map(graphPlan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node.sourceHash]));
10198
+ for (const op of graphPlan.plan.operations) {
10199
+ if (!op.desiredHash || op.action === "remove" || op.action === "keep") continue;
10200
+ const path = relative8(installRoot, op.destPath);
10201
+ if (path.startsWith("..") || path === "") throw new Error(`Planner destination escapes target: ${op.destPath}`);
10202
+ paths.add(path);
10203
+ const items = desired.get(path) ?? [];
10204
+ items.push(op);
10205
+ desired.set(path, items);
10206
+ }
10207
+ for (const item of manifests) for (const entry of item.manifest.entries) paths.add(entry.path);
10208
+ const resolutionWarnings = graphPlan.warnings.filter((warning) => !/^skip (?:artifact|dependency) .+ \((?:selected but )?not targeted: runtimes=\[[^\]]+\]\)$/.test(warning));
10209
+ const complete = resolutionWarnings.length === 0;
10210
+ const output = [];
10211
+ for (const path of [...paths].sort()) {
10212
+ const allClaims = manifests.flatMap((item) => item.manifest.entries.filter((entry) => entry.path === path).map((entry) => claimFor(item, entry, historicalLocks, binding)));
10213
+ const claims = allClaims.filter((claim) => claim.scope !== "foreign");
10214
+ const foreignClaims = allClaims.filter((claim) => claim.scope === "foreign");
10215
+ const current = desired.get(path) ?? [];
10216
+ const currentHashes = [...new Set(current.map((item) => item.desiredHash))];
10217
+ const currentSourceHashes = [...new Set(current.map((item) => item.graphNodeId ? nodeHashes.get(item.graphNodeId) : void 0).filter((value) => Boolean(value)))].sort();
10218
+ const currentNodeIds = [...new Set(current.map((item) => item.graphNodeId).filter((value) => Boolean(value)))].sort();
10219
+ const currentSelectors = [...new Set(current.map((item) => item.logicalSelector).filter((value) => Boolean(value)))].sort();
10220
+ const currentOwners = [...new Set(current.flatMap((item) => item.owners ?? []))].sort();
10221
+ const livePath = resolve22(installRoot, path);
10222
+ if (relative8(installRoot, livePath).startsWith("..")) throw new Error(`Manifest path escapes target: ${path}`);
10223
+ const liveHash = await transport.pathExists(livePath) ? await transport.hashPath(livePath) : null;
10224
+ const reasons = [];
10225
+ const currentResults = await Promise.all(current.map((op) => verifyCurrentOperation(op, liveHash, transport)));
10226
+ const currentGraphStatus = current.length === 0 ? "absent" : currentResults.every((result) => result === "exact-match") ? "exact-match" : currentResults.some((result) => result === "unverifiable") ? "unverifiable" : "mismatch";
10227
+ if (currentHashes.length > 1 && current.some((op) => !op.mergeStrategy && op.mode !== "managed-block")) {
10228
+ reasons.push("current graph has divergent hashes for the destination");
10229
+ }
10230
+ if (currentGraphStatus === "mismatch") reasons.push("live contribution differs from current source");
10231
+ if (currentGraphStatus === "unverifiable") reasons.push("current contribution could not be verified");
10232
+ if (current.some((op) => op.action === "conflict" || op.action === "drift")) reasons.push("install planner has a blocking operation");
10233
+ if (!complete) reasons.push("current source graph has resolution warnings; completeness unproven");
10234
+ if (claims.some((claim) => claim.scope === "unbound" || claim.evidence.length)) reasons.push("historical provenance unresolved");
10235
+ if (foreignClaims.length) reasons.push("foreign manifest claim conflicts with destination");
10236
+ if (new Set(claims.map((claim) => `${claim.owner}:${claim.hash}:${claim.sourceHash}:${claim.graphLockDigest ?? ""}`)).size > 1) {
10237
+ reasons.push("competing same-target claims diverge");
10238
+ }
10239
+ if (current.length === 0 && claims.length === 0) reasons.push("no same-target historical claim");
10240
+ if (current.length === 0) {
10241
+ for (const claim of claims) {
10242
+ if (claim.scope !== "same-target") continue;
10243
+ const item = manifests.find((manifest) => manifest.path === claim.manifestPath);
10244
+ const entry = item.manifest.entries.find((candidate) => candidate.path === path && candidate.sourceHash === claim.sourceHash);
10245
+ if (!await verifyHistoricalEntry(entry, livePath, liveHash, transport)) {
10246
+ reasons.push("live contribution differs from same-target historical claim");
10247
+ break;
10248
+ }
10249
+ }
10250
+ }
10251
+ const category = reasons.length ? "unresolved" : currentGraphStatus === "exact-match" ? "current-graph-exact-match" : "retirement-candidate";
10252
+ output.push({
10253
+ path,
10254
+ category,
10255
+ reasons,
10256
+ currentHash: currentHashes.length === 1 ? currentHashes[0] : null,
10257
+ currentHashes: currentHashes.sort(),
10258
+ currentSourceHashes,
10259
+ currentNodeIds,
10260
+ currentSelectors,
10261
+ currentOwners,
10262
+ currentGraphStatus,
10263
+ liveHash,
10264
+ claims,
10265
+ foreignClaims
10266
+ });
10267
+ }
10268
+ const counts = { "current-graph-exact-match": 0, "retirement-candidate": 0, unresolved: 0 };
10269
+ const currentGraphCounts = { "exact-match": 0, mismatch: 0, absent: 0, unverifiable: 0 };
10270
+ for (const path of output) counts[path.category]++;
10271
+ for (const path of output) currentGraphCounts[path.currentGraphStatus]++;
10272
+ return {
10273
+ targetRoot: installRoot,
10274
+ targetKey: targetKey2,
10275
+ adapter,
10276
+ installationType,
10277
+ currentGraphLockDigest: graphPlan.graphLockDigest,
10278
+ roots: graphPlan.graph.roots.map((root) => ({ rootId: root.rootId, source: root.source, graphNodeId: root.graphNodeId, selected: root.selected })),
10279
+ resolutionWarnings,
10280
+ complete,
10281
+ paths: output,
10282
+ counts,
10283
+ currentGraphCounts
10284
+ };
10285
+ }
10286
+ function claimFor(item, entry, locks, binding) {
10287
+ const digest2 = "graphLockDigest" in entry ? entry.graphLockDigest : void 0;
10288
+ const nodeId = "graphNodeId" in entry ? entry.graphNodeId : void 0;
10289
+ const selector = "logicalSelector" in entry ? entry.logicalSelector : void 0;
10290
+ const matches = locks.filter((candidate) => candidate.digest === digest2);
10291
+ const evidence = [];
10292
+ const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
10293
+ const sameRoot = binding.transportKind === "ssh" ? posix3.normalize(item.manifest.targetRoot) === posix3.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
10294
+ const inTargetDir = matches.filter((candidate) => resolve22(dirname21(candidate.path)) === resolve22(binding.targetLockDir));
10295
+ const boundLocks = inTargetDir.filter((candidate) => lockBindsState(candidate, item, binding));
10296
+ if (owner !== binding.expectedOwner) evidence.push("workspace owner differs from selected Fleet target");
10297
+ if (item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType) evidence.push("installation type differs or manifest is legacy v1");
10298
+ if (!sameRoot) evidence.push("manifest target root differs from selected Fleet target");
10299
+ if (item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter)) {
10300
+ evidence.push("manifest state key or adapter differs from filename");
10301
+ }
10302
+ if (!digest2) evidence.push("historical graph-lock digest missing");
10303
+ else if (matches.length === 0) evidence.push("historical graph-lock digest has no matching lock");
10304
+ if (matches.length && !inTargetDir.length) evidence.push("claim belongs to another Fleet target graph");
10305
+ if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
10306
+ if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
10307
+ evidence.push("historical graph-lock selector/node does not cover claim");
10308
+ } else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector && artifact.hash === entry.sourceHash))) {
10309
+ evidence.push("historical graph-lock artifact hash differs from manifest source hash");
10310
+ }
10311
+ 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;
10312
+ const scope = foreign ? "foreign" : boundLocks.length && evidence.length === 0 ? "same-target" : "unbound";
10313
+ return {
10314
+ scope,
10315
+ manifestPath: item.path,
10316
+ stateKey: item.stateKey,
10317
+ manifestStateKey: item.manifest.version === 2 ? item.manifest.stateKey : void 0,
10318
+ installationType: item.manifest.version === 2 ? item.manifest.installationType : void 0,
10319
+ targetRoot: item.manifest.targetRoot,
10320
+ owner,
10321
+ hash: entry.hash,
10322
+ sourceHash: entry.sourceHash,
10323
+ graphLockDigest: digest2,
10324
+ graphNodeId: nodeId,
10325
+ logicalSelector: selector,
10326
+ owners: "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"],
10327
+ lockPaths: matches.map((candidate) => candidate.path),
10328
+ evidence
10329
+ };
10330
+ }
10331
+ function lockBindsState(candidate, manifest, binding) {
10332
+ const name = basename20(candidate.path);
10333
+ if (!name.endsWith(".graph-lock.json")) return false;
10334
+ const fingerprint = name.slice(0, -".graph-lock.json".length);
10335
+ if (!/^[a-f0-9]{64}$/u.test(fingerprint)) return false;
10336
+ const stable = resolve22(candidate.path) === resolve22(binding.stableLockPath);
10337
+ if (stable) {
10338
+ return manifest.stateKey === binding.stableStateKey && candidate.lock.canonical.targetFingerprint === binding.currentFingerprint;
10339
+ }
10340
+ return candidate.lock.canonical.targetFingerprint === fingerprint && manifest.stateKey === stateKeyFor(binding.adapter, {
10341
+ installationType: binding.installationType,
10342
+ stateKey: binding.explicitStateKey,
10343
+ targetFingerprint: fingerprint,
10344
+ fleetId: binding.fleetId
10345
+ });
10346
+ }
10347
+ async function verifyCurrentOperation(op, liveHash, transport) {
10348
+ if (liveHash === null || !op.desiredHash) return "mismatch";
10349
+ if (op.semanticPlugin || op.programmaticOperation) return "unverifiable";
10350
+ if (op.mode === "managed-block") {
10351
+ const selector = managedInstructionSelector(op.logicalSelector, op.artifactType, op.artifactName);
10352
+ const state = await readManagedInstructionBlockState(op.destPath, selector, transport);
10353
+ return state.hasBlock && !state.drifted && state.hash === op.desiredHash ? "exact-match" : "mismatch";
10354
+ }
10355
+ if (op.mergeStrategy) {
10356
+ if (!op.sourcePath) return "unverifiable";
10357
+ try {
10358
+ await assertMergedSourceContribution(op.sourcePath, op.mergeStrategy, await transport.readFile(op.destPath));
10359
+ return "exact-match";
10360
+ } catch {
10361
+ return "mismatch";
10362
+ }
10363
+ }
10364
+ return liveHash === op.desiredHash ? "exact-match" : "mismatch";
10365
+ }
10366
+ async function verifyHistoricalEntry(entry, livePath, liveHash, transport) {
10367
+ if (liveHash === null || entry.semanticPlugin) return false;
10368
+ if (entry.mode === "managed-block") {
10369
+ const selector = managedInstructionSelector(
10370
+ "logicalSelector" in entry ? entry.logicalSelector : void 0,
10371
+ entry.artifactType,
10372
+ entry.artifactName
10373
+ );
10374
+ const state = await readManagedInstructionBlockState(livePath, selector, transport);
10375
+ return state.hasBlock && !state.drifted && state.hash === entry.hash;
10376
+ }
10377
+ if (entry.mergeStrategy) {
10378
+ if (!hasMergeRemovalContent(entry.mergeRemoval)) return false;
10379
+ try {
10380
+ assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, await transport.readFile(livePath));
10381
+ return true;
10382
+ } catch {
10383
+ return false;
10384
+ }
10385
+ }
10386
+ return entry.hash === liveHash;
10387
+ }
10388
+ function assertCompleteGraphPlan(plan) {
10389
+ const sourceNodes = plan.graph.nodes.filter((node) => node.selected.length > 0);
10390
+ const lockedNodes = new Map(plan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node]));
10391
+ for (const node of sourceNodes) {
10392
+ const locked = lockedNodes.get(node.id);
10393
+ if (!locked || locked.sourceHash !== node.sourceHash || node.selected.some((selector) => !locked.selected.includes(selector))) {
10394
+ throw new Error(`Current source graph omitted selected node or dependency: ${node.id}`);
10395
+ }
10396
+ }
10397
+ for (const edge of plan.graph.edges) {
10398
+ if (!plan.bundle.graphLock.canonical.edges.some((locked) => locked.from === edge.from && locked.to === edge.to && locked.alias === edge.alias)) {
10399
+ throw new Error(`Current source graph omitted dependency edge: ${edge.from} -> ${edge.to}`);
10400
+ }
10401
+ }
10402
+ }
10403
+ function sha2564(value) {
10404
+ return createHash10("sha256").update(value).digest("hex");
10405
+ }
10406
+
10152
10407
  // src/version/policy.ts
10153
10408
  import { execFile as execFile3 } from "child_process";
10154
10409
  import { readFile as readFile23 } from "fs/promises";
10155
- import { join as join39, resolve as resolve22 } from "path";
10410
+ import { join as join40, resolve as resolve23 } from "path";
10156
10411
  import { promisify as promisify3 } from "util";
10157
10412
  import { parse as parseJsonc } from "jsonc-parser";
10158
10413
  import { z as z5 } from "zod";
@@ -10246,7 +10501,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
10246
10501
  if (tagged.length > 0) return tagged;
10247
10502
  }
10248
10503
  if (driverName === "local") {
10249
- const root = resolve22(workspaceRoot, pkg.source);
10504
+ const root = resolve23(workspaceRoot, pkg.source);
10250
10505
  const manifest2 = await readPackageManifest(root);
10251
10506
  const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
10252
10507
  try {
@@ -10261,7 +10516,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
10261
10516
  }
10262
10517
  const driver = getSourceDriver(driverName);
10263
10518
  const resolved = await driver.resolve(pkg.source, {
10264
- cacheRoot: join39(workspaceRoot, ".agentwheel", "cache"),
10519
+ cacheRoot: join40(workspaceRoot, ".agentwheel", "cache"),
10265
10520
  mode: "tracking",
10266
10521
  ref: pkg.requestedRef
10267
10522
  });
@@ -10355,7 +10610,7 @@ function gitUrlFromSource(source) {
10355
10610
  throw new Error(`Version discovery does not support Git source: ${source}`);
10356
10611
  }
10357
10612
  function versionCachePath(workspaceRoot) {
10358
- return join39(workspaceRoot, ".agentwheel", "cache", "version-index.json");
10613
+ return join40(workspaceRoot, ".agentwheel", "cache", "version-index.json");
10359
10614
  }
10360
10615
  async function readVersionCache(path) {
10361
10616
  if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
@@ -10369,7 +10624,7 @@ async function readVersionCache(path) {
10369
10624
  // src/profile/members.ts
10370
10625
  import { execFile as execFile4 } from "child_process";
10371
10626
  import { readFile as readFile24 } from "fs/promises";
10372
- import { join as join40, resolve as resolve23 } from "path";
10627
+ import { join as join41, resolve as resolve24 } from "path";
10373
10628
  import { promisify as promisify4 } from "util";
10374
10629
  import { z as z7 } from "zod";
10375
10630
 
@@ -10544,7 +10799,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
10544
10799
  let stderr = "";
10545
10800
  try {
10546
10801
  if (member.transport === "local") {
10547
- const workspace = resolve23(parentWorkspace, member.workspace);
10802
+ const workspace = resolve24(parentWorkspace, member.workspace);
10548
10803
  const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
10549
10804
  cwd: workspace,
10550
10805
  env: env2,
@@ -10590,7 +10845,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
10590
10845
  process.execPath,
10591
10846
  [process.argv[1], ...memberCommandArgs(member, args)],
10592
10847
  {
10593
- cwd: resolve23(parentWorkspace, member.workspace),
10848
+ cwd: resolve24(parentWorkspace, member.workspace),
10594
10849
  env: env2,
10595
10850
  maxBuffer: 20 * 1024 * 1024
10596
10851
  }
@@ -10670,7 +10925,7 @@ function memberFailure(member, health, error) {
10670
10925
  };
10671
10926
  }
10672
10927
  function memberCachePath(workspaceRoot, profileName, memberId) {
10673
- return join40(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
10928
+ return join41(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
10674
10929
  }
10675
10930
  async function readMemberCache(path) {
10676
10931
  if (!await pathExists(path)) return void 0;
@@ -10696,7 +10951,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
10696
10951
  }
10697
10952
  }
10698
10953
  function compositeKey(workspaceRoot, profileName) {
10699
- return `${resolve23(workspaceRoot)}#${profileName}`;
10954
+ return `${resolve24(workspaceRoot)}#${profileName}`;
10700
10955
  }
10701
10956
 
10702
10957
  // src/status/repository.ts
@@ -10743,10 +10998,10 @@ function valueAfter(lines, prefix) {
10743
10998
  }
10744
10999
 
10745
11000
  // src/catalogue/client.ts
10746
- import { createHash as createHash10 } from "crypto";
11001
+ import { createHash as createHash11 } from "crypto";
10747
11002
  import { readFile as readFile25, rm as rm8 } from "fs/promises";
10748
11003
  import { homedir as homedir9 } from "os";
10749
- import { join as join41 } from "path";
11004
+ import { join as join42 } from "path";
10750
11005
 
10751
11006
  // src/model/catalogue.ts
10752
11007
  import { z as z8 } from "zod";
@@ -11018,18 +11273,18 @@ var CatalogueClient = class {
11018
11273
  }
11019
11274
  return {
11020
11275
  value: schema.parse(value),
11021
- digest: createHash10("sha256").update(bytes).digest("hex")
11276
+ digest: createHash11("sha256").update(bytes).digest("hex")
11022
11277
  };
11023
11278
  }
11024
11279
  };
11025
11280
  function defaultCatalogueCachePath() {
11026
- return join41(homedir9(), ".agentwheel", "catalogue-cache.json");
11281
+ return join42(homedir9(), ".agentwheel", "catalogue-cache.json");
11027
11282
  }
11028
11283
  function sameSources2(a, b) {
11029
11284
  return a.length === b.length && a.every((source, index) => source === b[index]);
11030
11285
  }
11031
11286
  function catalogueContentHash(enriched, vercel) {
11032
- return createHash10("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11287
+ return createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
11033
11288
  }
11034
11289
 
11035
11290
  // src/search/index.ts
@@ -11391,9 +11646,9 @@ function includesToken(normalizedText, token) {
11391
11646
  }
11392
11647
 
11393
11648
  // src/semantic/index.ts
11394
- import { createHash as createHash11 } from "crypto";
11649
+ import { createHash as createHash12 } from "crypto";
11395
11650
  import { homedir as homedir10 } from "os";
11396
- import { join as join42 } from "path";
11651
+ import { join as join43 } from "path";
11397
11652
  import { env, pipeline } from "@huggingface/transformers";
11398
11653
  var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
11399
11654
  var CONTRACT = {
@@ -11481,13 +11736,13 @@ var SemanticSearchClient = class {
11481
11736
  if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
11482
11737
  const bytes = new Uint8Array(await response.arrayBuffer());
11483
11738
  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");
11739
+ const digest2 = createHash12("sha256").update(bytes).digest("hex");
11485
11740
  if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
11486
11741
  return bytes;
11487
11742
  }
11488
11743
  };
11489
11744
  async function embedQuery(query) {
11490
- env.cacheDir = join42(homedir10(), ".agentwheel", "semantic-models");
11745
+ env.cacheDir = join43(homedir10(), ".agentwheel", "semantic-models");
11491
11746
  const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
11492
11747
  revision: CONTRACT.model.revision,
11493
11748
  dtype: CONTRACT.model.dtype,
@@ -11571,9 +11826,9 @@ function ensureTrailingSlash(value) {
11571
11826
  }
11572
11827
 
11573
11828
  // src/trial/skill.ts
11574
- import { createHash as createHash12 } from "crypto";
11829
+ import { createHash as createHash13 } from "crypto";
11575
11830
  import { readFile as readFile26, stat as stat13 } from "fs/promises";
11576
- import { join as join43 } from "path";
11831
+ import { join as join44 } from "path";
11577
11832
  import { parse as parseYaml2 } from "yaml";
11578
11833
  var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
11579
11834
  async function createSkillTrial(driver, resolved, selectors) {
@@ -11585,7 +11840,7 @@ async function createSkillTrial(driver, resolved, selectors) {
11585
11840
  throw new Error("Skill trial requires exactly one selected skill. Use --skill <name> or --select skills/<name>.");
11586
11841
  }
11587
11842
  const artifact = skills[0];
11588
- const path = artifact.kind === "dir" ? join43(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
11843
+ const path = artifact.kind === "dir" ? join44(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
11589
11844
  const info = await stat13(path);
11590
11845
  if (info.size > MAX_TRIAL_SKILL_BYTES) {
11591
11846
  throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
@@ -11601,7 +11856,7 @@ async function createSkillTrial(driver, resolved, selectors) {
11601
11856
  skill: {
11602
11857
  name: artifact.name,
11603
11858
  relativePath: artifact.relativePath,
11604
- sha256: createHash12("sha256").update(content).digest("hex"),
11859
+ sha256: createHash13("sha256").update(content).digest("hex"),
11605
11860
  frontmatter,
11606
11861
  content
11607
11862
  }
@@ -11620,10 +11875,10 @@ function readSkillFrontmatter(content, artifact) {
11620
11875
  }
11621
11876
 
11622
11877
  // 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";
11878
+ import { createHash as createHash14 } from "crypto";
11879
+ import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
11625
11880
  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";
11881
+ import { dirname as dirname22, isAbsolute as isAbsolute3, join as join45, relative as relative9, resolve as resolve25 } from "path";
11627
11882
  async function planFleetNormalization(request) {
11628
11883
  const normalizedRequest = normalizeRequest2(request);
11629
11884
  const destinationScope = await resolveWorkspaceScope({
@@ -11653,7 +11908,7 @@ async function planFleetNormalization(request) {
11653
11908
  `Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
11654
11909
  );
11655
11910
  }
11656
- packages.push({ name, declarationDigest: sha2564(sourceDeclaration) });
11911
+ packages.push({ name, declarationDigest: sha2565(sourceDeclaration) });
11657
11912
  }
11658
11913
  if (!legacySelfNormalization) await assertSourceFleetPostcondition(normalizedRequest, sourceScope, candidates);
11659
11914
  if ((normalizedRequest.profile || normalizedRequest.agent || normalizedRequest.artifacts || normalizedRequest.orphanedOwnerRoots) && !legacySelfNormalization) {
@@ -11684,7 +11939,7 @@ async function planFleetNormalization(request) {
11684
11939
  packages,
11685
11940
  installedState
11686
11941
  };
11687
- return { ...planWithoutDigest, planDigest: sha2564(canonicalJson2(planWithoutDigest)) };
11942
+ return { ...planWithoutDigest, planDigest: sha2565(canonicalJson2(planWithoutDigest)) };
11688
11943
  }
11689
11944
  async function applyFleetNormalization(request, hooks = {}) {
11690
11945
  if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
@@ -11822,7 +12077,7 @@ async function assertNormalizationPostconditions(journal) {
11822
12077
  }
11823
12078
  for (const graphLock of journal.graphLocks) {
11824
12079
  const raw = await readOptionalRecord(graphLock.path);
11825
- const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
12080
+ const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
11826
12081
  if (digest2 !== graphLock.afterDigest) {
11827
12082
  throw new Error(`Fleet normalization graph-lock postcheck failed: ${graphLock.path}`);
11828
12083
  }
@@ -11894,17 +12149,17 @@ function normalizeRequest2(request) {
11894
12149
  ...profile ? { profile } : {},
11895
12150
  ...agent ? { agent } : {},
11896
12151
  ...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
11897
- ...request.globalRoot ? { globalRoot: resolve24(request.globalRoot) } : {}
12152
+ ...request.globalRoot ? { globalRoot: resolve25(request.globalRoot) } : {}
11898
12153
  };
11899
12154
  }
11900
12155
  function normalizeOrphanedOwnerRoot(value) {
11901
12156
  const root = value.trim();
11902
12157
  if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
11903
- return resolve24(root);
12158
+ return resolve25(root);
11904
12159
  }
11905
12160
  async function normalizationRoots(request) {
11906
12161
  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;
12162
+ const source = request.from === "user" ? resolve25(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
11908
12163
  return { source, destination: destination.root };
11909
12164
  }
11910
12165
  async function withOrderedLocks(roots, fn) {
@@ -11912,7 +12167,7 @@ async function withOrderedLocks(roots, fn) {
11912
12167
  if (index === roots.length) return fn();
11913
12168
  const root = roots[index];
11914
12169
  return withFilesystemLock(
11915
- join44(root, ".agentwheel", "fleet-normalize.lock"),
12170
+ join45(root, ".agentwheel", "fleet-normalize.lock"),
11916
12171
  5e3,
11917
12172
  () => run(index + 1),
11918
12173
  "fleet normalization"
@@ -12011,10 +12266,10 @@ async function inspectInstalledState(source, destination, packageNames) {
12011
12266
  const destinationGraphAfter = destinationGraph?.lock ?? destinationGraphFromSource(sourceGraph.lock, destinationState.targetFingerprint);
12012
12267
  graphTransfers.push({
12013
12268
  sourceGraphLockPath: sourceGraph.path,
12014
- sourceGraphLockDigest: sha2564(canonicalJson2(sourceGraph.lock)),
12269
+ sourceGraphLockDigest: sha2565(canonicalJson2(sourceGraph.lock)),
12015
12270
  destinationGraphLockPath: destinationState.graphLockPath,
12016
- destinationGraphLockDigest: destinationGraph ? sha2564(canonicalJson2(destinationGraph.lock)) : null,
12017
- destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraphAfter)),
12271
+ destinationGraphLockDigest: destinationGraph ? sha2565(canonicalJson2(destinationGraph.lock)) : null,
12272
+ destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraphAfter)),
12018
12273
  targetKey: sourceGraph.targetKey,
12019
12274
  adapter: destinationState.adapter,
12020
12275
  targetFingerprint: destinationState.targetFingerprint
@@ -12118,7 +12373,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12118
12373
  let normalizedGraphCount = 0;
12119
12374
  for (const graph of graphCandidates) {
12120
12375
  const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
12121
- if (resolve24(destinationState.graphLockPath) === resolve24(graph.path)) {
12376
+ if (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
12122
12377
  const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
12123
12378
  if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
12124
12379
  assertCorrelatedGraphManifest(
@@ -12133,7 +12388,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12133
12388
  }
12134
12389
  }
12135
12390
  const legacyState = await legacyTargetStateForGraph(fleet, graph);
12136
- if (resolve24(legacyState.graphLockPath) !== resolve24(graph.path)) {
12391
+ if (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
12137
12392
  throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
12138
12393
  }
12139
12394
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
@@ -12241,7 +12496,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12241
12496
  )
12242
12497
  });
12243
12498
  if (!selectedArtifacts) {
12244
- const fullDigest = sha2564(canonicalJson2(graph.lock));
12499
+ const fullDigest = sha2565(canonicalJson2(graph.lock));
12245
12500
  const existingDestinationGraph = graphCandidates.find((candidate) => candidate.path === destinationState.graphLockPath);
12246
12501
  if (existingDestinationGraph) assertGraphIsSubset(graph, existingDestinationGraph);
12247
12502
  const destinationGraph = destinationGraphFromSource(graph.lock, destinationState.targetFingerprint);
@@ -12249,8 +12504,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12249
12504
  sourceGraphLockPath: graph.path,
12250
12505
  sourceGraphLockDigest: fullDigest,
12251
12506
  destinationGraphLockPath: destinationState.graphLockPath,
12252
- destinationGraphLockDigest: existingDestinationGraph ? sha2564(canonicalJson2(existingDestinationGraph.lock)) : null,
12253
- destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraph)),
12507
+ destinationGraphLockDigest: existingDestinationGraph ? sha2565(canonicalJson2(existingDestinationGraph.lock)) : null,
12508
+ destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraph)),
12254
12509
  targetKey: graph.targetKey,
12255
12510
  adapter: graph.adapter,
12256
12511
  targetFingerprint: destinationState.targetFingerprint
@@ -12308,7 +12563,7 @@ function targetKeysForAgent(scope, name) {
12308
12563
  async function runtimeRoots(scope) {
12309
12564
  const roots = /* @__PURE__ */ new Set([scope.root]);
12310
12565
  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()));
12566
+ roots.add(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
12312
12567
  }
12313
12568
  for (const [name, agent] of Object.entries(scope.config.agents)) {
12314
12569
  if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
@@ -12326,17 +12581,17 @@ async function runtimeRoots(scope) {
12326
12581
  async function collectManifests(roots) {
12327
12582
  const found = /* @__PURE__ */ new Map();
12328
12583
  for (const root of roots) {
12329
- const dir = join44(root, ".agentwheel");
12584
+ const dir = join45(root, ".agentwheel");
12330
12585
  let names;
12331
12586
  try {
12332
- names = await readdir10(dir);
12587
+ names = await readdir11(dir);
12333
12588
  } catch (error) {
12334
12589
  if (isMissing(error)) continue;
12335
12590
  throw error;
12336
12591
  }
12337
12592
  for (const name of names.sort((a, b) => a.localeCompare(b))) {
12338
12593
  if (!name.endsWith(".install-manifest.json")) continue;
12339
- const path = join44(dir, name);
12594
+ const path = join45(dir, name);
12340
12595
  const raw = JSON.parse(await readFile27(path, "utf8"));
12341
12596
  const parsed = installManifestSchema.parse(raw);
12342
12597
  if (parsed.version !== 2) {
@@ -12345,14 +12600,14 @@ async function collectManifests(roots) {
12345
12600
  }
12346
12601
  continue;
12347
12602
  }
12348
- if (resolve24(parsed.targetRoot) !== resolve24(root)) {
12603
+ if (resolve25(parsed.targetRoot) !== resolve25(root)) {
12349
12604
  throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
12350
12605
  }
12351
12606
  const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
12352
12607
  installationType: parsed.installationType,
12353
12608
  stateKey: parsed.stateKey
12354
12609
  });
12355
- if (resolve24(expectedPath) !== resolve24(path)) {
12610
+ if (resolve25(expectedPath) !== resolve25(path)) {
12356
12611
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12357
12612
  }
12358
12613
  found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12373,7 +12628,7 @@ async function collectManifestPaths(paths) {
12373
12628
  installationType: parsed.installationType,
12374
12629
  stateKey: parsed.stateKey
12375
12630
  });
12376
- if (resolve24(expectedPath) !== resolve24(path)) {
12631
+ if (resolve25(expectedPath) !== resolve25(path)) {
12377
12632
  throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
12378
12633
  }
12379
12634
  found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
@@ -12392,14 +12647,14 @@ function assertNoForeignSamePathOwnership(manifests, renderedPaths, allowedOwner
12392
12647
  }
12393
12648
  }
12394
12649
  async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include) {
12395
- const root = join44(workspaceRoot, ".agentwheel", "locks");
12650
+ const root = join45(workspaceRoot, ".agentwheel", "locks");
12396
12651
  if (!await pathExists(root)) return [];
12397
12652
  const results = [];
12398
12653
  for (const path of await listFiles(root)) {
12399
12654
  if (!path.endsWith(".graph-lock.json")) continue;
12400
12655
  const lock = await readGraphLock(path);
12401
12656
  if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
12402
- const parts = relative8(root, path).split(/[\\/]/);
12657
+ const parts = relative9(root, path).split(/[\\/]/);
12403
12658
  if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
12404
12659
  const [targetKey2, adapter] = parts;
12405
12660
  if (targetKeys && !targetKeys.has(targetKey2)) continue;
@@ -12416,7 +12671,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
12416
12671
  const projection = relevantGraphProjection(lock, selected);
12417
12672
  results.push({
12418
12673
  ...candidate,
12419
- digest: sha2564(canonicalJson2(projection)),
12674
+ digest: sha2565(canonicalJson2(projection)),
12420
12675
  artifactIdentities: lock.canonical.artifacts.filter((artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(lock, owner, selected))).map(graphArtifactIdentity).sort()
12421
12676
  });
12422
12677
  }
@@ -12441,7 +12696,7 @@ async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
12441
12696
  const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
12442
12697
  if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
12443
12698
  const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
12444
- if (resolve24(stableState.graphLockPath) !== resolve24(graph.path)) return false;
12699
+ if (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
12445
12700
  const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
12446
12701
  return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
12447
12702
  }
@@ -12462,7 +12717,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
12462
12717
  baseDir: scope.root
12463
12718
  });
12464
12719
  const targetRoot = resolveConfigPath(agent.root, scope.root);
12465
- const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
12720
+ const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
12466
12721
  const stateKey = stateKeyFor(adapter.name, {
12467
12722
  installationType: agent.installationType,
12468
12723
  stateKey: agent.stateKey,
@@ -12530,7 +12785,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12530
12785
  ssh: void 0,
12531
12786
  ...agent?.stateKey ? { stateKey: agent.stateKey } : {}
12532
12787
  };
12533
- const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
12788
+ const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
12534
12789
  const targetIdentity = resolveTargetStateIdentity({
12535
12790
  targetFingerprintParts: fingerprintParts,
12536
12791
  workspaceRoot: scope.root,
@@ -12562,16 +12817,16 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12562
12817
  async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
12563
12818
  const stable = await targetStateForGraph(scope, graph, identityFleetId);
12564
12819
  let state;
12565
- if (resolve24(graph.path) === resolve24(stable.graphLockPath)) {
12820
+ if (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
12566
12821
  state = stable;
12567
12822
  } else {
12568
12823
  const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
12569
- if (resolve24(graph.path) !== resolve24(released.graphLockPath)) {
12824
+ if (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
12570
12825
  throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
12571
12826
  }
12572
12827
  state = released;
12573
12828
  }
12574
- const manifest = manifests.find((candidate) => resolve24(candidate.path) === resolve24(state.manifestPath));
12829
+ const manifest = manifests.find((candidate) => resolve25(candidate.path) === resolve25(state.manifestPath));
12575
12830
  if (!manifest) {
12576
12831
  throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
12577
12832
  }
@@ -12595,7 +12850,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
12595
12850
  ...current,
12596
12851
  stateKey,
12597
12852
  targetFingerprint,
12598
- graphLockPath: join44(dirname21(graph.path), `${targetFingerprint}.graph-lock.json`),
12853
+ graphLockPath: join45(dirname22(graph.path), `${targetFingerprint}.graph-lock.json`),
12599
12854
  manifestPath: installManifestPath(current.installRoot, current.adapter, {
12600
12855
  installationType: current.installationType,
12601
12856
  stateKey
@@ -12603,7 +12858,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
12603
12858
  };
12604
12859
  }
12605
12860
  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)) {
12861
+ if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve25(manifest.manifest.targetRoot) !== resolve25(state.installRoot)) {
12607
12862
  throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
12608
12863
  }
12609
12864
  const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
@@ -12611,7 +12866,7 @@ function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected)
12611
12866
  throw new Error(`Graph lock is not covered by selected install-manifest contributions: ${graph.path}`);
12612
12867
  }
12613
12868
  const graphArtifacts = new Set(graph.artifactIdentities);
12614
- const graphLockDigest = sha2564(canonicalJson2(graph.lock));
12869
+ const graphLockDigest = sha2565(canonicalJson2(graph.lock));
12615
12870
  for (const entry of entries) {
12616
12871
  if (!owners.has(entry.workspaceOwner)) {
12617
12872
  throw new Error(`Graph lock manifest owner mismatch at ${manifest.path}: ${entry.workspaceOwner}.`);
@@ -12660,7 +12915,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
12660
12915
  function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
12661
12916
  if (existing) {
12662
12917
  const manifest2 = existing.manifest;
12663
- if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve24(manifest2.targetRoot) !== state.installRoot) {
12918
+ if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve25(manifest2.targetRoot) !== state.installRoot) {
12664
12919
  throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
12665
12920
  }
12666
12921
  }
@@ -12720,7 +12975,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
12720
12975
  const content = await readFile27(path, "utf8");
12721
12976
  if (hasMergeRemovalContent(entry.mergeRemoval)) {
12722
12977
  assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
12723
- } else if (resolve24(sourcePath) === resolve24(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
12978
+ } else if (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
12724
12979
  assertExactMergeContribution({}, entry.mergeStrategy, content);
12725
12980
  } else {
12726
12981
  throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
@@ -12731,8 +12986,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
12731
12986
  await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
12732
12987
  }
12733
12988
  function renderedEntryPathForRoot(root, entryPath) {
12734
- const normalizedRoot = resolve24(root);
12735
- const candidate = resolve24(normalizedRoot, entryPath);
12989
+ const normalizedRoot = resolve25(root);
12990
+ const candidate = resolve25(normalizedRoot, entryPath);
12736
12991
  if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
12737
12992
  throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
12738
12993
  }
@@ -12823,8 +13078,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
12823
13078
  return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
12824
13079
  }
12825
13080
  function renderedEntryPath(manifest, entry) {
12826
- const root = resolve24(manifest.targetRoot);
12827
- const candidate = resolve24(root, entry.path);
13081
+ const root = resolve25(manifest.targetRoot);
13082
+ const candidate = resolve25(root, entry.path);
12828
13083
  if (candidate !== root && !candidate.startsWith(`${root}/`)) {
12829
13084
  throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
12830
13085
  }
@@ -12979,7 +13234,7 @@ async function buildJournalGraphLockStates(plan) {
12979
13234
  for (const transfer of plan.installedState.graphTransfers) {
12980
13235
  const sourceBefore = JSON.parse(await readFile27(transfer.sourceGraphLockPath, "utf8"));
12981
13236
  const sourceLock = await readGraphLock(transfer.sourceGraphLockPath);
12982
- const sourceDigest = sha2564(canonicalJson2(sourceLock));
13237
+ const sourceDigest = sha2565(canonicalJson2(sourceLock));
12983
13238
  if (sourceDigest !== transfer.sourceGraphLockDigest) {
12984
13239
  throw new Error(`Source graph lock changed after planning: ${transfer.sourceGraphLockPath}`);
12985
13240
  }
@@ -12997,9 +13252,9 @@ async function buildJournalGraphLockStates(plan) {
12997
13252
  continue;
12998
13253
  }
12999
13254
  const destinationBefore = await readOptionalRecord(transfer.destinationGraphLockPath);
13000
- const destinationBeforeDigest = destinationBefore ? sha2564(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
13255
+ const destinationBeforeDigest = destinationBefore ? sha2565(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
13001
13256
  const destinationAfter = destinationBefore ? await readGraphLock(transfer.destinationGraphLockPath) : destinationGraphFromSource(sourceLock, transfer.targetFingerprint);
13002
- const destinationAfterDigest = sha2564(canonicalJson2(destinationAfter));
13257
+ const destinationAfterDigest = sha2565(canonicalJson2(destinationAfter));
13003
13258
  if (destinationBeforeDigest !== transfer.destinationGraphLockDigest || destinationAfterDigest !== transfer.destinationGraphLockAfterDigest) {
13004
13259
  throw new Error(`Destination graph lock changed after planning: ${transfer.destinationGraphLockPath}`);
13005
13260
  }
@@ -13109,7 +13364,7 @@ async function assertJournalRecoverable(journal) {
13109
13364
  }
13110
13365
  for (const graphLock of journal.graphLocks) {
13111
13366
  const raw = await readOptionalRecord(graphLock.path);
13112
- const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
13367
+ const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
13113
13368
  if (digest2 !== graphLock.beforeDigest && digest2 !== graphLock.afterDigest) {
13114
13369
  throw new Error(`Cannot recover fleet normalization because graph lock changed outside the transaction: ${graphLock.path}`);
13115
13370
  }
@@ -13162,10 +13417,10 @@ function packageMap(config) {
13162
13417
  return result;
13163
13418
  }
13164
13419
  function configRevision(config) {
13165
- return sha2564(canonicalJson2(config));
13420
+ return sha2565(canonicalJson2(config));
13166
13421
  }
13167
13422
  function normalizationJournalPath(sourceRoot, destinationFleet) {
13168
- return join44(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
13423
+ return join45(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
13169
13424
  }
13170
13425
  function canonicalJson2(value) {
13171
13426
  return JSON.stringify(sortValue(value));
@@ -13177,8 +13432,8 @@ function sortValue(value) {
13177
13432
  }
13178
13433
  return value;
13179
13434
  }
13180
- function sha2564(value) {
13181
- return createHash13("sha256").update(value).digest("hex");
13435
+ function sha2565(value) {
13436
+ return createHash14("sha256").update(value).digest("hex");
13182
13437
  }
13183
13438
  function assertDigest(value) {
13184
13439
  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
@@ -13216,6 +13471,7 @@ var commandGovernance = {
13216
13471
  "package migrate": "governed-always",
13217
13472
  remember: "governed-always",
13218
13473
  "ownership handoff": "governed-unless-dry-run",
13474
+ "ownership recovery-plan": "incidental-cache",
13219
13475
  "ownership retire-stale": "governed-apply",
13220
13476
  "mcp retire": "governed-apply",
13221
13477
  eject: "governed-always",
@@ -13322,7 +13578,7 @@ fleetCommand.command("normalize").description("plan or apply duplicate desired-s
13322
13578
  });
13323
13579
  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
13580
  const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
13325
- const cacheRoot = join45(targetRoot, ".agentwheel", "cache");
13581
+ const cacheRoot = join46(targetRoot, ".agentwheel", "cache");
13326
13582
  const result = await pruneGitCache(cacheRoot, { keepSnapshots: options.keep, dryRun: !options.apply });
13327
13583
  const verb = options.apply ? "Removed" : "Would remove";
13328
13584
  for (const path of result.removedPaths) console.log(`${verb} ${path}`);
@@ -13340,7 +13596,7 @@ program.command("list").description("list artifacts exposed by a package source"
13340
13596
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13341
13597
  const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
13342
13598
  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") }))));
13599
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
13344
13600
  try {
13345
13601
  const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
13346
13602
  for (const artifact of artifacts) {
@@ -13409,7 +13665,7 @@ program.command("try").description("read and validate one skill for the current
13409
13665
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13410
13666
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
13411
13667
  const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, {
13412
- cacheRoot: join45(targetRoot, ".agentwheel", "cache")
13668
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache")
13413
13669
  }))));
13414
13670
  try {
13415
13671
  const trial = await createSkillTrial(driver, resolved, selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry));
@@ -13431,7 +13687,7 @@ program.command("scan").description("scan a package source for validation findin
13431
13687
  const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
13432
13688
  const resolvedInput = await resolvePackageSource(source, targetRoot);
13433
13689
  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") }))));
13690
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
13435
13691
  try {
13436
13692
  const result = await driver.scan(resolved);
13437
13693
  if (result.findings.length === 0) {
@@ -13446,10 +13702,10 @@ program.command("scan").description("scan a package source for validation findin
13446
13702
  await releaseGitSnapshotLease(resolved.cacheLeasePath);
13447
13703
  }
13448
13704
  });
13449
- program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
13705
+ program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--recover-legacy-state", "build a stable state while preserving invalid legacy candidates", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
13450
13706
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
13451
13707
  });
13452
- program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
13708
+ program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--recover-legacy-state", "build a stable state while preserving invalid legacy candidates", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
13453
13709
  await runInstallCommand(source, options, { apply: !options.dryRun });
13454
13710
  });
13455
13711
  program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
@@ -13636,7 +13892,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
13636
13892
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
13637
13893
  if (options.toFleet) {
13638
13894
  const fleet = await showRegisteredFleet(options.toFleet);
13639
- if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
13895
+ if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
13640
13896
  throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
13641
13897
  }
13642
13898
  }
@@ -13662,11 +13918,85 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
13662
13918
  console.log(`Manifest revision: ${result.manifestRevision}`);
13663
13919
  console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
13664
13920
  });
13921
+ 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) => {
13922
+ const targets = await resolveCliTargets({ fleet: options.fleet, agent: options.agent, profile: options.profile });
13923
+ if (targets.length !== 1) throw new Error(`Recovery planning requires one Fleet target, found ${targets.length}.`);
13924
+ const target = targets[0];
13925
+ if (target.fleetId !== options.fleet) throw new Error("Resolved target does not belong to the selected Fleet.");
13926
+ const adapter = await resolveAdapterForTarget(target, {});
13927
+ const transport = transportForTarget(target);
13928
+ const requestedReport = normalizeCliPath(options.report);
13929
+ const reportPath = join46(await realpath2(dirname23(requestedReport)), basename21(requestedReport));
13930
+ const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
13931
+ if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute4(reportInsideTarget)) {
13932
+ throw new Error("Recovery report must be outside the runtime target root.");
13933
+ }
13934
+ const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
13935
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
13936
+ if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
13937
+ const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
13938
+ const priorTmpdir = process.env.TMPDIR;
13939
+ try {
13940
+ process.env.TMPDIR = stage;
13941
+ const graphPlan = await createGraphSourcePlan({
13942
+ roots: config.packages.map((pkg) => ({
13943
+ rootId: pkg.name,
13944
+ source: pkg.source,
13945
+ mode: pkg.mode,
13946
+ version: pkg.version,
13947
+ ref: pkg.requestedRef,
13948
+ select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
13949
+ selection: pkg.selection,
13950
+ aliases: pkg.aliases,
13951
+ overrides: pkg.overrides,
13952
+ includeSuggestions: pkg.withSuggestions,
13953
+ suggestionAliases: pkg.suggestions,
13954
+ useLock: false
13955
+ })),
13956
+ targetRoot: target.targetRoot,
13957
+ workspaceRoot: target.workspaceRoot,
13958
+ fleetId: target.fleetId,
13959
+ adapter,
13960
+ transport,
13961
+ targetKey: targetKeyForTarget(target, adapter.name),
13962
+ targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
13963
+ installationType,
13964
+ stateKey: target.stateKey,
13965
+ readOnly: true,
13966
+ yes: true,
13967
+ freshGraphOnly: true,
13968
+ deferForeignStateCheck: true,
13969
+ cacheRoot: join46(stage, "source-cache"),
13970
+ registryCachePath: join46(stage, "registry.json")
13971
+ });
13972
+ const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
13973
+ const report = await planLegacyRecovery({
13974
+ graphPlan,
13975
+ installRoot,
13976
+ workspaceRoot: target.workspaceRoot,
13977
+ fleetId: target.fleetId,
13978
+ explicitStateKey: target.stateKey,
13979
+ targetKey: targetKeyForTarget(target, adapter.name),
13980
+ adapter: adapter.name,
13981
+ installationType,
13982
+ historicalLocks: await inventoryHistoricalLocks(target.workspaceRoot),
13983
+ transport
13984
+ });
13985
+ await writeFile13(reportPath, `${JSON.stringify(report, null, 2)}
13986
+ `, { mode: 384, flag: "wx" });
13987
+ console.log(`Recovery report: ${reportPath}`);
13988
+ console.log(JSON.stringify(report.counts));
13989
+ } finally {
13990
+ if (priorTmpdir === void 0) delete process.env.TMPDIR;
13991
+ else process.env.TMPDIR = priorTmpdir;
13992
+ await rm10(stage, { recursive: true, force: true });
13993
+ }
13994
+ });
13665
13995
  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
13996
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
13667
13997
  const fleet = await showRegisteredFleet(options.fleet);
13668
13998
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
13669
- if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
13999
+ if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
13670
14000
  throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
13671
14001
  }
13672
14002
  const targets = await resolveCliTargets(normalizedOptions);
@@ -13674,7 +14004,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
13674
14004
  throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
13675
14005
  }
13676
14006
  const target = targets[0];
13677
- if (target.fleetId !== options.fleet || resolve25(target.workspaceRoot) !== resolve25(fleet.root)) {
14007
+ if (target.fleetId !== options.fleet || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
13678
14008
  throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
13679
14009
  }
13680
14010
  const state = await resolveCliTargetState(target, normalizedOptions);
@@ -13832,7 +14162,7 @@ journalCommand.command("list").description("show pending apply journals for reso
13832
14162
  if (!journal) continue;
13833
14163
  pending += 1;
13834
14164
  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`)}`);
14165
+ console.log(` journal: ${join46(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
13836
14166
  console.log(` stateKey: ${state.state.stateKey}`);
13837
14167
  console.log(` createdAt: ${journal.createdAt}`);
13838
14168
  console.log(` updatedAt: ${journal.updatedAt}`);
@@ -14168,14 +14498,14 @@ async function packageEntryFromSource(source, targetRoot, options) {
14168
14498
  );
14169
14499
  }
14170
14500
  const bundle = options.deferGraphRendering ? await stageSourceRaw(driver, resolvedSource, {
14171
- cacheRoot: join45(targetRoot, ".agentwheel", "cache"),
14501
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
14172
14502
  mode: options.mode,
14173
14503
  ref: initialVersion?.ref,
14174
14504
  frozenLock: lockMode
14175
14505
  }) : await stageSource(driver, resolvedSource, {
14176
14506
  workspaceRoot: targetRoot,
14177
14507
  adapter,
14178
- cacheRoot: join45(targetRoot, ".agentwheel", "cache"),
14508
+ cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
14179
14509
  mode: options.mode,
14180
14510
  ref: initialVersion?.ref,
14181
14511
  frozenLock: lockMode,
@@ -14576,7 +14906,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
14576
14906
  }
14577
14907
  const priorGroupState = await resolveCliTargetState(group.target, {
14578
14908
  ...targetOptions,
14579
- installationType: group.installationType
14909
+ installationType: group.installationType,
14910
+ recoverLegacyState: targetOptions.recoverLegacyState
14580
14911
  });
14581
14912
  const previousGroupLock = priorGroupState.graphLock;
14582
14913
  const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
@@ -14685,7 +15016,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
14685
15016
  deferForeignStateCheck: targetOptions.deferForeignStateCheck === true || targetOptions.focusedArtifact !== void 0,
14686
15017
  replaceConflict: targetOptions.replaceConflict,
14687
15018
  retireExactMcp: targetOptions.retireExactMcp,
14688
- expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner
15019
+ expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner,
15020
+ recoverLegacyState: targetOptions.recoverLegacyState
14689
15021
  });
14690
15022
  if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
14691
15023
  const manifest = result.previousManifest;
@@ -14756,7 +15088,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
14756
15088
  selectedPreviousNodeIds,
14757
15089
  selectedRootIds
14758
15090
  );
14759
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15091
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14760
15092
  return {
14761
15093
  ...result,
14762
15094
  bundle: { ...result.bundle, graphLock },
@@ -14882,7 +15214,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
14882
15214
  function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
14883
15215
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
14884
15216
  const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
14885
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15217
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14886
15218
  return {
14887
15219
  ...scoped,
14888
15220
  bundle: { ...scoped.bundle, graphLock },
@@ -14893,7 +15225,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
14893
15225
  function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
14894
15226
  const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
14895
15227
  const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
14896
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15228
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
14897
15229
  return {
14898
15230
  ...scoped,
14899
15231
  bundle: { ...scoped.bundle, graphLock },
@@ -15188,7 +15520,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
15188
15520
  selectedPreviousNodeIds,
15189
15521
  /* @__PURE__ */ new Set([rootId])
15190
15522
  );
15191
- const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15523
+ const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
15192
15524
  return {
15193
15525
  ...scoped,
15194
15526
  bundle: { ...scoped.bundle, graphLock },
@@ -15260,7 +15592,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
15260
15592
  artifactType: entry.artifactType,
15261
15593
  artifactName: entry.artifactName,
15262
15594
  kind: entry.kind,
15263
- destPath: operation?.destPath ?? join45(targetRoot, entry.path),
15595
+ destPath: operation?.destPath ?? join46(targetRoot, entry.path),
15264
15596
  relativeDestPath: entry.path,
15265
15597
  desiredHash: entry.sourceHash,
15266
15598
  currentHash: operation?.currentHash ?? entry.hash,
@@ -15511,7 +15843,9 @@ async function resolveCliTargetState(target, options) {
15511
15843
  graphLockPath: stableGraphLockPath,
15512
15844
  stableManifest,
15513
15845
  stableLock,
15514
- transport
15846
+ transport,
15847
+ recoverLegacyState: options.recoverLegacyState,
15848
+ warn: options.warn
15515
15849
  });
15516
15850
  return {
15517
15851
  adapter,
@@ -15526,7 +15860,7 @@ async function resolveCliTargetState(target, options) {
15526
15860
  migration: prior.migration,
15527
15861
  targetStateFilePreconditions: {
15528
15862
  graphLockPath: stableGraphLockPath,
15529
- graphLockRevision: stableLock ? createHash14("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
15863
+ graphLockRevision: stableLock ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
15530
15864
  sourceLockRevision: computeSourceLockRevision(stableSourceLock)
15531
15865
  }
15532
15866
  };
@@ -15549,7 +15883,7 @@ async function collectTargetStatus(target, options) {
15549
15883
  const pending = await collectPendingInstallWork(target, options);
15550
15884
  const foreignObservations = pending.foreignStateObservations;
15551
15885
  const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
15552
- const observedManifestRevision = foreignObservations.length > 0 ? createHash14("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
15886
+ const observedManifestRevision = foreignObservations.length > 0 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
15553
15887
  observation.fileName,
15554
15888
  observation.manifestRevision
15555
15889
  ]).sort())).digest("hex") : null;
@@ -15796,7 +16130,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
15796
16130
  `Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
15797
16131
  );
15798
16132
  }
15799
- const memberRoot = resolve25(workspaceRoot, member.workspace);
16133
+ const memberRoot = resolve26(workspaceRoot, member.workspace);
15800
16134
  const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
15801
16135
  if (memberPolicy?.revisioning.mode === "commit-after-verify") {
15802
16136
  throw new Error(
@@ -16032,12 +16366,12 @@ async function printDoctor(target, options) {
16032
16366
  const requestedSkills = doctorSkillRequests(target, options);
16033
16367
  const skills = [];
16034
16368
  for (const request of requestedSkills) {
16035
- const skillPath = join45(state.installRoot, targetMapping.dest, request.name);
16369
+ const skillPath = join46(state.installRoot, targetMapping.dest, request.name);
16036
16370
  const exists = await pathExists(skillPath);
16037
16371
  const manifestEntry = manifest?.entries.find((entry) => {
16038
16372
  if (entry.artifactType !== "skills") return false;
16039
16373
  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);
16374
+ return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join46(targetMapping.dest, request.name);
16041
16375
  });
16042
16376
  const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
16043
16377
  skills.push({
@@ -16117,7 +16451,7 @@ function doctorSkillLabel(name) {
16117
16451
  return `${name} skill`;
16118
16452
  }
16119
16453
  function isSyncwheelWorkspace(targetRoot) {
16120
- return existsSync(join45(targetRoot, ".syncwheel", "manifest.json"));
16454
+ return existsSync(join46(targetRoot, ".syncwheel", "manifest.json"));
16121
16455
  }
16122
16456
  function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
16123
16457
  const args = [
@@ -16280,14 +16614,14 @@ function looksLikeSourceSpecifier(value) {
16280
16614
  }
16281
16615
  function normalizeCliPath(value) {
16282
16616
  if (value === "~") return homedir12();
16283
- if (value.startsWith("~/")) return resolve25(homedir12(), value.slice(2));
16284
- return resolve25(value);
16617
+ if (value.startsWith("~/")) return resolve26(homedir12(), value.slice(2));
16618
+ return resolve26(value);
16285
16619
  }
16286
16620
  function shellQuoteArgument(value) {
16287
16621
  return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
16288
16622
  }
16289
16623
  function isHomePath(path) {
16290
- return resolve25(path) === resolve25(homedir12());
16624
+ return resolve26(path) === resolve26(homedir12());
16291
16625
  }
16292
16626
  function adapterListFromOption(adapter) {
16293
16627
  if (!adapter) return [];
@@ -16342,11 +16676,11 @@ function filterUninstallPlanBySelection(plan, selected) {
16342
16676
  };
16343
16677
  }
16344
16678
  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");
16679
+ await mkdir16(join46(root, "instructions"), { recursive: true });
16680
+ await mkdir16(join46(root, "rules"), { recursive: true });
16681
+ await mkdir16(join46(root, "skills"), { recursive: true });
16682
+ const manifestPath = join46(root, "openpack.json");
16683
+ const instructionsPath = join46(root, "instructions", "AGENTS.md");
16350
16684
  const manifest = {
16351
16685
  schemaVersion: CURRENT_OPENPACK_SCHEMA_VERSION,
16352
16686
  name: "example/agentwheel-package",
@@ -16364,7 +16698,7 @@ async function initPackage(root) {
16364
16698
  await writeFile13(instructionsPath, "# Agent Instructions\n", "utf8");
16365
16699
  }
16366
16700
  async function defaultBootstrapPackage(_root) {
16367
- const packageRoot = await findAgentwheelPackageRoot(dirname22(fileURLToPath3(import.meta.url)));
16701
+ const packageRoot = await findAgentwheelPackageRoot(dirname23(fileURLToPath3(import.meta.url)));
16368
16702
  if (!packageRoot) return void 0;
16369
16703
  return {
16370
16704
  name: "agentwheel",
@@ -16408,10 +16742,10 @@ function withFleetExample(config) {
16408
16742
  };
16409
16743
  }
16410
16744
  async function findAgentwheelPackageRoot(start) {
16411
- let current = resolve25(start);
16745
+ let current = resolve26(start);
16412
16746
  while (true) {
16413
16747
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
16414
- const parent = dirname22(current);
16748
+ const parent = dirname23(current);
16415
16749
  if (parent === current) return void 0;
16416
16750
  current = parent;
16417
16751
  }
@@ -16536,14 +16870,14 @@ async function governedCommandContext(command, options) {
16536
16870
  const kind = String(command.args[0] ?? "workspace");
16537
16871
  return {
16538
16872
  workspaceRoot: workspaceRoot2,
16539
- anticipatedPaths: kind === "package" ? [join45(targetRoot, "openpack.json"), join45(targetRoot, "instructions", "AGENTS.md")] : [workspaceConfigPath(targetRoot)]
16873
+ anticipatedPaths: kind === "package" ? [join46(targetRoot, "openpack.json"), join46(targetRoot, "instructions", "AGENTS.md")] : [workspaceConfigPath(targetRoot)]
16540
16874
  };
16541
16875
  }
16542
16876
  if (path === "package migrate") {
16543
16877
  const packageRoot = normalizeTargetRoot(String(command.args[0] ?? "."));
16544
16878
  return {
16545
16879
  workspaceRoot: await findExistingWorkspaceRoot(packageRoot) ?? packageRoot,
16546
- anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) => join45(packageRoot, name))
16880
+ anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) => join46(packageRoot, name))
16547
16881
  };
16548
16882
  }
16549
16883
  const source = typeof command.args[0] === "string" ? command.args[0] : void 0;
@@ -16555,7 +16889,7 @@ async function governedCommandContext(command, options) {
16555
16889
  const anticipatedPaths = [];
16556
16890
  if (path === "add") anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
16557
16891
  if (path === "remember" && options.runtime) {
16558
- anticipatedPaths.push(join45(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
16892
+ anticipatedPaths.push(join46(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
16559
16893
  }
16560
16894
  if (path === "uninstall" && source) anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
16561
16895
  return {