plugnz 0.0.10 → 0.0.12

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.
Files changed (2) hide show
  1. package/dist/plugnz.mjs +1165 -69
  2. package/package.json +1 -1
package/dist/plugnz.mjs CHANGED
@@ -9795,8 +9795,6 @@ import { existsSync as existsSync12, lstatSync as lstatSync5, readFileSync as re
9795
9795
  import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "node:path";
9796
9796
 
9797
9797
  // src/hermes-identity.ts
9798
- var HERMES_PORTABLE_SURFACE = "portable-surface";
9799
- var HERMES_PORTABLE_SURFACE_PROBE = "hermes-portable-surface";
9800
9798
  function hermesCommandCompanionId(pluginId) {
9801
9799
  return `${pluginId}.plgnz-commands`;
9802
9800
  }
@@ -10038,6 +10036,10 @@ function yamlStringify(value) {
10038
10036
  }
10039
10037
 
10040
10038
  // src/hosts/hermes.ts
10039
+ var hermesProvenManagedRevisions = [
10040
+ "c0d7294769a38c17ceae51d8f7995e66e1dcae27",
10041
+ "dce1e9b37581dd62e480a9064dc04a709c2940d3"
10042
+ ];
10041
10043
  var hermesTargetProfile = {
10042
10044
  kind: "hermes",
10043
10045
  parseSyncTarget(value, label) {
@@ -12393,6 +12395,203 @@ function isRecord3(value) {
12393
12395
  return typeof value === "object" && value !== null && !Array.isArray(value);
12394
12396
  }
12395
12397
 
12398
+ // src/hosts/claude-native-schema.ts
12399
+ var control = /[\p{Cc}\u200E\u200F\u202A-\u202E\u2066-\u2069]/u;
12400
+ var reserved = new Set(["inline", "builtin", "skills-dir", "synced", "claude-plugin-test"]);
12401
+ var official275 = new Set(["claude-community", "claude-plugins-community", "healthcare", "claude-code-marketplace", "claude-code-plugins", "claude-plugins-official", "anthropic-marketplace", "anthropic-plugins", "agent-skills", "anthropic-agent-skills", "life-sciences", "knowledge-work-plugins", "claude-for-legal", "claude-for-financial-services", "financial-services-plugins", "first-party-plugins", "claude-tag-plugins"]);
12402
+ var metadataHosts = new Set(["metadata.google.internal", "metadata.goog", "metadata", "instance-data", "instance-data.ec2.internal", "ip6-localhost", "ip6-loopback", "localhost.localdomain", "localhost4", "localhost4.localdomain4", "localhost6", "localhost6.localdomain6"]);
12403
+ var metadataIps = new Set(["100.100.100.200", "168.63.129.16", "192.0.0.192"]);
12404
+ function object3(value, path, invalid) {
12405
+ if (value === null || typeof value !== "object" || Array.isArray(value))
12406
+ return invalid(path, "object");
12407
+ return value;
12408
+ }
12409
+ function string2(value, path, invalid) {
12410
+ if (typeof value !== "string")
12411
+ return invalid(path, "string");
12412
+ return value;
12413
+ }
12414
+ function optionalStrings(value, keys, path, invalid) {
12415
+ for (const key of keys)
12416
+ if (value[key] !== undefined)
12417
+ string2(value[key], `${path}.${key}`, invalid);
12418
+ }
12419
+ function optionalBoolean(value, key, path, invalid) {
12420
+ if (value[key] !== undefined && typeof value[key] !== "boolean")
12421
+ invalid(`${path}.${key}`, "boolean");
12422
+ }
12423
+ function headers(value, path, invalid) {
12424
+ if (value.headers !== undefined)
12425
+ for (const [key, entry] of Object.entries(object3(value.headers, `${path}.headers`, invalid)))
12426
+ string2(entry, `${path}.headers.${key}`, invalid);
12427
+ if (value.headersHelper !== undefined)
12428
+ printable(value.headersHelper, `${path}.headersHelper`, false, invalid);
12429
+ }
12430
+ function printable(value, path, nonempty, invalid) {
12431
+ const text = string2(value, path, invalid);
12432
+ if (nonempty && text.length === 0 || text.length > 500 || /[^\x20-\x7E]| {4,}/u.test(text))
12433
+ invalid(path, "printable ASCII, at most 500 characters, no four-space runs");
12434
+ }
12435
+ function url(value, path, invalid) {
12436
+ const text = string2(value, path, invalid);
12437
+ try {
12438
+ return new URL(text);
12439
+ } catch {
12440
+ return invalid(path, "URL");
12441
+ }
12442
+ }
12443
+ function registry2(value, path, invalid) {
12444
+ if (value.registry === undefined)
12445
+ return;
12446
+ const text = string2(value.registry, `${path}.registry`, invalid), parsed = url(text, `${path}.registry`, invalid);
12447
+ if (!/^https?:\/\/[^/\\]/iu.test(text) || !["http:", "https:"].includes(parsed.protocol))
12448
+ invalid(`${path}.registry`, "http(s) registry URL");
12449
+ }
12450
+ function name(value, path, marketplace, version, invalid) {
12451
+ const text = string2(value, path, invalid), lower = text.toLowerCase();
12452
+ if (text.length === 0 || text.includes(" ") || control.test(text))
12453
+ invalid(path, "nonempty name without spaces, control or bidirectional-formatting characters");
12454
+ if (!marketplace)
12455
+ return;
12456
+ if (/[\\/]/u.test(text) || text.includes("..") || text === "." || reserved.has(lower))
12457
+ invalid(path, "native marketplace name");
12458
+ if (version === "2.1.275" && (official275.has(lower) || /[^\u0020-\u007E]/u.test(text) || /(?:official[^a-z0-9]*(anthropic|claude)|(?:anthropic|claude)[^a-z0-9]*official|^(?:anthropic|claude)[^a-z0-9]*(marketplace|plugins|official))/iu.test(text)))
12459
+ invalid(path, "2.1.275 settings marketplace name without reserved/official impersonation");
12460
+ }
12461
+ function blockedIpv4(bytes) {
12462
+ return bytes[0] === 127 || bytes[0] === 0 || bytes[0] === 169 && bytes[1] === 254 || metadataIps.has(bytes.join("."));
12463
+ }
12464
+ function ipv6Bytes(host) {
12465
+ const parts = host.split("::"), left = parts[0] ? parts[0].split(":") : [], right = parts.length === 2 && parts[1] ? parts[1].split(":") : [];
12466
+ const numbers = (values) => values.flatMap((value) => {
12467
+ const n = Number.parseInt(value, 16);
12468
+ return [n >> 8, n & 255];
12469
+ });
12470
+ const start = numbers(left), end = numbers(right);
12471
+ return [...start, ...Array(16 - start.length - end.length).fill(0), ...end];
12472
+ }
12473
+ function archiveHostBlocked(hostname) {
12474
+ let host = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
12475
+ if (host.endsWith("."))
12476
+ host = host.slice(0, -1);
12477
+ if (host === "" || host === "localhost" || host.endsWith(".localhost") || metadataHosts.has(host) || host.startsWith("instance-data.") && host.endsWith(".compute.internal"))
12478
+ return true;
12479
+ if (/^\d+\.\d+\.\d+\.\d+$/u.test(host))
12480
+ return blockedIpv4(host.split(".").map(Number));
12481
+ if (!host.includes(":"))
12482
+ return false;
12483
+ const b = ipv6Bytes(host);
12484
+ const nat64 = b[0] === 0 && b[1] === 100 && b[2] === 255 && b[3] === 155;
12485
+ if (nat64 && b[4] === 0 && b[5] === 1 || b.every((n) => n === 0) || b.slice(0, 15).every((n) => n === 0) && b[15] === 1 || host === "fd00:ec2::254" || b[0] === 254 && b[1] >= 128 && b[1] <= 191)
12486
+ return true;
12487
+ const embedded = [];
12488
+ if (b[0] === 32 && b[1] === 2)
12489
+ embedded.push(b.slice(2, 6));
12490
+ if (b.slice(0, 10).every((n) => n === 0) && (b[10] === 255 && b[11] === 255 || b[10] === 0 && b[11] === 0) || nat64 && b.slice(4, 12).every((n) => n === 0) || (b[8] === 0 || b[8] === 2) && b[9] === 0 && b[10] === 94 && b[11] === 254)
12491
+ embedded.push(b.slice(12, 16));
12492
+ return embedded.some(blockedIpv4);
12493
+ }
12494
+ function pluginSource(value, path, invalid) {
12495
+ const source = object3(value, path, invalid), kind = string2(source.source, `${path}.source`, invalid);
12496
+ switch (kind) {
12497
+ case "npm": {
12498
+ const pkg = string2(source.package, `${path}.package`, invalid);
12499
+ if (!/^(?:file|https?|git(?:\+https?|\+ssh)?|ssh|github|gitlab|bitbucket):/iu.test(pkg) && pkg.includes(".."))
12500
+ invalid(`${path}.package`, "npm reference without path traversal");
12501
+ optionalStrings(source, ["version"], path, invalid);
12502
+ registry2(source, path, invalid);
12503
+ break;
12504
+ }
12505
+ case "url":
12506
+ case "github":
12507
+ case "git-subdir":
12508
+ string2(source[kind === "github" ? "repo" : "url"], `${path}.${kind === "github" ? "repo" : "url"}`, invalid);
12509
+ optionalStrings(source, ["ref", "sha"], path, invalid);
12510
+ if (source.sha !== undefined && !/^[a-f0-9]{40}$/u.test(source.sha))
12511
+ invalid(`${path}.sha`, "40-character lowercase commit SHA");
12512
+ if (kind === "git-subdir" && string2(source.path, `${path}.path`, invalid).length === 0)
12513
+ invalid(`${path}.path`, "nonempty string");
12514
+ break;
12515
+ case "archive": {
12516
+ const parsed = url(source.url, `${path}.url`, invalid);
12517
+ if (parsed.protocol !== "https:" || archiveHostBlocked(parsed.hostname))
12518
+ invalid(`${path}.url`, "HTTPS archive URL without loopback/link-local/metadata host");
12519
+ optionalStrings(source, ["sha256"], path, invalid);
12520
+ if (source.sha256 !== undefined && !/^[a-f0-9]{64}$/iu.test(source.sha256))
12521
+ invalid(`${path}.sha256`, "64-character hex SHA-256");
12522
+ break;
12523
+ }
12524
+ case "command":
12525
+ printable(source.command, `${path}.command`, true, invalid);
12526
+ if (source.timeout !== undefined && (typeof source.timeout !== "number" || !Number.isInteger(source.timeout) || source.timeout <= 0 || source.timeout > 600))
12527
+ invalid(`${path}.timeout`, "positive integer at most 600");
12528
+ if (source.mode !== undefined && source.mode !== "copy" && source.mode !== "link")
12529
+ invalid(`${path}.mode`, "copy or link");
12530
+ break;
12531
+ default:
12532
+ invalid(`${path}.source`, "native remote inline plugin source");
12533
+ }
12534
+ }
12535
+ function assertClaudeMarketplaceSource(value, path, version, invalid) {
12536
+ const source = object3(value, path, invalid), kind = string2(source.source, `${path}.source`, invalid);
12537
+ switch (kind) {
12538
+ case "url":
12539
+ url(source.url, `${path}.url`, invalid);
12540
+ headers(source, path, invalid);
12541
+ break;
12542
+ case "github":
12543
+ case "git":
12544
+ string2(source[kind === "github" ? "repo" : "url"], `${path}.${kind === "github" ? "repo" : "url"}`, invalid);
12545
+ optionalStrings(source, ["ref", "path"], path, invalid);
12546
+ optionalBoolean(source, "skipLfs", path, invalid);
12547
+ if (source.sparsePaths !== undefined && (!Array.isArray(source.sparsePaths) || source.sparsePaths.some((v) => typeof v !== "string")))
12548
+ invalid(`${path}.sparsePaths`, "array of strings");
12549
+ break;
12550
+ case "npm": {
12551
+ const pkg = string2(source.package, `${path}.package`, invalid);
12552
+ if (!(/^@[a-z0-9][a-z0-9._-]*\/\*$/u.test(pkg) || !pkg.includes("..") && !pkg.includes("//") && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(pkg)))
12553
+ invalid(`${path}.package`, "native npm package name or scope wildcard");
12554
+ optionalStrings(source, ["version"], path, invalid);
12555
+ registry2(source, path, invalid);
12556
+ break;
12557
+ }
12558
+ case "file":
12559
+ case "directory":
12560
+ string2(source.path, `${path}.path`, invalid);
12561
+ break;
12562
+ case "skills-dir":
12563
+ break;
12564
+ case "hostPattern":
12565
+ string2(source.hostPattern, `${path}.hostPattern`, invalid);
12566
+ break;
12567
+ case "pathPattern":
12568
+ string2(source.pathPattern, `${path}.pathPattern`, invalid);
12569
+ break;
12570
+ case "settings": {
12571
+ name(source.name, `${path}.name`, true, version, invalid);
12572
+ if (!Array.isArray(source.plugins))
12573
+ return invalid(`${path}.plugins`, "array of inline plugins");
12574
+ for (const [index, value] of source.plugins.entries()) {
12575
+ const pluginPath = `${path}.plugins[${index}]`, plugin = object3(value, pluginPath, invalid);
12576
+ name(plugin.name, `${pluginPath}.name`, false, version, invalid);
12577
+ pluginSource(plugin.source, `${pluginPath}.source`, invalid);
12578
+ optionalStrings(plugin, ["description", "version"], pluginPath, invalid);
12579
+ optionalBoolean(plugin, "strict", pluginPath, invalid);
12580
+ headers(plugin, pluginPath, invalid);
12581
+ }
12582
+ if (source.owner !== undefined) {
12583
+ const owner = object3(source.owner, `${path}.owner`, invalid);
12584
+ if (string2(owner.name, `${path}.owner.name`, invalid).length === 0)
12585
+ invalid(`${path}.owner.name`, "nonempty string");
12586
+ optionalStrings(owner, ["email", "url"], `${path}.owner`, invalid);
12587
+ }
12588
+ break;
12589
+ }
12590
+ default:
12591
+ invalid(`${path}.source`, "native marketplace source discriminator");
12592
+ }
12593
+ }
12594
+
12396
12595
  // src/mcp-write.ts
12397
12596
  import { writeFileSync as writeFileSync2 } from "node:fs";
12398
12597
  import { join as join20 } from "node:path";
@@ -14010,7 +14209,8 @@ function selectLifecycleRoute(adapterId, profiles, request) {
14010
14209
  if (detectedVersion !== undefined && managedAdmission.status === "admitted") {
14011
14210
  return selectedDecision(adapterId, target, request, detectedVersion, "managed", managedAdmission);
14012
14211
  }
14013
- const gaps = dedupeGaps([...nativeGaps, ...managedAdmission.gaps]);
14212
+ const versionDiagnostic = request.version.kind === "detected" ? undefined : request.version.diagnostic;
14213
+ const gaps = dedupeGaps([...nativeGaps, ...managedAdmission.gaps].map((gap) => versionDiagnostic === undefined ? gap : { ...gap, diagnostic: `${gap.diagnostic}. ${versionDiagnostic}` }));
14014
14214
  if (gaps.length === 0)
14015
14215
  invariant("a refused route decision must retain at least one capability gap");
14016
14216
  return deepFreeze({
@@ -15127,7 +15327,9 @@ function normalizeVersionObservation(input) {
15127
15327
  });
15128
15328
  if (input.kind !== "unknown" && input.kind !== "unparseable")
15129
15329
  invariant("target version observation is invalid");
15130
- return Object.freeze({ kind: input.kind });
15330
+ return Object.freeze({ kind: input.kind, ...input.diagnostic === undefined ? {} : {
15331
+ diagnostic: stableCopy(input.diagnostic, "version probe diagnostic")
15332
+ } });
15131
15333
  }
15132
15334
  function frozenTarget(input) {
15133
15335
  const parsed = parsePersistedTargetIdentity(input, "lifecycle target");
@@ -15624,6 +15826,22 @@ function readMarketplaces(file) {
15624
15826
  throw new Error(`Invalid Claude Code marketplace registry: ${file}`);
15625
15827
  return value;
15626
15828
  }
15829
+ function assertLifecycleMarketplaceEnvelope(version) {
15830
+ const file = join22(pluginsDir(), "known_marketplaces.json");
15831
+ for (const [name, entry] of Object.entries(readMarketplaces(file))) {
15832
+ const invalid = (field, expectation) => {
15833
+ throw new Error(`Invalid Claude Code marketplace registry: ${file}: ${name}.${field}: expected ${expectation}; repair the native configuration before retrying (no automatic edits)`);
15834
+ };
15835
+ if (!isRecord6(entry))
15836
+ return invalid("entry", "object");
15837
+ assertClaudeMarketplaceSource(entry["source"], "source", version, invalid);
15838
+ for (const field of ["installLocation", "lastUpdated"])
15839
+ if (typeof entry[field] !== "string")
15840
+ invalid(field, "string");
15841
+ if (entry["autoUpdate"] !== undefined && typeof entry["autoUpdate"] !== "boolean")
15842
+ invalid("autoUpdate", "boolean");
15843
+ }
15844
+ }
15627
15845
  function marketplacesWithLocalSource(marketplaces, name, sourcePath, sourceIdentity, plugin, forceWrapper) {
15628
15846
  const manifest = join22(sourcePath, ".claude-plugin", "marketplace.json");
15629
15847
  const catalog = validateMarketplaceRegistration(marketplaces, name, sourcePath, sourceIdentity, plugin, forceWrapper);
@@ -16088,18 +16306,30 @@ function claudeCodeLifecycleDefinition(contracts) {
16088
16306
  id: "claude-code",
16089
16307
  evidenceProfiles: claudeEvidenceProfiles(detectedVersion, contracts),
16090
16308
  probeVersion: async () => version,
16091
- observeTarget: async (target) => ({ target, installations: observeClaudeInstallations() }),
16309
+ observeTarget: async (target) => ({ target, installations: observeClaudeInstallations(target, detectedVersion) }),
16092
16310
  observeNativeMutationScope: async (request) => ({
16093
16311
  kind: "bounded",
16094
16312
  mode: "exact-package",
16095
16313
  affectedNativeIds: [request.nativeId]
16096
16314
  }),
16097
- observeNativeProjection: async (request) => nativeProjection(contracts, request, detectedVersion),
16315
+ observeNativeProjection: async (request) => {
16316
+ const result = nativeProjection(contracts, request, detectedVersion);
16317
+ if ("snapshot" in request && result.kind === "requires-managed") {
16318
+ for (const path of ["known_marketplaces.json", "installed_plugins.json", "../settings.json"])
16319
+ assertManagedPath(resolve11(join22(pluginsDir(), path)));
16320
+ assertManagedPath(cacheSlot(request.snapshot.nativeId, slotVersion(request.snapshot.inventory.package.version, request.snapshot.immutableRevision)));
16321
+ if (!matchingForeignLifecycleCatalog(request.snapshot.nativeId, request.snapshot.scopeId, request.snapshot.target, request.snapshot.packageRoot, request.snapshot.inventory.package.version ?? request.snapshot.immutableRevision))
16322
+ validateLifecycleCatalog(request.snapshot.nativeId, request.snapshot.scopeId);
16323
+ }
16324
+ if (!("snapshot" in request) && request.operation === "retire")
16325
+ validateLifecycleCatalogRetirement(request.activation.nativeId, request.activation.scopeId);
16326
+ return result;
16327
+ },
16098
16328
  revalidateTargetPrecondition: async (handle) => ({
16099
16329
  version: claudeVersionObservation(observeClaudeCodeVersion()),
16100
16330
  targetObservationId: createTargetInventoryObservation("claude-code", {
16101
16331
  target: handle.target,
16102
- installations: observeClaudeInstallations()
16332
+ installations: observeClaudeInstallations(handle.target, detectedVersion)
16103
16333
  }).observationId
16104
16334
  }),
16105
16335
  stageActivation: async (request) => {
@@ -16125,7 +16355,8 @@ function claudeCodeLifecycleDefinition(contracts) {
16125
16355
  },
16126
16356
  captureActivationPreparation: async (projection, projectedFingerprint) => {
16127
16357
  const installPath = cacheSlot(projection.nativeId, slotVersion(projection.packageVersion, projection.sourceRevision));
16128
- const prior = claudeReadback(projection, "none");
16358
+ const prior = claudeReadback(projection, "none", detectedVersion);
16359
+ const nativeCatalog = matchingForeignLifecycleCatalog(projection.nativeId, projection.scopeId, projection.target, projection.stagingRoot, slotVersion(projection.packageVersion, projection.sourceRevision));
16129
16360
  return {
16130
16361
  prior,
16131
16362
  expected: {
@@ -16136,20 +16367,24 @@ function claudeCodeLifecycleDefinition(contracts) {
16136
16367
  activation: "active",
16137
16368
  transition: sessionRestart(),
16138
16369
  installedFingerprint: projectedFingerprint,
16139
- contentRoots: [{ label: "plugin", path: installPath, fingerprint: projectedFingerprint }]
16370
+ contentRoots: [
16371
+ { label: "plugin", path: installPath, fingerprint: projectedFingerprint },
16372
+ { label: "catalog-plugin", path: nativeCatalog ?? lifecycleCatalogCopy(projection.nativeId), fingerprint: nativeCatalog === null ? projectedFingerprint : fingerprintTree(nativeCatalog) },
16373
+ ...nativeCatalog === null ? [{ label: "selected-catalog-entry", path: join22(lifecycleCatalogRoot(projection.nativeId), ".claude-plugin", "marketplace.json"), fingerprint: catalogEntryFingerprint(ownedLifecycleCatalogEntry(projection.nativeId)) }] : lifecycleCatalogEntryObservation(projection.nativeId)
16374
+ ]
16140
16375
  },
16141
16376
  rollbackReference: captureRollback(projection.attemptId, projection.operationId, projection.nativeId),
16142
16377
  rollbackCoverageOperationIds: [...projection.affectedOperationIds]
16143
16378
  };
16144
16379
  },
16145
16380
  captureDisablePreparation: async (request) => ({
16146
- prior: claudeReadback(activationIdentity(request.activation), "none"),
16381
+ prior: claudeReadback(activationIdentity(request.activation), "none", detectedVersion),
16147
16382
  rollbackReference: captureRollback(request.attemptId, request.operationId, request.activation.nativeId),
16148
16383
  rollbackCoverageOperationIds: [...request.selection.affectedOperationIds],
16149
16384
  transition: sessionRestart()
16150
16385
  }),
16151
16386
  captureRetirementPreparation: async (request) => ({
16152
- prior: claudeReadback(activationIdentity(request.activation), "none"),
16387
+ prior: claudeReadback(activationIdentity(request.activation), "none", detectedVersion),
16153
16388
  rollbackReference: captureRollback(request.attemptId, request.operationId, request.activation.nativeId),
16154
16389
  rollbackCoverageOperationIds: [...request.selection.affectedOperationIds],
16155
16390
  transition: settledTransition()
@@ -16161,6 +16396,7 @@ function claudeCodeLifecycleDefinition(contracts) {
16161
16396
  },
16162
16397
  disable: async (prepared) => setEnabled(prepared.handle, false, `claude-disable-${prepared.handle.operationId}`),
16163
16398
  retire: async (prepared) => {
16399
+ retireLifecycleCatalog(prepared.handle.nativeId, prepared.handle.scopeId);
16164
16400
  const installPath = recordedInstallPath(prepared.handle.nativeId);
16165
16401
  const existed = installPath !== undefined && existsSync17(installPath);
16166
16402
  if (installPath !== undefined)
@@ -16170,7 +16406,7 @@ function claudeCodeLifecycleDefinition(contracts) {
16170
16406
  return { receiptId: `claude-retire-${prepared.handle.operationId}`, changed: existed };
16171
16407
  },
16172
16408
  readback: async (handle) => {
16173
- const observed = claudeReadback(handle, handle.action === "remove" || handle.action === "retire-orphan" ? handle.route : "none");
16409
+ const observed = claudeReadback(handle, handle.action === "remove" || handle.action === "retire-orphan" ? handle.route : "none", detectedVersion);
16174
16410
  return { ...observed, transition: readbackTransition(handle, observed) };
16175
16411
  },
16176
16412
  rollback: async (handle) => restoreRollback(handle),
@@ -16227,14 +16463,15 @@ function claudeVersionObservation(observed) {
16227
16463
  return { kind: "detected", version: observed.version, probeId: observed.probeId };
16228
16464
  return { kind: observed.kind };
16229
16465
  }
16466
+ var CLAUDE_MANAGED_RELEASES = new Set(["2.1.275", "2.1.295"]);
16230
16467
  function claudeEvidenceProfiles(version, contracts) {
16231
16468
  if (version === undefined)
16232
16469
  return [];
16233
- const managed = claudeProfile(version, "managed", ["local", "git"], ["install", "update", "disable", "retire"]);
16470
+ const managed = CLAUDE_MANAGED_RELEASES.has(version) ? [claudeProfile(version, "managed", ["local", "git"], ["install", "update", "disable", "retire"])] : [];
16234
16471
  const native = contracts.filter((contract) => contract.version === version && sandboxPasses(contract));
16235
16472
  if (native.length > 1)
16236
16473
  throw new Error(`ambiguous Claude Code native update contract for ${version}`);
16237
- return native.length === 0 ? [managed] : [managed, claudeProfile(version, "native", ["git"], ["update"])];
16474
+ return native.length === 0 ? managed : [...managed, claudeProfile(version, "native", ["git"], ["update"])];
16238
16475
  }
16239
16476
  function sandboxPasses(contract) {
16240
16477
  return contract.source === "git" && contract.action === "update" && contract.consumesExactSnapshot && contract.forcesSameVersionBytes && contract.rollbackProven && contract.readbackProven;
@@ -16273,7 +16510,8 @@ function claudeSemantics() {
16273
16510
  CLAUDE_PROJECTED_SEMANTICS.has(semantic) ? "supported" : "unsupported"
16274
16511
  ]));
16275
16512
  }
16276
- function observeClaudeInstallations() {
16513
+ function observeClaudeInstallations(target, version) {
16514
+ assertLifecycleMarketplaceEnvelope(version);
16277
16515
  const registry = readRegistry(join22(pluginsDir(), "installed_plugins.json"));
16278
16516
  const settings = readSettings(join22(pluginsDir(), "..", "settings.json"));
16279
16517
  const enabled = settings["enabledPlugins"];
@@ -16315,11 +16553,11 @@ function observeClaudeInstallations() {
16315
16553
  } : { kind: "unmanaged" },
16316
16554
  presence: "present",
16317
16555
  enablement: on ? "enabled" : "disabled",
16318
- activation: on ? "active" : "inactive",
16556
+ activation: on ? lifecycleCatalogConforms(id, installPath, target) ? "active" : "nonconforming" : "inactive",
16319
16557
  installedFingerprint: digest,
16320
16558
  installedVersion: typeof row["version"] === "string" ? row["version"] : null,
16321
16559
  source: revision !== undefined && locator !== undefined ? { type: "git", immutableRevision: revision, locator } : null,
16322
- contentRoots: [{ label: "plugin", path: installPath, fingerprint: digest }]
16560
+ contentRoots: lifecycleContentRoots(id, installPath, digest)
16323
16561
  });
16324
16562
  }
16325
16563
  return installations;
@@ -16367,7 +16605,8 @@ function sameReadbackState(observed, target) {
16367
16605
  function activationIdentity(activation) {
16368
16606
  return { adapterId: "claude-code", ...activation };
16369
16607
  }
16370
- function claudeReadback(identity, absentRoute) {
16608
+ function claudeReadback(identity, absentRoute, version) {
16609
+ assertLifecycleMarketplaceEnvelope(version);
16371
16610
  const installPath = recordedInstallPath(identity.nativeId);
16372
16611
  const present = installPath !== undefined && existsSync17(installPath);
16373
16612
  const retention = {
@@ -16394,10 +16633,10 @@ function claudeReadback(identity, absentRoute) {
16394
16633
  route: installedRoute(installPath),
16395
16634
  presence: "present",
16396
16635
  enablement: enabled ? "enabled" : "disabled",
16397
- activation: enabled ? "active" : "inactive",
16636
+ activation: enabled ? lifecycleCatalogConforms(identity.nativeId, installPath, identity.target) ? "active" : "unknown" : "inactive",
16398
16637
  transition: settledTransition(),
16399
16638
  installedFingerprint: digest,
16400
- contentRoots: [{ label: "plugin", path: installPath, fingerprint: digest }],
16639
+ contentRoots: lifecycleContentRoots(identity.nativeId, installPath, digest),
16401
16640
  retention
16402
16641
  };
16403
16642
  }
@@ -16442,6 +16681,306 @@ function cacheSlot(nativeId, version) {
16442
16681
  const market = at === -1 ? "local" : nativeId.slice(at + 1);
16443
16682
  return resolve11(join22(pluginsDir(), "cache", market, name, version));
16444
16683
  }
16684
+ function lifecycleCatalogRoot(nativeId) {
16685
+ const market = marketplaceName(nativeId);
16686
+ if (market === undefined || market.includes("/") || market.includes("\\"))
16687
+ throw new Error("Claude Code lifecycle native identity has no safe marketplace");
16688
+ return resolve11(join22(pluginsDir(), "marketplaces", `.plgnz-${market}`));
16689
+ }
16690
+ function lifecycleCatalogCopy(nativeId) {
16691
+ const name = nativeId.slice(0, nativeId.indexOf("@"));
16692
+ if (!name || name.includes("/") || name.includes("\\"))
16693
+ throw new Error("Claude Code lifecycle native identity has no safe package name");
16694
+ return join22(lifecycleCatalogRoot(nativeId), "plugins", name);
16695
+ }
16696
+ function registeredLifecycleCatalogMember(nativeId) {
16697
+ const market = marketplaceName(nativeId);
16698
+ if (market === undefined)
16699
+ return null;
16700
+ const entry = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"))[market];
16701
+ if (!isRecord6(entry) || !isRecord6(entry["source"]) || entry["source"]["source"] !== "directory" || typeof entry["source"]["path"] !== "string" || entry["installLocation"] !== entry["source"]["path"])
16702
+ return null;
16703
+ const root = entry["source"]["path"];
16704
+ if (!root.startsWith("/") || resolve11(root) !== root || !existsSync17(root))
16705
+ return null;
16706
+ assertCatalogPath(root, join22(root, ".claude-plugin", "marketplace.json"));
16707
+ const selected = catalogEntries(lifecycleCatalogDocument(root, market)).filter((item) => isCatalogMember(item, nativeId));
16708
+ if (selected.length !== 1 || !isRecord6(selected[0]) || typeof selected[0]["source"] !== "string" || !selected[0]["source"].startsWith("./"))
16709
+ return null;
16710
+ const member = resolve11(root, selected[0]["source"]);
16711
+ if (!containedPath(root, member) || root === member || !existsSync17(member))
16712
+ return null;
16713
+ assertCatalogPath(root, member);
16714
+ assertNoSymlinks2(member);
16715
+ return { root, member, entry: selected[0] };
16716
+ }
16717
+ function matchingForeignLifecycleCatalog(nativeId, scopeId, target, packageRoot, version) {
16718
+ const catalog = registeredLifecycleCatalogMember(nativeId);
16719
+ if (catalog === null || catalog.root === lifecycleCatalogRoot(nativeId))
16720
+ return null;
16721
+ if (createDeploymentScopeIdentity({ kind: "local", locator: catalog.root }, target).id !== scopeId)
16722
+ return null;
16723
+ if (!foreignLifecycleEntryEquivalent(catalog.entry))
16724
+ return null;
16725
+ if (!existsSync17(join22(catalog.member, ".claude-plugin", "plugin.json")))
16726
+ return null;
16727
+ const proof = mkdtempSync3(join22(tmpdir(), "plgnz-claude-native-member-"));
16728
+ try {
16729
+ stagePlugin(packageRoot, join22(proof, "incoming"), nativeId.slice(0, nativeId.indexOf("@")), version);
16730
+ cpSync3(catalog.member, join22(proof, "native"), { recursive: true });
16731
+ for (const path of [join22(proof, "incoming"), join22(proof, "native")]) {
16732
+ rmSync3(join22(path, ".plgnz-lifecycle.json"), { force: true });
16733
+ rmSync3(join22(path, OWNERSHIP), { force: true });
16734
+ }
16735
+ return fingerprintTree(join22(proof, "incoming")) === fingerprintTree(join22(proof, "native")) ? catalog.member : null;
16736
+ } finally {
16737
+ rmSync3(proof, { recursive: true, force: true });
16738
+ }
16739
+ }
16740
+ function lifecycleCatalogDocument(root, market) {
16741
+ const file = join22(root, ".claude-plugin", "marketplace.json");
16742
+ if (!existsSync17(file))
16743
+ return null;
16744
+ const parsed = JSON.parse(readFileSync16(file, "utf8"));
16745
+ if (!isRecord6(parsed) || parsed["name"] !== market || !Array.isArray(parsed["plugins"]))
16746
+ throw new Error(`Invalid Claude Code lifecycle catalog identity: ${file}`);
16747
+ return parsed;
16748
+ }
16749
+ function catalogEntries(document) {
16750
+ return document === null ? [] : document["plugins"];
16751
+ }
16752
+ function isCatalogMember(entry, nativeId) {
16753
+ return isRecord6(entry) && entry["name"] === nativeId.slice(0, nativeId.indexOf("@"));
16754
+ }
16755
+ function ownedLifecycleCatalogEntry(nativeId) {
16756
+ const name = nativeId.slice(0, nativeId.indexOf("@"));
16757
+ return { name, source: `./plugins/${name}` };
16758
+ }
16759
+ function catalogEntryFingerprint(entry) {
16760
+ const canonical = (value) => Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : isRecord6(value) ? `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value);
16761
+ const hash = new CryptoHasher("sha256");
16762
+ hash.update(canonical(entry));
16763
+ return hash.digest("hex");
16764
+ }
16765
+ function foreignLifecycleEntryEquivalent(entry) {
16766
+ if (["commands", "agents", "skills", "hooks", "outputStyles", "themes"].some((key) => Object.hasOwn(entry, key)))
16767
+ return false;
16768
+ if (entry["strict"] !== undefined && typeof entry["strict"] !== "boolean")
16769
+ return false;
16770
+ if (entry["dependencies"] !== undefined && (!Array.isArray(entry["dependencies"]) || entry["dependencies"].length !== 0))
16771
+ return false;
16772
+ if (entry["defaultEnabled"] !== undefined && typeof entry["defaultEnabled"] !== "boolean")
16773
+ return false;
16774
+ return true;
16775
+ }
16776
+ function lifecycleCatalogEntryObservation(nativeId) {
16777
+ const root = registeredLifecycleCatalogMember(nativeId)?.root ?? lifecycleCatalogRoot(nativeId);
16778
+ const document = lifecycleCatalogDocument(root, marketplaceName(nativeId));
16779
+ const entries = catalogEntries(document).filter((entry) => isCatalogMember(entry, nativeId));
16780
+ return entries.length === 1 ? [{ label: "selected-catalog-entry", path: join22(root, ".claude-plugin", "marketplace.json"), fingerprint: catalogEntryFingerprint(entries[0]) }] : [];
16781
+ }
16782
+ function validateLifecycleCatalog(nativeId, scopeId) {
16783
+ const root = lifecycleCatalogRoot(nativeId);
16784
+ const copy = lifecycleCatalogCopy(nativeId);
16785
+ const market = marketplaceName(nativeId);
16786
+ assertManagedPath(root);
16787
+ assertManagedPath(join22(root, ".claude-plugin", "marketplace.json"));
16788
+ assertManagedPath(join22(root, OWNERSHIP));
16789
+ assertManagedPath(copy);
16790
+ if (existsSync17(root)) {
16791
+ const owner = readOwnership(root);
16792
+ if (owner?.source !== "plgnz-wrapper" || owner.pluginId !== `marketplace@${market}`)
16793
+ throw new Error(`Claude Code marketplace wrapper ${root} is foreign; refusing to replace it`);
16794
+ }
16795
+ const document = lifecycleCatalogDocument(root, market);
16796
+ const selected = catalogEntries(document).filter((entry) => isCatalogMember(entry, nativeId));
16797
+ if (selected.length > 1 || selected.some((entry) => !isRecord6(entry) || entry["source"] !== `./plugins/${nativeId.slice(0, nativeId.indexOf("@"))}`))
16798
+ throw new Error(`Claude Code marketplace member ${nativeId} is foreign or ambiguous; refusing to replace it`);
16799
+ if (existsSync17(copy)) {
16800
+ assertNoSymlinks2(copy);
16801
+ const owner = readOwnership(copy);
16802
+ if (owner?.pluginId !== nativeId || owner.scopeId !== scopeId)
16803
+ throw new Error(`Claude Code marketplace member ${copy} has a different owned scope; refusing to replace it`);
16804
+ } else if (selected.length > 0) {
16805
+ const installed = recordedInstallPath(nativeId);
16806
+ const owner = installed === undefined ? null : readOwnership(installed);
16807
+ if (owner?.pluginId !== nativeId || owner.scopeId !== scopeId)
16808
+ throw new Error(`Claude Code marketplace member ${nativeId} has no ownership proof; refusing to replace it`);
16809
+ }
16810
+ const known = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"));
16811
+ const extra = readSettings(join22(pluginsDir(), "..", "settings.json"))["extraKnownMarketplaces"];
16812
+ for (const entry of [known[market], isRecord6(extra) ? extra[market] : undefined]) {
16813
+ if (entry === undefined)
16814
+ continue;
16815
+ if (!isRecord6(entry) || !isRecord6(entry["source"]) || entry["source"]["source"] !== "directory" || entry["source"]["path"] !== root || entry["installLocation"] !== undefined && entry["installLocation"] !== root)
16816
+ throw new Error(`Claude Code marketplace ${market} is registered from a different source; refusing to replace it`);
16817
+ }
16818
+ }
16819
+ function installLifecycleCatalog(nativeId, scopeId, stagingRoot) {
16820
+ validateLifecycleCatalog(nativeId, scopeId);
16821
+ const root = lifecycleCatalogRoot(nativeId);
16822
+ const copy = lifecycleCatalogCopy(nativeId);
16823
+ const market = marketplaceName(nativeId);
16824
+ const document = lifecycleCatalogDocument(root, market) ?? { name: market, owner: { name: "plgnz" }, plugins: [] };
16825
+ rmSync3(copy, { recursive: true, force: true });
16826
+ mkdirSync3(dirname6(copy), { recursive: true });
16827
+ cpSync3(stagingRoot, copy, { recursive: true });
16828
+ mkdirSync3(join22(root, ".claude-plugin"), { recursive: true });
16829
+ writeFileSync3(join22(root, ".claude-plugin", "marketplace.json"), JSON.stringify({ ...document, plugins: [...catalogEntries(document).filter((entry) => !isCatalogMember(entry, nativeId)), ownedLifecycleCatalogEntry(nativeId)] }));
16830
+ writeFileSync3(join22(root, OWNERSHIP), JSON.stringify({ source: "plgnz-wrapper", pluginId: `marketplace@${market}`, fingerprint: "" }));
16831
+ const file = join22(pluginsDir(), "known_marketplaces.json");
16832
+ const known = readMarketplaces(file);
16833
+ const previous = isRecord6(known[market]) ? known[market] : {};
16834
+ writeMarketplaces(file, { ...known, [market]: { ...previous, source: { source: "directory", path: root }, installLocation: root, lastUpdated: new Date().toISOString() } });
16835
+ }
16836
+ function lifecycleCatalogConforms(nativeId, installPath, target) {
16837
+ const market = marketplaceName(nativeId);
16838
+ if (market === undefined)
16839
+ return false;
16840
+ const registered = registeredLifecycleCatalogMember(nativeId);
16841
+ const owner = readOwnership(installPath);
16842
+ if (registered !== null && registered.root !== lifecycleCatalogRoot(nativeId)) {
16843
+ if (owner?.scopeId === undefined)
16844
+ return false;
16845
+ const manifest = existsSync17(join22(installPath, ".plugin", "plugin.json")) ? join22(installPath, ".plugin", "plugin.json") : join22(installPath, "plugin.json");
16846
+ return matchingForeignLifecycleCatalog(nativeId, owner.scopeId, target, installPath, parseManifest(manifest, "canonical").version ?? "") !== null;
16847
+ }
16848
+ const known = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"))[market];
16849
+ if (!isRecord6(known) || !isRecord6(known["source"]) || known["source"]["source"] !== "directory" || known["source"]["path"] !== lifecycleCatalogRoot(nativeId) || known["installLocation"] !== lifecycleCatalogRoot(nativeId))
16850
+ return false;
16851
+ const entries = catalogEntries(lifecycleCatalogDocument(lifecycleCatalogRoot(nativeId), market)).filter((entry) => isCatalogMember(entry, nativeId));
16852
+ if (entries.length !== 1 || catalogEntryFingerprint(entries[0]) !== catalogEntryFingerprint(ownedLifecycleCatalogEntry(nativeId)))
16853
+ return false;
16854
+ const copy = lifecycleCatalogCopy(nativeId);
16855
+ if (!existsSync17(copy))
16856
+ return false;
16857
+ assertManagedPath(copy);
16858
+ assertNoSymlinks2(copy);
16859
+ const catalogOwner = readOwnership(copy);
16860
+ return owner?.pluginId === nativeId && catalogOwner?.pluginId === nativeId && owner.scopeId === catalogOwner.scopeId && fingerprintTree(copy) === fingerprintTree(installPath);
16861
+ }
16862
+ function lifecycleContentRoots(nativeId, installPath, digest) {
16863
+ const copy = registeredLifecycleCatalogMember(nativeId)?.member ?? lifecycleCatalogCopy(nativeId);
16864
+ return [{ label: "plugin", path: installPath, fingerprint: digest }, ...existsSync17(copy) ? [{ label: "catalog-plugin", path: copy, fingerprint: fingerprintTree(copy) }] : [], ...lifecycleCatalogEntryObservation(nativeId)];
16865
+ }
16866
+ function retireLifecycleCatalog(nativeId, scopeId) {
16867
+ validateLifecycleCatalogRetirement(nativeId, scopeId);
16868
+ const root = lifecycleCatalogRoot(nativeId);
16869
+ const copy = lifecycleCatalogCopy(nativeId);
16870
+ if (!existsSync17(root))
16871
+ return;
16872
+ assertManagedPath(root);
16873
+ const market = marketplaceName(nativeId);
16874
+ const owner = readOwnership(root);
16875
+ if (owner?.source !== "plgnz-wrapper" || owner.pluginId !== `marketplace@${market}`)
16876
+ return;
16877
+ const document = lifecycleCatalogDocument(root, market);
16878
+ if (existsSync17(copy)) {
16879
+ const member = readOwnership(copy);
16880
+ if (member?.pluginId !== nativeId || member.scopeId !== scopeId)
16881
+ throw new Error(`Claude Code catalog retirement cannot prove ownership: ${copy}`);
16882
+ rmSync3(copy, { recursive: true, force: true });
16883
+ }
16884
+ const entries = catalogEntries(document).filter((entry) => !isCatalogMember(entry, nativeId));
16885
+ if (document !== null)
16886
+ writeFileSync3(join22(root, ".claude-plugin", "marketplace.json"), JSON.stringify({ ...document, plugins: entries }));
16887
+ if (entries.length === 0 && emptyOwnedCatalogContainer(root)) {
16888
+ rmSync3(root, { recursive: true, force: true });
16889
+ const file = join22(pluginsDir(), "known_marketplaces.json");
16890
+ const known = readMarketplaces(file);
16891
+ const entry = known[market];
16892
+ if (isRecord6(entry) && isRecord6(entry["source"]) && entry["source"]["path"] === root) {
16893
+ const next = { ...known };
16894
+ delete next[market];
16895
+ writeMarketplaces(file, next);
16896
+ }
16897
+ }
16898
+ }
16899
+ function emptyOwnedCatalogContainer(root) {
16900
+ if (!existsSync17(root))
16901
+ return true;
16902
+ if (readdirSync11(root).some((name) => ![OWNERSHIP, ".claude-plugin", "plugins"].includes(name)))
16903
+ return false;
16904
+ if (existsSync17(join22(root, ".claude-plugin")) && readdirSync11(join22(root, ".claude-plugin")).some((name) => name !== "marketplace.json"))
16905
+ return false;
16906
+ return !existsSync17(join22(root, "plugins")) || readdirSync11(join22(root, "plugins")).length === 0;
16907
+ }
16908
+ function validateLifecycleCatalogRetirement(nativeId, scopeId) {
16909
+ const root = lifecycleCatalogRoot(nativeId);
16910
+ if (!existsSync17(root))
16911
+ return;
16912
+ assertManagedPath(root);
16913
+ assertManagedPath(join22(root, OWNERSHIP));
16914
+ const owner = readOwnership(root);
16915
+ if (owner?.source !== "plgnz-wrapper" || owner.pluginId !== `marketplace@${marketplaceName(nativeId)}`) {
16916
+ const member = readOwnership(lifecycleCatalogCopy(nativeId));
16917
+ if (member?.pluginId === nativeId && member.scopeId === scopeId)
16918
+ throw new Error(`Claude Code catalog retirement lost its container ownership proof: ${root}`);
16919
+ return;
16920
+ }
16921
+ assertManagedPath(join22(root, ".claude-plugin", "marketplace.json"));
16922
+ const copy = lifecycleCatalogCopy(nativeId);
16923
+ assertManagedPath(copy);
16924
+ const selected = catalogEntries(lifecycleCatalogDocument(root, marketplaceName(nativeId))).filter((entry) => isCatalogMember(entry, nativeId));
16925
+ if (selected.length > 1 || selected.some((entry) => !isRecord6(entry) || entry["source"] !== `./plugins/${nativeId.slice(0, nativeId.indexOf("@"))}`))
16926
+ throw new Error(`Claude Code catalog retirement found a foreign or ambiguous member: ${nativeId}`);
16927
+ if (existsSync17(copy)) {
16928
+ assertNoSymlinks2(copy);
16929
+ const member = readOwnership(copy);
16930
+ if (member?.pluginId !== nativeId || member.scopeId !== scopeId)
16931
+ throw new Error(`Claude Code catalog retirement cannot prove ownership: ${copy}`);
16932
+ }
16933
+ }
16934
+ function captureLifecycleCatalog(reference, nativeId) {
16935
+ const root = lifecycleCatalogRoot(nativeId);
16936
+ const market = marketplaceName(nativeId);
16937
+ const document = lifecycleCatalogDocument(root, market);
16938
+ writeFileSync3(join22(reference, "catalog.json"), JSON.stringify({ document, selected: catalogEntries(document).filter((entry) => isCatalogMember(entry, nativeId)), marker: existsSync17(join22(root, OWNERSHIP)) ? readFileSync16(join22(root, OWNERSHIP), "utf8") : null }));
16939
+ const copy = lifecycleCatalogCopy(nativeId);
16940
+ if (existsSync17(copy)) {
16941
+ assertNoSymlinks2(copy);
16942
+ cpSync3(copy, join22(reference, "catalog-plugin"), { recursive: true });
16943
+ }
16944
+ }
16945
+ function restoreLifecycleCatalog(reference, nativeId, scopeId) {
16946
+ const saved = JSON.parse(readFileSync16(join22(reference, "catalog.json"), "utf8"));
16947
+ const root = lifecycleCatalogRoot(nativeId);
16948
+ assertManagedPath(root);
16949
+ if (existsSync17(root)) {
16950
+ assertManagedPath(join22(root, OWNERSHIP));
16951
+ const owner = readOwnership(root);
16952
+ if (owner?.source !== "plgnz-wrapper" || owner.pluginId !== `marketplace@${marketplaceName(nativeId)}`)
16953
+ throw new Error(`Claude Code rollback refuses a foreign catalog: ${root}`);
16954
+ }
16955
+ assertManagedPath(join22(root, ".claude-plugin", "marketplace.json"));
16956
+ const market = marketplaceName(nativeId);
16957
+ const current = lifecycleCatalogDocument(root, market);
16958
+ const siblings = catalogEntries(current).filter((entry) => !isCatalogMember(entry, nativeId));
16959
+ const copy = lifecycleCatalogCopy(nativeId);
16960
+ assertManagedPath(copy);
16961
+ if (existsSync17(copy)) {
16962
+ assertNoSymlinks2(copy);
16963
+ const owner = readOwnership(copy);
16964
+ if (owner?.pluginId !== nativeId || owner.scopeId !== scopeId)
16965
+ throw new Error(`Claude Code rollback refuses a foreign catalog member: ${copy}`);
16966
+ }
16967
+ rmSync3(copy, { recursive: true, force: true });
16968
+ if (existsSync17(join22(reference, "catalog-plugin"))) {
16969
+ mkdirSync3(dirname6(copy), { recursive: true });
16970
+ cpSync3(join22(reference, "catalog-plugin"), copy, { recursive: true });
16971
+ }
16972
+ if (saved.document === null && siblings.length === 0 && emptyOwnedCatalogContainer(root)) {
16973
+ rmSync3(root, { recursive: true, force: true });
16974
+ return;
16975
+ }
16976
+ const document = current ?? saved.document;
16977
+ if (document === null)
16978
+ throw new Error("Claude Code catalog rollback has no manifest");
16979
+ mkdirSync3(join22(root, ".claude-plugin"), { recursive: true });
16980
+ writeFileSync3(join22(root, ".claude-plugin", "marketplace.json"), JSON.stringify({ ...document, plugins: [...siblings, ...saved.selected] }));
16981
+ if (saved.marker !== null)
16982
+ writeFileSync3(join22(root, OWNERSHIP), saved.marker);
16983
+ }
16445
16984
  function slotVersion(version, revision) {
16446
16985
  const selected = version ?? revision;
16447
16986
  if (selected.length === 0 || selected.includes("/") || selected.includes("\\")) {
@@ -16467,6 +17006,7 @@ function captureRollback(attemptId, operationId, nativeId) {
16467
17006
  const known = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"));
16468
17007
  const knownEntry = marketplace !== undefined && Object.hasOwn(known, marketplace) ? known[marketplace] ?? null : null;
16469
17008
  writeFileSync3(join22(reference, "known-marketplace.json"), JSON.stringify({ marketplace: marketplace ?? null, entry: knownEntry }));
17009
+ captureLifecycleCatalog(reference, nativeId);
16470
17010
  const installPath = recordedInstallPath(nativeId);
16471
17011
  if (installPath !== undefined && existsSync17(installPath)) {
16472
17012
  cpSync3(installPath, join22(reference, "install"), { recursive: true });
@@ -16477,6 +17017,18 @@ function captureRollback(attemptId, operationId, nativeId) {
16477
17017
  function mutateInstall(handle, stagingRoot) {
16478
17018
  if (handle.projectedFingerprint === null)
16479
17019
  throw new Error("claude-code activation is missing its sealed fingerprint");
17020
+ const nativeCatalog = matchingForeignLifecycleCatalog(handle.nativeId, handle.scopeId, handle.target, stagingRoot, slotVersion(handle.packageVersion, handle.sourceRevision));
17021
+ if (nativeCatalog === null)
17022
+ validateLifecycleCatalog(handle.nativeId, handle.scopeId);
17023
+ const capturedEntry = handle.expected.contentRoots.find((root) => root.label === "selected-catalog-entry");
17024
+ const ownedEntryPath = join22(lifecycleCatalogRoot(handle.nativeId), ".claude-plugin", "marketplace.json");
17025
+ if (nativeCatalog !== null || capturedEntry !== undefined && capturedEntry.path !== ownedEntryPath) {
17026
+ const liveEntry = lifecycleCatalogEntryObservation(handle.nativeId)[0];
17027
+ if (nativeCatalog === null || capturedEntry === undefined || capturedEntry.path === ownedEntryPath || liveEntry?.path !== capturedEntry.path || liveEntry.fingerprint !== capturedEntry.fingerprint) {
17028
+ throw new Error("claude-code selected foreign catalog entry changed after preparation; re-plan before applying");
17029
+ }
17030
+ }
17031
+ const beforeCatalog = lifecycleSelectedMutationState(handle.nativeId);
16480
17032
  const installPath = cacheSlot(handle.nativeId, slotVersion(handle.packageVersion, handle.sourceRevision));
16481
17033
  const before = existsSync17(installPath) ? fingerprintTree(installPath) : null;
16482
17034
  rmSync3(installPath, { recursive: true, force: true });
@@ -16491,6 +17043,8 @@ function mutateInstall(handle, stagingRoot) {
16491
17043
  plugins: []
16492
17044
  };
16493
17045
  try {
17046
+ if (nativeCatalog === null)
17047
+ installLifecycleCatalog(handle.nativeId, handle.scopeId, stagingRoot);
16494
17048
  writeRegistry(registryFile, registryWithEntry(readRegistry(registryFile), handle.nativeId, installPath, slotVersion(handle.packageVersion, handle.sourceRevision), source));
16495
17049
  writeSettings(settingsFile, settingsWithEnabled(readSettings(settingsFile), handle.nativeId, true));
16496
17050
  disableMarketplaceAutoUpdate(handle.nativeId);
@@ -16500,9 +17054,24 @@ function mutateInstall(handle, stagingRoot) {
16500
17054
  }
16501
17055
  return {
16502
17056
  receiptId: `claude-apply-${handle.operationId}`,
16503
- changed: before !== handle.projectedFingerprint
17057
+ changed: before !== handle.projectedFingerprint || beforeCatalog !== lifecycleSelectedMutationState(handle.nativeId)
16504
17058
  };
16505
17059
  }
17060
+ function lifecycleSelectedMutationState(nativeId) {
17061
+ const market = marketplaceName(nativeId);
17062
+ const known = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"));
17063
+ const settings = readSettings(join22(pluginsDir(), "..", "settings.json"));
17064
+ const extra = settings["extraKnownMarketplaces"];
17065
+ const member = registeredLifecycleCatalogMember(nativeId)?.member ?? lifecycleCatalogCopy(nativeId);
17066
+ return catalogEntryFingerprint({
17067
+ catalogEntry: lifecycleCatalogEntryObservation(nativeId),
17068
+ member: existsSync17(member) ? fingerprintTree(member) : null,
17069
+ registration: known[market] ?? null,
17070
+ extraRegistration: isRecord6(extra) ? extra[market] ?? null : null,
17071
+ installedRows: readRegistry(join22(pluginsDir(), "installed_plugins.json")).plugins[nativeId] ?? null,
17072
+ enabled: enabledPlugins()[nativeId] ?? null
17073
+ });
17074
+ }
16506
17075
  function setEnabled(handle, enabled, receiptId) {
16507
17076
  const settingsFile = join22(pluginsDir(), "..", "settings.json");
16508
17077
  const was = enabledPlugins()[handle.nativeId] === true;
@@ -16537,6 +17106,7 @@ function restoreRollback(handle) {
16537
17106
  const beforeMarkets = existsSync17(join22(pluginsDir(), "known_marketplaces.json")) ? readFileSync16(join22(pluginsDir(), "known_marketplaces.json"), "utf8") : "";
16538
17107
  const beforeApplied = existsSync17(appliedSlot) ? fingerprintTree(appliedSlot) : null;
16539
17108
  const beforePrevious = previousSlot !== undefined && existsSync17(previousSlot) ? fingerprintTree(previousSlot) : null;
17109
+ restoreLifecycleCatalog(reference, handle.nativeId, handle.scopeId);
16540
17110
  restorePluginSlice(handle.nativeId, reference);
16541
17111
  if (previousSlot !== undefined && resolve11(previousSlot) !== appliedSlot)
16542
17112
  rmSync3(appliedSlot, { recursive: true, force: true });
@@ -16604,6 +17174,9 @@ function restoreKnownMarketplace(reference) {
16604
17174
  if (saved.entry === null) {
16605
17175
  if (!hadFile || !Object.hasOwn(current, saved.marketplace))
16606
17176
  return;
17177
+ const root = lifecycleCatalogRoot(`rollback@${saved.marketplace}`);
17178
+ if (existsSync17(root) && (catalogEntries(lifecycleCatalogDocument(root, saved.marketplace)).length > 0 || !emptyOwnedCatalogContainer(root)))
17179
+ return;
16607
17180
  const next = { ...current };
16608
17181
  delete next[saved.marketplace];
16609
17182
  writeMarketplaces(file, next);
@@ -16648,10 +17221,29 @@ function marketplaceAutoUpdateRecords(marketplace) {
16648
17221
  }
16649
17222
  function hasForeignMarketplaceSibling(nativeId, marketplace) {
16650
17223
  const registry = readRegistry(join22(pluginsDir(), "installed_plugins.json"));
16651
- return Object.entries(registry.plugins).some(([id, rows]) => {
17224
+ if (Object.entries(registry.plugins).some(([id, rows]) => {
16652
17225
  if (id === nativeId || marketplaceName(id) !== marketplace || !Array.isArray(rows))
16653
17226
  return false;
16654
17227
  return rows.some((row) => isRecord6(row) && row["scope"] === "user" && !plgnzOwnedInstall(id, row["installPath"]));
17228
+ }))
17229
+ return true;
17230
+ const registration = readMarketplaces(join22(pluginsDir(), "known_marketplaces.json"))[marketplace];
17231
+ if (!isRecord6(registration) || !isRecord6(registration["source"]) || registration["source"]["source"] !== "directory" || typeof registration["source"]["path"] !== "string")
17232
+ return false;
17233
+ const root = registration["source"]["path"];
17234
+ if (!root.startsWith("/") || !existsSync17(root))
17235
+ return false;
17236
+ const document = lifecycleCatalogDocument(root, marketplace);
17237
+ return catalogEntries(document).some((entry) => {
17238
+ if (isCatalogMember(entry, nativeId))
17239
+ return false;
17240
+ if (!isRecord6(entry) || typeof entry["name"] !== "string" || typeof entry["source"] !== "string" || !entry["source"].startsWith("./"))
17241
+ return true;
17242
+ const member = resolve11(root, entry["source"]);
17243
+ if (!containedPath(root, member) || member === root || !existsSync17(member))
17244
+ return true;
17245
+ assertCatalogPath(root, member);
17246
+ return readOwnership(member)?.pluginId !== `${entry["name"]}@${marketplace}`;
16655
17247
  });
16656
17248
  }
16657
17249
  function plgnzOwnedInstall(id, installPath) {
@@ -19771,9 +20363,9 @@ var ompWriter = {
19771
20363
  const lockFile = join26(pluginsDir3(), "omp-plugins.lock.json");
19772
20364
  const registryFile = join26(pluginsDir3(), "installed_plugins.json");
19773
20365
  const lock = readDoc(lockFile, { plugins: {}, settings: {} }, "lockfile");
19774
- const lockPlugins = object3(lock.plugins, "lockfile plugins");
20366
+ const lockPlugins = object4(lock.plugins, "lockfile plugins");
19775
20367
  const registry = readDoc(registryFile, { version: 2, plugins: {} }, "registry");
19776
- const registryPlugins = object3(registry.plugins, "registry plugins");
20368
+ const registryPlugins = object4(registry.plugins, "registry plugins");
19777
20369
  const rows = userRows(registryPlugins[id]);
19778
20370
  if (rows.length > 0)
19779
20371
  validateLegacy(rows, id, plugin.name, version, opts?.adoptExisting === true);
@@ -20096,7 +20688,7 @@ function readDoc(path, fallback, label) {
20096
20688
  throw new Error(`invalid OMP ${label}: ${path} (${error.message})`);
20097
20689
  }
20098
20690
  }
20099
- function object3(value, label) {
20691
+ function object4(value, label) {
20100
20692
  if (!isDoc(value))
20101
20693
  throw new Error(`invalid OMP ${label}`);
20102
20694
  return value;
@@ -20517,7 +21109,7 @@ async function applyManaged(handle, stagingRoot) {
20517
21109
  const link = managedLink(handle.nativeId);
20518
21110
  const file = lockPath();
20519
21111
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20520
- const plugins = object3(lock.plugins, "lockfile plugins");
21112
+ const plugins = object4(lock.plugins, "lockfile plugins");
20521
21113
  const before = snapshot(file);
20522
21114
  mkdirSafe(dirname9(target));
20523
21115
  mkdirSafe(dirname9(link));
@@ -20547,7 +21139,7 @@ function activationAlreadyCurrent(nativeId, stage, version) {
20547
21139
  if (!existsSync21(target) || !existsSync21(stage) || fingerprintTree(stage) !== fingerprintTree(target) || !linkPointsTo(managedLink(nativeId), target))
20548
21140
  return false;
20549
21141
  const lock = readDoc(lockPath(), { plugins: {}, settings: {} }, "lockfile");
20550
- const plugins = object3(lock.plugins, "lockfile plugins");
21142
+ const plugins = object4(lock.plugins, "lockfile plugins");
20551
21143
  const row = entry(plugins[npmName(nativeId)]);
20552
21144
  return row.enabled === true && row.version === version;
20553
21145
  }
@@ -20555,7 +21147,7 @@ async function disableManaged(handle) {
20555
21147
  const receiptId = `disable:${handle.attemptId}:${handle.operationId}`;
20556
21148
  const file = lockPath();
20557
21149
  const lock = readDoc(file, { plugins: {}, settings: {} }, "lockfile");
20558
- const plugins = object3(lock.plugins, "lockfile plugins");
21150
+ const plugins = object4(lock.plugins, "lockfile plugins");
20559
21151
  const npm = npmName(handle.nativeId);
20560
21152
  const previous = entry(plugins[npm]);
20561
21153
  if (previous.enabled === false)
@@ -20998,7 +21590,7 @@ function observeDcodeTarget(target) {
20998
21590
  throw new Error("unsupported dcode target identity");
20999
21591
  const registry = readRegistry3(dcodeRegistryFile());
21000
21592
  const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
21001
- return Object.entries(object4(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
21593
+ return Object.entries(object5(registry.plugins, "registry plugins")).map(([nativeId, raw]) => {
21002
21594
  const rows = rowsFor(raw);
21003
21595
  if (rows.length !== 1)
21004
21596
  throw new Error(`dcode install record ${nativeId} has duplicate rows`);
@@ -21053,7 +21645,7 @@ function captureDcodeRollback(binding, attemptId, operationId) {
21053
21645
  mkdirSync9(dir, { recursive: true });
21054
21646
  const registry = readRegistry3(dcodeRegistryFile());
21055
21647
  const enabled = boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins");
21056
- const row = object4(registry.plugins, "registry plugins")[binding.nativeId];
21648
+ const row = object5(registry.plugins, "registry plugins")[binding.nativeId];
21057
21649
  const state = { row: row ?? null, enabled: enabled[binding.nativeId] ?? null, registryExisted: existsSync23(dcodeRegistryFile()), enablementExisted: existsSync23(dcodeEnablementFile()) };
21058
21650
  writeFileSync9(join28(dir, "state.json"), JSON.stringify(state));
21059
21651
  for (const [index, item] of rowsFor(row).entries()) {
@@ -21100,7 +21692,7 @@ function preflightAdoption(request) {
21100
21692
  if (path === undefined)
21101
21693
  throw new Error("dcode unmanaged adoption has no native root");
21102
21694
  const manifest = declaredManifest(snapshot.packageRoot, snapshot.packageName);
21103
- assessPrior(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[snapshot.nativeId], snapshot.nativeId, snapshot.nativeGit?.locator ?? `local:${snapshot.scopeId}`, true, snapshot.packageName, manifest.version, snapshot.packageRoot);
21695
+ assessPrior(object5(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[snapshot.nativeId], snapshot.nativeId, snapshot.nativeGit?.locator ?? `local:${snapshot.scopeId}`, true, snapshot.packageName, manifest.version, snapshot.packageRoot);
21104
21696
  if (fingerprintTree(path) !== installed.installedFingerprint)
21105
21697
  throw new Error("dcode unmanaged adoption changed during preflight");
21106
21698
  }
@@ -21108,7 +21700,7 @@ function setDcodeRow(nativeId, row, enabled) {
21108
21700
  assertManagedPath4(dcodeRegistryFile());
21109
21701
  assertManagedPath4(dcodeEnablementFile());
21110
21702
  const registry = readRegistry3(dcodeRegistryFile()), enablement = readEnablement(dcodeEnablementFile());
21111
- const plugins = { ...object4(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
21703
+ const plugins = { ...object5(registry.plugins, "registry plugins") }, flags = { ...boolObject(enablement.enabledPlugins, "enabledPlugins") };
21112
21704
  if (row === null)
21113
21705
  delete plugins[nativeId];
21114
21706
  else
@@ -21232,13 +21824,13 @@ var dcodeDefinition = {
21232
21824
  },
21233
21825
  disable: async (prepared) => {
21234
21826
  await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
21235
- const row = object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
21827
+ const row = object5(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId];
21236
21828
  setDcodeRow(prepared.handle.nativeId, row ?? null, false);
21237
21829
  return { receiptId: `dcode-disable:${prepared.handle.operationId}`, changed: true };
21238
21830
  },
21239
21831
  retire: async (prepared) => {
21240
21832
  await acquireNativeLock(prepared.handle.attemptId, prepared.handle.operationId);
21241
- const rows = rowsFor(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
21833
+ const rows = rowsFor(object5(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")[prepared.handle.nativeId]);
21242
21834
  for (const row of rows) {
21243
21835
  const path = rowInstall(row, prepared.handle.nativeId);
21244
21836
  assertCachePath(path);
@@ -21288,7 +21880,7 @@ var dcodeDefinition = {
21288
21880
  }
21289
21881
  }
21290
21882
  setDcodeRow(handle.nativeId, state.row ?? null, typeof state.enabled === "boolean" ? state.enabled : null);
21291
- if (state.registryExisted === false && Object.keys(object4(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")).length === 0)
21883
+ if (state.registryExisted === false && Object.keys(object5(readRegistry3(dcodeRegistryFile()).plugins, "registry plugins")).length === 0)
21292
21884
  rmSync9(dcodeRegistryFile());
21293
21885
  if (state.enablementExisted === false && Object.keys(boolObject(readEnablement(dcodeEnablementFile()).enabledPlugins, "enabledPlugins")).length === 0)
21294
21886
  rmSync9(dcodeEnablementFile());
@@ -21365,7 +21957,7 @@ var dcodeWriter = {
21365
21957
  assertManagedPath4(enablementFile);
21366
21958
  const registry = readRegistry3(registryFile);
21367
21959
  const enablement = readEnablement(enablementFile);
21368
- const plugins = object4(registry.plugins, "registry plugins");
21960
+ const plugins = object5(registry.plugins, "registry plugins");
21369
21961
  const enabled = boolObject(enablement.enabledPlugins, "enabledPlugins");
21370
21962
  const manifest = declaredManifest(plugin.dir, plugin.name);
21371
21963
  const observation = probeDcodeVersion();
@@ -21473,7 +22065,7 @@ var dcodeWriter = {
21473
22065
  assertManagedPath4(enablementFile);
21474
22066
  const registry = readRegistry3(registryFile);
21475
22067
  const enablement = readEnablement(enablementFile);
21476
- const plugins = object4(registry.plugins, "registry plugins");
22068
+ const plugins = object5(registry.plugins, "registry plugins");
21477
22069
  const rows = rowsFor(plugins[id]);
21478
22070
  if (rows.length === 0)
21479
22071
  return;
@@ -21567,7 +22159,7 @@ function declaredManifest(dir, expectedName) {
21567
22159
  throw new Error("dcode stage has no supported plugin manifest");
21568
22160
  let parsed;
21569
22161
  try {
21570
- parsed = object4(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
22162
+ parsed = object5(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21571
22163
  } catch (error) {
21572
22164
  throw new Error(`invalid dcode manifest: ${manifest} (${error.message})`);
21573
22165
  }
@@ -21590,10 +22182,10 @@ function forceAutoUpdateOff(dir) {
21590
22182
  const manifest = MANIFESTS.map((relative) => join28(dir, relative)).find((file) => existsSync23(file));
21591
22183
  if (manifest === undefined)
21592
22184
  throw new Error("dcode stage has no supported plugin manifest");
21593
- const parsed = object4(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
21594
- const extensions = parsed.extensions === undefined ? {} : object4(parsed.extensions, "manifest extensions");
22185
+ const parsed = object5(JSON.parse(readFileSync22(manifest, "utf8")), "manifest");
22186
+ const extensions = parsed.extensions === undefined ? {} : object5(parsed.extensions, "manifest extensions");
21595
22187
  const current = extensions["com.langchain.deepagents.code"];
21596
- const dcodeExtension = current === undefined ? {} : object4(current, "dcode manifest extension");
22188
+ const dcodeExtension = current === undefined ? {} : object5(current, "dcode manifest extension");
21597
22189
  dcodeExtension.autoUpdate = false;
21598
22190
  extensions["com.langchain.deepagents.code"] = dcodeExtension;
21599
22191
  parsed.extensions = extensions;
@@ -21605,7 +22197,7 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
21605
22197
  throw new Error(`dcode readback failed for ${id}`);
21606
22198
  const registry = readRegistry3(registryFile);
21607
22199
  const enablement = readEnablement(enablementFile);
21608
- const rows = rowsFor(object4(registry.plugins, "registry plugins")[id]);
22200
+ const rows = rowsFor(object5(registry.plugins, "registry plugins")[id]);
21609
22201
  if (rows.length !== 1)
21610
22202
  throw new Error(`dcode readback failed for ${id}`);
21611
22203
  const install = rowInstall(rows[0], id);
@@ -21615,7 +22207,7 @@ function assertReadback(id, registryFile, enablementFile, target, version, proje
21615
22207
  throw new Error(`dcode managed install is eligible for native auto-update: ${id}`);
21616
22208
  if (declaredManifest(target, id.split("@")[0]).version !== version)
21617
22209
  throw new Error(`dcode readback version mismatch for ${id}`);
21618
- if (boolObject(object4(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
22210
+ if (boolObject(object5(enablement.enabledPlugins, "enabledPlugins"), "enabledPlugins")[id] !== true)
21619
22211
  throw new Error(`dcode readback enablement mismatch for ${id}`);
21620
22212
  const marker = ownership3(target);
21621
22213
  if (marker?.kind !== "current" || marker.projectedFingerprint !== projected || marker.sourceRevision !== sourceRevision) {
@@ -21695,7 +22287,7 @@ function readRegistry3(file) {
21695
22287
  const registry = readDoc2(file, { version: 2, plugins: {} }, "registry");
21696
22288
  if (registry.version !== 1 && registry.version !== 2)
21697
22289
  throw new Error(`invalid dcode registry version: ${file}`);
21698
- const plugins = object4(registry.plugins, "registry plugins");
22290
+ const plugins = object5(registry.plugins, "registry plugins");
21699
22291
  for (const [id, value] of Object.entries(plugins)) {
21700
22292
  if (!id || !Array.isArray(value) || value.length === 0)
21701
22293
  throw new Error(`invalid dcode registry record: ${id}`);
@@ -21714,13 +22306,13 @@ function readEnablement(file) {
21714
22306
  boolObject(enablement.enabledPlugins, "enabledPlugins");
21715
22307
  return enablement;
21716
22308
  }
21717
- function object4(value, label) {
22309
+ function object5(value, label) {
21718
22310
  if (typeof value !== "object" || value === null || Array.isArray(value))
21719
22311
  throw new Error(`invalid dcode ${label}`);
21720
22312
  return value;
21721
22313
  }
21722
22314
  function boolObject(value, label) {
21723
- const result = object4(value, label);
22315
+ const result = object5(value, label);
21724
22316
  if (Object.values(result).some((entry) => typeof entry !== "boolean"))
21725
22317
  throw new Error(`invalid dcode ${label}`);
21726
22318
  return result;
@@ -21743,7 +22335,7 @@ function ownership3(dir) {
21743
22335
  if (!existsSync23(file))
21744
22336
  return null;
21745
22337
  try {
21746
- const value = object4(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
22338
+ const value = object5(JSON.parse(readFileSync22(file, "utf8")), "ownership marker");
21747
22339
  if (typeof value.source !== "string" || typeof value.pluginId !== "string" || typeof value.fingerprint !== "string")
21748
22340
  throw new Error("marker fields are invalid");
21749
22341
  const projected = value.projectedFingerprint;
@@ -24456,8 +25048,240 @@ var zcodeCliLifecycle = createLifecycleHostAdapter(zcodeCliLifecycleDefinition);
24456
25048
 
24457
25049
  // src/hosts/hermes-writer.ts
24458
25050
  import { chmodSync, cpSync as cpSync12, existsSync as existsSync27, lstatSync as lstatSync20, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync13, readFileSync as readFileSync26, readdirSync as readdirSync21, realpathSync as realpathSync5, renameSync as renameSync12, rmSync as rmSync13, statSync as statSync14, writeFileSync as writeFileSync13 } from "node:fs";
24459
- import { dirname as dirname15, join as join32, resolve as resolve21 } from "node:path";
25051
+ import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute9, join as join32, resolve as resolve21 } from "node:path";
24460
25052
  import { tmpdir as tmpdir11 } from "node:os";
25053
+
25054
+ // src/hosts/hermes-launcher-templates.ts
25055
+ var hermesNativeBootstrap = `import os, re, sys
25056
+ import contextlib, hashlib, io, pathlib, shutil, subprocess
25057
+ os.environ.pop('PYTHONHOME', None)
25058
+ os.environ.pop('PYTHONPATH', None)
25059
+ sys.path.insert(0, '/__PLUGNZ_HERMES_ROOT__')
25060
+ def _hermes_closure_repair():
25061
+ import contextlib, hashlib, io, shutil, subprocess
25062
+ from pathlib import Path
25063
+ root = Path('/__PLUGNZ_HERMES_ROOT__')
25064
+ git = root / '.git'
25065
+ try:
25066
+ if git.is_file():
25067
+ text = git.read_text(encoding='utf-8-sig').strip()
25068
+ git = root / text[7:].strip() if text.startswith('gitdir:') else git
25069
+ fields = dict(line.partition('=')[::2] for line in (git / 'hermes-update-pull').read_text(encoding='utf-8-sig').splitlines())
25070
+ except OSError:
25071
+ return
25072
+ pre = fields.get('pre', '').strip()
25073
+ if not re.fullmatch('[0-9a-f]{40}|[0-9a-f]{64}', pre):
25074
+ return
25075
+ closure = git / 'hermes-update-recovery' / pre
25076
+ files = (*('hermes_cli/_early_recovery.py', 'hermes_cli/update_lock.py', 'hermes_cli/update_custody.py'), 'hermes_cli/__init__.py')
25077
+
25078
+ def blob_id(data):
25079
+ return hashlib.new('sha1' if len(pre) == 40 else 'sha256', b'blob %d\\0' % len(data) + data).hexdigest()
25080
+
25081
+ def verified():
25082
+ try:
25083
+ listed = dict(line.split(' ', 1)[::-1] for line in (closure / 'MANIFEST').read_text(encoding='utf-8-sig').splitlines())
25084
+ return (set(listed) == set(files) and listed['hermes_cli/__init__.py'] == blob_id(b'')
25085
+ and all(blob_id((closure / rel).read_bytes()) == oid for rel, oid in listed.items()))
25086
+ except (OSError, ValueError):
25087
+ return False
25088
+
25089
+ if not verified():
25090
+ exe = fields.get('git', '').strip()
25091
+ if not (os.path.isabs(exe) and os.path.isfile(exe)):
25092
+ names = ('git.exe',) if os.name == 'nt' else ('git',)
25093
+ exe = next((os.path.join(d, n) for d in os.environ.get('PATH', '').split(os.pathsep) if os.path.isabs(d)
25094
+ for n in names if os.path.isfile(os.path.join(d, n))), '')
25095
+ if not exe:
25096
+ return
25097
+
25098
+ def git_out(*args):
25099
+ done = subprocess.run([exe, '--no-replace-objects', '-C', str(root), *args], capture_output=True, timeout=60,
25100
+ stdin=subprocess.DEVNULL, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
25101
+ if done.returncode != 0:
25102
+ raise OSError(done.stderr)
25103
+ return done.stdout
25104
+
25105
+ staging = closure.with_name('.%s.%d.launch' % (pre, os.getpid()))
25106
+ try:
25107
+ ids = {}
25108
+ for entry in git_out('ls-tree', '-z', '--full-tree', pre, '--', *('hermes_cli/_early_recovery.py', 'hermes_cli/update_lock.py', 'hermes_cli/update_custody.py')).split(b'\\0'):
25109
+ meta, _, rel = entry.decode('utf-8', 'replace').partition('\\t')
25110
+ if meta.split()[1:2] == ['blob']:
25111
+ ids[rel] = meta.split()[2]
25112
+ blobs = {'hermes_cli/__init__.py': b''}
25113
+ for rel in ('hermes_cli/_early_recovery.py', 'hermes_cli/update_lock.py', 'hermes_cli/update_custody.py'):
25114
+ blobs[rel] = git_out('cat-file', 'blob', ids[rel])
25115
+ if blob_id(blobs[rel]) != ids[rel]: # an object that inflates to bytes pre never had
25116
+ raise OSError('%s does not hash to %s' % (rel, ids[rel]))
25117
+ blobs['MANIFEST'] = ''.join('%s %s\\n' % (blob_id(data), rel) for rel, data in blobs.items()).encode('utf-8')
25118
+ for rel, data in blobs.items():
25119
+ (staging / rel).parent.mkdir(parents=True, exist_ok=True)
25120
+ (staging / rel).write_bytes(data)
25121
+ shutil.rmtree(closure, ignore_errors=True)
25122
+ os.replace(staging, closure)
25123
+ except (OSError, KeyError, subprocess.SubprocessError):
25124
+ pass
25125
+ finally:
25126
+ shutil.rmtree(staging, ignore_errors=True)
25127
+ if not verified():
25128
+ return
25129
+ for name in [n for n in sys.modules if n.split('.')[0] in ('hermes_cli', 'hermes_bootstrap', 'hermes_constants', 'pm')]:
25130
+ del sys.modules[name]
25131
+ tree = os.path.normcase(os.path.realpath(root))
25132
+ sys.path[:] = [str(closure)] + [p for p in sys.path if p and os.path.normcase(os.path.realpath(p)) != tree
25133
+ and not os.path.normcase(os.path.realpath(p)).startswith(tree + os.sep)]
25134
+ sys.dont_write_bytecode = True
25135
+ said = io.StringIO()
25136
+ restored = False
25137
+ try:
25138
+ with contextlib.redirect_stderr(said):
25139
+ from hermes_cli import _early_recovery
25140
+ restored = _early_recovery.restore_interrupted_pull(root)
25141
+ except Exception as exc:
25142
+ said.write('hermes: the checkout cannot start after an interrupted \`hermes update\`, and it was not '
25143
+ 'repaired: %s\\n' % (exc,))
25144
+ if restored:
25145
+ sys.stderr.write('hermes: the checkout could not start after an interrupted \`hermes update\`; '
25146
+ 'repaired it with the recovery code saved before the update.\\n' + said.getvalue())
25147
+ _early_recovery.relaunch_after_restore()
25148
+ sys.stderr.write(said.getvalue() or 'hermes: the checkout cannot start after an interrupted \`hermes update\`; '
25149
+ 'launch again once the update that owns it finishes.\\n')
25150
+ raise SystemExit(1)
25151
+ if sys.argv[1:2] == ['--print-runtime-command']:
25152
+ sys.dont_write_bytecode = True
25153
+ from pathlib import Path
25154
+ try:
25155
+ from hermes_cli import _early_recovery
25156
+ except Exception as exc:
25157
+ if _hermes_closure_repair() or not isinstance(exc, ImportError):
25158
+ raise
25159
+ _early_recovery = None # a tree without the repair: the imports below report real damage
25160
+ if _early_recovery is not None and _early_recovery.restore_interrupted_pull(Path('/__PLUGNZ_HERMES_ROOT__')):
25161
+ _early_recovery.relaunch_after_restore()
25162
+ from hermes_constants import get_default_hermes_root
25163
+ os.environ['HERMES_HOME'] = os.environ.get('HERMES_HOME') or str(get_default_hermes_root())
25164
+ from hermes_cli._launchers import print_runtime_command
25165
+ print_runtime_command(Path('/__PLUGNZ_HERMES_ROOT__'), sys.argv[2:])
25166
+ sys.exit(0)
25167
+ sys._hermes_pin_default_home = True
25168
+ try:
25169
+ import hermes_bootstrap
25170
+ except Exception:
25171
+ _hermes_closure_repair()
25172
+ raise
25173
+ if not os.environ.get('HERMES_HOME'):
25174
+ from hermes_constants import get_default_hermes_root
25175
+ os.environ['HERMES_HOME'] = str(get_default_hermes_root())
25176
+ if sys.argv[1:2] == ['--run-module']:
25177
+ import runpy
25178
+ if len(sys.argv) < 3: sys.exit('hermes: --run-module needs a module')
25179
+ module = sys.argv.pop(2)
25180
+ del sys.argv[1]
25181
+ runpy.run_module(module, run_name='__main__', alter_sys=True)
25182
+ sys.exit(0)
25183
+ from hermes_cli.main import main
25184
+ sys.argv[0] = re.sub(r'(-script\\.pyw|\\.exe)?$', '', sys.argv[0])
25185
+ sys.exit(main())
25186
+ `;
25187
+ var hermesEditableFinderBody = `from __future__ import annotations
25188
+ import sys
25189
+ from importlib.machinery import ModuleSpec, PathFinder
25190
+ from importlib.machinery import all_suffixes as module_suffixes
25191
+ from importlib.util import spec_from_file_location
25192
+ from itertools import chain
25193
+ from pathlib import Path
25194
+
25195
+ __MAPPING__
25196
+ __NAMESPACES__
25197
+ PATH_PLACEHOLDER = '__EDITABLE_NAME__' + ".__path_hook__"
25198
+
25199
+
25200
+ class _EditableFinder: # MetaPathFinder
25201
+ @classmethod
25202
+ def find_spec(cls, fullname: str, path=None, target=None) -> ModuleSpec | None: # type: ignore
25203
+ # Top-level packages and modules (we know these exist in the FS)
25204
+ if fullname in MAPPING:
25205
+ pkg_path = MAPPING[fullname]
25206
+ return cls._find_spec(fullname, Path(pkg_path))
25207
+
25208
+ # Handle immediate children modules (required for namespaces to work)
25209
+ # To avoid problems with case sensitivity in the file system we delegate
25210
+ # to the importlib.machinery implementation.
25211
+ parent, _, child = fullname.rpartition(".")
25212
+ if parent and parent in MAPPING:
25213
+ return PathFinder.find_spec(fullname, path=[MAPPING[parent]])
25214
+
25215
+ # Other levels of nesting should be handled automatically by importlib
25216
+ # using the parent path.
25217
+ return None
25218
+
25219
+ @classmethod
25220
+ def _find_spec(cls, fullname: str, candidate_path: Path) -> ModuleSpec | None:
25221
+ init = candidate_path / "__init__.py"
25222
+ candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
25223
+ for candidate in chain([init], candidates):
25224
+ if candidate.exists():
25225
+ return spec_from_file_location(fullname, candidate)
25226
+ return None
25227
+
25228
+
25229
+ class _EditableNamespaceFinder: # PathEntryFinder
25230
+ @classmethod
25231
+ def _path_hook(cls, path) -> type[_EditableNamespaceFinder]:
25232
+ if path == PATH_PLACEHOLDER:
25233
+ return cls
25234
+ raise ImportError
25235
+
25236
+ @classmethod
25237
+ def _paths(cls, fullname: str) -> list[str]:
25238
+ paths = NAMESPACES[fullname]
25239
+ if not paths and fullname in MAPPING:
25240
+ paths = [MAPPING[fullname]]
25241
+ # Always add placeholder, for 2 reasons:
25242
+ # 1. __path__ cannot be empty for the spec to be considered namespace.
25243
+ # 2. In the case of nested namespaces, we need to force
25244
+ # import machinery to query _EditableNamespaceFinder again.
25245
+ return [*paths, PATH_PLACEHOLDER]
25246
+
25247
+ @classmethod
25248
+ def find_spec(cls, fullname: str, target=None) -> ModuleSpec | None: # type: ignore
25249
+ if fullname in NAMESPACES:
25250
+ spec = ModuleSpec(fullname, None, is_package=True)
25251
+ spec.submodule_search_locations = cls._paths(fullname)
25252
+ return spec
25253
+ return None
25254
+
25255
+ @classmethod
25256
+ def find_module(cls, _fullname) -> None:
25257
+ return None
25258
+
25259
+
25260
+ def install():
25261
+ if not any(finder == _EditableFinder for finder in sys.meta_path):
25262
+ sys.meta_path.append(_EditableFinder)
25263
+
25264
+ if not NAMESPACES:
25265
+ return
25266
+
25267
+ if not any(hook == _EditableNamespaceFinder._path_hook for hook in sys.path_hooks):
25268
+ # PathEntryFinder is needed to create NamespaceSpec without private APIS
25269
+ sys.path_hooks.append(_EditableNamespaceFinder._path_hook)
25270
+ if PATH_PLACEHOLDER not in sys.path:
25271
+ sys.path.append(PATH_PLACEHOLDER) # Used just to trigger the path hook
25272
+ `;
25273
+ var hermesUvConsoleBody = `# -*- coding: utf-8 -*-
25274
+ import sys
25275
+ from hermes_cli.main import main
25276
+ if __name__ == "__main__":
25277
+ if sys.argv[0].endswith("-script.pyw"):
25278
+ sys.argv[0] = sys.argv[0][:-11]
25279
+ elif sys.argv[0].endswith(".exe"):
25280
+ sys.argv[0] = sys.argv[0][:-4]
25281
+ sys.exit(main())
25282
+ `;
25283
+
25284
+ // src/hosts/hermes-writer.ts
24461
25285
  var MARKER10 = ".plgnz-install.json";
24462
25286
  var hermesWriter = {
24463
25287
  ...hermes,
@@ -24799,6 +25623,278 @@ function matches(marker, id, ownership) {
24799
25623
  return marker === id || marker === ownership;
24800
25624
  }
24801
25625
  var EFFECTIVE = { requirement: "none", status: "effective" };
25626
+ function launcherWords(body) {
25627
+ const words = [];
25628
+ let word = "", active = false, quote = "";
25629
+ for (let at = 0;at < body.length; at++) {
25630
+ const ch = body[at];
25631
+ if (quote === "'") {
25632
+ if (ch === "'")
25633
+ quote = "";
25634
+ else
25635
+ word += ch;
25636
+ } else if (quote === '"') {
25637
+ if (ch === '"')
25638
+ quote = "";
25639
+ else if (ch === "\\") {
25640
+ if (++at >= body.length)
25641
+ return;
25642
+ word += body[at];
25643
+ } else if (ch === "$" && !(word === "" && body.slice(at, at + 3) === '$@"'))
25644
+ return;
25645
+ else if (ch === "`")
25646
+ return;
25647
+ else
25648
+ word += ch;
25649
+ } else if (ch === "'" || ch === '"') {
25650
+ quote = ch;
25651
+ active = true;
25652
+ } else if (/\s/u.test(ch)) {
25653
+ if (active) {
25654
+ words.push(word);
25655
+ word = "";
25656
+ active = false;
25657
+ }
25658
+ } else if (ch === "\\") {
25659
+ if (++at >= body.length)
25660
+ return;
25661
+ word += body[at];
25662
+ active = true;
25663
+ } else if (/[;$`|&<>]/u.test(ch))
25664
+ return;
25665
+ else {
25666
+ word += ch;
25667
+ active = true;
25668
+ }
25669
+ }
25670
+ if (quote !== "")
25671
+ return;
25672
+ if (active)
25673
+ words.push(word);
25674
+ return words;
25675
+ }
25676
+ function literalPythonPath(literal) {
25677
+ if (literal.startsWith('"')) {
25678
+ try {
25679
+ const value = JSON.parse(literal);
25680
+ return typeof value === "string" ? value : undefined;
25681
+ } catch {
25682
+ return;
25683
+ }
25684
+ }
25685
+ if (!/^'(?:[^'\\]|\\['\\])*'$/u.test(literal))
25686
+ return;
25687
+ return literal.slice(1, -1).replace(/\\(['\\])/gu, "$1");
25688
+ }
25689
+ function verifiedEditableFinder(text, root, version) {
25690
+ const literal = String.raw`(?:'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")`;
25691
+ const mapping = /^MAPPING: dict\[str, str\] = (.*)$/mu.exec(text);
25692
+ const namespaces = /^NAMESPACES: dict\[str, list\[str\]\] = (.*)$/mu.exec(text);
25693
+ if (mapping?.[1] === undefined || namespaces?.[1] === undefined)
25694
+ return false;
25695
+ const entry = `${literal}:\\s*${literal}`;
25696
+ const list = `\\[(?:${literal}(?:,\\s*${literal})*)?\\]`;
25697
+ const namespace = `${literal}:\\s*${list}`;
25698
+ if (!new RegExp(`^\\{(?:${entry}(?:,\\s*${entry})*)?\\}$`, "u").test(mapping[1]) || !new RegExp(`^\\{(?:${namespace}(?:,\\s*${namespace})*)?\\}$`, "u").test(namespaces[1]))
25699
+ return false;
25700
+ const data = [...mapping[1].matchAll(new RegExp(`(${literal}):\\s*(${literal})`, "gu"))];
25701
+ if (!data.some((row) => literalPythonPath(row[1]) === "hermes_cli" && literalPythonPath(row[2]) === join32(root, "hermes_cli")))
25702
+ return false;
25703
+ const canonical = (keyLiteral, pathLiteral) => {
25704
+ const key = literalPythonPath(keyLiteral), path = literalPythonPath(pathLiteral);
25705
+ return key !== undefined && /^[a-zA-Z0-9_.-]+$/u.test(key) && path === join32(root, ...key.split("."));
25706
+ };
25707
+ if (data.some((row) => !canonical(row[1], row[2])))
25708
+ return false;
25709
+ for (const row of namespaces[1].matchAll(new RegExp(`(${literal}):\\s*\\[([^\\]]*)\\]`, "gu"))) {
25710
+ if ([...row[2].matchAll(new RegExp(literal, "gu"))].some((path) => !canonical(row[1], path[0])))
25711
+ return false;
25712
+ }
25713
+ return text.replace(mapping[0], "__MAPPING__").replace(namespaces[0], "__NAMESPACES__").replaceAll(`__editable__.hermes_agent-${version}.finder`, "__EDITABLE_NAME__") === hermesEditableFinderBody;
25714
+ }
25715
+ function isolatedVenvConfig(text) {
25716
+ const values = new Map;
25717
+ for (const line of text.split(/\r?\n/u)) {
25718
+ if (line.trim() === "" || line.trimStart().startsWith("#"))
25719
+ continue;
25720
+ const row = /^\s*([^=]+?)\s*=\s*(.*?)\s*$/u.exec(line);
25721
+ if (row === null)
25722
+ return false;
25723
+ const key = row[1].trim().toLowerCase();
25724
+ if (key === "" || values.has(key))
25725
+ return false;
25726
+ values.set(key, row[2].toLowerCase());
25727
+ }
25728
+ return values.get("include-system-site-packages") === "false";
25729
+ }
25730
+ function hasImportShadow(directory, modules, allowed = []) {
25731
+ return readdirSync21(directory).some((entry) => !allowed.includes(entry) && modules.some((module) => entry === module || entry === `${module}_d.pyd` || entry.startsWith(`${module}.`) && /\.(?:py|pyc|so|pyd)$/u.test(entry)));
25732
+ }
25733
+ function verifiedSourceImportClosure(root) {
25734
+ let remaining = 20000;
25735
+ const visit = (directory, top) => {
25736
+ const entries = readdirSync21(directory);
25737
+ remaining -= entries.length;
25738
+ if (remaining < 0)
25739
+ return false;
25740
+ const packages = entries.filter((entry) => /^[a-zA-Z_]\w*$/u.test(entry) && entry !== "__pycache__" && statSync14(join32(directory, entry)).isDirectory());
25741
+ const modules = ["__init__", ...packages, ...entries.filter((entry) => entry.endsWith(".py")).map((entry) => entry.slice(0, -3))];
25742
+ if (top)
25743
+ modules.push("hermes_cli", "hermes_bootstrap", "hermes_constants", "pm");
25744
+ const allowed = entries.filter((entry) => entry.endsWith(".py"));
25745
+ allowed.push(...packages.filter((module) => !entries.includes(`${module}.py`)));
25746
+ if (hasImportShadow(directory, modules, allowed))
25747
+ return false;
25748
+ for (const entry of entries) {
25749
+ if (entry.startsWith(".") || entry === "__pycache__")
25750
+ continue;
25751
+ const path = join32(directory, entry);
25752
+ const stat = lstatSync20(path);
25753
+ if (stat.isSymbolicLink()) {
25754
+ if (entry.endsWith(".py") || statSync14(path).isDirectory())
25755
+ return false;
25756
+ } else if (stat.isDirectory() && !visit(path, false))
25757
+ return false;
25758
+ }
25759
+ return true;
25760
+ };
25761
+ return visit(root, true);
25762
+ }
25763
+ function hermesExecutableSource(binary, depth = 0) {
25764
+ if (depth > 4 || !isAbsolute9(binary) || !existsSync27(binary))
25765
+ return;
25766
+ const executable = realpathSync5(binary);
25767
+ if (!statSync14(executable).isFile())
25768
+ return;
25769
+ const text = readFileSync26(executable, "utf8");
25770
+ if (text.startsWith(`#!/bin/sh
25771
+ exec `)) {
25772
+ const args = launcherWords(text.slice(`#!/bin/sh
25773
+ exec `.length));
25774
+ if (args?.length === 2 && args[1] === "$@")
25775
+ return hermesExecutableSource(args[0], depth + 1);
25776
+ if (args?.length !== 5 || args[1] !== "-I" || args[2] !== "-c" || args[4] !== "$@" || !isAbsolute9(args[0]) || !/^python(?:\d+(?:\.\d+)*)?$/u.test(basename8(args[0])) || !existsSync27(args[0]))
25777
+ return;
25778
+ const code = args[3];
25779
+ if (!code.startsWith(`import os, re, sys
25780
+ `) || !/\nfrom hermes_cli\.main import main\n/u.test(code) || !/\nsys\.exit\(main\(\)\)\s*$/u.test(code))
25781
+ return;
25782
+ const bindings = [...code.matchAll(/^sys\.path\.insert\(0, ('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")\)$/gmu)];
25783
+ if (bindings.length !== 1)
25784
+ return;
25785
+ const root = literalPythonPath(bindings[0][1]);
25786
+ if (root === undefined || code.replaceAll(bindings[0][1], "'/__PLUGNZ_HERMES_ROOT__'") !== hermesNativeBootstrap)
25787
+ return;
25788
+ return root !== undefined && isAbsolute9(root) && existsSync27(join32(root, "hermes_cli", "main.py")) && realpathSync5(join32(root, "hermes_cli", "main.py")).startsWith(`${realpathSync5(root)}/`) && verifiedSourceImportClosure(realpathSync5(root)) ? realpathSync5(root) : undefined;
25789
+ }
25790
+ const interpreter = /^#!([^\n]+)\n/u.exec(text)?.[1];
25791
+ if (interpreter === undefined || !isAbsolute9(interpreter) || !existsSync27(interpreter) || !/^python(?:\d+(?:\.\d+)*)?$/u.test(basename8(interpreter)) || text.slice(text.indexOf(`
25792
+ `) + 1) !== hermesUvConsoleBody)
25793
+ return;
25794
+ const venv = dirname15(dirname15(executable));
25795
+ if (dirname15(interpreter) !== join32(venv, "bin") || !existsSync27(join32(venv, "pyvenv.cfg")))
25796
+ return;
25797
+ if (existsSync27(join32(venv, "bin", "pyvenv.cfg")) || !isolatedVenvConfig(readFileSync26(join32(venv, "pyvenv.cfg"), "utf8")) || process.env["PYTHONHOME"] || process.env["PYTHONPATH"])
25798
+ return;
25799
+ if (hasImportShadow(join32(venv, "bin"), ["hermes_cli", "sitecustomize", "usercustomize", "_virtualenv"]))
25800
+ return;
25801
+ const lib = join32(venv, "lib");
25802
+ if (!existsSync27(lib))
25803
+ return;
25804
+ const roots = [];
25805
+ for (const python of readdirSync21(lib).filter((name) => /^python\d+\.\d+$/u.test(name))) {
25806
+ const packages = join32(lib, python, "site-packages");
25807
+ if (!existsSync27(packages))
25808
+ continue;
25809
+ if (hasImportShadow(packages, ["hermes_cli", "sitecustomize", "usercustomize", "_virtualenv"], ["_virtualenv.py"]))
25810
+ return;
25811
+ for (const name of readdirSync21(packages).filter((name) => /^hermes_agent-[^/]+\.dist-info$/u.test(name))) {
25812
+ const distribution = join32(packages, name);
25813
+ const metadata = join32(distribution, "direct_url.json");
25814
+ const entry = join32(distribution, "entry_points.txt");
25815
+ if (!existsSync27(metadata) || !existsSync27(entry) || !/^hermes\s*=\s*hermes_cli\.main:main$/mu.test(readFileSync26(entry, "utf8")))
25816
+ continue;
25817
+ const direct = JSON.parse(readFileSync26(metadata, "utf8"));
25818
+ if (typeof direct !== "object" || direct === null || !("url" in direct) || typeof direct.url !== "string" || !("dir_info" in direct) || typeof direct.dir_info !== "object" || direct.dir_info === null || !("editable" in direct.dir_info) || direct.dir_info.editable !== true || !direct.url.startsWith("file:///"))
25819
+ continue;
25820
+ const root = decodeURIComponent(direct.url.slice("file://".length));
25821
+ if (!isAbsolute9(root) || !existsSync27(join32(root, "hermes_cli", "main.py")))
25822
+ continue;
25823
+ const sourceModules = [
25824
+ "hermes_cli",
25825
+ "hermes_bootstrap",
25826
+ "hermes_constants",
25827
+ "pm",
25828
+ ...readdirSync21(root).filter((name) => name.endsWith(".py")).map((name) => name.slice(0, -3)),
25829
+ ...readdirSync21(root).filter((name) => existsSync27(join32(root, name, "__init__.py")))
25830
+ ];
25831
+ if (hasImportShadow(packages, sourceModules) || hasImportShadow(join32(venv, "bin"), sourceModules))
25832
+ return;
25833
+ if (hasImportShadow(root, ["hermes_cli", "sitecustomize", "usercustomize", "_virtualenv"], ["hermes_cli"]))
25834
+ return;
25835
+ const version = name.slice("hermes_agent-".length, -".dist-info".length);
25836
+ const finder = `__editable___hermes_agent_${version.replace(/[^a-zA-Z0-9]/gu, "_")}_finder`;
25837
+ if (hasImportShadow(packages, [finder], [`${finder}.py`]) || hasImportShadow(join32(venv, "bin"), [finder]) || hasImportShadow(root, [finder]))
25838
+ return;
25839
+ let binding = false;
25840
+ for (const pthName of readdirSync21(packages).filter((name) => name.endsWith(".pth"))) {
25841
+ const pth = readFileSync26(join32(packages, pthName), "utf8").trim();
25842
+ if (pthName === `_virtualenv.pth` && pth === "import _virtualenv") {
25843
+ const helper = join32(packages, "_virtualenv.py");
25844
+ if (!existsSync27(helper) || new CryptoHasher("sha256").update(readFileSync26(helper, "utf8")).digest("hex") !== "cfb3db86aaa53bb62b5ff764970bec2d71c9228590a0ebec57f6ec926cc0bf1a")
25845
+ return;
25846
+ } else if (pthName === `__editable__.hermes_agent-${version}.pth` && pth === root)
25847
+ binding = true;
25848
+ else if (pthName === `__editable__.hermes_agent-${version}.pth` && pth === `import ${finder}; ${finder}.install()` && existsSync27(join32(packages, `${finder}.py`)) && verifiedEditableFinder(readFileSync26(join32(packages, `${finder}.py`), "utf8"), root, version))
25849
+ binding = true;
25850
+ else
25851
+ return;
25852
+ }
25853
+ if (binding && realpathSync5(join32(root, "hermes_cli", "main.py")).startsWith(`${realpathSync5(root)}/`) && verifiedSourceImportClosure(realpathSync5(root)))
25854
+ roots.push(realpathSync5(root));
25855
+ }
25856
+ }
25857
+ return roots.length === 1 ? roots[0] : undefined;
25858
+ }
25859
+ function observeHermesRevision() {
25860
+ const binary = process.env["OPEN_PLUGIN_HERMES_BIN"] ?? which("hermes");
25861
+ if (!binary || !existsSync27(binary))
25862
+ return { kind: "unknown", diagnostic: "Select an existing Hermes launcher with OPEN_PLUGIN_HERMES_BIN; a profile config does not identify its runtime" };
25863
+ try {
25864
+ const root = hermesExecutableSource(binary);
25865
+ if (root === undefined)
25866
+ return { kind: "unparseable", diagnostic: "Hermes launcher source binding is unverified; select its actual executable with OPEN_PLUGIN_HERMES_BIN and prove its whole native/uv body and editable import closure against docs/hosts/hermes.md before admission. Hermes was not executed" };
25867
+ const git = which("git");
25868
+ if (git === null)
25869
+ return { kind: "unknown" };
25870
+ const env = {
25871
+ ...process.env,
25872
+ GIT_NO_LAZY_FETCH: "1",
25873
+ GIT_OPTIONAL_LOCKS: "0",
25874
+ GIT_CONFIG_NOSYSTEM: "1",
25875
+ GIT_CONFIG_GLOBAL: "/dev/null"
25876
+ };
25877
+ for (const key of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"])
25878
+ delete env[key];
25879
+ const output = (args) => {
25880
+ const result = spawnSync([git, "-C", root, ...args], { env, stdout: "pipe", stderr: "pipe", timeout: 1e4 });
25881
+ return result.exitCode === 0 ? new TextDecoder().decode(result.stdout) : undefined;
25882
+ };
25883
+ const lines = output(["rev-parse", "--show-toplevel", "HEAD"])?.trim().split(`
25884
+ `);
25885
+ if (lines?.length !== 2 || lines[0] === undefined || lines[1] === undefined || !/^[a-f0-9]{40}$/u.test(lines[1]))
25886
+ return { kind: "unknown" };
25887
+ if (realpathSync5(lines[0]) !== root)
25888
+ return { kind: "unparseable" };
25889
+ const dirty = output(["-c", "core.fsmonitor=false", "status", "--porcelain=v1", "--untracked-files=no"]);
25890
+ if (dirty === undefined)
25891
+ return { kind: "unknown" };
25892
+ const revision = dirty.trim() === "" ? lines[1] : `${lines[1]}+dirty`;
25893
+ return { kind: "detected", version: revision, probeId: `hermes-launcher-git-${revision}` };
25894
+ } catch {
25895
+ return { kind: "unknown" };
25896
+ }
25897
+ }
24802
25898
  var HERMES_SUPPORTED = new Set([
24803
25899
  "ordinary-skills",
24804
25900
  "mcp",
@@ -24813,20 +25909,20 @@ var HERMES_SUPPORTED = new Set([
24813
25909
  "activation-reload",
24814
25910
  "reversible-disable"
24815
25911
  ]);
24816
- var hermesManagedProfile = createCapabilityEvidenceProfile({
25912
+ var hermesManagedProfiles = hermesProvenManagedRevisions.map((detectedVersion) => createCapabilityEvidenceProfile({
24817
25913
  host: "hermes",
24818
- detectedVersion: HERMES_PORTABLE_SURFACE,
25914
+ detectedVersion,
24819
25915
  sourceTypes: ["local", "git"],
24820
25916
  operations: ["install", "update", "disable", "retire"],
24821
25917
  route: "managed",
24822
25918
  operationStatus: "supported",
24823
25919
  semantics: Object.fromEntries(PACKAGE_SEMANTICS.map((semantic) => [semantic, HERMES_SUPPORTED.has(semantic) ? "supported" : "unsupported"])),
24824
25920
  evidence: [
24825
- "docs/evidence/hermes-native-loader-c0d7294-20260923.json",
25921
+ detectedVersion === "c0d7294769a38c17ceae51d8f7995e66e1dcae27" ? "docs/evidence/hermes-native-loader-c0d7294-20260923.json" : "docs/evidence/hermes-native-loader-dce1e9b-20261011.json",
24826
25922
  "docs/hosts/hermes.md",
24827
25923
  "docs/research/native-plugin-update-capabilities-2026-10-09.md"
24828
25924
  ]
24829
- });
25925
+ }));
24830
25926
  function storeFor(target) {
24831
25927
  const identity = hermesInstanceIdentity({ kind: target.kind, instance: target.instance, ...target.context === undefined ? {} : { context: { ...target.context } } });
24832
25928
  return { root: identity.root, configPath: resolveInstanceConfig(identity.root, identity.configPath), pluginsDir: join32(identity.root, "plugins") };
@@ -25176,7 +26272,7 @@ function markerBody2(input) {
25176
26272
  }
25177
26273
  var hermesLifecycleDefinition = {
25178
26274
  id: "hermes",
25179
- evidenceProfiles: [hermesManagedProfile],
26275
+ evidenceProfiles: hermesManagedProfiles,
25180
26276
  async probeVersion(target) {
25181
26277
  try {
25182
26278
  const store = storeFor(target);
@@ -25194,7 +26290,7 @@ var hermesLifecycleDefinition = {
25194
26290
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
25195
26291
  return { kind: "unparseable" };
25196
26292
  }
25197
- return { kind: "detected", version: HERMES_PORTABLE_SURFACE, probeId: HERMES_PORTABLE_SURFACE_PROBE };
26293
+ return observeHermesRevision();
25198
26294
  } catch {
25199
26295
  return { kind: "unknown" };
25200
26296
  }
@@ -25419,7 +26515,7 @@ var cleanupWriters = [...writers, piWriter, opencodeWriter];
25419
26515
 
25420
26516
  // src/executor.ts
25421
26517
  import { existsSync as existsSync28, readFileSync as readFileSync27 } from "node:fs";
25422
- import { isAbsolute as isAbsolute10, join as join34, resolve as resolve22 } from "node:path";
26518
+ import { isAbsolute as isAbsolute11, join as join34, resolve as resolve22 } from "node:path";
25423
26519
 
25424
26520
  // src/owned-activation.ts
25425
26521
  function ownedActivation(scope, source, activation, installation, target) {
@@ -25566,7 +26662,7 @@ class SyncManifestValidationError extends Error {
25566
26662
  }
25567
26663
  }
25568
26664
  function parseSyncManifest(value) {
25569
- const manifest = object5(value, "sync manifest");
26665
+ const manifest = object6(value, "sync manifest");
25570
26666
  exactFields5(manifest, ["schemaVersion", "entries"], "sync manifest");
25571
26667
  if (manifest["schemaVersion"] !== SYNC_MANIFEST_SCHEMA_VERSION)
25572
26668
  invalidArgument2("sync manifest schemaVersion must be 1");
@@ -25600,7 +26696,7 @@ function selectManifestPackages(entry, discovered) {
25600
26696
  });
25601
26697
  }
25602
26698
  function parseEntry(value, index) {
25603
- const entry = object5(value, `sync manifest entry ${index}`);
26699
+ const entry = object6(value, `sync manifest entry ${index}`);
25604
26700
  if (entry["operation"] === "sync") {
25605
26701
  exactFields5(entry, ["operation", "source", "target", "selectors"], `sync manifest entry ${index}`);
25606
26702
  const source = parseSourceBinding(entry["source"], `sync manifest entry ${index} source`);
@@ -25678,7 +26774,7 @@ function assertUniqueScopes(entries) {
25678
26774
  }
25679
26775
  }
25680
26776
  function parseSourceBinding(value, label) {
25681
- const binding = object5(value, label);
26777
+ const binding = object6(value, label);
25682
26778
  if (binding["kind"] === "local") {
25683
26779
  exactFields5(binding, ["kind", "locator"], label);
25684
26780
  if (typeof binding["locator"] !== "string")
@@ -25704,7 +26800,7 @@ function parseTarget(value, label) {
25704
26800
  }
25705
26801
  function parseSelector(value, entryIndex, selectorIndex) {
25706
26802
  const label = `sync manifest entry ${entryIndex} selector ${selectorIndex}`;
25707
- const selector = object5(value, label);
26803
+ const selector = object6(value, label);
25708
26804
  exactFields5(selector, ["package", "adoptExisting"], label);
25709
26805
  if (typeof selector["package"] !== "string" || !/^[a-z0-9][a-z0-9._-]*$/iu.test(selector["package"])) {
25710
26806
  invalidArgument2(`${label} package must be a valid package name`);
@@ -25722,7 +26818,7 @@ function validateBinding(source, label) {
25722
26818
  }
25723
26819
  }
25724
26820
  function targetProfile(value, label) {
25725
- const target = object5(value, label);
26821
+ const target = object6(value, label);
25726
26822
  if (typeof target["kind"] !== "string")
25727
26823
  invalidArgument2(`${label} kind must be a string`);
25728
26824
  return targetProfileForKind(target["kind"], label);
@@ -25747,7 +26843,7 @@ function invokeTargetProfile(operation) {
25747
26843
  throw error;
25748
26844
  }
25749
26845
  }
25750
- function object5(value, label) {
26846
+ function object6(value, label) {
25751
26847
  if (typeof value !== "object" || value === null || Array.isArray(value))
25752
26848
  invalidArgument2(`${label} must be an object`);
25753
26849
  return value;
@@ -26675,7 +27771,7 @@ function defect(reason, terminalPhase) {
26675
27771
  // src/state-write.ts
26676
27772
  import { mkdirSync as mkdirSync14, rmSync as rmSync14, writeFileSync as writeFileSync14 } from "node:fs";
26677
27773
  import * as fs from "node:fs";
26678
- import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute9, join as join33, relative as relative7 } from "node:path";
27774
+ import { basename as basename9, dirname as dirname16, isAbsolute as isAbsolute10, join as join33, relative as relative7 } from "node:path";
26679
27775
  function writeState(records, file = stateFile()) {
26680
27776
  const existing = readLifecycleState(file);
26681
27777
  if (existing.sourceVersion === 2)
@@ -26765,12 +27861,12 @@ function persistedSource(binding) {
26765
27861
  }
26766
27862
  }
26767
27863
  function legacyRelativeDir2(record) {
26768
- if (record.sourceDir === undefined || !isAbsolute9(record.source) || !isAbsolute9(record.sourceDir))
27864
+ if (record.sourceDir === undefined || !isAbsolute10(record.source) || !isAbsolute10(record.sourceDir))
26769
27865
  return;
26770
27866
  const candidate = relative7(record.source, record.sourceDir).replaceAll("\\", "/");
26771
27867
  if (candidate === "")
26772
27868
  return ".";
26773
- if (candidate === ".." || candidate.startsWith("../") || isAbsolute9(candidate))
27869
+ if (candidate === ".." || candidate.startsWith("../") || isAbsolute10(candidate))
26774
27870
  return;
26775
27871
  return candidate;
26776
27872
  }
@@ -26830,7 +27926,7 @@ function legacyScopes(state, scopeId, source, host, attemptId, retiredAttemptId)
26830
27926
  function atomicWrite(file, contents) {
26831
27927
  const parent = dirname16(file);
26832
27928
  mkdirSync14(parent, { recursive: true });
26833
- const temporary = join33(parent, `.${basename8(file)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
27929
+ const temporary = join33(parent, `.${basename9(file)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
26834
27930
  try {
26835
27931
  writeFileSync14(temporary, contents);
26836
27932
  fs.renameSync(temporary, file);
@@ -28026,7 +29122,7 @@ function recordedPinCommand(packageRoot, server) {
28026
29122
  }
28027
29123
  function absolutePinExecutable(command) {
28028
29124
  if (command.includes("/")) {
28029
- if (!isAbsolute10(command))
29125
+ if (!isAbsolute11(command))
28030
29126
  return null;
28031
29127
  const canonical = resolve22(command);
28032
29128
  return canonical === command ? command : null;
@@ -28263,7 +29359,7 @@ function createLifecyclePlannerHosts() {
28263
29359
  // src/pin-lifecycle.ts
28264
29360
  import { cpSync as cpSync13, mkdtempSync as mkdtempSync14, rmSync as rmSync15 } from "node:fs";
28265
29361
  import { tmpdir as tmpdir12 } from "node:os";
28266
- import { basename as basename9, join as join35, relative as relative8 } from "node:path";
29362
+ import { basename as basename10, join as join35, relative as relative8 } from "node:path";
28267
29363
  async function runOfflineLifecyclePin(host, plugin, dryRun) {
28268
29364
  const adapter = host.lifecyclePinAdapter;
28269
29365
  if (adapter === undefined || plugin.path === undefined)
@@ -28285,7 +29381,7 @@ async function runOfflineLifecyclePin(host, plugin, dryRun) {
28285
29381
  return preview;
28286
29382
  const now = new Date().toISOString();
28287
29383
  const frozenRoot = mkdtempSync14(join35(tmpdir12(), "plgnz-offline-pin-"));
28288
- const attemptId = `pin-${basename9(frozenRoot)}`;
29384
+ const attemptId = `pin-${basename10(frozenRoot)}`;
28289
29385
  const operationId = `${attemptId}:${plugin.id}`;
28290
29386
  let handle;
28291
29387
  let preparation;
@@ -28977,7 +30073,7 @@ function legacyCommandAction(command) {
28977
30073
  // package.json
28978
30074
  var package_default = {
28979
30075
  name: "plugnz",
28980
- version: "0.0.10",
30076
+ version: "0.0.12",
28981
30077
  description: "Install, diagnose and update agent plugins (Agent Plugins spec) and MCP configs across Claude Code, Codex, Cursor, Kimi and more",
28982
30078
  license: "MIT",
28983
30079
  type: "module",