@hasna/instructions 0.7.2 → 0.7.5

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 (41) hide show
  1. package/README.md +13 -0
  2. package/dist/chunks/{apply-wj7wh5km.js → apply-1cpkx082.js} +2 -2
  3. package/dist/chunks/{apply-ps6jjt6k.js → apply-xraar7p0.js} +2 -2
  4. package/dist/chunks/{index-pq18fdbj.js → index-16j8x10s.js} +64 -3
  5. package/dist/chunks/{index-n6nh6f5r.js → index-6qfy0542.js} +193 -15
  6. package/dist/chunks/{index-ek15201v.js → index-rvb4znxa.js} +2 -2
  7. package/dist/chunks/{index-ape24zc0.js → index-src5t3my.js} +1 -1
  8. package/dist/chunks/{index-y8410ve0.js → index-x0v4m19f.js} +1 -1
  9. package/dist/chunks/{index-18j7x3xw.js → index-ysnp8y58.js} +2 -2
  10. package/dist/chunks/{local-1c1qxzvx.js → local-66fck74w.js} +1 -1
  11. package/dist/chunks/{local-hc67bynj.js → local-jqfx3kbq.js} +1 -1
  12. package/dist/chunks/{sync-azd9t4p8.js → sync-8763n4sc.js} +3 -3
  13. package/dist/chunks/{sync-12m3vs6g.js → sync-8ca3ew84.js} +3 -3
  14. package/dist/cli/index.js +526 -554
  15. package/dist/index.d.ts +4 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +727 -587
  18. package/dist/lib/asset-plan.d.ts +4 -1
  19. package/dist/lib/asset-plan.d.ts.map +1 -1
  20. package/dist/lib/harness-discovery.d.ts +52 -0
  21. package/dist/lib/harness-discovery.d.ts.map +1 -0
  22. package/dist/lib/machine.d.ts.map +1 -1
  23. package/dist/lib/managed-skill-runtimes.d.ts +12 -28
  24. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
  25. package/dist/lib/session-apply.d.ts +11 -0
  26. package/dist/lib/session-apply.d.ts.map +1 -1
  27. package/dist/lib/session-refresh.d.ts.map +1 -1
  28. package/dist/lib/session-render.d.ts +7 -0
  29. package/dist/lib/session-render.d.ts.map +1 -1
  30. package/dist/mcp/index.js +7 -7
  31. package/dist/sdk/index.d.ts +2 -0
  32. package/dist/sdk/index.d.ts.map +1 -1
  33. package/dist/sdk/index.js +153 -0
  34. package/dist/sdk/v1.generated.d.ts +6 -0
  35. package/dist/sdk/v1.generated.d.ts.map +1 -1
  36. package/dist/server/index.js +77 -0
  37. package/dist/server/openapi.d.ts +27 -0
  38. package/dist/server/openapi.d.ts.map +1 -1
  39. package/dist/types/index.d.ts +8 -0
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7823,6 +7823,10 @@ function normalizeProfileAssetBinding(value) {
7823
7823
  const rollback = record["rollback"];
7824
7824
  if (!ASSET_ROLLBACK_POLICIES.includes(rollback))
7825
7825
  throw new Error(`Invalid asset rollback policy: ${String(rollback)}`);
7826
+ const nativeAgent = record["nativeAgent"] === undefined ? undefined : normalizeNativeAgentMetadata(record["nativeAgent"]);
7827
+ if (nativeAgent && (kind !== "custom-agent" || strategy !== "emit-file" || !["claude", "sumi", "opencode"].includes(provider))) {
7828
+ throw new Error("nativeAgent metadata requires a supported emitted custom-agent definition.");
7829
+ }
7826
7830
  return {
7827
7831
  schema: PROFILE_ASSET_BINDING_SCHEMA,
7828
7832
  assetKey,
@@ -7842,6 +7846,7 @@ function normalizeProfileAssetBinding(value) {
7842
7846
  root,
7843
7847
  relativePath: nonEmptyString(destinationRecord["relativePath"], "destination.relativePath")
7844
7848
  },
7849
+ ...nativeAgent ? { nativeAgent } : {},
7845
7850
  uninstall,
7846
7851
  rollback
7847
7852
  };
@@ -7917,6 +7922,7 @@ function compileAssetPlan(input) {
7917
7922
  allowed: binding.source.allowed
7918
7923
  },
7919
7924
  destination: binding.destination,
7925
+ ...binding.nativeAgent ? { nativeAgent: binding.nativeAgent } : {},
7920
7926
  support,
7921
7927
  action,
7922
7928
  mutationMode: input.mode,
@@ -7972,6 +7978,18 @@ function validateAssetSourceAndDestination(bundle, binding, diagnostics) {
7972
7978
  pathSafe = false;
7973
7979
  diagnostics.push(diagnostic("error", "ASSET_DESTINATION_UNSAFE", binding.assetKey, error.message));
7974
7980
  }
7981
+ if (pathSafe && binding.kind === "custom-agent" && binding.destination.strategy === "emit-file" && (binding.selector.provider === "claude" || binding.selector.provider === "sumi" || binding.nativeAgent) && (binding.destination.root !== "target-home" || !/^agents\/[A-Za-z0-9][A-Za-z0-9_-]*\.md$/.test(safeRelativePath(binding.destination.relativePath)))) {
7982
+ pathSafe = false;
7983
+ diagnostics.push(diagnostic("error", "ASSET_CUSTOM_AGENT_DESTINATION_UNSUPPORTED", binding.assetKey, "Native custom-agent definitions require agents/<name>.md beneath an explicit target-home."));
7984
+ }
7985
+ if (binding.nativeAgent) {
7986
+ if (posix.basename(binding.destination.relativePath, ".md") !== binding.nativeAgent.name) {
7987
+ diagnostics.push(diagnostic("error", "ASSET_NATIVE_AGENT_NAME_MISMATCH", binding.assetKey, "Native role name must match its destination filename."));
7988
+ }
7989
+ if (/^\uFEFF?---[ \t]*\r?\n/.test(bundle.content)) {
7990
+ diagnostics.push(diagnostic("error", "ASSET_NATIVE_AGENT_DOUBLE_HEADER", binding.assetKey, "Canonical role prose must not already contain frontmatter when nativeAgent metadata is supplied."));
7991
+ }
7992
+ }
7975
7993
  const expectedLocator = bundle.locator;
7976
7994
  if (!binding.source.immutable || binding.source.locator !== expectedLocator) {
7977
7995
  diagnostics.push(diagnostic("error", "ASSET_SOURCE_MUTABLE_OR_UNPINNED", binding.assetKey, `Asset source must be pinned to ${expectedLocator}.`));
@@ -8027,6 +8045,72 @@ function safeRelativePath(value) {
8027
8045
  }
8028
8046
  return normalized;
8029
8047
  }
8048
+ function normalizeNativeAgentMetadata(value) {
8049
+ const record = objectRecord(value, "nativeAgent");
8050
+ if (Object.keys(record).some((key) => !["name", "description", "frontmatter"].includes(key)))
8051
+ throw new Error("Unknown nativeAgent metadata field.");
8052
+ const name = nonEmptyString(record["name"], "nativeAgent.name");
8053
+ const description = nonEmptyString(record["description"], "nativeAgent.description");
8054
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name) || name.length > 128)
8055
+ throw new Error("Invalid nativeAgent.name.");
8056
+ if (description.length > 4096 || /[\r\n\0]/.test(description))
8057
+ throw new Error("Invalid nativeAgent.description.");
8058
+ if (record["frontmatter"] === undefined)
8059
+ return { name, description };
8060
+ const frontmatter = record["frontmatter"];
8061
+ if (typeof frontmatter !== "string" || frontmatter.length > 16384 || frontmatter.includes("\x00"))
8062
+ throw new Error("Invalid nativeAgent.frontmatter.");
8063
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n$/.exec(frontmatter);
8064
+ if (!match)
8065
+ throw new Error("nativeAgent.frontmatter requires one complete newline-terminated YAML header.");
8066
+ const scalar = (raw) => {
8067
+ if (raw.startsWith('"')) {
8068
+ try {
8069
+ const parsed = JSON.parse(raw);
8070
+ return typeof parsed === "string" ? parsed : undefined;
8071
+ } catch {
8072
+ return;
8073
+ }
8074
+ }
8075
+ if (!/^[A-Za-z0-9][A-Za-z0-9 _.,:;()/?@+-]*$/.test(raw) || /:(?:\s|$)/.test(raw))
8076
+ return;
8077
+ if (/^(?:true|yes|on|y)$/i.test(raw))
8078
+ return true;
8079
+ if (/^(?:false|no|off|n)$/i.test(raw))
8080
+ return false;
8081
+ if (/^null$/i.test(raw))
8082
+ return null;
8083
+ if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(raw))
8084
+ return Number(raw);
8085
+ if (/^[0-9]/.test(raw))
8086
+ return;
8087
+ return raw;
8088
+ };
8089
+ const fields = new Map;
8090
+ for (const line of match[1].split(/\r?\n/)) {
8091
+ if (!line.trim() || line.startsWith("#"))
8092
+ continue;
8093
+ const field = /^([A-Za-z][A-Za-z0-9_-]*):[ \t]+(.+)$/.exec(line);
8094
+ if (!field || fields.has(field[1]) || scalar(field[2]) === undefined)
8095
+ throw new Error("nativeAgent.frontmatter requires unambiguous, unique flat scalar fields.");
8096
+ fields.set(field[1], scalar(field[2]));
8097
+ }
8098
+ if (fields.get("name") !== name || fields.get("description") !== description)
8099
+ throw new Error("nativeAgent.frontmatter name and description must match explicit metadata.");
8100
+ return { name, description, frontmatter };
8101
+ }
8102
+ function renderNativeAgentContent(content, metadata) {
8103
+ if (!metadata)
8104
+ return content;
8105
+ const value = normalizeNativeAgentMetadata(metadata);
8106
+ if (/^\uFEFF?---[ \t]*\r?\n/.test(content))
8107
+ throw new Error("Canonical role prose already contains frontmatter.");
8108
+ return (value.frontmatter ?? `---
8109
+ name: ${JSON.stringify(value.name)}
8110
+ description: ${JSON.stringify(value.description)}
8111
+ ---
8112
+ `) + content;
8113
+ }
8030
8114
  function diagnostic(severity, code, assetKey, message) {
8031
8115
  return { severity, code, assetKey, message };
8032
8116
  }
@@ -8072,6 +8156,7 @@ var init_asset_plan = __esm(() => {
8072
8156
  ASSET_CAPABILITY_DESCRIPTORS = Object.freeze([
8073
8157
  assetCapability("claude", "code", "skill", "supported", ["emit-file"], "Claude Code project or profile skill files."),
8074
8158
  assetCapability("claude", "code", "workflow", "supported", ["emit-file"], "Claude Code command and workflow files."),
8159
+ assetCapability("claude", "code", "custom-agent", "supported", ["emit-file"], "Claude Code Markdown subagents with native frontmatter in agents/.", ">=2.1.276 <3.0.0"),
8075
8160
  assetCapability("claude", "code", "hook", "supported", ["emit-file"], "Claude Code hook configuration fragments."),
8076
8161
  assetCapability("claude", "code", "plugin", "conditional", ["install-marketplace"], "Claude marketplace installation requires an explicit installer."),
8077
8162
  assetCapability("codex", "cli", "skill", "supported", ["emit-file"], "Codex skill bundle files."),
@@ -8082,6 +8167,7 @@ var init_asset_plan = __esm(() => {
8082
8167
  assetCapability("opencode", "cli", "workflow", "supported", ["emit-file"], "OpenCode command/workflow files."),
8083
8168
  assetCapability("opencode", "cli", "plugin", "supported", ["emit-file"], "OpenCode local plugin modules."),
8084
8169
  assetCapability("opencode", "cli", "custom-agent", "supported", ["emit-file"], "OpenCode custom agent files."),
8170
+ assetCapability("sumi", "cli", "custom-agent", "supported", ["emit-file"], "Sumi Markdown agent definitions in its explicit resolved config or project directory.", ">=0.2.22 <0.3.0"),
8085
8171
  assetCapability("codewith", "cli", "skill", "supported", ["emit-file"], "Codewith skill bundle files."),
8086
8172
  assetCapability("codewith", "cli", "plugin", "conditional", ["install-local", "install-marketplace"], "Codewith plugin installation requires an explicit installer."),
8087
8173
  assetCapability("aicopilot", "cli", "skill", "supported", ["emit-file"], "AICopilot discovered skill files."),
@@ -9230,7 +9316,7 @@ function buildCursorRuleFiles(targetHome, adapter, sources) {
9230
9316
  return [sourceFile, ...ruleFiles];
9231
9317
  });
9232
9318
  }
9233
- function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig) {
9319
+ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot) {
9234
9320
  const surface = adapter.providerSurface ?? "legacy-dual";
9235
9321
  const fragments = sources.flatMap((source, index) => [
9236
9322
  makeFile(targetHome, fragmentPath(adapter, index, source), "fragment", sectionForSource(source), [source.id]),
@@ -9250,13 +9336,13 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
9250
9336
  ]);
9251
9337
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
9252
9338
  const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
9253
- const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
9339
+ const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir, targetHome, projectRoot));
9254
9340
  const config = {
9255
9341
  ...selectedConfig,
9256
9342
  $schema: typeof selectedConfig["$schema"] === "string" ? selectedConfig["$schema"] : "https://opencode.ai/config.json",
9257
9343
  instructions: [
9258
9344
  ...preservedInstructions,
9259
- ...fragments.map((file) => file.relativePath)
9345
+ ...fragments.map((file) => file.path)
9260
9346
  ]
9261
9347
  };
9262
9348
  const configSourceIds = [
@@ -9299,9 +9385,99 @@ function normalizeOpenCodeInstructions(value) {
9299
9385
  }
9300
9386
  return value;
9301
9387
  }
9302
- function pathIsManagedOpenCodeInstruction(path, managedDir) {
9303
- const normalized = posix2.normalize(path.replaceAll("\\", "/")).replace(/^\.\//, "");
9304
- return normalized === managedDir || normalized.startsWith(`${managedDir}/`);
9388
+ function pathIsManagedOpenCodeInstruction(reference, managedDir, targetHome, projectRoot) {
9389
+ const normalizedManagedDir = posix2.normalize(managedDir.replaceAll("\\", "/"));
9390
+ const candidates = canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot);
9391
+ return candidates.some((candidate) => {
9392
+ const path = candidate.caseInsensitive ? candidate.path.toLowerCase() : candidate.path;
9393
+ const namespace = candidate.caseInsensitive ? normalizedManagedDir.toLowerCase() : normalizedManagedDir;
9394
+ return path === namespace || path.endsWith(`/${namespace}`) || path.includes(`/${namespace}/`);
9395
+ });
9396
+ }
9397
+ function canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot) {
9398
+ const decodedReference = decodeOpenCodePathEscapes(reference);
9399
+ const portable = decodedReference.replaceAll("\\", "/");
9400
+ if (portable.includes("\x00")) {
9401
+ throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
9402
+ }
9403
+ if (/^[A-Za-z]:(?!\/)/.test(portable)) {
9404
+ throw new Error("OpenCode config instruction references cannot use Win32 drive-relative paths.");
9405
+ }
9406
+ const windowsAbsolute = /^[A-Za-z]:\//.test(portable);
9407
+ const uncAbsolute = portable.startsWith("//");
9408
+ const windowsRootRelative = /^\\(?![\\/])/.test(decodedReference);
9409
+ const uriScheme = windowsAbsolute || uncAbsolute || windowsRootRelative ? null : /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(portable)?.[1]?.toLowerCase();
9410
+ if (uriScheme && uriScheme !== "file")
9411
+ return [];
9412
+ if (uriScheme === "file") {
9413
+ const fileReference = reference.replaceAll("\\", "/");
9414
+ if (!/^file:\/\//i.test(fileReference)) {
9415
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9416
+ }
9417
+ let url;
9418
+ try {
9419
+ url = new URL(fileReference);
9420
+ } catch {
9421
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9422
+ }
9423
+ if (url.protocol !== "file:" || url.username || url.password || url.port || url.search || url.hash) {
9424
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9425
+ }
9426
+ const pathname = decodeOpenCodePathEscapes(url.pathname).replaceAll("\\", "/");
9427
+ if (pathname.includes("\x00")) {
9428
+ throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
9429
+ }
9430
+ if (/^\/[A-Za-z]:(?!\/)/.test(pathname)) {
9431
+ throw new Error("OpenCode config instruction references cannot use Win32 drive-relative paths.");
9432
+ }
9433
+ const host = url.hostname && url.hostname !== "localhost" ? `//${url.hostname}` : "";
9434
+ return [{
9435
+ path: normalizePortableOpenCodePath(`${host}${pathname}`, true),
9436
+ caseInsensitive: true
9437
+ }];
9438
+ }
9439
+ if (posix2.isAbsolute(portable) || windowsAbsolute || uncAbsolute || windowsRootRelative) {
9440
+ const windowsPathSemantics = windowsAbsolute || uncAbsolute || windowsRootRelative;
9441
+ return [{
9442
+ path: normalizePortableOpenCodePath(portable, windowsPathSemantics),
9443
+ caseInsensitive: windowsPathSemantics
9444
+ }];
9445
+ }
9446
+ const roots = [
9447
+ ...projectRoot ? [resolveSessionPath(projectRoot)] : [],
9448
+ resolveSessionPath(targetHome)
9449
+ ];
9450
+ const candidates = new Map;
9451
+ for (const root of roots) {
9452
+ const portableRoot = root.replaceAll("\\", "/");
9453
+ const caseInsensitive = /^[A-Za-z]:\//.test(portableRoot) || portableRoot.startsWith("//");
9454
+ const path = normalizePortableOpenCodePath(posix2.join(portableRoot, portable));
9455
+ candidates.set(`${caseInsensitive ? "i" : "s"}:${path}`, { path, caseInsensitive });
9456
+ }
9457
+ return [...candidates.values()];
9458
+ }
9459
+ function decodeOpenCodePathEscapes(value) {
9460
+ return value.replace(/(?:%[a-fA-F0-9]{2})+/g, (encoded) => {
9461
+ try {
9462
+ return decodeURIComponent(encoded);
9463
+ } catch {
9464
+ return encoded;
9465
+ }
9466
+ });
9467
+ }
9468
+ function normalizePortableOpenCodePath(value, windowsPathSemantics = false) {
9469
+ const portable = value.replaceAll("\\", "/");
9470
+ const componentNormalized = windowsPathSemantics ? portable.split("/").map(normalizeWin32PathComponent).join("/") : portable;
9471
+ const normalized = posix2.normalize(componentNormalized);
9472
+ return normalized.startsWith("//") ? `/${normalized.replace(/^\/+/u, "")}` : normalized;
9473
+ }
9474
+ function normalizeWin32PathComponent(component) {
9475
+ if (!component || /^[A-Za-z]:$/.test(component))
9476
+ return component;
9477
+ const withoutTrailingSpaces = component.replace(/ +$/u, "");
9478
+ if (withoutTrailingSpaces === "." || withoutTrailingSpaces === "..")
9479
+ return withoutTrailingSpaces;
9480
+ return component.replace(/[ .]+$/u, "");
9305
9481
  }
9306
9482
  function buildAntigravityRuleFiles(targetHome, adapter, sources) {
9307
9483
  return sources.flatMap((source, index) => {
@@ -9355,7 +9531,7 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
9355
9531
  }
9356
9532
  return file;
9357
9533
  }
9358
- function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
9534
+ function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion, projectRoot) {
9359
9535
  switch (adapter.mode) {
9360
9536
  case "native-imports":
9361
9537
  return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
@@ -9364,7 +9540,7 @@ function buildFiles(targetHome, adapter, profile, sources, providerConfig, provi
9364
9540
  case "cursor-mdc":
9365
9541
  return buildCursorRuleFiles(targetHome, adapter, sources);
9366
9542
  case "opencode-instructions":
9367
- return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig);
9543
+ return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot);
9368
9544
  case "antigravity-rules":
9369
9545
  return buildAntigravityRuleFiles(targetHome, adapter, sources);
9370
9546
  case "provider-rules":
@@ -9393,12 +9569,13 @@ function buildAssetFiles(input, targetHome, blocked) {
9393
9569
  if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
9394
9570
  throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
9395
9571
  }
9572
+ const renderedContent = renderNativeAgentContent(content, item.nativeAgent);
9396
9573
  return {
9397
9574
  path,
9398
9575
  relativePath: assertSafeRelativePath(relativePath),
9399
9576
  role: "asset",
9400
- content,
9401
- sha256: sha2566(content),
9577
+ content: renderedContent,
9578
+ sha256: sha2566(renderedContent),
9402
9579
  sourceIds: [item.sourceConfigId, item.assetId]
9403
9580
  };
9404
9581
  });
@@ -9577,7 +9754,7 @@ function resolveSessionTargetOwnership(input, target) {
9577
9754
  tool: input.tool,
9578
9755
  profile: input.profile,
9579
9756
  targetHome: target.targetHome,
9580
- projectRoot: null,
9757
+ projectRoot: input.tool === "opencode" && input.projectRoot ? resolveSessionPath(input.projectRoot) : null,
9581
9758
  ownedBy: "open-configs",
9582
9759
  canonicalOwner: "instructions",
9583
9760
  writer: {
@@ -9638,7 +9815,7 @@ function planSessionRender(input) {
9638
9815
  if (input.providerConfig && input.tool !== "opencode") {
9639
9816
  throw new Error("Provider base config is supported only for OpenCode session renders.");
9640
9817
  }
9641
- const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
9818
+ const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version, input.projectRoot);
9642
9819
  const projectContext = blocked ? null : composeProjectContextSessionRender({
9643
9820
  tool: input.tool,
9644
9821
  adapter_mode: adapter.mode,
@@ -9739,6 +9916,7 @@ function planSessionRender(input) {
9739
9916
  mutationMode: item.mutationMode,
9740
9917
  destination: item.destination,
9741
9918
  digest: item.source.digest,
9919
+ ...item.nativeAgent ? { nativeAgent: item.nativeAgent } : {},
9742
9920
  exactOnceKey: item.exactOnceKey
9743
9921
  }))
9744
9922
  }
@@ -13768,7 +13946,7 @@ function detectMachineContext(overrides = {}) {
13768
13946
  created_at: "",
13769
13947
  os_family: osFamily,
13770
13948
  home_dir: homeDir6,
13771
- workspace_root: overrides.workspace_root ?? join10(homeDir6, osFamily === "macos" ? "Workspace" : "workspace"),
13949
+ workspace_root: overrides.workspace_root ?? "",
13772
13950
  bun_bin_dir: bunBinDir,
13773
13951
  bun_path: overrides.bun_path ?? defaultBunPath,
13774
13952
  path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join10("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
@@ -13781,7 +13959,7 @@ function machineContextToVariables(machine) {
13781
13959
  OS_FAMILY: machine.os_family,
13782
13960
  ARCH: machine.arch ?? "",
13783
13961
  HOME_DIR: machine.home_dir,
13784
- WORKSPACE_ROOT: machine.workspace_root,
13962
+ ...machine.workspace_root ? { WORKSPACE_ROOT: machine.workspace_root } : {},
13785
13963
  BUN_BIN_DIR: machine.bun_bin_dir,
13786
13964
  BUN_PATH: machine.bun_path,
13787
13965
  PATH_PREFIX: machine.path_prefix
@@ -14845,7 +15023,7 @@ init_types();
14845
15023
 
14846
15024
  // src/status.ts
14847
15025
  init_config_store();
14848
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
15026
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
14849
15027
 
14850
15028
  // src/lib/apply.ts
14851
15029
  init_types();
@@ -15395,19 +15573,9 @@ function getPackageVersion() {
15395
15573
  }
15396
15574
 
15397
15575
  // src/lib/managed-skill-runtimes.ts
15398
- import { createHash as createHash8 } from "crypto";
15399
- import { spawnSync as spawnSync3 } from "child_process";
15400
- import {
15401
- existsSync as existsSync11,
15402
- lstatSync as lstatSync4,
15403
- mkdirSync as mkdirSync4,
15404
- readFileSync as readFileSync10,
15405
- renameSync as renameSync2,
15406
- rmSync as rmSync3,
15407
- writeFileSync as writeFileSync3
15408
- } from "fs";
15576
+ import { lstatSync as lstatSync4 } from "fs";
15409
15577
  import { homedir as homedir8 } from "os";
15410
- import { dirname as dirname8, join as join14, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
15578
+ import { join as join14, resolve as resolve10 } from "path";
15411
15579
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
15412
15580
  var INBOX_SKILL_MARKERS = [
15413
15581
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -15416,369 +15584,67 @@ var INBOX_SKILL_MARKERS = [
15416
15584
  [".config", "opencode", "skills", "inbox", "SKILL.md"],
15417
15585
  [".cursor", "skills", "inbox", "SKILL.md"]
15418
15586
  ];
15419
- var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
15420
- function sha2568(content) {
15421
- return createHash8("sha256").update(content).digest("hex");
15422
- }
15423
- function lstatOrNull(path) {
15424
- try {
15425
- return lstatSync4(path);
15426
- } catch {
15427
- return null;
15428
- }
15429
- }
15430
- function findSymlinkedAncestor(path) {
15431
- const normalized = resolve10(path);
15432
- const parsed = parse4(normalized);
15433
- let current = parsed.root;
15434
- const rel = relative5(parsed.root, normalized);
15435
- for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
15587
+ function needsMigrationReview(homeDir6, parts) {
15588
+ let current = resolve10(homeDir6);
15589
+ const absolute = join14(current, ...parts);
15590
+ for (const segment of parts) {
15436
15591
  current = join14(current, segment);
15437
- if (!existsSync11(current))
15438
- return null;
15439
- if (lstatSync4(current).isSymbolicLink())
15440
- return current;
15441
- }
15442
- return null;
15443
- }
15444
- function assertNoSymlinkAncestors2(path) {
15445
- const found = findSymlinkedAncestor(path);
15446
- if (found !== null) {
15447
- throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
15448
- }
15449
- }
15450
- function packagedInboxSkillPath(explicitPath) {
15451
- if (explicitPath)
15452
- return explicitPath;
15453
- throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
15454
- }
15455
- function readCanonicalSkill(explicitPath) {
15456
- const assetPath = packagedInboxSkillPath(explicitPath);
15457
- const stat = lstatOrNull(assetPath);
15458
- if (!stat?.isFile()) {
15459
- throw new Error("packaged inbox skill contract is not a regular file");
15460
- }
15461
- const content = readFileSync10(assetPath, "utf8");
15462
- if (!content.includes("conversations watch --from <agent> --all")) {
15463
- throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
15464
- }
15465
- if (!content.includes("There is no separate")) {
15466
- throw new Error("packaged inbox skill contract does not retire the legacy executable");
15467
- }
15468
- return { content, sha256: sha2568(content) };
15469
- }
15470
- function runProbe(command, args) {
15471
- const result = spawnSync3(command, args, {
15472
- encoding: "utf8",
15473
- timeout: 5000,
15474
- stdio: ["ignore", "pipe", "pipe"]
15475
- });
15476
- if (result.error || result.status !== 0) {
15477
- return { ok: false, output: "" };
15478
- }
15479
- return {
15480
- ok: true,
15481
- output: `${result.stdout ?? ""}
15482
- ${result.stderr ?? ""}`.trim()
15483
- };
15484
- }
15485
- function parseVersion(output) {
15486
- return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
15487
- }
15488
- function compareVersions(left, right) {
15489
- const a = left.split(".").map(Number);
15490
- const b = right.split(".").map(Number);
15491
- for (let i = 0;i < Math.max(a.length, b.length); i++) {
15492
- const delta = (a[i] ?? 0) - (b[i] ?? 0);
15493
- if (delta !== 0)
15494
- return delta;
15495
- }
15496
- return 0;
15497
- }
15498
- function inspectSkillMarkers(homeDir6) {
15499
- return INBOX_SKILL_MARKERS.map((parts) => join14(homeDir6, ...parts)).map((path) => {
15500
- const stat = lstatOrNull(path);
15501
- if (!stat)
15502
- return null;
15503
- if (!stat.isFile()) {
15504
- return { path, content: null, mode: null, regular: false };
15592
+ let stat;
15593
+ try {
15594
+ stat = lstatSync4(current);
15595
+ } catch (error) {
15596
+ if (error.code === "ENOENT")
15597
+ return false;
15598
+ throw error;
15505
15599
  }
15506
- return {
15507
- path,
15508
- content: readFileSync10(path, "utf8"),
15509
- mode: stat.mode & 511,
15510
- regular: true
15511
- };
15512
- }).filter((snapshot) => snapshot !== null);
15600
+ if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
15601
+ return true;
15602
+ }
15603
+ return true;
15513
15604
  }
15514
- function inspectInbox(options) {
15605
+ function inspectLegacyInbox(options) {
15515
15606
  const homeDir6 = options.homeDir ?? homedir8();
15516
- const runtimeCommand = options.conversationsCommand ?? "conversations";
15517
- const snapshots = inspectSkillMarkers(homeDir6);
15518
- const skillPresent = snapshots.length > 0;
15519
- let canonicalContent = null;
15520
- let canonicalSha256 = null;
15521
- let assetError = null;
15522
- try {
15523
- const canonical = readCanonicalSkill(options.assetPath);
15524
- canonicalContent = canonical.content;
15525
- canonicalSha256 = canonical.sha256;
15526
- } catch (error) {
15527
- assetError = error instanceof Error ? error.message : String(error);
15528
- }
15529
- const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
15530
- const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
15531
- const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
15532
- const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
15533
- const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
15534
- const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
15535
- const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
15536
- const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
15537
- const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
15538
- const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
15539
- const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
15540
- let reason = "skill not installed";
15541
- if (skillPresent) {
15542
- const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
15543
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
15544
- if (nonRegular)
15545
- reason = "managed skill target is not a regular file";
15546
- else if (symlinkAncestor)
15547
- reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
15548
- else if (assetError)
15549
- reason = assetError;
15550
- else if (!versionProbe.ok)
15551
- reason = "conversations command unavailable";
15552
- else if (!runtimeVersion)
15553
- reason = "conversations version is unreadable";
15554
- else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
15555
- reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
15556
- } else if (!helpProbe.ok)
15557
- reason = "conversations watch help is unavailable";
15558
- else if (!supportsFrom || !supportsAll || !supportsFullContent) {
15559
- const missing = [
15560
- !supportsFrom ? "--from" : null,
15561
- !supportsAll ? "--all" : null,
15562
- !supportsFullContent ? "--full-content" : null
15563
- ].filter((flag) => flag !== null);
15564
- reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
15565
- } else if (staleMarkers.length > 0)
15566
- reason = "skill contract stale";
15567
- else if (hostedHeartbeat === "failed")
15568
- reason = "hosted heartbeat failed; manual fallback required";
15569
- else if (hostedHeartbeat === "unverified")
15570
- reason = "hosted heartbeat unverified; manual fallback required";
15571
- else if (!deliveryVerified)
15572
- reason = "hosted heartbeat passed; channel and DM delivery verification required";
15573
- else
15574
- reason = "ready";
15575
- }
15607
+ const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir6, parts)).map((parts) => join14(homeDir6, ...parts));
15608
+ const present = markers.length > 0;
15576
15609
  return {
15577
- status: {
15578
- skill: "inbox",
15579
- runtime: "conversations watch",
15580
- minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
15581
- skill_present: skillPresent,
15582
- skill_markers: snapshots.map((snapshot) => snapshot.path),
15583
- skill_contracts_current: snapshots.length - staleMarkers.length,
15584
- stale_skill_markers: staleMarkers,
15585
- expected_skill_sha256: canonicalSha256,
15586
- runtime_command: runtimeCommand,
15587
- runtime_present: versionProbe.ok,
15588
- runtime_version: runtimeVersion,
15589
- watch_supports_from: supportsFrom,
15590
- watch_supports_all: supportsAll,
15591
- watch_supports_full_content: supportsFullContent,
15592
- hosted_heartbeat: hostedHeartbeat,
15593
- delivery_verified: deliveryVerified,
15594
- manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
15595
- healthy: !skillPresent || reason === "ready",
15596
- reason
15597
- },
15598
- canonicalContent,
15599
- snapshots
15610
+ skill: "inbox",
15611
+ runtime: "conversations watch",
15612
+ minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
15613
+ skill_present: present,
15614
+ skill_markers: markers,
15615
+ skill_contracts_current: 0,
15616
+ stale_skill_markers: markers,
15617
+ expected_skill_sha256: null,
15618
+ runtime_command: options.conversationsCommand ?? "conversations",
15619
+ runtime_present: false,
15620
+ runtime_version: null,
15621
+ watch_supports_from: false,
15622
+ watch_supports_all: false,
15623
+ watch_supports_full_content: false,
15624
+ hosted_heartbeat: "unverified",
15625
+ delivery_verified: false,
15626
+ manual_fallback_ready: false,
15627
+ healthy: !present,
15628
+ reason: present ? "Native Inbox skill management is retired; use the Skills CLI to review skills migrate native, then sync and load the selected skills" : "No legacy native Inbox skill found; skill availability is managed by the Skills CLI"
15600
15629
  };
15601
15630
  }
15602
15631
  function inspectManagedSkillRuntimes(options = {}) {
15603
- const runtime = inspectInbox(options).status;
15604
- const installed = runtime.skill_present ? [runtime] : [];
15632
+ const runtime = inspectLegacyInbox(options);
15605
15633
  return {
15606
15634
  runtimes: [runtime],
15607
- skills_present: installed.length,
15608
- healthy: installed.filter((item) => item.healthy).length,
15609
- missing: installed.filter((item) => !item.healthy).length
15610
- };
15611
- }
15612
- function runtimeReadyForWrite(status) {
15613
- return status.runtime_present && status.runtime_version !== null && compareVersions(status.runtime_version, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && status.watch_supports_from && status.watch_supports_all && status.watch_supports_full_content;
15614
- }
15615
- function projectUpdatedStatus(status, contractCount) {
15616
- const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
15617
- return {
15618
- ...status,
15619
- skill_contracts_current: contractCount,
15620
- stale_skill_markers: [],
15621
- manual_fallback_ready: true,
15622
- healthy,
15623
- reason: healthy ? "ready" : status.hosted_heartbeat === "failed" ? "hosted heartbeat failed; manual fallback required" : status.hosted_heartbeat === "unverified" ? "hosted heartbeat unverified; manual fallback required" : "hosted heartbeat passed; channel and DM delivery verification required"
15635
+ skills_present: runtime.skill_present ? 1 : 0,
15636
+ healthy: 0,
15637
+ missing: runtime.skill_present ? 1 : 0
15624
15638
  };
15625
15639
  }
15626
- function cleanup(path) {
15627
- rmSync3(path, { force: true });
15628
- }
15629
- function writeAtomic(path, content, mode) {
15630
- assertNoSymlinkAncestors2(dirname8(path));
15631
- const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
15632
- try {
15633
- mkdirSync4(dirname8(path), { recursive: true, mode: 493 });
15634
- writeFileSync3(tempPath, content, { mode, flag: "wx" });
15635
- renameSync2(tempPath, path);
15636
- } finally {
15637
- cleanup(tempPath);
15638
- }
15639
- }
15640
- var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
15641
- lstat: lstatOrNull,
15642
- read: (path) => readFileSync10(path, "utf8"),
15643
- write: writeAtomic
15644
- };
15645
- function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
15646
- const written = [];
15647
- try {
15648
- for (const snapshot of snapshots) {
15649
- const currentStat = fileOperations.lstat(snapshot.path);
15650
- if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
15651
- throw new Error("managed skill changed after inspection; refusing a stale write");
15652
- }
15653
- fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
15654
- written.push(snapshot);
15655
- }
15656
- return { ok: true, error: null, rollback_conflicts: [] };
15657
- } catch (error) {
15658
- const rollbackConflicts = [];
15659
- for (const snapshot of written.reverse()) {
15660
- const currentStat = fileOperations.lstat(snapshot.path);
15661
- if (!currentStat?.isFile()) {
15662
- rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
15663
- continue;
15664
- }
15665
- let currentContent;
15666
- try {
15667
- currentContent = fileOperations.read(snapshot.path);
15668
- } catch {
15669
- rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
15670
- continue;
15671
- }
15672
- if (currentContent !== canonicalContent) {
15673
- rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
15674
- continue;
15675
- }
15676
- try {
15677
- fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
15678
- } catch {
15679
- rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
15680
- }
15681
- }
15682
- return {
15683
- ok: false,
15684
- error: error instanceof Error ? error.message : String(error),
15685
- rollback_conflicts: rollbackConflicts
15686
- };
15687
- }
15688
- }
15689
15640
  async function reconcileManagedSkillRuntimes(options = {}) {
15641
+ const runtime = inspectLegacyInbox(options);
15690
15642
  const dryRun = options.dryRun ?? false;
15691
- const before = inspectInbox(options);
15692
- const status = before.status;
15693
- if (!status.skill_present) {
15694
- return {
15695
- runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
15696
- changed: 0,
15697
- failed: 0,
15698
- dry_run: dryRun
15699
- };
15700
- }
15701
- if (before.snapshots.some((snapshot) => !snapshot.regular)) {
15702
- return {
15703
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
15704
- changed: 0,
15705
- failed: 1,
15706
- dry_run: dryRun
15707
- };
15708
- }
15709
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname8(path))).find((found) => found !== null);
15710
- if (symlinkedAncestor) {
15711
- return {
15712
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
15713
- changed: 0,
15714
- failed: 1,
15715
- dry_run: dryRun
15716
- };
15717
- }
15718
- if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
15719
- return {
15720
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
15721
- changed: 0,
15722
- failed: 1,
15723
- dry_run: dryRun
15724
- };
15725
- }
15726
- const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
15727
- if (staleSnapshots.length === 0) {
15728
- return {
15729
- runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
15730
- changed: 0,
15731
- failed: 0,
15732
- dry_run: dryRun
15733
- };
15734
- }
15735
- if (dryRun) {
15736
- const projected = projectUpdatedStatus(status, before.snapshots.length);
15737
- return {
15738
- runtimes: [{
15739
- ...projected,
15740
- action: "update",
15741
- dry_run: true,
15742
- skill_contracts_changed: staleSnapshots.length
15743
- }],
15744
- changed: 1,
15745
- failed: 0,
15746
- dry_run: true
15747
- };
15748
- }
15749
- const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
15750
- path: snapshot.path,
15751
- content: snapshot.content,
15752
- mode: snapshot.mode ?? 420
15753
- })), before.canonicalContent);
15754
- if (!transaction.ok) {
15755
- const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
15756
- const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
15757
- return {
15758
- runtimes: [{
15759
- ...status,
15760
- action: "failed",
15761
- dry_run: false,
15762
- skill_contracts_changed: 0,
15763
- reason
15764
- }],
15765
- changed: 0,
15766
- failed: 1,
15767
- dry_run: false
15768
- };
15769
- }
15770
- const after = inspectInbox(options).status;
15771
- const accepted = after.healthy || after.manual_fallback_ready;
15772
15643
  return {
15773
- runtimes: [{
15774
- ...after,
15775
- action: accepted ? "update" : "failed",
15776
- dry_run: false,
15777
- skill_contracts_changed: accepted ? staleSnapshots.length : 0
15778
- }],
15779
- changed: accepted ? 1 : 0,
15780
- failed: accepted ? 0 : 1,
15781
- dry_run: false
15644
+ runtimes: [{ ...runtime, action: runtime.skill_present ? "failed" : "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
15645
+ changed: 0,
15646
+ failed: runtime.skill_present ? 1 : 0,
15647
+ dry_run: dryRun
15782
15648
  };
15783
15649
  }
15784
15650
 
@@ -15842,11 +15708,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
15842
15708
  continue;
15843
15709
  knownTargets += 1;
15844
15710
  const targetPath = expandPath(config.target_path);
15845
- if (!existsSync12(targetPath)) {
15711
+ if (!existsSync11(targetPath)) {
15846
15712
  missingTargets += 1;
15847
15713
  continue;
15848
15714
  }
15849
- const disk = readFileSync11(targetPath, "utf-8");
15715
+ const disk = readFileSync10(targetPath, "utf-8");
15850
15716
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
15851
15717
  if (redactedDisk !== config.content) {
15852
15718
  driftedTargets += 1;
@@ -15937,8 +15803,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
15937
15803
  };
15938
15804
  }
15939
15805
  // src/lib/provider-context.ts
15940
- import { createHash as createHash9 } from "crypto";
15941
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
15806
+ import { createHash as createHash8 } from "crypto";
15807
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
15942
15808
  import { join as join15 } from "path";
15943
15809
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
15944
15810
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
@@ -16074,8 +15940,8 @@ function renderPerEndpointFragment(entry) {
16074
15940
  function renderProviderFragment(entry) {
16075
15941
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
16076
15942
  }
16077
- function sha2569(content) {
16078
- return createHash9("sha256").update(content).digest("hex");
15943
+ function sha2568(content) {
15944
+ return createHash8("sha256").update(content).digest("hex");
16079
15945
  }
16080
15946
  function resolveAndRenderProviderContext(opts) {
16081
15947
  const entry = matchProviderEndpoint(opts.origin);
@@ -16085,17 +15951,17 @@ function resolveAndRenderProviderContext(opts) {
16085
15951
  const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
16086
15952
  const content = renderProviderFragment(entry);
16087
15953
  const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
16088
- if (!existsSync13(dir))
16089
- mkdirSync5(dir, { recursive: true });
15954
+ if (!existsSync12(dir))
15955
+ mkdirSync4(dir, { recursive: true });
16090
15956
  const filename = `${entry ? entry.key : "invariant"}.md`;
16091
15957
  const fragmentPath2 = join15(dir, filename);
16092
- const fragmentSha256 = sha2569(content);
16093
- writeFileSync4(fragmentPath2, content, "utf8");
15958
+ const fragmentSha256 = sha2568(content);
15959
+ writeFileSync3(fragmentPath2, content, "utf8");
16094
15960
  const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
16095
15961
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
16096
15962
  try {
16097
- if (existsSync13(manifestPath)) {
16098
- const parsed = JSON.parse(readFileSync12(manifestPath, "utf8"));
15963
+ if (existsSync12(manifestPath)) {
15964
+ const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
16099
15965
  if (parsed && typeof parsed === "object")
16100
15966
  manifest = parsed;
16101
15967
  }
@@ -16110,7 +15976,7 @@ function resolveAndRenderProviderContext(opts) {
16110
15976
  rawModel: opts.rawModel || null
16111
15977
  };
16112
15978
  manifest.fragments = fragmentsObj;
16113
- writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
15979
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
16114
15980
  return {
16115
15981
  entry,
16116
15982
  rawEndpoint: opts.rawEndpoint,
@@ -16210,10 +16076,10 @@ init_session_render();
16210
16076
 
16211
16077
  // src/lib/station-profile.ts
16212
16078
  init_raw_store_root();
16213
- import { spawnSync as spawnSync4 } from "child_process";
16214
- import { existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync13, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
16079
+ import { spawnSync as spawnSync3 } from "child_process";
16080
+ import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
16215
16081
  import { arch as osArch, homedir as homedir9, hostname as osHostname3, platform as osPlatform, userInfo as osUserInfo } from "os";
16216
- import { dirname as dirname9, join as join16 } from "path";
16082
+ import { dirname as dirname8, join as join16 } from "path";
16217
16083
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
16218
16084
  var STATION_PROFILE_SOURCE_ID = "station-profile";
16219
16085
  var STATION_PROFILE_LAYER = "machine";
@@ -16237,9 +16103,9 @@ function getBunGlobalModulesDir(env = process.env) {
16237
16103
  }
16238
16104
  function readMachinesManifest(path) {
16239
16105
  try {
16240
- if (!existsSync14(path))
16106
+ if (!existsSync13(path))
16241
16107
  return null;
16242
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
16108
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
16243
16109
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
16244
16110
  return null;
16245
16111
  const machines = parsed["machines"];
@@ -16269,7 +16135,7 @@ function metadataUser(record) {
16269
16135
  }
16270
16136
  function probeMachineStatus(machineId) {
16271
16137
  try {
16272
- const result = spawnSync4("machines", ["details", "--json", "--machine", machineId], {
16138
+ const result = spawnSync3("machines", ["details", "--json", "--machine", machineId], {
16273
16139
  encoding: "utf8",
16274
16140
  timeout: 3000,
16275
16141
  stdio: ["ignore", "pipe", "pipe"]
@@ -16295,7 +16161,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
16295
16161
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
16296
16162
  const home = homeDir6(env);
16297
16163
  const platform = stringField(record, "platform") ?? osPlatform();
16298
- const workspacePath = stringField(record, "workspacePath") ?? join16(home, platform === "darwin" ? "Workspace" : "workspace");
16164
+ const workspacePath = stringField(record, "workspacePath");
16299
16165
  const machine = {
16300
16166
  id: stringField(record, "id") ?? hostname2,
16301
16167
  hostname: stringField(record, "hostname") ?? hostname2,
@@ -16314,7 +16180,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
16314
16180
  function scopedPackageNames(modulesDir, scope) {
16315
16181
  const scopeDir = join16(modulesDir, scope);
16316
16182
  try {
16317
- if (!existsSync14(scopeDir))
16183
+ if (!existsSync13(scopeDir))
16318
16184
  return null;
16319
16185
  return readdirNames(scopeDir).sort();
16320
16186
  } catch {
@@ -16334,7 +16200,7 @@ function resolveStationProfilePackages(env = process.env) {
16334
16200
  const modulesDir = getBunGlobalModulesDir(env);
16335
16201
  let scopeDirs;
16336
16202
  try {
16337
- if (!existsSync14(modulesDir))
16203
+ if (!existsSync13(modulesDir))
16338
16204
  return null;
16339
16205
  scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
16340
16206
  } catch {
@@ -16409,10 +16275,10 @@ function refreshStationProfile(options = {}) {
16409
16275
  const path = getStationProfileCachePath(env);
16410
16276
  const generatedAt = new Date().toISOString();
16411
16277
  if (!options.dryRun) {
16412
- const existing = existsSync14(path) ? readFileSync13(path, "utf8") : null;
16278
+ const existing = existsSync13(path) ? readFileSync12(path, "utf8") : null;
16413
16279
  if (existing !== content) {
16414
- mkdirSync6(dirname9(path), { recursive: true });
16415
- writeFileSync5(path, content, "utf8");
16280
+ mkdirSync5(dirname8(path), { recursive: true });
16281
+ writeFileSync4(path, content, "utf8");
16416
16282
  }
16417
16283
  }
16418
16284
  return {
@@ -16428,9 +16294,9 @@ function refreshStationProfile(options = {}) {
16428
16294
  function readStationProfile(env = process.env) {
16429
16295
  const path = getStationProfileCachePath(env);
16430
16296
  try {
16431
- if (!existsSync14(path))
16297
+ if (!existsSync13(path))
16432
16298
  return null;
16433
- return readFileSync13(path, "utf8");
16299
+ return readFileSync12(path, "utf8");
16434
16300
  } catch {
16435
16301
  return null;
16436
16302
  }
@@ -16454,22 +16320,172 @@ function stationProfileSource(env = process.env) {
16454
16320
  init_instruction_graph();
16455
16321
  init_asset_plan();
16456
16322
 
16323
+ // src/lib/harness-discovery.ts
16324
+ import { accessSync, constants as constants2, lstatSync as lstatSync6, realpathSync as realpathSync4, statSync as statSync5 } from "fs";
16325
+ import { homedir as homedir10 } from "os";
16326
+ import { delimiter, dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve11 } from "path";
16327
+ var HARNESS_DISCOVERY_SCHEMA = "hasna.instructions.harness-discovery/v1";
16328
+ var DISCOVERABLE_HARNESSES = ["claude", "codex", "opencode", "sumi"];
16329
+ function pathInput(value, home) {
16330
+ if (!value || value !== value.trim() || /[\x00-\x1f\x7f]/.test(value))
16331
+ throw new Error("Harness paths must be nonempty and contain no surrounding whitespace or control characters.");
16332
+ if (value.split("/").some((segment) => segment === "." || segment === ".."))
16333
+ throw new Error("Harness paths must not contain dot segments; normalizing them can change a symlink destination.");
16334
+ let path = value;
16335
+ if (home) {
16336
+ for (const prefix of ["~", "{{HOME}}", "{{HOME_DIR}}", "${HOME}"]) {
16337
+ if (path === prefix)
16338
+ path = home;
16339
+ else if (path.startsWith(`${prefix}/`))
16340
+ path = join17(home, path.slice(prefix.length + 1));
16341
+ }
16342
+ }
16343
+ if (!isAbsolute7(path))
16344
+ throw new Error("Harness paths must be absolute or explicitly relative to the owner home (~/ or {{HOME_DIR}}/).");
16345
+ return resolve11(path);
16346
+ }
16347
+ function observe(path) {
16348
+ let symlink = false;
16349
+ let viaSymlink = false;
16350
+ for (let parent = dirname9(path);parent !== dirname9(parent); parent = dirname9(parent)) {
16351
+ try {
16352
+ if (lstatSync6(parent).isSymbolicLink()) {
16353
+ viaSymlink = true;
16354
+ break;
16355
+ }
16356
+ } catch {}
16357
+ }
16358
+ try {
16359
+ symlink = lstatSync6(path).isSymbolicLink();
16360
+ const stat = statSync5(path);
16361
+ return { path, state: "present", realPath: realpathSync4(path), symlink, viaSymlink: viaSymlink || symlink, type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other" };
16362
+ } catch (error) {
16363
+ const missing = error.code === "ENOENT";
16364
+ return { path, state: missing ? symlink ? "dangling-symlink" : "missing" : "unreadable", realPath: null, symlink, viaSymlink: viaSymlink || symlink, type: null };
16365
+ }
16366
+ }
16367
+ function executableAt(path) {
16368
+ const observation = observe(path);
16369
+ if (observation.type !== "file")
16370
+ return null;
16371
+ try {
16372
+ accessSync(path, constants2.X_OK);
16373
+ return observation;
16374
+ } catch {
16375
+ return null;
16376
+ }
16377
+ }
16378
+ function configSelection(tool, home, env, override) {
16379
+ if (override !== undefined)
16380
+ return { path: pathInput(override, home), source: "override" };
16381
+ const key = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", opencode: "OPENCODE_CONFIG_DIR", sumi: "SUMI_CONFIG_DIR" }[tool];
16382
+ const nativePath = (value, selector) => {
16383
+ try {
16384
+ return pathInput(value);
16385
+ } catch {
16386
+ throw new Error(`${selector} is present but is not an unambiguous absolute native path; supply the reviewed runtime path explicitly.`);
16387
+ }
16388
+ };
16389
+ if (env[key] !== undefined && !(tool === "codex" && env[key] === ""))
16390
+ return { path: nativePath(env[key], key), source: key };
16391
+ if (tool === "claude" || tool === "codex")
16392
+ return { path: join17(home, `.${tool}`), source: "native-default" };
16393
+ if (env.XDG_CONFIG_HOME !== undefined)
16394
+ return { path: join17(nativePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME"), tool), source: "XDG_CONFIG_HOME" };
16395
+ if (tool === "sumi") {
16396
+ if (env.SUMI_HOME !== undefined)
16397
+ return { path: join17(nativePath(env.SUMI_HOME, "SUMI_HOME"), "config"), source: "SUMI_HOME" };
16398
+ return null;
16399
+ }
16400
+ return { path: join17(home, ".config", tool), source: "native-default" };
16401
+ }
16402
+ function discoverHarnesses(options = {}) {
16403
+ const env = options.env ?? process.env;
16404
+ const home = pathInput(options.ownerHome ?? env.HOME ?? env.USERPROFILE ?? homedir10());
16405
+ const project = options.projectRoot === undefined ? null : observe(pathInput(options.projectRoot, home));
16406
+ const variables = { HOME_DIR: home };
16407
+ if (project)
16408
+ variables.PROJECT_ROOT = project.path;
16409
+ const tools = DISCOVERABLE_HARNESSES.map((tool) => {
16410
+ const diagnostics = [];
16411
+ const override = options.overrides?.[tool];
16412
+ let executable = null;
16413
+ let executableSource = null;
16414
+ if (override?.executable !== undefined) {
16415
+ executable = executableAt(pathInput(override.executable, home));
16416
+ executableSource = "override";
16417
+ if (!executable)
16418
+ diagnostics.push("Explicit executable is missing, unreadable, not a file, or not executable; PATH fallback is disabled.");
16419
+ } else {
16420
+ for (const directory of (env.PATH ?? "").split(delimiter)) {
16421
+ if (!isAbsolute7(directory)) {
16422
+ const warning = "Relative or empty PATH entries were ignored; this is an absolute-path candidate inventory, not proof of the launcher's selected executable.";
16423
+ if (!diagnostics.includes(warning))
16424
+ diagnostics.push(warning);
16425
+ continue;
16426
+ }
16427
+ let validated;
16428
+ try {
16429
+ validated = pathInput(directory);
16430
+ } catch {
16431
+ diagnostics.push("An ambiguous PATH directory was ignored; use an explicit executable override after reviewing its native path.");
16432
+ continue;
16433
+ }
16434
+ executable = executableAt(join17(validated, tool));
16435
+ if (executable) {
16436
+ executableSource = "PATH";
16437
+ break;
16438
+ }
16439
+ }
16440
+ }
16441
+ let selected = null;
16442
+ let configError = false;
16443
+ try {
16444
+ selected = configSelection(tool, home, env, override?.configDir);
16445
+ } catch (error) {
16446
+ if (override?.configDir !== undefined)
16447
+ throw error;
16448
+ configError = true;
16449
+ diagnostics.push(`Config root unresolved: ${error.message}`);
16450
+ }
16451
+ const config = selected ? { ...observe(selected.path), source: selected.source } : null;
16452
+ const filename = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
16453
+ const globalPrompt = config ? observe(join17(config.path, filename)) : null;
16454
+ const promptOverrides = tool === "codex" ? [...new Set([config?.path, project?.path].filter((path) => !!path))].map((path) => observe(join17(path, "AGENTS.override.md"))) : [];
16455
+ if (!config && !configError)
16456
+ diagnostics.push("Config root unresolved: supply the actual `sumi debug paths config` result as a configDir override; no runtime or migration was invoked.");
16457
+ if (executable) {
16458
+ variables[`${tool.toUpperCase()}_EXECUTABLE`] = executable.path;
16459
+ if (config)
16460
+ variables[`${tool.toUpperCase()}_CONFIG_DIR`] = config.path;
16461
+ }
16462
+ const linkedGlobal = !!globalPrompt?.viaSymlink || !!config?.viaSymlink;
16463
+ const hasOverride = promptOverrides.some((path) => path.state !== "missing" || path.viaSymlink);
16464
+ const scopeReviewRequired = linkedGlobal || hasOverride;
16465
+ if (linkedGlobal)
16466
+ diagnostics.push("Global config or prompt resolves through a symlink; preserve its lexical path and review the destination scope before planning writes.");
16467
+ if (hasOverride)
16468
+ diagnostics.push("A Codex AGENTS.override.md candidate may take precedence; review its native loader behavior and scope before applying AGENTS.md.");
16469
+ return { tool, status: executable ? "executable-found" : "absent", executable, executableSource, versionEvidence: "not-probed", config, globalPrompt, projectPrompt: project ? observe(join17(project.path, filename)) : null, promptOverrides, scopeReviewRequired, diagnostics };
16470
+ });
16471
+ return { schema: HARNESS_DISCOVERY_SCHEMA, ownerHome: observe(home), projectRoot: project, tools, templateVariables: variables };
16472
+ }
16457
16473
  // src/lib/session-apply.ts
16458
16474
  init_project_context();
16459
16475
  init_session_render_state();
16460
16476
  init_session_render();
16461
16477
  init_cursor_authority();
16462
16478
  init_session_authority();
16463
- import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
16479
+ import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
16464
16480
  import {
16465
- existsSync as existsSync15,
16466
- lstatSync as lstatSync6,
16467
- mkdirSync as mkdirSync7,
16468
- readFileSync as readFileSync14,
16481
+ existsSync as existsSync14,
16482
+ lstatSync as lstatSync7,
16483
+ mkdirSync as mkdirSync6,
16484
+ readFileSync as readFileSync13,
16469
16485
  readdirSync as readdirSync3,
16470
- statSync as statSync5
16486
+ statSync as statSync6
16471
16487
  } from "fs";
16472
- import { dirname as dirname10, isAbsolute as isAbsolute7, join as join17, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
16488
+ import { dirname as dirname10, isAbsolute as isAbsolute8, join as join18, parse as parse4, posix as posix3, relative as relative5, resolve as resolve12 } from "path";
16473
16489
  class SessionApplyError extends Error {
16474
16490
  constructor(message) {
16475
16491
  super(message);
@@ -16499,13 +16515,15 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16499
16515
  const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
16500
16516
  const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
16501
16517
  const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
16502
- const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations);
16518
+ const retirements = validateFileRetirements(plan, targetHome, previousManifest, options);
16519
+ const retiredHashes = new Map(retirements.map((entry) => [entry.relativePath, entry.preimageSha256]));
16520
+ const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements);
16503
16521
  const files = [...payloadFiles, manifestFile];
16504
16522
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
16505
16523
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
16506
16524
  const results = [
16507
16525
  ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
16508
- ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
16526
+ ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
16509
16527
  ];
16510
16528
  assertNotSilentManagedWipeout(plan, results);
16511
16529
  const conflicts = results.filter((result) => result.action === "conflict");
@@ -16529,7 +16547,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16529
16547
  conflicts,
16530
16548
  drift,
16531
16549
  adoptions,
16532
- reconciliations
16550
+ reconciliations,
16551
+ retirements
16533
16552
  };
16534
16553
  }
16535
16554
  let snapshotPath = null;
@@ -16544,10 +16563,14 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16544
16563
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
16545
16564
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
16546
16565
  ensureSessionTargetHome(targetHome);
16547
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
16566
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
16548
16567
  snapshotPath = rollback.snapshotPath;
16549
16568
  options.test_hooks?.before_apply_writes?.({ plan, results });
16550
16569
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
16570
+ for (const result of results) {
16571
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
16572
+ coordination?.assert_held();
16573
+ }
16551
16574
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
16552
16575
  for (const file of payloadFiles) {
16553
16576
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -16583,7 +16606,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16583
16606
  conflicts,
16584
16607
  drift,
16585
16608
  adoptions,
16586
- reconciliations
16609
+ reconciliations,
16610
+ retirements
16587
16611
  };
16588
16612
  }
16589
16613
  function assertManifestPrecondition(path, targetHome, expected) {
@@ -16612,7 +16636,7 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
16612
16636
  throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
16613
16637
  }
16614
16638
  seen.add(request.relativePath);
16615
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
16639
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
16616
16640
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
16617
16641
  throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
16618
16642
  }
@@ -16631,15 +16655,19 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
16631
16655
  };
16632
16656
  });
16633
16657
  }
16658
+ function exactPreimageCandidates(plan) {
16659
+ const customAgentPaths = new Set((plan.assetPlan?.assets ?? []).filter((asset) => asset.kind === "custom-agent" && asset.support === "supported" && asset.action === "write" && asset.destination.strategy === "emit-file").map((asset) => posix3.normalize(asset.destination.relativePath)));
16660
+ return [...plan.files, ...(plan.assetFiles ?? []).filter((file) => customAgentPaths.has(file.relativePath))];
16661
+ }
16634
16662
  function readExactFilePreimage(path, targetHome, request, operation) {
16635
16663
  if (currentSessionFileHash(path, targetHome) === null) {
16636
16664
  throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
16637
16665
  }
16638
- const bytes = readFileSync14(path);
16666
+ const bytes = readFileSync13(path);
16639
16667
  if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
16640
16668
  throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
16641
16669
  }
16642
- const observedSha256 = createHash10("sha256").update(bytes).digest("hex");
16670
+ const observedSha256 = createHash9("sha256").update(bytes).digest("hex");
16643
16671
  if (observedSha256 !== request.sha256) {
16644
16672
  throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
16645
16673
  }
@@ -16668,7 +16696,7 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
16668
16696
  throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
16669
16697
  }
16670
16698
  seen.add(request.relativePath);
16671
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
16699
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
16672
16700
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
16673
16701
  throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
16674
16702
  }
@@ -16695,8 +16723,47 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
16695
16723
  };
16696
16724
  });
16697
16725
  }
16698
- function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations) {
16699
- if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined)
16726
+ function validateFileRetirements(plan, targetHome, previousManifest, options) {
16727
+ const requests = options.retireFiles ?? [];
16728
+ if (!Array.isArray(requests))
16729
+ throw new SessionApplyError("Session file retirements must be an array.");
16730
+ if (requests.length === 0)
16731
+ return [];
16732
+ if (options.force)
16733
+ throw new SessionApplyError("Exact file retirement cannot be combined with force.");
16734
+ if (options.expectedManifestSha256 === undefined) {
16735
+ throw new SessionApplyError("Exact file retirement requires an expected manifest SHA-256 precondition.");
16736
+ }
16737
+ if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
16738
+ throw new SessionApplyError("File retirement requires this renderer's manifest for the same tool and target home.");
16739
+ }
16740
+ const seen = new Set;
16741
+ return requests.map((request) => {
16742
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
16743
+ throw new SessionApplyError("Each file retirement requires a plan-relative path and a 64-character lowercase SHA-256.");
16744
+ }
16745
+ if (seen.has(request.relativePath))
16746
+ throw new SessionApplyError(`Duplicate file retirement target: ${request.relativePath}`);
16747
+ seen.add(request.relativePath);
16748
+ if (plan.allFiles.some((file) => file.relativePath === request.relativePath)) {
16749
+ throw new SessionApplyError(`File retirement target is retained by the new plan: ${request.relativePath}`);
16750
+ }
16751
+ const owned = previousManifest.files.filter((entry) => entry.relativePath === request.relativePath);
16752
+ const path = resolveManifestRelativePath(request.relativePath, targetHome);
16753
+ if (owned.length !== 1 || owned[0].path !== path || !/^[a-f0-9]{64}$/.test(owned[0].sha256) || !Array.isArray(owned[0].sourceIds) || owned[0].sourceIds.some((id) => typeof id !== "string") || !isPlanManagedFile(plan, request.relativePath, owned[0].role) && owned[0].role !== "asset") {
16754
+ throw new SessionApplyError(`File retirement target is not an obsolete managed fragment, rule, or asset: ${request.relativePath}`);
16755
+ }
16756
+ return {
16757
+ path,
16758
+ relativePath: request.relativePath,
16759
+ preimageSha256: readExactFilePreimage(path, targetHome, request, "retirement"),
16760
+ previousManagedSha256: owned[0].sha256,
16761
+ sourceIds: [...owned[0].sourceIds]
16762
+ };
16763
+ });
16764
+ }
16765
+ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements) {
16766
+ if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined && retirements.length === 0 && previousManifest?.retirements === undefined)
16700
16767
  return plan.manifestFile;
16701
16768
  if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
16702
16769
  throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
@@ -16737,14 +16804,32 @@ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconcili
16737
16804
  };
16738
16805
  reconciledEntries.set(JSON.stringify(canonical), canonical);
16739
16806
  }
16807
+ if (previousManifest?.retirements !== undefined && !Array.isArray(previousManifest.retirements)) {
16808
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
16809
+ }
16810
+ const retiredEntries = new Map;
16811
+ for (const entry of [...previousManifest?.retirements ?? [], ...retirements.map(({ path: _path, ...receipt }) => receipt)]) {
16812
+ if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.previousManagedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
16813
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
16814
+ }
16815
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
16816
+ const canonical = {
16817
+ relativePath: entry.relativePath,
16818
+ preimageSha256: entry.preimageSha256,
16819
+ previousManagedSha256: entry.previousManagedSha256,
16820
+ sourceIds: [...entry.sourceIds]
16821
+ };
16822
+ retiredEntries.set(JSON.stringify(canonical), canonical);
16823
+ }
16740
16824
  const manifest = {
16741
16825
  ...plan.manifest,
16742
16826
  ...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
16743
- ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
16827
+ ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
16828
+ ...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
16744
16829
  };
16745
16830
  const content = `${JSON.stringify(manifest, null, 2)}
16746
16831
  `;
16747
- return { ...plan.manifestFile, content, sha256: sha25610(content) };
16832
+ return { ...plan.manifestFile, content, sha256: sha2569(content) };
16748
16833
  }
16749
16834
  function assertCursorAuthorityUnchanged(plan) {
16750
16835
  if (plan.tool !== "cursor" || plan.targetKind === "blocked")
@@ -16772,13 +16857,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
16772
16857
  throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
16773
16858
  }
16774
16859
  function ensureSessionTargetHome(targetHome) {
16775
- if (!existsSync15(targetHome))
16776
- mkdirSync7(targetHome, { recursive: true, mode: 448 });
16860
+ if (!existsSync14(targetHome))
16861
+ mkdirSync6(targetHome, { recursive: true, mode: 448 });
16777
16862
  assertSafeTargetHome(targetHome);
16778
16863
  }
16779
16864
  function checkSessionRenderDrift(targetHome, manifestPath) {
16780
16865
  const safeTargetHome = assertSafeTargetHome(targetHome);
16781
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative6(safeTargetHome, resolve11(manifestPath)), safeTargetHome) : resolve11(safeTargetHome, ".hasna", "session-render-manifest.json");
16866
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
16782
16867
  const checkedAt = new Date().toISOString();
16783
16868
  const previousManifest = readPreviousManifest(resolvedManifestPath);
16784
16869
  if (!previousManifest) {
@@ -16795,7 +16880,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16795
16880
  const drifted = [];
16796
16881
  for (const file of previousManifest.files) {
16797
16882
  const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
16798
- if (!existsSync15(target)) {
16883
+ if (!existsSync14(target)) {
16799
16884
  missing.push({
16800
16885
  path: target,
16801
16886
  relativePath: file.relativePath,
@@ -16805,7 +16890,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16805
16890
  });
16806
16891
  continue;
16807
16892
  }
16808
- const actualSha256 = sha25610(readFileSync14(target, "utf-8"));
16893
+ const actualSha256 = sha2569(readFileSync13(target, "utf-8"));
16809
16894
  if (actualSha256 !== file.sha256) {
16810
16895
  drifted.push({
16811
16896
  path: target,
@@ -16828,13 +16913,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16828
16913
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
16829
16914
  const snapshot = readSessionRenderSnapshot(snapshotPath);
16830
16915
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
16831
- const resolvedSnapshotPath = resolve11(snapshotPath);
16916
+ const resolvedSnapshotPath = resolve12(snapshotPath);
16832
16917
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
16833
- const snapshotDirRelative = relative6(snapshotDir, resolvedSnapshotPath);
16834
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute7(snapshotDirRelative);
16918
+ const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
16919
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute8(snapshotDirRelative);
16835
16920
  if (!insideSnapshotDir) {
16836
- const snapshotRelativePath = relative6(targetHome, resolvedSnapshotPath);
16837
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute7(snapshotRelativePath)) {
16921
+ const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
16922
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute8(snapshotRelativePath)) {
16838
16923
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
16839
16924
  }
16840
16925
  }
@@ -16951,19 +17036,19 @@ function requiredRestoreHash(file) {
16951
17036
  return file.previousSha256;
16952
17037
  }
16953
17038
  function readSessionRenderSnapshot(snapshotPath) {
16954
- const resolved = resolve11(snapshotPath);
16955
- if (!existsSync15(resolved))
17039
+ const resolved = resolve12(snapshotPath);
17040
+ if (!existsSync14(resolved))
16956
17041
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
16957
- const stat = lstatSync6(resolved);
17042
+ const stat = lstatSync7(resolved);
16958
17043
  if (stat.isSymbolicLink() || !stat.isFile()) {
16959
17044
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
16960
17045
  }
16961
- if (statSync5(resolved).size > 32 * 1024 * 1024) {
17046
+ if (statSync6(resolved).size > 32 * 1024 * 1024) {
16962
17047
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
16963
17048
  }
16964
17049
  let parsed;
16965
17050
  try {
16966
- parsed = JSON.parse(readFileSync14(resolved, "utf8"));
17051
+ parsed = JSON.parse(readFileSync13(resolved, "utf8"));
16967
17052
  } catch {
16968
17053
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
16969
17054
  }
@@ -16985,7 +17070,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16985
17070
  const previousManifest = snapshot.previousManifest;
16986
17071
  const previousFiles = new Map;
16987
17072
  for (const file of snapshot.files) {
16988
- if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha25610(file.content) !== file.sha256) {
17073
+ if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2569(file.content) !== file.sha256) {
16989
17074
  throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
16990
17075
  }
16991
17076
  resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
@@ -17032,8 +17117,8 @@ function readSessionRenderSnapshot(snapshotPath) {
17032
17117
  }
17033
17118
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
17034
17119
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
17035
- const manifestPath = resolve11(snapshot.manifestPath);
17036
- const manifestRelativePath = relative6(targetHome, manifestPath).replaceAll("\\", "/");
17120
+ const manifestPath = resolve12(snapshot.manifestPath);
17121
+ const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
17037
17122
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
17038
17123
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
17039
17124
  if (manifestSha256 === null) {
@@ -17041,7 +17126,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17041
17126
  }
17042
17127
  let parsedManifest;
17043
17128
  try {
17044
- parsedManifest = JSON.parse(readFileSync14(manifestPath, "utf8"));
17129
+ parsedManifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
17045
17130
  } catch {
17046
17131
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
17047
17132
  }
@@ -17049,7 +17134,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17049
17134
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
17050
17135
  }
17051
17136
  const appliedManifest = parsedManifest;
17052
- if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve11(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
17137
+ if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve12(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
17053
17138
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
17054
17139
  }
17055
17140
  const afterFiles = [];
@@ -17134,16 +17219,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
17134
17219
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
17135
17220
  }
17136
17221
  for (const entry of readdirSync3(dirname10(snapshotPath))) {
17137
- const candidatePath = resolve11(dirname10(snapshotPath), entry);
17138
- if (candidatePath === resolve11(snapshotPath) || !entry.endsWith(".json"))
17222
+ const candidatePath = resolve12(dirname10(snapshotPath), entry);
17223
+ if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
17139
17224
  continue;
17140
- const candidateStat = lstatSync6(candidatePath);
17225
+ const candidateStat = lstatSync7(candidatePath);
17141
17226
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
17142
17227
  continue;
17143
17228
  try {
17144
- const candidate = JSON.parse(readFileSync14(candidatePath, "utf8"));
17229
+ const candidate = JSON.parse(readFileSync13(candidatePath, "utf8"));
17145
17230
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
17146
- if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve11(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
17231
+ if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve12(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
17147
17232
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
17148
17233
  }
17149
17234
  } catch (error) {
@@ -17201,7 +17286,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
17201
17286
  return "create";
17202
17287
  }
17203
17288
  if (file.role === "manifest" && previousManifest) {
17204
- const previousManifestSha256 = sha25610(`${JSON.stringify(previousManifest, null, 2)}
17289
+ const previousManifestSha256 = sha2569(`${JSON.stringify(previousManifest, null, 2)}
17205
17290
  `);
17206
17291
  if (previousManifestSha256 !== file.sha256) {
17207
17292
  throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
@@ -17212,15 +17297,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
17212
17297
  }
17213
17298
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
17214
17299
  const path = resolveManifestRelativePath(relativePath, targetHome);
17215
- if (resolve11(recordedPath) !== path) {
17300
+ if (resolve12(recordedPath) !== path) {
17216
17301
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
17217
17302
  }
17218
17303
  return path;
17219
17304
  }
17220
17305
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
17221
17306
  const target = resolvePlannedFilePath(plan, file, targetHome);
17222
- const previousContent = existsSync15(target) ? readFileSync14(target, "utf-8") : null;
17223
- const previousSha256 = previousContent === null ? null : sha25610(previousContent);
17307
+ const previousContent = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
17308
+ const previousSha256 = previousContent === null ? null : sha2569(previousContent);
17224
17309
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
17225
17310
  const changed = previousContent !== file.content;
17226
17311
  const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
@@ -17328,17 +17413,55 @@ function isPlanManagedFile(plan, relativePath, role) {
17328
17413
  return true;
17329
17414
  return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
17330
17415
  }
17331
- function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
17416
+ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
17332
17417
  if (!previousManifest)
17333
17418
  return [];
17334
- return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
17419
+ return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role) || file.role === "asset" || retiredHashes.has(file.relativePath)).map((file) => planStaleFileResult(file, targetHome, options, retiredHashes.get(file.relativePath))).filter((result) => result !== null);
17335
17420
  }
17336
- function planStaleFileResult(file, targetHome, options) {
17421
+ function planStaleFileResult(file, targetHome, options, retiredHash) {
17337
17422
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
17338
- if (!existsSync15(target))
17339
- return null;
17340
- const previousContent = readFileSync14(target, "utf-8");
17341
- const previousSha256 = sha25610(previousContent);
17423
+ if (!existsSync14(target)) {
17424
+ if (file.role !== "asset")
17425
+ return null;
17426
+ return {
17427
+ path: target,
17428
+ relativePath: file.relativePath,
17429
+ role: file.role,
17430
+ action: "conflict",
17431
+ changed: true,
17432
+ previousSha256: null,
17433
+ newSha256: "",
17434
+ reason: "obsolete managed asset is missing; preserve manifest ownership until its reviewed preimage is restored and retired exactly"
17435
+ };
17436
+ }
17437
+ const previousContent = readFileSync13(target, "utf-8");
17438
+ const previousSha256 = sha2569(previousContent);
17439
+ if (retiredHash !== undefined) {
17440
+ if (previousSha256 !== retiredHash)
17441
+ throw new SessionApplyError(`File retirement preimage changed after validation: ${file.relativePath}`);
17442
+ return {
17443
+ path: target,
17444
+ relativePath: file.relativePath,
17445
+ role: file.role,
17446
+ action: "delete",
17447
+ changed: true,
17448
+ previousSha256,
17449
+ newSha256: "",
17450
+ reason: "exact reviewed obsolete managed file retired"
17451
+ };
17452
+ }
17453
+ if (file.role === "asset") {
17454
+ return {
17455
+ path: target,
17456
+ relativePath: file.relativePath,
17457
+ role: file.role,
17458
+ action: "conflict",
17459
+ changed: true,
17460
+ previousSha256,
17461
+ newSha256: "",
17462
+ reason: "obsolete managed asset requires exact retirement; preserve ownership until --retire-file and --expected-manifest-sha256 are supplied"
17463
+ };
17464
+ }
17342
17465
  if (!options.force && previousSha256 !== file.sha256) {
17343
17466
  return {
17344
17467
  path: target,
@@ -17382,31 +17505,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
17382
17505
  return previousHashes.get(file.relativePath) === previousSha256;
17383
17506
  }
17384
17507
  function resolvePlannedFilePath(plan, file, targetHome) {
17385
- const target = resolve11(targetHome, ...file.relativePath.split("/"));
17386
- const rel = relative6(targetHome, target);
17387
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute7(rel)) {
17508
+ const target = resolve12(targetHome, ...file.relativePath.split("/"));
17509
+ const rel = relative5(targetHome, target);
17510
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
17388
17511
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
17389
17512
  }
17390
- if (resolve11(file.path) !== target) {
17513
+ if (resolve12(file.path) !== target) {
17391
17514
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
17392
17515
  }
17393
17516
  assertNoSymlinkSegments2(targetHome, target);
17394
17517
  return target;
17395
17518
  }
17396
17519
  function resolveManifestRelativePath(relativePath, targetHome) {
17397
- const target = resolve11(targetHome, ...relativePath.split(/[\\/]+/));
17398
- const rel = relative6(targetHome, target);
17399
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute7(rel)) {
17520
+ const target = resolve12(targetHome, ...relativePath.split(/[\\/]+/));
17521
+ const rel = relative5(targetHome, target);
17522
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
17400
17523
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
17401
17524
  }
17402
17525
  assertNoSymlinkSegments2(targetHome, target);
17403
17526
  return target;
17404
17527
  }
17405
17528
  function readPreviousManifest(path) {
17406
- if (!existsSync15(path))
17529
+ if (!existsSync14(path))
17407
17530
  return null;
17408
17531
  try {
17409
- const parsed = JSON.parse(readFileSync14(path, "utf-8"));
17532
+ const parsed = JSON.parse(readFileSync13(path, "utf-8"));
17410
17533
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
17411
17534
  return null;
17412
17535
  if (!Array.isArray(parsed.files))
@@ -17440,18 +17563,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
17440
17563
  function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
17441
17564
  const actualHash = currentSessionFileHash(path, targetHome);
17442
17565
  if (actualHash !== expectedHash) {
17443
- throw new SessionApplyError(`Session apply path changed after planning: ${relative6(targetHome, path)}`);
17566
+ throw new SessionApplyError(`Session apply path changed after planning: ${relative5(targetHome, path)}`);
17444
17567
  }
17445
17568
  }
17446
17569
  function currentSessionFileHash(path, targetHome) {
17447
17570
  assertNoSymlinkSegments2(targetHome, path);
17448
- if (!existsSync15(path))
17571
+ if (!existsSync14(path))
17449
17572
  return null;
17450
- const stat = lstatSync6(path);
17573
+ const stat = lstatSync7(path);
17451
17574
  if (stat.isSymbolicLink() || !stat.isFile()) {
17452
17575
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
17453
17576
  }
17454
- return sha25610(readFileSync14(path, "utf-8"));
17577
+ return sha2569(readFileSync13(path, "utf-8"));
17455
17578
  }
17456
17579
  function requiredPreviousHash(result) {
17457
17580
  if (result.previousSha256 === null) {
@@ -17459,18 +17582,18 @@ function requiredPreviousHash(result) {
17459
17582
  }
17460
17583
  return result.previousSha256;
17461
17584
  }
17462
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
17585
+ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
17463
17586
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
17464
17587
  assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
17465
- const content = readFileSync14(result.path, "utf-8");
17466
- if (sha25610(content) !== result.previousSha256) {
17588
+ const content = readFileSync13(result.path, "utf-8");
17589
+ if (sha2569(content) !== result.previousSha256) {
17467
17590
  throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
17468
17591
  }
17469
17592
  return {
17470
17593
  path: result.path,
17471
17594
  relativePath: result.relativePath,
17472
17595
  role: result.role,
17473
- sha256: sha25610(content),
17596
+ sha256: sha2569(content),
17474
17597
  content
17475
17598
  };
17476
17599
  });
@@ -17483,7 +17606,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17483
17606
  };
17484
17607
  }
17485
17608
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17486
- const snapshotPath = join17(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
17609
+ const snapshotPath = join18(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
17487
17610
  const afterFiles = results.map((result) => {
17488
17611
  if (result.action === "conflict") {
17489
17612
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -17508,11 +17631,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17508
17631
  files: existingFiles,
17509
17632
  afterFiles,
17510
17633
  ...adoptions.length > 0 ? { adoptions } : {},
17511
- ...reconciliations.length > 0 ? { reconciliations } : {}
17634
+ ...reconciliations.length > 0 ? { reconciliations } : {},
17635
+ ...retirements.length > 0 ? { retirements } : {}
17512
17636
  };
17513
17637
  const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
17514
17638
  `;
17515
- if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
17639
+ if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
17516
17640
  throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
17517
17641
  }
17518
17642
  coordination?.assert_held();
@@ -17535,51 +17659,51 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17535
17659
  };
17536
17660
  }
17537
17661
  function assertSafeTargetHome(targetHome) {
17538
- if (!isAbsolute7(targetHome))
17662
+ if (!isAbsolute8(targetHome))
17539
17663
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
17540
- const normalized = resolve11(targetHome);
17541
- if (normalized === parse5(normalized).root) {
17664
+ const normalized = resolve12(targetHome);
17665
+ if (normalized === parse4(normalized).root) {
17542
17666
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17543
17667
  }
17544
- assertNoSymlinkAncestors3(normalized);
17545
- if (existsSync15(normalized) && lstatSync6(normalized).isSymbolicLink()) {
17668
+ assertNoSymlinkAncestors2(normalized);
17669
+ if (existsSync14(normalized) && lstatSync7(normalized).isSymbolicLink()) {
17546
17670
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17547
17671
  }
17548
17672
  return normalized;
17549
17673
  }
17550
17674
  function assertNoSymlinkSegments2(root, target) {
17551
- assertNoSymlinkAncestors3(root);
17552
- const rel = relative6(root, target);
17675
+ assertNoSymlinkAncestors2(root);
17676
+ const rel = relative5(root, target);
17553
17677
  let current = root;
17554
17678
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17555
- current = join17(current, segment);
17556
- if (existsSync15(current) && lstatSync6(current).isSymbolicLink()) {
17679
+ current = join18(current, segment);
17680
+ if (existsSync14(current) && lstatSync7(current).isSymbolicLink()) {
17557
17681
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17558
17682
  }
17559
17683
  }
17560
17684
  }
17561
- function assertNoSymlinkAncestors3(path) {
17562
- const normalized = resolve11(path);
17563
- const parsed = parse5(normalized);
17685
+ function assertNoSymlinkAncestors2(path) {
17686
+ const normalized = resolve12(path);
17687
+ const parsed = parse4(normalized);
17564
17688
  let current = parsed.root;
17565
- const rel = relative6(parsed.root, normalized);
17689
+ const rel = relative5(parsed.root, normalized);
17566
17690
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17567
- current = join17(current, segment);
17568
- if (!existsSync15(current))
17691
+ current = join18(current, segment);
17692
+ if (!existsSync14(current))
17569
17693
  return;
17570
- if (lstatSync6(current).isSymbolicLink()) {
17694
+ if (lstatSync7(current).isSymbolicLink()) {
17571
17695
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
17572
17696
  }
17573
17697
  }
17574
17698
  }
17575
- function sha25610(content) {
17576
- return createHash10("sha256").update(content).digest("hex");
17699
+ function sha2569(content) {
17700
+ return createHash9("sha256").update(content).digest("hex");
17577
17701
  }
17578
17702
  // src/lib/session-refresh.ts
17579
17703
  init_zod();
17580
- import { createHash as createHash11 } from "crypto";
17581
- import { closeSync as closeSync4, constants as constants2, fstatSync as fstatSync4, lstatSync as lstatSync7, openSync as openSync4, readFileSync as readFileSync15 } from "fs";
17582
- import { dirname as dirname11, join as join18, parse as parse6, resolve as resolve12 } from "path";
17704
+ import { createHash as createHash10 } from "crypto";
17705
+ import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readFileSync as readFileSync14 } from "fs";
17706
+ import { basename as basename6, dirname as dirname11, isAbsolute as isAbsolute9, join as join19, parse as parse5, resolve as resolve13 } from "path";
17583
17707
 
17584
17708
  // src/lib/global-source-coverage.ts
17585
17709
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
@@ -17612,7 +17736,6 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
17612
17736
  init_instruction_graph();
17613
17737
  init_project_context();
17614
17738
  init_session_render();
17615
- import { basename as basename6 } from "path";
17616
17739
  var nonempty = exports_external.string().min(1).max(4096);
17617
17740
  var selectorSchema = exports_external.object({
17618
17741
  schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
@@ -17663,10 +17786,10 @@ function assertHostedProfileBindings(profileId, configs, bindings) {
17663
17786
  }
17664
17787
  }
17665
17788
  function readManagedManifest(targetHome) {
17666
- const path = join18(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
17789
+ const path = join19(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
17667
17790
  let ancestor = dirname11(path);
17668
17791
  while (true) {
17669
- const stat = lstatSync7(ancestor);
17792
+ const stat = lstatSync8(ancestor);
17670
17793
  if (stat.isSymbolicLink() || !stat.isDirectory())
17671
17794
  throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
17672
17795
  const parent = dirname11(ancestor);
@@ -17674,13 +17797,13 @@ function readManagedManifest(targetHome) {
17674
17797
  break;
17675
17798
  ancestor = parent;
17676
17799
  }
17677
- const fd = openSync4(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
17800
+ const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
17678
17801
  let raw;
17679
17802
  try {
17680
17803
  const stat = fstatSync4(fd);
17681
17804
  if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
17682
17805
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
17683
- raw = readFileSync15(fd);
17806
+ raw = readFileSync14(fd);
17684
17807
  if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
17685
17808
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
17686
17809
  } finally {
@@ -17690,15 +17813,28 @@ function readManagedManifest(targetHome) {
17690
17813
  if (manifest.schema !== SESSION_RENDER_SCHEMA || !SESSION_RENDER_TOOLS.includes(manifest.tool) || manifest.targetHome !== targetHome || !manifest.profile?.trim() || !["session-home", "project-root"].includes(manifest.targetKind) || !Array.isArray(manifest.files) || !manifest.refreshSelector || manifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || manifest.targetOwner?.targetHome !== targetHome) {
17691
17814
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
17692
17815
  }
17816
+ const recordedProjectRoot = manifest.targetOwner.projectRoot;
17817
+ if (recordedProjectRoot !== null && recordedProjectRoot !== undefined) {
17818
+ if (typeof recordedProjectRoot !== "string" || !isAbsolute9(recordedProjectRoot)) {
17819
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root must be an absolute path.");
17820
+ }
17821
+ const normalizedProjectRoot = resolve13(recordedProjectRoot);
17822
+ if (normalizedProjectRoot === parse5(normalizedProjectRoot).root || manifest.targetKind === "project-root" && normalizedProjectRoot !== targetHome || manifest.targetKind === "session-home" && manifest.tool !== "opencode") {
17823
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root does not match the managed target.");
17824
+ }
17825
+ manifest.targetOwner.projectRoot = normalizedProjectRoot;
17826
+ } else if (manifest.targetKind === "project-root") {
17827
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: project-scoped render is missing its recorded project root.");
17828
+ }
17693
17829
  manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
17694
- return { manifest, sha256: createHash11("sha256").update(raw).digest("hex") };
17830
+ return { manifest, sha256: createHash10("sha256").update(raw).digest("hex") };
17695
17831
  }
17696
17832
  async function refreshSessionRender(input) {
17697
17833
  const { store } = input;
17698
17834
  if (store.mode !== "api" || !store.v1BaseUrl)
17699
17835
  throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
17700
- const targetHome = resolve12(input.targetHome);
17701
- if (targetHome === parse6(targetHome).root)
17836
+ const targetHome = resolve13(input.targetHome);
17837
+ if (targetHome === parse5(targetHome).root)
17702
17838
  throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
17703
17839
  const previous = readManagedManifest(targetHome);
17704
17840
  const selector = previous.manifest.refreshSelector;
@@ -17732,7 +17868,7 @@ async function refreshSessionRender(input) {
17732
17868
  profile_id: profile.id,
17733
17869
  provider_version: selector.providerVersion,
17734
17870
  targetHome,
17735
- ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
17871
+ ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
17736
17872
  sessionId: previous.manifest.sessionId ?? undefined,
17737
17873
  refreshSelector: selector,
17738
17874
  codewithNativeImports: selector.codewithNativeImports,
@@ -17741,7 +17877,7 @@ async function refreshSessionRender(input) {
17741
17877
  bindings,
17742
17878
  asset_configs: assetConfigs,
17743
17879
  asset_bindings: assetBindings,
17744
- asset_plan_mode: input.dryRun ? "dry-run" : "apply",
17880
+ asset_plan_mode: "apply",
17745
17881
  asset_scope: selector.assetScope,
17746
17882
  asset_surface: selector.assetSurface,
17747
17883
  extra_sources: station ? [station] : undefined,
@@ -18117,8 +18253,8 @@ init_codewith_shared_todos_storage_standard();
18117
18253
 
18118
18254
  // src/lib/sync.ts
18119
18255
  init_config_store();
18120
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync17 } from "fs";
18121
- import { basename as basename7, extname as extname3, join as join20 } from "path";
18256
+ import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
18257
+ import { basename as basename7, extname as extname3, join as join21 } from "path";
18122
18258
  init_config_agents();
18123
18259
  init_redact();
18124
18260
  init_machine();
@@ -18126,8 +18262,8 @@ init_transforms();
18126
18262
 
18127
18263
  // src/lib/sync-dir.ts
18128
18264
  init_config_store();
18129
- import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync16, statSync as statSync6 } from "fs";
18130
- import { join as join19, relative as relative7 } from "path";
18265
+ import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
18266
+ import { join as join20, relative as relative6 } from "path";
18131
18267
  init_redact();
18132
18268
  var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
18133
18269
  function shouldSkip(p) {
@@ -18136,9 +18272,9 @@ function shouldSkip(p) {
18136
18272
  async function syncFromDir(dir, opts = {}) {
18137
18273
  const store = opts.store ?? resolveConfigStore();
18138
18274
  const absDir = expandPath(dir);
18139
- if (!existsSync16(absDir))
18275
+ if (!existsSync15(absDir))
18140
18276
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
18141
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join19(absDir, f)).filter((f) => statSync6(f).isFile());
18277
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join20(absDir, f)).filter((f) => statSync7(f).isFile());
18142
18278
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
18143
18279
  const allConfigs = await store.listConfigs();
18144
18280
  for (const file of files) {
@@ -18147,7 +18283,7 @@ async function syncFromDir(dir, opts = {}) {
18147
18283
  continue;
18148
18284
  }
18149
18285
  try {
18150
- const content = readFileSync16(file, "utf-8");
18286
+ const content = readFileSync15(file, "utf-8");
18151
18287
  if (content.length > 500000) {
18152
18288
  result.skipped.push(file + " (too large)");
18153
18289
  continue;
@@ -18157,7 +18293,7 @@ async function syncFromDir(dir, opts = {}) {
18157
18293
  const existing = allConfigs.find((c) => c.target_path === targetPath);
18158
18294
  if (!existing) {
18159
18295
  if (!opts.dryRun)
18160
- await store.createConfig({ name: relative7(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
18296
+ await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
18161
18297
  result.added++;
18162
18298
  } else if (existing.content !== redacted.content) {
18163
18299
  if (!opts.dryRun)
@@ -18197,7 +18333,7 @@ async function syncToDir(dir, opts = {}) {
18197
18333
  }
18198
18334
  function walkDir(dir, files = []) {
18199
18335
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
18200
- const full = join19(dir, entry.name);
18336
+ const full = join20(dir, entry.name);
18201
18337
  if (shouldSkip(full))
18202
18338
  continue;
18203
18339
  if (entry.isDirectory())
@@ -18258,7 +18394,7 @@ function isGeneratedOutputTarget2(config, owners) {
18258
18394
  return !!ownerIds && !ownerIds.has(config.id);
18259
18395
  }
18260
18396
  function hasClaudePromptSource() {
18261
- return existsSync17(expandPath("~/.claude/CLAUDE.md"));
18397
+ return existsSync16(expandPath("~/.claude/CLAUDE.md"));
18262
18398
  }
18263
18399
  function hasClaudeRuleSourceForCursorTarget(targetPath) {
18264
18400
  const absoluteTargetPath = expandPath(targetPath);
@@ -18266,7 +18402,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
18266
18402
  if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
18267
18403
  return false;
18268
18404
  const stem = basename7(absoluteTargetPath, ".mdc");
18269
- return existsSync17(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync17(expandPath(`~/.claude/rules/${stem}.mdc`));
18405
+ return existsSync16(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync16(expandPath(`~/.claude/rules/${stem}.mdc`));
18270
18406
  }
18271
18407
  function isKnownGeneratedTargetPath(targetPath) {
18272
18408
  const normalizedTargetPath = normalizeTargetPath(targetPath);
@@ -18331,11 +18467,11 @@ async function syncProject(opts) {
18331
18467
  const allConfigs = await store.listConfigs();
18332
18468
  const machine = detectMachineContext();
18333
18469
  for (const pf of PROJECT_CONFIG_FILES) {
18334
- const abs = join20(absDir, pf.file);
18335
- if (!existsSync17(abs))
18470
+ const abs = join21(absDir, pf.file);
18471
+ if (!existsSync16(abs))
18336
18472
  continue;
18337
18473
  try {
18338
- const rawContent = readFileSync17(abs, "utf-8");
18474
+ const rawContent = readFileSync16(abs, "utf-8");
18339
18475
  if (rawContent.length > 500000) {
18340
18476
  result.skipped.push(pf.file);
18341
18477
  continue;
@@ -18364,20 +18500,20 @@ async function syncProject(opts) {
18364
18500
  }
18365
18501
  }
18366
18502
  for (const ruleDir of [
18367
- { dir: join20(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
18368
- { dir: join20(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
18369
- { dir: join20(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
18370
- { dir: join20(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
18371
- { dir: join20(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
18372
- { dir: join20(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
18373
- { dir: join20(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
18503
+ { dir: join21(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
18504
+ { dir: join21(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
18505
+ { dir: join21(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
18506
+ { dir: join21(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
18507
+ { dir: join21(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
18508
+ { dir: join21(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
18509
+ { dir: join21(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
18374
18510
  ]) {
18375
- if (!existsSync17(ruleDir.dir))
18511
+ if (!existsSync16(ruleDir.dir))
18376
18512
  continue;
18377
18513
  const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
18378
18514
  for (const f of mdFiles) {
18379
- const abs = join20(ruleDir.dir, f);
18380
- const raw = readFileSync17(abs, "utf-8");
18515
+ const abs = join21(ruleDir.dir, f);
18516
+ const raw = readFileSync16(abs, "utf-8");
18381
18517
  const redacted = redactContent(raw, "markdown");
18382
18518
  const machineAware = templateizeMachineContent(redacted.content, machine);
18383
18519
  const content = machineAware.content;
@@ -18416,20 +18552,20 @@ async function syncKnown(opts = {}) {
18416
18552
  for (const known of targets) {
18417
18553
  if (known.rulesDir) {
18418
18554
  const absDir = expandPath(known.rulesDir);
18419
- if (!existsSync17(absDir)) {
18555
+ if (!existsSync16(absDir)) {
18420
18556
  result.skipped.push(known.rulesDir);
18421
18557
  continue;
18422
18558
  }
18423
18559
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
18424
18560
  const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
18425
18561
  for (const f of ruleFiles) {
18426
- const abs2 = join20(absDir, f);
18562
+ const abs2 = join21(absDir, f);
18427
18563
  const targetPath = abs2.replace(home, "~");
18428
18564
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
18429
18565
  result.skipped.push(`${targetPath} (generated output)`);
18430
18566
  continue;
18431
18567
  }
18432
- const raw = readFileSync17(abs2, "utf-8");
18568
+ const raw = readFileSync16(abs2, "utf-8");
18433
18569
  const redacted = redactContent(raw, "markdown");
18434
18570
  const machineAware = templateizeMachineContent(redacted.content, machine);
18435
18571
  const content = machineAware.content;
@@ -18457,12 +18593,12 @@ async function syncKnown(opts = {}) {
18457
18593
  continue;
18458
18594
  }
18459
18595
  const abs = expandPath(known.path);
18460
- if (!existsSync17(abs)) {
18596
+ if (!existsSync16(abs)) {
18461
18597
  result.skipped.push(known.path);
18462
18598
  continue;
18463
18599
  }
18464
18600
  try {
18465
- const rawContent = normalizeKnownConfigSource(known, readFileSync17(abs, "utf-8"));
18601
+ const rawContent = normalizeKnownConfigSource(known, readFileSync16(abs, "utf-8"));
18466
18602
  if (rawContent.length > 500000) {
18467
18603
  result.skipped.push(known.path + " (too large)");
18468
18604
  continue;
@@ -18565,9 +18701,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
18565
18701
  }
18566
18702
  function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
18567
18703
  const path = expandPath(targetPath);
18568
- if (!existsSync17(path))
18704
+ if (!existsSync16(path))
18569
18705
  return `(file not found on disk: ${path})`;
18570
- const diskContent = readFileSync17(path, "utf-8");
18706
+ const diskContent = readFileSync16(path, "utf-8");
18571
18707
  if (diskContent === expectedContent)
18572
18708
  return "(no diff \u2014 identical)";
18573
18709
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -18727,9 +18863,9 @@ function detectFormat(filePath) {
18727
18863
  // src/lib/export.ts
18728
18864
  init_types();
18729
18865
  init_config_store();
18730
- import { createHash as createHash12 } from "crypto";
18731
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
18732
- import { dirname as dirname12, join as join21, resolve as resolve13 } from "path";
18866
+ import { createHash as createHash11 } from "crypto";
18867
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
18868
+ import { dirname as dirname12, join as join22, resolve as resolve14 } from "path";
18733
18869
  import { tmpdir } from "os";
18734
18870
  var ARCHIVE_EXCLUSIONS = [
18735
18871
  {
@@ -18751,8 +18887,8 @@ var ARCHIVE_EXCLUSIONS = [
18751
18887
  function compareText(left, right) {
18752
18888
  return left < right ? -1 : left > right ? 1 : 0;
18753
18889
  }
18754
- function sha25611(value) {
18755
- return createHash12("sha256").update(value).digest("hex");
18890
+ function sha25610(value) {
18891
+ return createHash11("sha256").update(value).digest("hex");
18756
18892
  }
18757
18893
  function canonicalize(value) {
18758
18894
  if (Array.isArray(value))
@@ -18876,19 +19012,19 @@ function computeIntegrity(domain, options) {
18876
19012
  machines: collections.machines.length
18877
19013
  };
18878
19014
  const hashes = {
18879
- configs: sha25611(canonicalDomainJson(collections.configs)),
18880
- config_snapshots: sha25611(canonicalDomainJson(collections.config_snapshots)),
18881
- profiles: sha25611(canonicalDomainJson(collections.profiles)),
18882
- profile_config_bindings: sha25611(canonicalDomainJson(collections.profile_config_bindings)),
18883
- profile_asset_bindings: sha25611(canonicalDomainJson(collections.profile_asset_bindings)),
18884
- machines: sha25611(canonicalDomainJson(collections.machines))
19015
+ configs: sha25610(canonicalDomainJson(collections.configs)),
19016
+ config_snapshots: sha25610(canonicalDomainJson(collections.config_snapshots)),
19017
+ profiles: sha25610(canonicalDomainJson(collections.profiles)),
19018
+ profile_config_bindings: sha25610(canonicalDomainJson(collections.profile_config_bindings)),
19019
+ profile_asset_bindings: sha25610(canonicalDomainJson(collections.profile_asset_bindings)),
19020
+ machines: sha25610(canonicalDomainJson(collections.machines))
18885
19021
  };
18886
19022
  return {
18887
19023
  algorithm: "sha256",
18888
19024
  canonicalization: options.canonicalization,
18889
19025
  counts,
18890
19026
  hashes,
18891
- domain_sha256: sha25611(canonicalDomainJson({ counts, hashes }))
19027
+ domain_sha256: sha25610(canonicalDomainJson({ counts, hashes }))
18892
19028
  };
18893
19029
  }
18894
19030
  function computeDomainIntegrity(domain) {
@@ -19059,19 +19195,19 @@ async function exportConfigs(outputPath, opts = {}) {
19059
19195
  exported_at: new Date().toISOString(),
19060
19196
  payload: {
19061
19197
  path: "domain.json",
19062
- sha256: sha25611(payload),
19198
+ sha256: sha25610(payload),
19063
19199
  size_bytes: Buffer.byteLength(payload)
19064
19200
  },
19065
19201
  integrity,
19066
19202
  exclusions: ARCHIVE_EXCLUSIONS
19067
19203
  };
19068
- const absOutput = resolve13(outputPath);
19069
- mkdirSync8(dirname12(absOutput), { recursive: true });
19070
- const stagingDir = mkdtempSync(join21(tmpdir(), "instructions-domain-export-"));
19204
+ const absOutput = resolve14(outputPath);
19205
+ mkdirSync7(dirname12(absOutput), { recursive: true });
19206
+ const stagingDir = mkdtempSync(join22(tmpdir(), "instructions-domain-export-"));
19071
19207
  try {
19072
- writeFileSync6(join21(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
19208
+ writeFileSync5(join22(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
19073
19209
  `, "utf-8");
19074
- writeFileSync6(join21(stagingDir, "domain.json"), payload, "utf-8");
19210
+ writeFileSync5(join22(stagingDir, "domain.json"), payload, "utf-8");
19075
19211
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
19076
19212
  stdout: "pipe",
19077
19213
  stderr: "pipe"
@@ -19083,15 +19219,15 @@ async function exportConfigs(outputPath, opts = {}) {
19083
19219
  }
19084
19220
  return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
19085
19221
  } finally {
19086
- if (existsSync18(stagingDir))
19087
- rmSync4(stagingDir, { recursive: true, force: true });
19222
+ if (existsSync17(stagingDir))
19223
+ rmSync3(stagingDir, { recursive: true, force: true });
19088
19224
  }
19089
19225
  }
19090
19226
  // src/lib/import.ts
19091
19227
  init_types();
19092
19228
  init_config_store();
19093
- import { createHash as createHash13 } from "crypto";
19094
- import { resolve as resolve14 } from "path";
19229
+ import { createHash as createHash12 } from "crypto";
19230
+ import { resolve as resolve15 } from "path";
19095
19231
  function compareText2(left, right) {
19096
19232
  return left < right ? -1 : left > right ? 1 : 0;
19097
19233
  }
@@ -19115,8 +19251,8 @@ function emptyResult() {
19115
19251
  integrity: null
19116
19252
  };
19117
19253
  }
19118
- function sha25612(value) {
19119
- return createHash13("sha256").update(value).digest("hex");
19254
+ function sha25611(value) {
19255
+ return createHash12("sha256").update(value).digest("hex");
19120
19256
  }
19121
19257
  function normalizeMemberName(name) {
19122
19258
  let normalized = name;
@@ -19186,7 +19322,7 @@ async function readV2(reader, manifest) {
19186
19322
  throw new Error("Invalid v2 archive manifest");
19187
19323
  }
19188
19324
  const payload = await reader.read("domain.json");
19189
- if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25612(payload) !== manifest.payload.sha256) {
19325
+ if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25611(payload) !== manifest.payload.sha256) {
19190
19326
  throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
19191
19327
  }
19192
19328
  const domain = validateInstructionsDomainArchive(JSON.parse(payload));
@@ -19379,7 +19515,7 @@ async function importV1(reader, manifest, store, conflict) {
19379
19515
  async function importConfigs(bundlePath, opts = {}) {
19380
19516
  const store = opts.store ?? resolveConfigStore();
19381
19517
  const conflict = opts.conflict ?? "skip";
19382
- const reader = await openArchive(resolve14(bundlePath));
19518
+ const reader = await openArchive(resolve15(bundlePath));
19383
19519
  const manifestText = await reader.read("manifest.json");
19384
19520
  let manifest;
19385
19521
  try {
@@ -19407,9 +19543,9 @@ init_redact();
19407
19543
 
19408
19544
  // src/lib/package-manager-guard.ts
19409
19545
  import { execFileSync as execFileSync2 } from "child_process";
19410
- import { existsSync as existsSync19, lstatSync as lstatSync8, readdirSync as readdirSync6, readFileSync as readFileSync18 } from "fs";
19411
- import { homedir as homedir10 } from "os";
19412
- import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute8, join as join22, relative as relative8, resolve as resolve15 } from "path";
19546
+ import { existsSync as existsSync18, lstatSync as lstatSync9, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
19547
+ import { homedir as homedir11 } from "os";
19548
+ import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute10, join as join23, relative as relative7, resolve as resolve16 } from "path";
19413
19549
  var SKIP_DIRS = new Set([
19414
19550
  ".git",
19415
19551
  "node_modules",
@@ -19446,14 +19582,14 @@ var TOKEN_VALUE_PATTERNS = [
19446
19582
  { re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
19447
19583
  ];
19448
19584
  function scanPackageManagerSecrets(options = {}) {
19449
- const cwd = options.cwd ? resolve15(options.cwd) : process.cwd();
19450
- const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve15(cwd, root));
19585
+ const cwd = options.cwd ? resolve16(options.cwd) : process.cwd();
19586
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve16(cwd, root));
19451
19587
  const findings = [];
19452
19588
  let scannedFiles = 0;
19453
19589
  for (const root of roots) {
19454
- if (!existsSync19(root))
19590
+ if (!existsSync18(root))
19455
19591
  continue;
19456
- const stat = lstatSync8(root);
19592
+ const stat = lstatSync9(root);
19457
19593
  if (stat.isFile()) {
19458
19594
  if (!shouldScanRepoFile(root))
19459
19595
  continue;
@@ -19468,7 +19604,7 @@ function scanPackageManagerSecrets(options = {}) {
19468
19604
  continue;
19469
19605
  const tracked = trackedFiles(root);
19470
19606
  for (const file of collectRepoFiles(root)) {
19471
- const rel = toPosix(relative8(root, file));
19607
+ const rel = toPosix(relative7(root, file));
19472
19608
  const isTracked = tracked.has(rel);
19473
19609
  const text = readTextFile(file);
19474
19610
  if (text === null)
@@ -19478,10 +19614,10 @@ function scanPackageManagerSecrets(options = {}) {
19478
19614
  }
19479
19615
  }
19480
19616
  if (options.includeHome) {
19481
- const home = homedir10();
19617
+ const home = homedir11();
19482
19618
  for (const name of HOME_FILES) {
19483
- const file = join22(home, name);
19484
- if (!existsSync19(file))
19619
+ const file = join23(home, name);
19620
+ if (!existsSync18(file))
19485
19621
  continue;
19486
19622
  const text = readTextFile(file);
19487
19623
  if (text === null)
@@ -19505,12 +19641,12 @@ function collectRepoFiles(root) {
19505
19641
  if (entry.isDirectory()) {
19506
19642
  if (SKIP_DIRS.has(entry.name))
19507
19643
  continue;
19508
- visit(join22(dir, entry.name));
19644
+ visit(join23(dir, entry.name));
19509
19645
  continue;
19510
19646
  }
19511
19647
  if (!entry.isFile())
19512
19648
  continue;
19513
- const file = join22(dir, entry.name);
19649
+ const file = join23(dir, entry.name);
19514
19650
  if (shouldScanRepoFile(file))
19515
19651
  out.push(file);
19516
19652
  }
@@ -19545,10 +19681,10 @@ function isNpmrcName(name) {
19545
19681
  }
19546
19682
  function readTextFile(file) {
19547
19683
  try {
19548
- const stat = lstatSync8(file);
19684
+ const stat = lstatSync9(file);
19549
19685
  if (!stat.isFile() || stat.size > 5000000)
19550
19686
  return null;
19551
- const buf = readFileSync18(file);
19687
+ const buf = readFileSync17(file);
19552
19688
  if (buf.includes(0))
19553
19689
  return null;
19554
19690
  return buf.toString("utf-8");
@@ -19752,7 +19888,7 @@ function isTrackedFile(file) {
19752
19888
  encoding: "utf-8",
19753
19889
  stdio: ["ignore", "pipe", "ignore"]
19754
19890
  }).trim();
19755
- const rel = toPosix(relative8(repoRoot, file));
19891
+ const rel = toPosix(relative7(repoRoot, file));
19756
19892
  execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
19757
19893
  stdio: ["ignore", "ignore", "ignore"]
19758
19894
  });
@@ -19778,13 +19914,13 @@ function stripInlineComment(value) {
19778
19914
  return value.replace(/\s[#;].*$/, "").trim();
19779
19915
  }
19780
19916
  function displayPath(file, root) {
19781
- const home = homedir10();
19917
+ const home = homedir11();
19782
19918
  if (root === home && (file === home || file.startsWith(home + "/")))
19783
- return "~/" + toPosix(relative8(home, file));
19784
- if (isAbsolute8(root) && file.startsWith(root + "/"))
19785
- return toPosix(relative8(root, file));
19919
+ return "~/" + toPosix(relative7(home, file));
19920
+ if (isAbsolute10(root) && file.startsWith(root + "/"))
19921
+ return toPosix(relative7(root, file));
19786
19922
  if (file === home || file.startsWith(home + "/"))
19787
- return "~/" + toPosix(relative8(home, file));
19923
+ return "~/" + toPosix(relative7(home, file));
19788
19924
  return file;
19789
19925
  }
19790
19926
  function toPosix(path) {
@@ -19824,6 +19960,7 @@ export {
19824
19960
  renderTemplatePreview,
19825
19961
  renderTemplate,
19826
19962
  renderProviderFragment,
19963
+ renderNativeAgentContent,
19827
19964
  renderMachineAwareContentPreview,
19828
19965
  renderMachineAwareContent,
19829
19966
  refreshStationProfile,
@@ -19872,6 +20009,7 @@ export {
19872
20009
  ensureGlobalAgentRulesStandardConfig,
19873
20010
  ensureDangerousOperationGuardStandardConfig,
19874
20011
  ensureCodewithSharedTodosStorageStandardConfig,
20012
+ discoverHarnesses,
19875
20013
  diffConfig,
19876
20014
  detectMachineContext,
19877
20015
  detectFormat,
@@ -19957,8 +20095,10 @@ export {
19957
20095
  INSTRUCTIONS_LOCAL_OPT_IN_ENV,
19958
20096
  INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
19959
20097
  INBOX_CONVERSATIONS_MINIMUM_VERSION,
20098
+ HARNESS_DISCOVERY_SCHEMA,
19960
20099
  GLOBAL_AGENT_RULES_STANDARD_SLUG,
19961
20100
  GLOBAL_AGENT_RULES_STANDARD_CONTENT,
20101
+ DISCOVERABLE_HARNESSES,
19962
20102
  DANGEROUS_OPERATION_GUARD_STANDARD_SLUG,
19963
20103
  DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
19964
20104
  ConfigVersionConflictError,