@hasna/instructions 0.7.2 → 0.7.4

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-ps6jjt6k.js → apply-1gyr8r0x.js} +2 -2
  3. package/dist/chunks/{apply-wj7wh5km.js → apply-fyfzqc7s.js} +2 -2
  4. package/dist/chunks/{index-y8410ve0.js → index-6sbmg1vc.js} +1 -1
  5. package/dist/chunks/{index-18j7x3xw.js → index-axs3yftz.js} +2 -2
  6. package/dist/chunks/{index-pq18fdbj.js → index-dwagz53p.js} +64 -3
  7. package/dist/chunks/{index-n6nh6f5r.js → index-enh4yv85.js} +156 -15
  8. package/dist/chunks/{index-ape24zc0.js → index-n9arv47w.js} +1 -1
  9. package/dist/chunks/{index-ek15201v.js → index-z27xp7fh.js} +2 -2
  10. package/dist/chunks/{local-1c1qxzvx.js → local-5dsm856v.js} +1 -1
  11. package/dist/chunks/{local-hc67bynj.js → local-yxsbs8x3.js} +1 -1
  12. package/dist/chunks/{sync-azd9t4p8.js → sync-6r1v8hd3.js} +3 -3
  13. package/dist/chunks/{sync-12m3vs6g.js → sync-k3pab4s6.js} +3 -3
  14. package/dist/cli/index.js +526 -555
  15. package/dist/index.d.ts +4 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +690 -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,62 @@ 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) => candidate === normalizedManagedDir || candidate.endsWith(`/${normalizedManagedDir}`) || candidate.includes(`/${normalizedManagedDir}/`));
9392
+ }
9393
+ function canonicalOpenCodeInstructionPaths(reference, targetHome, projectRoot) {
9394
+ const portable = decodeOpenCodePathEscapes(reference.replaceAll("\\", "/")).replaceAll("\\", "/");
9395
+ if (portable.includes("\x00")) {
9396
+ throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
9397
+ }
9398
+ const windowsAbsolute = /^[A-Za-z]:\//.test(portable);
9399
+ const uriScheme = windowsAbsolute ? null : /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(portable)?.[1]?.toLowerCase();
9400
+ if (uriScheme && uriScheme !== "file")
9401
+ return [];
9402
+ if (uriScheme === "file") {
9403
+ const fileReference = reference.replaceAll("\\", "/");
9404
+ if (!/^file:\/\//i.test(fileReference)) {
9405
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9406
+ }
9407
+ let url;
9408
+ try {
9409
+ url = new URL(fileReference);
9410
+ } catch {
9411
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9412
+ }
9413
+ if (url.protocol !== "file:" || url.username || url.password || url.port || url.search || url.hash) {
9414
+ throw new Error("OpenCode config contains an invalid file URL instruction reference.");
9415
+ }
9416
+ const pathname = decodeOpenCodePathEscapes(url.pathname);
9417
+ if (pathname.includes("\x00")) {
9418
+ throw new Error("OpenCode config instruction references cannot contain NUL bytes.");
9419
+ }
9420
+ const host = url.hostname && url.hostname !== "localhost" ? `//${url.hostname}` : "";
9421
+ return [normalizePortableOpenCodePath(`${host}${pathname}`)];
9422
+ }
9423
+ if (posix2.isAbsolute(portable) || windowsAbsolute || portable.startsWith("//")) {
9424
+ return [normalizePortableOpenCodePath(portable)];
9425
+ }
9426
+ const roots = [
9427
+ ...projectRoot ? [resolveSessionPath(projectRoot)] : [],
9428
+ resolveSessionPath(targetHome)
9429
+ ];
9430
+ return [...new Set(roots.map((root) => normalizePortableOpenCodePath(posix2.join(root.replaceAll("\\", "/"), portable))))];
9431
+ }
9432
+ function decodeOpenCodePathEscapes(value) {
9433
+ return value.replace(/(?:%[a-fA-F0-9]{2})+/g, (encoded) => {
9434
+ try {
9435
+ return decodeURIComponent(encoded);
9436
+ } catch {
9437
+ return encoded;
9438
+ }
9439
+ });
9440
+ }
9441
+ function normalizePortableOpenCodePath(value) {
9442
+ const normalized = posix2.normalize(value.replaceAll("\\", "/"));
9443
+ return normalized.startsWith("//") ? `/${normalized.replace(/^\/+/u, "")}` : normalized;
9305
9444
  }
9306
9445
  function buildAntigravityRuleFiles(targetHome, adapter, sources) {
9307
9446
  return sources.flatMap((source, index) => {
@@ -9355,7 +9494,7 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
9355
9494
  }
9356
9495
  return file;
9357
9496
  }
9358
- function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
9497
+ function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion, projectRoot) {
9359
9498
  switch (adapter.mode) {
9360
9499
  case "native-imports":
9361
9500
  return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
@@ -9364,7 +9503,7 @@ function buildFiles(targetHome, adapter, profile, sources, providerConfig, provi
9364
9503
  case "cursor-mdc":
9365
9504
  return buildCursorRuleFiles(targetHome, adapter, sources);
9366
9505
  case "opencode-instructions":
9367
- return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig);
9506
+ return buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfig, projectRoot);
9368
9507
  case "antigravity-rules":
9369
9508
  return buildAntigravityRuleFiles(targetHome, adapter, sources);
9370
9509
  case "provider-rules":
@@ -9393,12 +9532,13 @@ function buildAssetFiles(input, targetHome, blocked) {
9393
9532
  if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute3(relativePath)) {
9394
9533
  throw new Error(`Asset ${item.assetKey} is outside the session snapshot root; use a project-scoped session plan for atomic application.`);
9395
9534
  }
9535
+ const renderedContent = renderNativeAgentContent(content, item.nativeAgent);
9396
9536
  return {
9397
9537
  path,
9398
9538
  relativePath: assertSafeRelativePath(relativePath),
9399
9539
  role: "asset",
9400
- content,
9401
- sha256: sha2566(content),
9540
+ content: renderedContent,
9541
+ sha256: sha2566(renderedContent),
9402
9542
  sourceIds: [item.sourceConfigId, item.assetId]
9403
9543
  };
9404
9544
  });
@@ -9577,7 +9717,7 @@ function resolveSessionTargetOwnership(input, target) {
9577
9717
  tool: input.tool,
9578
9718
  profile: input.profile,
9579
9719
  targetHome: target.targetHome,
9580
- projectRoot: null,
9720
+ projectRoot: input.tool === "opencode" && input.projectRoot ? resolveSessionPath(input.projectRoot) : null,
9581
9721
  ownedBy: "open-configs",
9582
9722
  canonicalOwner: "instructions",
9583
9723
  writer: {
@@ -9638,7 +9778,7 @@ function planSessionRender(input) {
9638
9778
  if (input.providerConfig && input.tool !== "opencode") {
9639
9779
  throw new Error("Provider base config is supported only for OpenCode session renders.");
9640
9780
  }
9641
- const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
9781
+ const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version, input.projectRoot);
9642
9782
  const projectContext = blocked ? null : composeProjectContextSessionRender({
9643
9783
  tool: input.tool,
9644
9784
  adapter_mode: adapter.mode,
@@ -9739,6 +9879,7 @@ function planSessionRender(input) {
9739
9879
  mutationMode: item.mutationMode,
9740
9880
  destination: item.destination,
9741
9881
  digest: item.source.digest,
9882
+ ...item.nativeAgent ? { nativeAgent: item.nativeAgent } : {},
9742
9883
  exactOnceKey: item.exactOnceKey
9743
9884
  }))
9744
9885
  }
@@ -13768,7 +13909,7 @@ function detectMachineContext(overrides = {}) {
13768
13909
  created_at: "",
13769
13910
  os_family: osFamily,
13770
13911
  home_dir: homeDir6,
13771
- workspace_root: overrides.workspace_root ?? join10(homeDir6, osFamily === "macos" ? "Workspace" : "workspace"),
13912
+ workspace_root: overrides.workspace_root ?? "",
13772
13913
  bun_bin_dir: bunBinDir,
13773
13914
  bun_path: overrides.bun_path ?? defaultBunPath,
13774
13915
  path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join10("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
@@ -13781,7 +13922,7 @@ function machineContextToVariables(machine) {
13781
13922
  OS_FAMILY: machine.os_family,
13782
13923
  ARCH: machine.arch ?? "",
13783
13924
  HOME_DIR: machine.home_dir,
13784
- WORKSPACE_ROOT: machine.workspace_root,
13925
+ ...machine.workspace_root ? { WORKSPACE_ROOT: machine.workspace_root } : {},
13785
13926
  BUN_BIN_DIR: machine.bun_bin_dir,
13786
13927
  BUN_PATH: machine.bun_path,
13787
13928
  PATH_PREFIX: machine.path_prefix
@@ -14845,7 +14986,7 @@ init_types();
14845
14986
 
14846
14987
  // src/status.ts
14847
14988
  init_config_store();
14848
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
14989
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
14849
14990
 
14850
14991
  // src/lib/apply.ts
14851
14992
  init_types();
@@ -15395,19 +15536,9 @@ function getPackageVersion() {
15395
15536
  }
15396
15537
 
15397
15538
  // 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";
15539
+ import { lstatSync as lstatSync4 } from "fs";
15409
15540
  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";
15541
+ import { join as join14, resolve as resolve10 } from "path";
15411
15542
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
15412
15543
  var INBOX_SKILL_MARKERS = [
15413
15544
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -15416,369 +15547,67 @@ var INBOX_SKILL_MARKERS = [
15416
15547
  [".config", "opencode", "skills", "inbox", "SKILL.md"],
15417
15548
  [".cursor", "skills", "inbox", "SKILL.md"]
15418
15549
  ];
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)) {
15550
+ function needsMigrationReview(homeDir6, parts) {
15551
+ let current = resolve10(homeDir6);
15552
+ const absolute = join14(current, ...parts);
15553
+ for (const segment of parts) {
15436
15554
  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 };
15555
+ let stat;
15556
+ try {
15557
+ stat = lstatSync4(current);
15558
+ } catch (error) {
15559
+ if (error.code === "ENOENT")
15560
+ return false;
15561
+ throw error;
15505
15562
  }
15506
- return {
15507
- path,
15508
- content: readFileSync10(path, "utf8"),
15509
- mode: stat.mode & 511,
15510
- regular: true
15511
- };
15512
- }).filter((snapshot) => snapshot !== null);
15563
+ if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
15564
+ return true;
15565
+ }
15566
+ return true;
15513
15567
  }
15514
- function inspectInbox(options) {
15568
+ function inspectLegacyInbox(options) {
15515
15569
  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
- }
15570
+ const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir6, parts)).map((parts) => join14(homeDir6, ...parts));
15571
+ const present = markers.length > 0;
15576
15572
  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
15573
+ skill: "inbox",
15574
+ runtime: "conversations watch",
15575
+ minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
15576
+ skill_present: present,
15577
+ skill_markers: markers,
15578
+ skill_contracts_current: 0,
15579
+ stale_skill_markers: markers,
15580
+ expected_skill_sha256: null,
15581
+ runtime_command: options.conversationsCommand ?? "conversations",
15582
+ runtime_present: false,
15583
+ runtime_version: null,
15584
+ watch_supports_from: false,
15585
+ watch_supports_all: false,
15586
+ watch_supports_full_content: false,
15587
+ hosted_heartbeat: "unverified",
15588
+ delivery_verified: false,
15589
+ manual_fallback_ready: false,
15590
+ healthy: !present,
15591
+ 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
15592
  };
15601
15593
  }
15602
15594
  function inspectManagedSkillRuntimes(options = {}) {
15603
- const runtime = inspectInbox(options).status;
15604
- const installed = runtime.skill_present ? [runtime] : [];
15595
+ const runtime = inspectLegacyInbox(options);
15605
15596
  return {
15606
15597
  runtimes: [runtime],
15607
- skills_present: installed.length,
15608
- healthy: installed.filter((item) => item.healthy).length,
15609
- missing: installed.filter((item) => !item.healthy).length
15598
+ skills_present: runtime.skill_present ? 1 : 0,
15599
+ healthy: 0,
15600
+ missing: runtime.skill_present ? 1 : 0
15610
15601
  };
15611
15602
  }
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"
15624
- };
15625
- }
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
15603
  async function reconcileManagedSkillRuntimes(options = {}) {
15604
+ const runtime = inspectLegacyInbox(options);
15690
15605
  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
15606
  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
15607
+ runtimes: [{ ...runtime, action: runtime.skill_present ? "failed" : "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
15608
+ changed: 0,
15609
+ failed: runtime.skill_present ? 1 : 0,
15610
+ dry_run: dryRun
15782
15611
  };
15783
15612
  }
15784
15613
 
@@ -15842,11 +15671,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
15842
15671
  continue;
15843
15672
  knownTargets += 1;
15844
15673
  const targetPath = expandPath(config.target_path);
15845
- if (!existsSync12(targetPath)) {
15674
+ if (!existsSync11(targetPath)) {
15846
15675
  missingTargets += 1;
15847
15676
  continue;
15848
15677
  }
15849
- const disk = readFileSync11(targetPath, "utf-8");
15678
+ const disk = readFileSync10(targetPath, "utf-8");
15850
15679
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
15851
15680
  if (redactedDisk !== config.content) {
15852
15681
  driftedTargets += 1;
@@ -15937,8 +15766,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
15937
15766
  };
15938
15767
  }
15939
15768
  // 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";
15769
+ import { createHash as createHash8 } from "crypto";
15770
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
15942
15771
  import { join as join15 } from "path";
15943
15772
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
15944
15773
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
@@ -16074,8 +15903,8 @@ function renderPerEndpointFragment(entry) {
16074
15903
  function renderProviderFragment(entry) {
16075
15904
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
16076
15905
  }
16077
- function sha2569(content) {
16078
- return createHash9("sha256").update(content).digest("hex");
15906
+ function sha2568(content) {
15907
+ return createHash8("sha256").update(content).digest("hex");
16079
15908
  }
16080
15909
  function resolveAndRenderProviderContext(opts) {
16081
15910
  const entry = matchProviderEndpoint(opts.origin);
@@ -16085,17 +15914,17 @@ function resolveAndRenderProviderContext(opts) {
16085
15914
  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
15915
  const content = renderProviderFragment(entry);
16087
15916
  const dir = join15(opts.homeDir, PROVIDER_CONTEXT_DIR);
16088
- if (!existsSync13(dir))
16089
- mkdirSync5(dir, { recursive: true });
15917
+ if (!existsSync12(dir))
15918
+ mkdirSync4(dir, { recursive: true });
16090
15919
  const filename = `${entry ? entry.key : "invariant"}.md`;
16091
15920
  const fragmentPath2 = join15(dir, filename);
16092
- const fragmentSha256 = sha2569(content);
16093
- writeFileSync4(fragmentPath2, content, "utf8");
15921
+ const fragmentSha256 = sha2568(content);
15922
+ writeFileSync3(fragmentPath2, content, "utf8");
16094
15923
  const manifestPath = join15(dir, PROVIDER_CONTEXT_MANIFEST);
16095
15924
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
16096
15925
  try {
16097
- if (existsSync13(manifestPath)) {
16098
- const parsed = JSON.parse(readFileSync12(manifestPath, "utf8"));
15926
+ if (existsSync12(manifestPath)) {
15927
+ const parsed = JSON.parse(readFileSync11(manifestPath, "utf8"));
16099
15928
  if (parsed && typeof parsed === "object")
16100
15929
  manifest = parsed;
16101
15930
  }
@@ -16110,7 +15939,7 @@ function resolveAndRenderProviderContext(opts) {
16110
15939
  rawModel: opts.rawModel || null
16111
15940
  };
16112
15941
  manifest.fragments = fragmentsObj;
16113
- writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
15942
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
16114
15943
  return {
16115
15944
  entry,
16116
15945
  rawEndpoint: opts.rawEndpoint,
@@ -16210,10 +16039,10 @@ init_session_render();
16210
16039
 
16211
16040
  // src/lib/station-profile.ts
16212
16041
  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";
16042
+ import { spawnSync as spawnSync3 } from "child_process";
16043
+ import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
16215
16044
  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";
16045
+ import { dirname as dirname8, join as join16 } from "path";
16217
16046
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
16218
16047
  var STATION_PROFILE_SOURCE_ID = "station-profile";
16219
16048
  var STATION_PROFILE_LAYER = "machine";
@@ -16237,9 +16066,9 @@ function getBunGlobalModulesDir(env = process.env) {
16237
16066
  }
16238
16067
  function readMachinesManifest(path) {
16239
16068
  try {
16240
- if (!existsSync14(path))
16069
+ if (!existsSync13(path))
16241
16070
  return null;
16242
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
16071
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
16243
16072
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
16244
16073
  return null;
16245
16074
  const machines = parsed["machines"];
@@ -16269,7 +16098,7 @@ function metadataUser(record) {
16269
16098
  }
16270
16099
  function probeMachineStatus(machineId) {
16271
16100
  try {
16272
- const result = spawnSync4("machines", ["details", "--json", "--machine", machineId], {
16101
+ const result = spawnSync3("machines", ["details", "--json", "--machine", machineId], {
16273
16102
  encoding: "utf8",
16274
16103
  timeout: 3000,
16275
16104
  stdio: ["ignore", "pipe", "pipe"]
@@ -16295,7 +16124,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
16295
16124
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
16296
16125
  const home = homeDir6(env);
16297
16126
  const platform = stringField(record, "platform") ?? osPlatform();
16298
- const workspacePath = stringField(record, "workspacePath") ?? join16(home, platform === "darwin" ? "Workspace" : "workspace");
16127
+ const workspacePath = stringField(record, "workspacePath");
16299
16128
  const machine = {
16300
16129
  id: stringField(record, "id") ?? hostname2,
16301
16130
  hostname: stringField(record, "hostname") ?? hostname2,
@@ -16314,7 +16143,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
16314
16143
  function scopedPackageNames(modulesDir, scope) {
16315
16144
  const scopeDir = join16(modulesDir, scope);
16316
16145
  try {
16317
- if (!existsSync14(scopeDir))
16146
+ if (!existsSync13(scopeDir))
16318
16147
  return null;
16319
16148
  return readdirNames(scopeDir).sort();
16320
16149
  } catch {
@@ -16334,7 +16163,7 @@ function resolveStationProfilePackages(env = process.env) {
16334
16163
  const modulesDir = getBunGlobalModulesDir(env);
16335
16164
  let scopeDirs;
16336
16165
  try {
16337
- if (!existsSync14(modulesDir))
16166
+ if (!existsSync13(modulesDir))
16338
16167
  return null;
16339
16168
  scopeDirs = readdirNames(modulesDir).filter((name) => name.startsWith("@") && name.toLowerCase().includes("hasna"));
16340
16169
  } catch {
@@ -16409,10 +16238,10 @@ function refreshStationProfile(options = {}) {
16409
16238
  const path = getStationProfileCachePath(env);
16410
16239
  const generatedAt = new Date().toISOString();
16411
16240
  if (!options.dryRun) {
16412
- const existing = existsSync14(path) ? readFileSync13(path, "utf8") : null;
16241
+ const existing = existsSync13(path) ? readFileSync12(path, "utf8") : null;
16413
16242
  if (existing !== content) {
16414
- mkdirSync6(dirname9(path), { recursive: true });
16415
- writeFileSync5(path, content, "utf8");
16243
+ mkdirSync5(dirname8(path), { recursive: true });
16244
+ writeFileSync4(path, content, "utf8");
16416
16245
  }
16417
16246
  }
16418
16247
  return {
@@ -16428,9 +16257,9 @@ function refreshStationProfile(options = {}) {
16428
16257
  function readStationProfile(env = process.env) {
16429
16258
  const path = getStationProfileCachePath(env);
16430
16259
  try {
16431
- if (!existsSync14(path))
16260
+ if (!existsSync13(path))
16432
16261
  return null;
16433
- return readFileSync13(path, "utf8");
16262
+ return readFileSync12(path, "utf8");
16434
16263
  } catch {
16435
16264
  return null;
16436
16265
  }
@@ -16454,22 +16283,172 @@ function stationProfileSource(env = process.env) {
16454
16283
  init_instruction_graph();
16455
16284
  init_asset_plan();
16456
16285
 
16286
+ // src/lib/harness-discovery.ts
16287
+ import { accessSync, constants as constants2, lstatSync as lstatSync6, realpathSync as realpathSync4, statSync as statSync5 } from "fs";
16288
+ import { homedir as homedir10 } from "os";
16289
+ import { delimiter, dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve11 } from "path";
16290
+ var HARNESS_DISCOVERY_SCHEMA = "hasna.instructions.harness-discovery/v1";
16291
+ var DISCOVERABLE_HARNESSES = ["claude", "codex", "opencode", "sumi"];
16292
+ function pathInput(value, home) {
16293
+ if (!value || value !== value.trim() || /[\x00-\x1f\x7f]/.test(value))
16294
+ throw new Error("Harness paths must be nonempty and contain no surrounding whitespace or control characters.");
16295
+ if (value.split("/").some((segment) => segment === "." || segment === ".."))
16296
+ throw new Error("Harness paths must not contain dot segments; normalizing them can change a symlink destination.");
16297
+ let path = value;
16298
+ if (home) {
16299
+ for (const prefix of ["~", "{{HOME}}", "{{HOME_DIR}}", "${HOME}"]) {
16300
+ if (path === prefix)
16301
+ path = home;
16302
+ else if (path.startsWith(`${prefix}/`))
16303
+ path = join17(home, path.slice(prefix.length + 1));
16304
+ }
16305
+ }
16306
+ if (!isAbsolute7(path))
16307
+ throw new Error("Harness paths must be absolute or explicitly relative to the owner home (~/ or {{HOME_DIR}}/).");
16308
+ return resolve11(path);
16309
+ }
16310
+ function observe(path) {
16311
+ let symlink = false;
16312
+ let viaSymlink = false;
16313
+ for (let parent = dirname9(path);parent !== dirname9(parent); parent = dirname9(parent)) {
16314
+ try {
16315
+ if (lstatSync6(parent).isSymbolicLink()) {
16316
+ viaSymlink = true;
16317
+ break;
16318
+ }
16319
+ } catch {}
16320
+ }
16321
+ try {
16322
+ symlink = lstatSync6(path).isSymbolicLink();
16323
+ const stat = statSync5(path);
16324
+ return { path, state: "present", realPath: realpathSync4(path), symlink, viaSymlink: viaSymlink || symlink, type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other" };
16325
+ } catch (error) {
16326
+ const missing = error.code === "ENOENT";
16327
+ return { path, state: missing ? symlink ? "dangling-symlink" : "missing" : "unreadable", realPath: null, symlink, viaSymlink: viaSymlink || symlink, type: null };
16328
+ }
16329
+ }
16330
+ function executableAt(path) {
16331
+ const observation = observe(path);
16332
+ if (observation.type !== "file")
16333
+ return null;
16334
+ try {
16335
+ accessSync(path, constants2.X_OK);
16336
+ return observation;
16337
+ } catch {
16338
+ return null;
16339
+ }
16340
+ }
16341
+ function configSelection(tool, home, env, override) {
16342
+ if (override !== undefined)
16343
+ return { path: pathInput(override, home), source: "override" };
16344
+ const key = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", opencode: "OPENCODE_CONFIG_DIR", sumi: "SUMI_CONFIG_DIR" }[tool];
16345
+ const nativePath = (value, selector) => {
16346
+ try {
16347
+ return pathInput(value);
16348
+ } catch {
16349
+ throw new Error(`${selector} is present but is not an unambiguous absolute native path; supply the reviewed runtime path explicitly.`);
16350
+ }
16351
+ };
16352
+ if (env[key] !== undefined && !(tool === "codex" && env[key] === ""))
16353
+ return { path: nativePath(env[key], key), source: key };
16354
+ if (tool === "claude" || tool === "codex")
16355
+ return { path: join17(home, `.${tool}`), source: "native-default" };
16356
+ if (env.XDG_CONFIG_HOME !== undefined)
16357
+ return { path: join17(nativePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME"), tool), source: "XDG_CONFIG_HOME" };
16358
+ if (tool === "sumi") {
16359
+ if (env.SUMI_HOME !== undefined)
16360
+ return { path: join17(nativePath(env.SUMI_HOME, "SUMI_HOME"), "config"), source: "SUMI_HOME" };
16361
+ return null;
16362
+ }
16363
+ return { path: join17(home, ".config", tool), source: "native-default" };
16364
+ }
16365
+ function discoverHarnesses(options = {}) {
16366
+ const env = options.env ?? process.env;
16367
+ const home = pathInput(options.ownerHome ?? env.HOME ?? env.USERPROFILE ?? homedir10());
16368
+ const project = options.projectRoot === undefined ? null : observe(pathInput(options.projectRoot, home));
16369
+ const variables = { HOME_DIR: home };
16370
+ if (project)
16371
+ variables.PROJECT_ROOT = project.path;
16372
+ const tools = DISCOVERABLE_HARNESSES.map((tool) => {
16373
+ const diagnostics = [];
16374
+ const override = options.overrides?.[tool];
16375
+ let executable = null;
16376
+ let executableSource = null;
16377
+ if (override?.executable !== undefined) {
16378
+ executable = executableAt(pathInput(override.executable, home));
16379
+ executableSource = "override";
16380
+ if (!executable)
16381
+ diagnostics.push("Explicit executable is missing, unreadable, not a file, or not executable; PATH fallback is disabled.");
16382
+ } else {
16383
+ for (const directory of (env.PATH ?? "").split(delimiter)) {
16384
+ if (!isAbsolute7(directory)) {
16385
+ const warning = "Relative or empty PATH entries were ignored; this is an absolute-path candidate inventory, not proof of the launcher's selected executable.";
16386
+ if (!diagnostics.includes(warning))
16387
+ diagnostics.push(warning);
16388
+ continue;
16389
+ }
16390
+ let validated;
16391
+ try {
16392
+ validated = pathInput(directory);
16393
+ } catch {
16394
+ diagnostics.push("An ambiguous PATH directory was ignored; use an explicit executable override after reviewing its native path.");
16395
+ continue;
16396
+ }
16397
+ executable = executableAt(join17(validated, tool));
16398
+ if (executable) {
16399
+ executableSource = "PATH";
16400
+ break;
16401
+ }
16402
+ }
16403
+ }
16404
+ let selected = null;
16405
+ let configError = false;
16406
+ try {
16407
+ selected = configSelection(tool, home, env, override?.configDir);
16408
+ } catch (error) {
16409
+ if (override?.configDir !== undefined)
16410
+ throw error;
16411
+ configError = true;
16412
+ diagnostics.push(`Config root unresolved: ${error.message}`);
16413
+ }
16414
+ const config = selected ? { ...observe(selected.path), source: selected.source } : null;
16415
+ const filename = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
16416
+ const globalPrompt = config ? observe(join17(config.path, filename)) : null;
16417
+ const promptOverrides = tool === "codex" ? [...new Set([config?.path, project?.path].filter((path) => !!path))].map((path) => observe(join17(path, "AGENTS.override.md"))) : [];
16418
+ if (!config && !configError)
16419
+ diagnostics.push("Config root unresolved: supply the actual `sumi debug paths config` result as a configDir override; no runtime or migration was invoked.");
16420
+ if (executable) {
16421
+ variables[`${tool.toUpperCase()}_EXECUTABLE`] = executable.path;
16422
+ if (config)
16423
+ variables[`${tool.toUpperCase()}_CONFIG_DIR`] = config.path;
16424
+ }
16425
+ const linkedGlobal = !!globalPrompt?.viaSymlink || !!config?.viaSymlink;
16426
+ const hasOverride = promptOverrides.some((path) => path.state !== "missing" || path.viaSymlink);
16427
+ const scopeReviewRequired = linkedGlobal || hasOverride;
16428
+ if (linkedGlobal)
16429
+ diagnostics.push("Global config or prompt resolves through a symlink; preserve its lexical path and review the destination scope before planning writes.");
16430
+ if (hasOverride)
16431
+ diagnostics.push("A Codex AGENTS.override.md candidate may take precedence; review its native loader behavior and scope before applying AGENTS.md.");
16432
+ 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 };
16433
+ });
16434
+ return { schema: HARNESS_DISCOVERY_SCHEMA, ownerHome: observe(home), projectRoot: project, tools, templateVariables: variables };
16435
+ }
16457
16436
  // src/lib/session-apply.ts
16458
16437
  init_project_context();
16459
16438
  init_session_render_state();
16460
16439
  init_session_render();
16461
16440
  init_cursor_authority();
16462
16441
  init_session_authority();
16463
- import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
16442
+ import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
16464
16443
  import {
16465
- existsSync as existsSync15,
16466
- lstatSync as lstatSync6,
16467
- mkdirSync as mkdirSync7,
16468
- readFileSync as readFileSync14,
16444
+ existsSync as existsSync14,
16445
+ lstatSync as lstatSync7,
16446
+ mkdirSync as mkdirSync6,
16447
+ readFileSync as readFileSync13,
16469
16448
  readdirSync as readdirSync3,
16470
- statSync as statSync5
16449
+ statSync as statSync6
16471
16450
  } from "fs";
16472
- import { dirname as dirname10, isAbsolute as isAbsolute7, join as join17, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
16451
+ 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
16452
  class SessionApplyError extends Error {
16474
16453
  constructor(message) {
16475
16454
  super(message);
@@ -16499,13 +16478,15 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16499
16478
  const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
16500
16479
  const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
16501
16480
  const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
16502
- const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations);
16481
+ const retirements = validateFileRetirements(plan, targetHome, previousManifest, options);
16482
+ const retiredHashes = new Map(retirements.map((entry) => [entry.relativePath, entry.preimageSha256]));
16483
+ const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements);
16503
16484
  const files = [...payloadFiles, manifestFile];
16504
16485
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
16505
16486
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
16506
16487
  const results = [
16507
16488
  ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
16508
- ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
16489
+ ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
16509
16490
  ];
16510
16491
  assertNotSilentManagedWipeout(plan, results);
16511
16492
  const conflicts = results.filter((result) => result.action === "conflict");
@@ -16529,7 +16510,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16529
16510
  conflicts,
16530
16511
  drift,
16531
16512
  adoptions,
16532
- reconciliations
16513
+ reconciliations,
16514
+ retirements
16533
16515
  };
16534
16516
  }
16535
16517
  let snapshotPath = null;
@@ -16544,10 +16526,14 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16544
16526
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
16545
16527
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
16546
16528
  ensureSessionTargetHome(targetHome);
16547
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
16529
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
16548
16530
  snapshotPath = rollback.snapshotPath;
16549
16531
  options.test_hooks?.before_apply_writes?.({ plan, results });
16550
16532
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
16533
+ for (const result of results) {
16534
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
16535
+ coordination?.assert_held();
16536
+ }
16551
16537
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
16552
16538
  for (const file of payloadFiles) {
16553
16539
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -16583,7 +16569,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16583
16569
  conflicts,
16584
16570
  drift,
16585
16571
  adoptions,
16586
- reconciliations
16572
+ reconciliations,
16573
+ retirements
16587
16574
  };
16588
16575
  }
16589
16576
  function assertManifestPrecondition(path, targetHome, expected) {
@@ -16612,7 +16599,7 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
16612
16599
  throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
16613
16600
  }
16614
16601
  seen.add(request.relativePath);
16615
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
16602
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
16616
16603
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
16617
16604
  throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
16618
16605
  }
@@ -16631,15 +16618,19 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
16631
16618
  };
16632
16619
  });
16633
16620
  }
16621
+ function exactPreimageCandidates(plan) {
16622
+ 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)));
16623
+ return [...plan.files, ...(plan.assetFiles ?? []).filter((file) => customAgentPaths.has(file.relativePath))];
16624
+ }
16634
16625
  function readExactFilePreimage(path, targetHome, request, operation) {
16635
16626
  if (currentSessionFileHash(path, targetHome) === null) {
16636
16627
  throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
16637
16628
  }
16638
- const bytes = readFileSync14(path);
16629
+ const bytes = readFileSync13(path);
16639
16630
  if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
16640
16631
  throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
16641
16632
  }
16642
- const observedSha256 = createHash10("sha256").update(bytes).digest("hex");
16633
+ const observedSha256 = createHash9("sha256").update(bytes).digest("hex");
16643
16634
  if (observedSha256 !== request.sha256) {
16644
16635
  throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
16645
16636
  }
@@ -16668,7 +16659,7 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
16668
16659
  throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
16669
16660
  }
16670
16661
  seen.add(request.relativePath);
16671
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
16662
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
16672
16663
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
16673
16664
  throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
16674
16665
  }
@@ -16695,8 +16686,47 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
16695
16686
  };
16696
16687
  });
16697
16688
  }
16698
- function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations) {
16699
- if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined)
16689
+ function validateFileRetirements(plan, targetHome, previousManifest, options) {
16690
+ const requests = options.retireFiles ?? [];
16691
+ if (!Array.isArray(requests))
16692
+ throw new SessionApplyError("Session file retirements must be an array.");
16693
+ if (requests.length === 0)
16694
+ return [];
16695
+ if (options.force)
16696
+ throw new SessionApplyError("Exact file retirement cannot be combined with force.");
16697
+ if (options.expectedManifestSha256 === undefined) {
16698
+ throw new SessionApplyError("Exact file retirement requires an expected manifest SHA-256 precondition.");
16699
+ }
16700
+ if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
16701
+ throw new SessionApplyError("File retirement requires this renderer's manifest for the same tool and target home.");
16702
+ }
16703
+ const seen = new Set;
16704
+ return requests.map((request) => {
16705
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
16706
+ throw new SessionApplyError("Each file retirement requires a plan-relative path and a 64-character lowercase SHA-256.");
16707
+ }
16708
+ if (seen.has(request.relativePath))
16709
+ throw new SessionApplyError(`Duplicate file retirement target: ${request.relativePath}`);
16710
+ seen.add(request.relativePath);
16711
+ if (plan.allFiles.some((file) => file.relativePath === request.relativePath)) {
16712
+ throw new SessionApplyError(`File retirement target is retained by the new plan: ${request.relativePath}`);
16713
+ }
16714
+ const owned = previousManifest.files.filter((entry) => entry.relativePath === request.relativePath);
16715
+ const path = resolveManifestRelativePath(request.relativePath, targetHome);
16716
+ 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") {
16717
+ throw new SessionApplyError(`File retirement target is not an obsolete managed fragment, rule, or asset: ${request.relativePath}`);
16718
+ }
16719
+ return {
16720
+ path,
16721
+ relativePath: request.relativePath,
16722
+ preimageSha256: readExactFilePreimage(path, targetHome, request, "retirement"),
16723
+ previousManagedSha256: owned[0].sha256,
16724
+ sourceIds: [...owned[0].sourceIds]
16725
+ };
16726
+ });
16727
+ }
16728
+ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements) {
16729
+ if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined && retirements.length === 0 && previousManifest?.retirements === undefined)
16700
16730
  return plan.manifestFile;
16701
16731
  if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
16702
16732
  throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
@@ -16737,14 +16767,32 @@ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconcili
16737
16767
  };
16738
16768
  reconciledEntries.set(JSON.stringify(canonical), canonical);
16739
16769
  }
16770
+ if (previousManifest?.retirements !== undefined && !Array.isArray(previousManifest.retirements)) {
16771
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
16772
+ }
16773
+ const retiredEntries = new Map;
16774
+ for (const entry of [...previousManifest?.retirements ?? [], ...retirements.map(({ path: _path, ...receipt }) => receipt)]) {
16775
+ 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")) {
16776
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
16777
+ }
16778
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
16779
+ const canonical = {
16780
+ relativePath: entry.relativePath,
16781
+ preimageSha256: entry.preimageSha256,
16782
+ previousManagedSha256: entry.previousManagedSha256,
16783
+ sourceIds: [...entry.sourceIds]
16784
+ };
16785
+ retiredEntries.set(JSON.stringify(canonical), canonical);
16786
+ }
16740
16787
  const manifest = {
16741
16788
  ...plan.manifest,
16742
16789
  ...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
16743
- ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
16790
+ ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
16791
+ ...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
16744
16792
  };
16745
16793
  const content = `${JSON.stringify(manifest, null, 2)}
16746
16794
  `;
16747
- return { ...plan.manifestFile, content, sha256: sha25610(content) };
16795
+ return { ...plan.manifestFile, content, sha256: sha2569(content) };
16748
16796
  }
16749
16797
  function assertCursorAuthorityUnchanged(plan) {
16750
16798
  if (plan.tool !== "cursor" || plan.targetKind === "blocked")
@@ -16772,13 +16820,13 @@ function assertClaudeAuthorityStillClear(plan, targetHome, ownedClaudeAuthoritie
16772
16820
  throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
16773
16821
  }
16774
16822
  function ensureSessionTargetHome(targetHome) {
16775
- if (!existsSync15(targetHome))
16776
- mkdirSync7(targetHome, { recursive: true, mode: 448 });
16823
+ if (!existsSync14(targetHome))
16824
+ mkdirSync6(targetHome, { recursive: true, mode: 448 });
16777
16825
  assertSafeTargetHome(targetHome);
16778
16826
  }
16779
16827
  function checkSessionRenderDrift(targetHome, manifestPath) {
16780
16828
  const safeTargetHome = assertSafeTargetHome(targetHome);
16781
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative6(safeTargetHome, resolve11(manifestPath)), safeTargetHome) : resolve11(safeTargetHome, ".hasna", "session-render-manifest.json");
16829
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve12(manifestPath)), safeTargetHome) : resolve12(safeTargetHome, ".hasna", "session-render-manifest.json");
16782
16830
  const checkedAt = new Date().toISOString();
16783
16831
  const previousManifest = readPreviousManifest(resolvedManifestPath);
16784
16832
  if (!previousManifest) {
@@ -16795,7 +16843,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16795
16843
  const drifted = [];
16796
16844
  for (const file of previousManifest.files) {
16797
16845
  const target = resolveManifestRelativePath(file.relativePath, safeTargetHome);
16798
- if (!existsSync15(target)) {
16846
+ if (!existsSync14(target)) {
16799
16847
  missing.push({
16800
16848
  path: target,
16801
16849
  relativePath: file.relativePath,
@@ -16805,7 +16853,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16805
16853
  });
16806
16854
  continue;
16807
16855
  }
16808
- const actualSha256 = sha25610(readFileSync14(target, "utf-8"));
16856
+ const actualSha256 = sha2569(readFileSync13(target, "utf-8"));
16809
16857
  if (actualSha256 !== file.sha256) {
16810
16858
  drifted.push({
16811
16859
  path: target,
@@ -16828,13 +16876,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16828
16876
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
16829
16877
  const snapshot = readSessionRenderSnapshot(snapshotPath);
16830
16878
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
16831
- const resolvedSnapshotPath = resolve11(snapshotPath);
16879
+ const resolvedSnapshotPath = resolve12(snapshotPath);
16832
16880
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
16833
- const snapshotDirRelative = relative6(snapshotDir, resolvedSnapshotPath);
16834
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute7(snapshotDirRelative);
16881
+ const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
16882
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute8(snapshotDirRelative);
16835
16883
  if (!insideSnapshotDir) {
16836
- const snapshotRelativePath = relative6(targetHome, resolvedSnapshotPath);
16837
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute7(snapshotRelativePath)) {
16884
+ const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
16885
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute8(snapshotRelativePath)) {
16838
16886
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
16839
16887
  }
16840
16888
  }
@@ -16951,19 +16999,19 @@ function requiredRestoreHash(file) {
16951
16999
  return file.previousSha256;
16952
17000
  }
16953
17001
  function readSessionRenderSnapshot(snapshotPath) {
16954
- const resolved = resolve11(snapshotPath);
16955
- if (!existsSync15(resolved))
17002
+ const resolved = resolve12(snapshotPath);
17003
+ if (!existsSync14(resolved))
16956
17004
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
16957
- const stat = lstatSync6(resolved);
17005
+ const stat = lstatSync7(resolved);
16958
17006
  if (stat.isSymbolicLink() || !stat.isFile()) {
16959
17007
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
16960
17008
  }
16961
- if (statSync5(resolved).size > 32 * 1024 * 1024) {
17009
+ if (statSync6(resolved).size > 32 * 1024 * 1024) {
16962
17010
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
16963
17011
  }
16964
17012
  let parsed;
16965
17013
  try {
16966
- parsed = JSON.parse(readFileSync14(resolved, "utf8"));
17014
+ parsed = JSON.parse(readFileSync13(resolved, "utf8"));
16967
17015
  } catch {
16968
17016
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
16969
17017
  }
@@ -16985,7 +17033,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16985
17033
  const previousManifest = snapshot.previousManifest;
16986
17034
  const previousFiles = new Map;
16987
17035
  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) {
17036
+ if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2569(file.content) !== file.sha256) {
16989
17037
  throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
16990
17038
  }
16991
17039
  resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
@@ -17032,8 +17080,8 @@ function readSessionRenderSnapshot(snapshotPath) {
17032
17080
  }
17033
17081
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
17034
17082
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
17035
- const manifestPath = resolve11(snapshot.manifestPath);
17036
- const manifestRelativePath = relative6(targetHome, manifestPath).replaceAll("\\", "/");
17083
+ const manifestPath = resolve12(snapshot.manifestPath);
17084
+ const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
17037
17085
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
17038
17086
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
17039
17087
  if (manifestSha256 === null) {
@@ -17041,7 +17089,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17041
17089
  }
17042
17090
  let parsedManifest;
17043
17091
  try {
17044
- parsedManifest = JSON.parse(readFileSync14(manifestPath, "utf8"));
17092
+ parsedManifest = JSON.parse(readFileSync13(manifestPath, "utf8"));
17045
17093
  } catch {
17046
17094
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
17047
17095
  }
@@ -17049,7 +17097,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
17049
17097
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
17050
17098
  }
17051
17099
  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)) {
17100
+ 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
17101
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
17054
17102
  }
17055
17103
  const afterFiles = [];
@@ -17134,16 +17182,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
17134
17182
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
17135
17183
  }
17136
17184
  for (const entry of readdirSync3(dirname10(snapshotPath))) {
17137
- const candidatePath = resolve11(dirname10(snapshotPath), entry);
17138
- if (candidatePath === resolve11(snapshotPath) || !entry.endsWith(".json"))
17185
+ const candidatePath = resolve12(dirname10(snapshotPath), entry);
17186
+ if (candidatePath === resolve12(snapshotPath) || !entry.endsWith(".json"))
17139
17187
  continue;
17140
- const candidateStat = lstatSync6(candidatePath);
17188
+ const candidateStat = lstatSync7(candidatePath);
17141
17189
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
17142
17190
  continue;
17143
17191
  try {
17144
- const candidate = JSON.parse(readFileSync14(candidatePath, "utf8"));
17192
+ const candidate = JSON.parse(readFileSync13(candidatePath, "utf8"));
17145
17193
  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) {
17194
+ 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
17195
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
17148
17196
  }
17149
17197
  } catch (error) {
@@ -17201,7 +17249,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
17201
17249
  return "create";
17202
17250
  }
17203
17251
  if (file.role === "manifest" && previousManifest) {
17204
- const previousManifestSha256 = sha25610(`${JSON.stringify(previousManifest, null, 2)}
17252
+ const previousManifestSha256 = sha2569(`${JSON.stringify(previousManifest, null, 2)}
17205
17253
  `);
17206
17254
  if (previousManifestSha256 !== file.sha256) {
17207
17255
  throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
@@ -17212,15 +17260,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
17212
17260
  }
17213
17261
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
17214
17262
  const path = resolveManifestRelativePath(relativePath, targetHome);
17215
- if (resolve11(recordedPath) !== path) {
17263
+ if (resolve12(recordedPath) !== path) {
17216
17264
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
17217
17265
  }
17218
17266
  return path;
17219
17267
  }
17220
17268
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
17221
17269
  const target = resolvePlannedFilePath(plan, file, targetHome);
17222
- const previousContent = existsSync15(target) ? readFileSync14(target, "utf-8") : null;
17223
- const previousSha256 = previousContent === null ? null : sha25610(previousContent);
17270
+ const previousContent = existsSync14(target) ? readFileSync13(target, "utf-8") : null;
17271
+ const previousSha256 = previousContent === null ? null : sha2569(previousContent);
17224
17272
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
17225
17273
  const changed = previousContent !== file.content;
17226
17274
  const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
@@ -17328,17 +17376,55 @@ function isPlanManagedFile(plan, relativePath, role) {
17328
17376
  return true;
17329
17377
  return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
17330
17378
  }
17331
- function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
17379
+ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
17332
17380
  if (!previousManifest)
17333
17381
  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);
17382
+ 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
17383
  }
17336
- function planStaleFileResult(file, targetHome, options) {
17384
+ function planStaleFileResult(file, targetHome, options, retiredHash) {
17337
17385
  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);
17386
+ if (!existsSync14(target)) {
17387
+ if (file.role !== "asset")
17388
+ return null;
17389
+ return {
17390
+ path: target,
17391
+ relativePath: file.relativePath,
17392
+ role: file.role,
17393
+ action: "conflict",
17394
+ changed: true,
17395
+ previousSha256: null,
17396
+ newSha256: "",
17397
+ reason: "obsolete managed asset is missing; preserve manifest ownership until its reviewed preimage is restored and retired exactly"
17398
+ };
17399
+ }
17400
+ const previousContent = readFileSync13(target, "utf-8");
17401
+ const previousSha256 = sha2569(previousContent);
17402
+ if (retiredHash !== undefined) {
17403
+ if (previousSha256 !== retiredHash)
17404
+ throw new SessionApplyError(`File retirement preimage changed after validation: ${file.relativePath}`);
17405
+ return {
17406
+ path: target,
17407
+ relativePath: file.relativePath,
17408
+ role: file.role,
17409
+ action: "delete",
17410
+ changed: true,
17411
+ previousSha256,
17412
+ newSha256: "",
17413
+ reason: "exact reviewed obsolete managed file retired"
17414
+ };
17415
+ }
17416
+ if (file.role === "asset") {
17417
+ return {
17418
+ path: target,
17419
+ relativePath: file.relativePath,
17420
+ role: file.role,
17421
+ action: "conflict",
17422
+ changed: true,
17423
+ previousSha256,
17424
+ newSha256: "",
17425
+ reason: "obsolete managed asset requires exact retirement; preserve ownership until --retire-file and --expected-manifest-sha256 are supplied"
17426
+ };
17427
+ }
17342
17428
  if (!options.force && previousSha256 !== file.sha256) {
17343
17429
  return {
17344
17430
  path: target,
@@ -17382,31 +17468,31 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
17382
17468
  return previousHashes.get(file.relativePath) === previousSha256;
17383
17469
  }
17384
17470
  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)) {
17471
+ const target = resolve12(targetHome, ...file.relativePath.split("/"));
17472
+ const rel = relative5(targetHome, target);
17473
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
17388
17474
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
17389
17475
  }
17390
- if (resolve11(file.path) !== target) {
17476
+ if (resolve12(file.path) !== target) {
17391
17477
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
17392
17478
  }
17393
17479
  assertNoSymlinkSegments2(targetHome, target);
17394
17480
  return target;
17395
17481
  }
17396
17482
  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)) {
17483
+ const target = resolve12(targetHome, ...relativePath.split(/[\\/]+/));
17484
+ const rel = relative5(targetHome, target);
17485
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute8(rel)) {
17400
17486
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
17401
17487
  }
17402
17488
  assertNoSymlinkSegments2(targetHome, target);
17403
17489
  return target;
17404
17490
  }
17405
17491
  function readPreviousManifest(path) {
17406
- if (!existsSync15(path))
17492
+ if (!existsSync14(path))
17407
17493
  return null;
17408
17494
  try {
17409
- const parsed = JSON.parse(readFileSync14(path, "utf-8"));
17495
+ const parsed = JSON.parse(readFileSync13(path, "utf-8"));
17410
17496
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
17411
17497
  return null;
17412
17498
  if (!Array.isArray(parsed.files))
@@ -17440,18 +17526,18 @@ function applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, a
17440
17526
  function assertExpectedSessionFileHash(path, targetHome, expectedHash) {
17441
17527
  const actualHash = currentSessionFileHash(path, targetHome);
17442
17528
  if (actualHash !== expectedHash) {
17443
- throw new SessionApplyError(`Session apply path changed after planning: ${relative6(targetHome, path)}`);
17529
+ throw new SessionApplyError(`Session apply path changed after planning: ${relative5(targetHome, path)}`);
17444
17530
  }
17445
17531
  }
17446
17532
  function currentSessionFileHash(path, targetHome) {
17447
17533
  assertNoSymlinkSegments2(targetHome, path);
17448
- if (!existsSync15(path))
17534
+ if (!existsSync14(path))
17449
17535
  return null;
17450
- const stat = lstatSync6(path);
17536
+ const stat = lstatSync7(path);
17451
17537
  if (stat.isSymbolicLink() || !stat.isFile()) {
17452
17538
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
17453
17539
  }
17454
- return sha25610(readFileSync14(path, "utf-8"));
17540
+ return sha2569(readFileSync13(path, "utf-8"));
17455
17541
  }
17456
17542
  function requiredPreviousHash(result) {
17457
17543
  if (result.previousSha256 === null) {
@@ -17459,18 +17545,18 @@ function requiredPreviousHash(result) {
17459
17545
  }
17460
17546
  return result.previousSha256;
17461
17547
  }
17462
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
17548
+ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
17463
17549
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
17464
17550
  assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
17465
- const content = readFileSync14(result.path, "utf-8");
17466
- if (sha25610(content) !== result.previousSha256) {
17551
+ const content = readFileSync13(result.path, "utf-8");
17552
+ if (sha2569(content) !== result.previousSha256) {
17467
17553
  throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
17468
17554
  }
17469
17555
  return {
17470
17556
  path: result.path,
17471
17557
  relativePath: result.relativePath,
17472
17558
  role: result.role,
17473
- sha256: sha25610(content),
17559
+ sha256: sha2569(content),
17474
17560
  content
17475
17561
  };
17476
17562
  });
@@ -17483,7 +17569,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17483
17569
  };
17484
17570
  }
17485
17571
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17486
- const snapshotPath = join17(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
17572
+ const snapshotPath = join18(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID4()}.json`);
17487
17573
  const afterFiles = results.map((result) => {
17488
17574
  if (result.action === "conflict") {
17489
17575
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -17508,11 +17594,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17508
17594
  files: existingFiles,
17509
17595
  afterFiles,
17510
17596
  ...adoptions.length > 0 ? { adoptions } : {},
17511
- ...reconciliations.length > 0 ? { reconciliations } : {}
17597
+ ...reconciliations.length > 0 ? { reconciliations } : {},
17598
+ ...retirements.length > 0 ? { retirements } : {}
17512
17599
  };
17513
17600
  const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
17514
17601
  `;
17515
- if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
17602
+ if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
17516
17603
  throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
17517
17604
  }
17518
17605
  coordination?.assert_held();
@@ -17535,51 +17622,51 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17535
17622
  };
17536
17623
  }
17537
17624
  function assertSafeTargetHome(targetHome) {
17538
- if (!isAbsolute7(targetHome))
17625
+ if (!isAbsolute8(targetHome))
17539
17626
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
17540
- const normalized = resolve11(targetHome);
17541
- if (normalized === parse5(normalized).root) {
17627
+ const normalized = resolve12(targetHome);
17628
+ if (normalized === parse4(normalized).root) {
17542
17629
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17543
17630
  }
17544
- assertNoSymlinkAncestors3(normalized);
17545
- if (existsSync15(normalized) && lstatSync6(normalized).isSymbolicLink()) {
17631
+ assertNoSymlinkAncestors2(normalized);
17632
+ if (existsSync14(normalized) && lstatSync7(normalized).isSymbolicLink()) {
17546
17633
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17547
17634
  }
17548
17635
  return normalized;
17549
17636
  }
17550
17637
  function assertNoSymlinkSegments2(root, target) {
17551
- assertNoSymlinkAncestors3(root);
17552
- const rel = relative6(root, target);
17638
+ assertNoSymlinkAncestors2(root);
17639
+ const rel = relative5(root, target);
17553
17640
  let current = root;
17554
17641
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17555
- current = join17(current, segment);
17556
- if (existsSync15(current) && lstatSync6(current).isSymbolicLink()) {
17642
+ current = join18(current, segment);
17643
+ if (existsSync14(current) && lstatSync7(current).isSymbolicLink()) {
17557
17644
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17558
17645
  }
17559
17646
  }
17560
17647
  }
17561
- function assertNoSymlinkAncestors3(path) {
17562
- const normalized = resolve11(path);
17563
- const parsed = parse5(normalized);
17648
+ function assertNoSymlinkAncestors2(path) {
17649
+ const normalized = resolve12(path);
17650
+ const parsed = parse4(normalized);
17564
17651
  let current = parsed.root;
17565
- const rel = relative6(parsed.root, normalized);
17652
+ const rel = relative5(parsed.root, normalized);
17566
17653
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17567
- current = join17(current, segment);
17568
- if (!existsSync15(current))
17654
+ current = join18(current, segment);
17655
+ if (!existsSync14(current))
17569
17656
  return;
17570
- if (lstatSync6(current).isSymbolicLink()) {
17657
+ if (lstatSync7(current).isSymbolicLink()) {
17571
17658
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
17572
17659
  }
17573
17660
  }
17574
17661
  }
17575
- function sha25610(content) {
17576
- return createHash10("sha256").update(content).digest("hex");
17662
+ function sha2569(content) {
17663
+ return createHash9("sha256").update(content).digest("hex");
17577
17664
  }
17578
17665
  // src/lib/session-refresh.ts
17579
17666
  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";
17667
+ import { createHash as createHash10 } from "crypto";
17668
+ import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readFileSync as readFileSync14 } from "fs";
17669
+ import { basename as basename6, dirname as dirname11, isAbsolute as isAbsolute9, join as join19, parse as parse5, resolve as resolve13 } from "path";
17583
17670
 
17584
17671
  // src/lib/global-source-coverage.ts
17585
17672
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
@@ -17612,7 +17699,6 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
17612
17699
  init_instruction_graph();
17613
17700
  init_project_context();
17614
17701
  init_session_render();
17615
- import { basename as basename6 } from "path";
17616
17702
  var nonempty = exports_external.string().min(1).max(4096);
17617
17703
  var selectorSchema = exports_external.object({
17618
17704
  schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
@@ -17663,10 +17749,10 @@ function assertHostedProfileBindings(profileId, configs, bindings) {
17663
17749
  }
17664
17750
  }
17665
17751
  function readManagedManifest(targetHome) {
17666
- const path = join18(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
17752
+ const path = join19(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
17667
17753
  let ancestor = dirname11(path);
17668
17754
  while (true) {
17669
- const stat = lstatSync7(ancestor);
17755
+ const stat = lstatSync8(ancestor);
17670
17756
  if (stat.isSymbolicLink() || !stat.isDirectory())
17671
17757
  throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
17672
17758
  const parent = dirname11(ancestor);
@@ -17674,13 +17760,13 @@ function readManagedManifest(targetHome) {
17674
17760
  break;
17675
17761
  ancestor = parent;
17676
17762
  }
17677
- const fd = openSync4(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
17763
+ const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
17678
17764
  let raw;
17679
17765
  try {
17680
17766
  const stat = fstatSync4(fd);
17681
17767
  if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
17682
17768
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
17683
- raw = readFileSync15(fd);
17769
+ raw = readFileSync14(fd);
17684
17770
  if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
17685
17771
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
17686
17772
  } finally {
@@ -17690,15 +17776,28 @@ function readManagedManifest(targetHome) {
17690
17776
  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
17777
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
17692
17778
  }
17779
+ const recordedProjectRoot = manifest.targetOwner.projectRoot;
17780
+ if (recordedProjectRoot !== null && recordedProjectRoot !== undefined) {
17781
+ if (typeof recordedProjectRoot !== "string" || !isAbsolute9(recordedProjectRoot)) {
17782
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root must be an absolute path.");
17783
+ }
17784
+ const normalizedProjectRoot = resolve13(recordedProjectRoot);
17785
+ if (normalizedProjectRoot === parse5(normalizedProjectRoot).root || manifest.targetKind === "project-root" && normalizedProjectRoot !== targetHome || manifest.targetKind === "session-home" && manifest.tool !== "opencode") {
17786
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root does not match the managed target.");
17787
+ }
17788
+ manifest.targetOwner.projectRoot = normalizedProjectRoot;
17789
+ } else if (manifest.targetKind === "project-root") {
17790
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: project-scoped render is missing its recorded project root.");
17791
+ }
17693
17792
  manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
17694
- return { manifest, sha256: createHash11("sha256").update(raw).digest("hex") };
17793
+ return { manifest, sha256: createHash10("sha256").update(raw).digest("hex") };
17695
17794
  }
17696
17795
  async function refreshSessionRender(input) {
17697
17796
  const { store } = input;
17698
17797
  if (store.mode !== "api" || !store.v1BaseUrl)
17699
17798
  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)
17799
+ const targetHome = resolve13(input.targetHome);
17800
+ if (targetHome === parse5(targetHome).root)
17702
17801
  throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
17703
17802
  const previous = readManagedManifest(targetHome);
17704
17803
  const selector = previous.manifest.refreshSelector;
@@ -17732,7 +17831,7 @@ async function refreshSessionRender(input) {
17732
17831
  profile_id: profile.id,
17733
17832
  provider_version: selector.providerVersion,
17734
17833
  targetHome,
17735
- ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
17834
+ ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
17736
17835
  sessionId: previous.manifest.sessionId ?? undefined,
17737
17836
  refreshSelector: selector,
17738
17837
  codewithNativeImports: selector.codewithNativeImports,
@@ -17741,7 +17840,7 @@ async function refreshSessionRender(input) {
17741
17840
  bindings,
17742
17841
  asset_configs: assetConfigs,
17743
17842
  asset_bindings: assetBindings,
17744
- asset_plan_mode: input.dryRun ? "dry-run" : "apply",
17843
+ asset_plan_mode: "apply",
17745
17844
  asset_scope: selector.assetScope,
17746
17845
  asset_surface: selector.assetSurface,
17747
17846
  extra_sources: station ? [station] : undefined,
@@ -18117,8 +18216,8 @@ init_codewith_shared_todos_storage_standard();
18117
18216
 
18118
18217
  // src/lib/sync.ts
18119
18218
  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";
18219
+ import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "fs";
18220
+ import { basename as basename7, extname as extname3, join as join21 } from "path";
18122
18221
  init_config_agents();
18123
18222
  init_redact();
18124
18223
  init_machine();
@@ -18126,8 +18225,8 @@ init_transforms();
18126
18225
 
18127
18226
  // src/lib/sync-dir.ts
18128
18227
  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";
18228
+ import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
18229
+ import { join as join20, relative as relative6 } from "path";
18131
18230
  init_redact();
18132
18231
  var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
18133
18232
  function shouldSkip(p) {
@@ -18136,9 +18235,9 @@ function shouldSkip(p) {
18136
18235
  async function syncFromDir(dir, opts = {}) {
18137
18236
  const store = opts.store ?? resolveConfigStore();
18138
18237
  const absDir = expandPath(dir);
18139
- if (!existsSync16(absDir))
18238
+ if (!existsSync15(absDir))
18140
18239
  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());
18240
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync4(absDir).map((f) => join20(absDir, f)).filter((f) => statSync7(f).isFile());
18142
18241
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
18143
18242
  const allConfigs = await store.listConfigs();
18144
18243
  for (const file of files) {
@@ -18147,7 +18246,7 @@ async function syncFromDir(dir, opts = {}) {
18147
18246
  continue;
18148
18247
  }
18149
18248
  try {
18150
- const content = readFileSync16(file, "utf-8");
18249
+ const content = readFileSync15(file, "utf-8");
18151
18250
  if (content.length > 500000) {
18152
18251
  result.skipped.push(file + " (too large)");
18153
18252
  continue;
@@ -18157,7 +18256,7 @@ async function syncFromDir(dir, opts = {}) {
18157
18256
  const existing = allConfigs.find((c) => c.target_path === targetPath);
18158
18257
  if (!existing) {
18159
18258
  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 });
18259
+ await store.createConfig({ name: relative6(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content: redacted.content });
18161
18260
  result.added++;
18162
18261
  } else if (existing.content !== redacted.content) {
18163
18262
  if (!opts.dryRun)
@@ -18197,7 +18296,7 @@ async function syncToDir(dir, opts = {}) {
18197
18296
  }
18198
18297
  function walkDir(dir, files = []) {
18199
18298
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
18200
- const full = join19(dir, entry.name);
18299
+ const full = join20(dir, entry.name);
18201
18300
  if (shouldSkip(full))
18202
18301
  continue;
18203
18302
  if (entry.isDirectory())
@@ -18258,7 +18357,7 @@ function isGeneratedOutputTarget2(config, owners) {
18258
18357
  return !!ownerIds && !ownerIds.has(config.id);
18259
18358
  }
18260
18359
  function hasClaudePromptSource() {
18261
- return existsSync17(expandPath("~/.claude/CLAUDE.md"));
18360
+ return existsSync16(expandPath("~/.claude/CLAUDE.md"));
18262
18361
  }
18263
18362
  function hasClaudeRuleSourceForCursorTarget(targetPath) {
18264
18363
  const absoluteTargetPath = expandPath(targetPath);
@@ -18266,7 +18365,7 @@ function hasClaudeRuleSourceForCursorTarget(targetPath) {
18266
18365
  if (!absoluteTargetPath.startsWith(`${absolutePrefix}/`) || !absoluteTargetPath.endsWith(".mdc"))
18267
18366
  return false;
18268
18367
  const stem = basename7(absoluteTargetPath, ".mdc");
18269
- return existsSync17(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync17(expandPath(`~/.claude/rules/${stem}.mdc`));
18368
+ return existsSync16(expandPath(`~/.claude/rules/${stem}.md`)) || existsSync16(expandPath(`~/.claude/rules/${stem}.mdc`));
18270
18369
  }
18271
18370
  function isKnownGeneratedTargetPath(targetPath) {
18272
18371
  const normalizedTargetPath = normalizeTargetPath(targetPath);
@@ -18331,11 +18430,11 @@ async function syncProject(opts) {
18331
18430
  const allConfigs = await store.listConfigs();
18332
18431
  const machine = detectMachineContext();
18333
18432
  for (const pf of PROJECT_CONFIG_FILES) {
18334
- const abs = join20(absDir, pf.file);
18335
- if (!existsSync17(abs))
18433
+ const abs = join21(absDir, pf.file);
18434
+ if (!existsSync16(abs))
18336
18435
  continue;
18337
18436
  try {
18338
- const rawContent = readFileSync17(abs, "utf-8");
18437
+ const rawContent = readFileSync16(abs, "utf-8");
18339
18438
  if (rawContent.length > 500000) {
18340
18439
  result.skipped.push(pf.file);
18341
18440
  continue;
@@ -18364,20 +18463,20 @@ async function syncProject(opts) {
18364
18463
  }
18365
18464
  }
18366
18465
  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" }
18466
+ { dir: join21(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
18467
+ { dir: join21(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
18468
+ { dir: join21(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
18469
+ { dir: join21(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
18470
+ { dir: join21(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
18471
+ { dir: join21(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
18472
+ { dir: join21(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
18374
18473
  ]) {
18375
- if (!existsSync17(ruleDir.dir))
18474
+ if (!existsSync16(ruleDir.dir))
18376
18475
  continue;
18377
18476
  const mdFiles = readdirSync5(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
18378
18477
  for (const f of mdFiles) {
18379
- const abs = join20(ruleDir.dir, f);
18380
- const raw = readFileSync17(abs, "utf-8");
18478
+ const abs = join21(ruleDir.dir, f);
18479
+ const raw = readFileSync16(abs, "utf-8");
18381
18480
  const redacted = redactContent(raw, "markdown");
18382
18481
  const machineAware = templateizeMachineContent(redacted.content, machine);
18383
18482
  const content = machineAware.content;
@@ -18416,20 +18515,20 @@ async function syncKnown(opts = {}) {
18416
18515
  for (const known of targets) {
18417
18516
  if (known.rulesDir) {
18418
18517
  const absDir = expandPath(known.rulesDir);
18419
- if (!existsSync17(absDir)) {
18518
+ if (!existsSync16(absDir)) {
18420
18519
  result.skipped.push(known.rulesDir);
18421
18520
  continue;
18422
18521
  }
18423
18522
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
18424
18523
  const ruleFiles = readdirSync5(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
18425
18524
  for (const f of ruleFiles) {
18426
- const abs2 = join20(absDir, f);
18525
+ const abs2 = join21(absDir, f);
18427
18526
  const targetPath = abs2.replace(home, "~");
18428
18527
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
18429
18528
  result.skipped.push(`${targetPath} (generated output)`);
18430
18529
  continue;
18431
18530
  }
18432
- const raw = readFileSync17(abs2, "utf-8");
18531
+ const raw = readFileSync16(abs2, "utf-8");
18433
18532
  const redacted = redactContent(raw, "markdown");
18434
18533
  const machineAware = templateizeMachineContent(redacted.content, machine);
18435
18534
  const content = machineAware.content;
@@ -18457,12 +18556,12 @@ async function syncKnown(opts = {}) {
18457
18556
  continue;
18458
18557
  }
18459
18558
  const abs = expandPath(known.path);
18460
- if (!existsSync17(abs)) {
18559
+ if (!existsSync16(abs)) {
18461
18560
  result.skipped.push(known.path);
18462
18561
  continue;
18463
18562
  }
18464
18563
  try {
18465
- const rawContent = normalizeKnownConfigSource(known, readFileSync17(abs, "utf-8"));
18564
+ const rawContent = normalizeKnownConfigSource(known, readFileSync16(abs, "utf-8"));
18466
18565
  if (rawContent.length > 500000) {
18467
18566
  result.skipped.push(known.path + " (too large)");
18468
18567
  continue;
@@ -18565,9 +18664,9 @@ function storedPlaceholderIsLiteralOnDisk(storedLine, diskLine) {
18565
18664
  }
18566
18665
  function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
18567
18666
  const path = expandPath(targetPath);
18568
- if (!existsSync17(path))
18667
+ if (!existsSync16(path))
18569
18668
  return `(file not found on disk: ${path})`;
18570
- const diskContent = readFileSync17(path, "utf-8");
18669
+ const diskContent = readFileSync16(path, "utf-8");
18571
18670
  if (diskContent === expectedContent)
18572
18671
  return "(no diff \u2014 identical)";
18573
18672
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -18727,9 +18826,9 @@ function detectFormat(filePath) {
18727
18826
  // src/lib/export.ts
18728
18827
  init_types();
18729
18828
  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";
18829
+ import { createHash as createHash11 } from "crypto";
18830
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
18831
+ import { dirname as dirname12, join as join22, resolve as resolve14 } from "path";
18733
18832
  import { tmpdir } from "os";
18734
18833
  var ARCHIVE_EXCLUSIONS = [
18735
18834
  {
@@ -18751,8 +18850,8 @@ var ARCHIVE_EXCLUSIONS = [
18751
18850
  function compareText(left, right) {
18752
18851
  return left < right ? -1 : left > right ? 1 : 0;
18753
18852
  }
18754
- function sha25611(value) {
18755
- return createHash12("sha256").update(value).digest("hex");
18853
+ function sha25610(value) {
18854
+ return createHash11("sha256").update(value).digest("hex");
18756
18855
  }
18757
18856
  function canonicalize(value) {
18758
18857
  if (Array.isArray(value))
@@ -18876,19 +18975,19 @@ function computeIntegrity(domain, options) {
18876
18975
  machines: collections.machines.length
18877
18976
  };
18878
18977
  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))
18978
+ configs: sha25610(canonicalDomainJson(collections.configs)),
18979
+ config_snapshots: sha25610(canonicalDomainJson(collections.config_snapshots)),
18980
+ profiles: sha25610(canonicalDomainJson(collections.profiles)),
18981
+ profile_config_bindings: sha25610(canonicalDomainJson(collections.profile_config_bindings)),
18982
+ profile_asset_bindings: sha25610(canonicalDomainJson(collections.profile_asset_bindings)),
18983
+ machines: sha25610(canonicalDomainJson(collections.machines))
18885
18984
  };
18886
18985
  return {
18887
18986
  algorithm: "sha256",
18888
18987
  canonicalization: options.canonicalization,
18889
18988
  counts,
18890
18989
  hashes,
18891
- domain_sha256: sha25611(canonicalDomainJson({ counts, hashes }))
18990
+ domain_sha256: sha25610(canonicalDomainJson({ counts, hashes }))
18892
18991
  };
18893
18992
  }
18894
18993
  function computeDomainIntegrity(domain) {
@@ -19059,19 +19158,19 @@ async function exportConfigs(outputPath, opts = {}) {
19059
19158
  exported_at: new Date().toISOString(),
19060
19159
  payload: {
19061
19160
  path: "domain.json",
19062
- sha256: sha25611(payload),
19161
+ sha256: sha25610(payload),
19063
19162
  size_bytes: Buffer.byteLength(payload)
19064
19163
  },
19065
19164
  integrity,
19066
19165
  exclusions: ARCHIVE_EXCLUSIONS
19067
19166
  };
19068
- const absOutput = resolve13(outputPath);
19069
- mkdirSync8(dirname12(absOutput), { recursive: true });
19070
- const stagingDir = mkdtempSync(join21(tmpdir(), "instructions-domain-export-"));
19167
+ const absOutput = resolve14(outputPath);
19168
+ mkdirSync7(dirname12(absOutput), { recursive: true });
19169
+ const stagingDir = mkdtempSync(join22(tmpdir(), "instructions-domain-export-"));
19071
19170
  try {
19072
- writeFileSync6(join21(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
19171
+ writeFileSync5(join22(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
19073
19172
  `, "utf-8");
19074
- writeFileSync6(join21(stagingDir, "domain.json"), payload, "utf-8");
19173
+ writeFileSync5(join22(stagingDir, "domain.json"), payload, "utf-8");
19075
19174
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
19076
19175
  stdout: "pipe",
19077
19176
  stderr: "pipe"
@@ -19083,15 +19182,15 @@ async function exportConfigs(outputPath, opts = {}) {
19083
19182
  }
19084
19183
  return { path: absOutput, count: domain.configs.length, counts: integrity.counts, integrity };
19085
19184
  } finally {
19086
- if (existsSync18(stagingDir))
19087
- rmSync4(stagingDir, { recursive: true, force: true });
19185
+ if (existsSync17(stagingDir))
19186
+ rmSync3(stagingDir, { recursive: true, force: true });
19088
19187
  }
19089
19188
  }
19090
19189
  // src/lib/import.ts
19091
19190
  init_types();
19092
19191
  init_config_store();
19093
- import { createHash as createHash13 } from "crypto";
19094
- import { resolve as resolve14 } from "path";
19192
+ import { createHash as createHash12 } from "crypto";
19193
+ import { resolve as resolve15 } from "path";
19095
19194
  function compareText2(left, right) {
19096
19195
  return left < right ? -1 : left > right ? 1 : 0;
19097
19196
  }
@@ -19115,8 +19214,8 @@ function emptyResult() {
19115
19214
  integrity: null
19116
19215
  };
19117
19216
  }
19118
- function sha25612(value) {
19119
- return createHash13("sha256").update(value).digest("hex");
19217
+ function sha25611(value) {
19218
+ return createHash12("sha256").update(value).digest("hex");
19120
19219
  }
19121
19220
  function normalizeMemberName(name) {
19122
19221
  let normalized = name;
@@ -19186,7 +19285,7 @@ async function readV2(reader, manifest) {
19186
19285
  throw new Error("Invalid v2 archive manifest");
19187
19286
  }
19188
19287
  const payload = await reader.read("domain.json");
19189
- if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25612(payload) !== manifest.payload.sha256) {
19288
+ if (Buffer.byteLength(payload) !== manifest.payload.size_bytes || sha25611(payload) !== manifest.payload.sha256) {
19190
19289
  throw new Error("Archive integrity failure: domain payload hash or size does not match the manifest");
19191
19290
  }
19192
19291
  const domain = validateInstructionsDomainArchive(JSON.parse(payload));
@@ -19379,7 +19478,7 @@ async function importV1(reader, manifest, store, conflict) {
19379
19478
  async function importConfigs(bundlePath, opts = {}) {
19380
19479
  const store = opts.store ?? resolveConfigStore();
19381
19480
  const conflict = opts.conflict ?? "skip";
19382
- const reader = await openArchive(resolve14(bundlePath));
19481
+ const reader = await openArchive(resolve15(bundlePath));
19383
19482
  const manifestText = await reader.read("manifest.json");
19384
19483
  let manifest;
19385
19484
  try {
@@ -19407,9 +19506,9 @@ init_redact();
19407
19506
 
19408
19507
  // src/lib/package-manager-guard.ts
19409
19508
  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";
19509
+ import { existsSync as existsSync18, lstatSync as lstatSync9, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
19510
+ import { homedir as homedir11 } from "os";
19511
+ import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute10, join as join23, relative as relative7, resolve as resolve16 } from "path";
19413
19512
  var SKIP_DIRS = new Set([
19414
19513
  ".git",
19415
19514
  "node_modules",
@@ -19446,14 +19545,14 @@ var TOKEN_VALUE_PATTERNS = [
19446
19545
  { re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
19447
19546
  ];
19448
19547
  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));
19548
+ const cwd = options.cwd ? resolve16(options.cwd) : process.cwd();
19549
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve16(cwd, root));
19451
19550
  const findings = [];
19452
19551
  let scannedFiles = 0;
19453
19552
  for (const root of roots) {
19454
- if (!existsSync19(root))
19553
+ if (!existsSync18(root))
19455
19554
  continue;
19456
- const stat = lstatSync8(root);
19555
+ const stat = lstatSync9(root);
19457
19556
  if (stat.isFile()) {
19458
19557
  if (!shouldScanRepoFile(root))
19459
19558
  continue;
@@ -19468,7 +19567,7 @@ function scanPackageManagerSecrets(options = {}) {
19468
19567
  continue;
19469
19568
  const tracked = trackedFiles(root);
19470
19569
  for (const file of collectRepoFiles(root)) {
19471
- const rel = toPosix(relative8(root, file));
19570
+ const rel = toPosix(relative7(root, file));
19472
19571
  const isTracked = tracked.has(rel);
19473
19572
  const text = readTextFile(file);
19474
19573
  if (text === null)
@@ -19478,10 +19577,10 @@ function scanPackageManagerSecrets(options = {}) {
19478
19577
  }
19479
19578
  }
19480
19579
  if (options.includeHome) {
19481
- const home = homedir10();
19580
+ const home = homedir11();
19482
19581
  for (const name of HOME_FILES) {
19483
- const file = join22(home, name);
19484
- if (!existsSync19(file))
19582
+ const file = join23(home, name);
19583
+ if (!existsSync18(file))
19485
19584
  continue;
19486
19585
  const text = readTextFile(file);
19487
19586
  if (text === null)
@@ -19505,12 +19604,12 @@ function collectRepoFiles(root) {
19505
19604
  if (entry.isDirectory()) {
19506
19605
  if (SKIP_DIRS.has(entry.name))
19507
19606
  continue;
19508
- visit(join22(dir, entry.name));
19607
+ visit(join23(dir, entry.name));
19509
19608
  continue;
19510
19609
  }
19511
19610
  if (!entry.isFile())
19512
19611
  continue;
19513
- const file = join22(dir, entry.name);
19612
+ const file = join23(dir, entry.name);
19514
19613
  if (shouldScanRepoFile(file))
19515
19614
  out.push(file);
19516
19615
  }
@@ -19545,10 +19644,10 @@ function isNpmrcName(name) {
19545
19644
  }
19546
19645
  function readTextFile(file) {
19547
19646
  try {
19548
- const stat = lstatSync8(file);
19647
+ const stat = lstatSync9(file);
19549
19648
  if (!stat.isFile() || stat.size > 5000000)
19550
19649
  return null;
19551
- const buf = readFileSync18(file);
19650
+ const buf = readFileSync17(file);
19552
19651
  if (buf.includes(0))
19553
19652
  return null;
19554
19653
  return buf.toString("utf-8");
@@ -19752,7 +19851,7 @@ function isTrackedFile(file) {
19752
19851
  encoding: "utf-8",
19753
19852
  stdio: ["ignore", "pipe", "ignore"]
19754
19853
  }).trim();
19755
- const rel = toPosix(relative8(repoRoot, file));
19854
+ const rel = toPosix(relative7(repoRoot, file));
19756
19855
  execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
19757
19856
  stdio: ["ignore", "ignore", "ignore"]
19758
19857
  });
@@ -19778,13 +19877,13 @@ function stripInlineComment(value) {
19778
19877
  return value.replace(/\s[#;].*$/, "").trim();
19779
19878
  }
19780
19879
  function displayPath(file, root) {
19781
- const home = homedir10();
19880
+ const home = homedir11();
19782
19881
  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));
19882
+ return "~/" + toPosix(relative7(home, file));
19883
+ if (isAbsolute10(root) && file.startsWith(root + "/"))
19884
+ return toPosix(relative7(root, file));
19786
19885
  if (file === home || file.startsWith(home + "/"))
19787
- return "~/" + toPosix(relative8(home, file));
19886
+ return "~/" + toPosix(relative7(home, file));
19788
19887
  return file;
19789
19888
  }
19790
19889
  function toPosix(path) {
@@ -19824,6 +19923,7 @@ export {
19824
19923
  renderTemplatePreview,
19825
19924
  renderTemplate,
19826
19925
  renderProviderFragment,
19926
+ renderNativeAgentContent,
19827
19927
  renderMachineAwareContentPreview,
19828
19928
  renderMachineAwareContent,
19829
19929
  refreshStationProfile,
@@ -19872,6 +19972,7 @@ export {
19872
19972
  ensureGlobalAgentRulesStandardConfig,
19873
19973
  ensureDangerousOperationGuardStandardConfig,
19874
19974
  ensureCodewithSharedTodosStorageStandardConfig,
19975
+ discoverHarnesses,
19875
19976
  diffConfig,
19876
19977
  detectMachineContext,
19877
19978
  detectFormat,
@@ -19957,8 +20058,10 @@ export {
19957
20058
  INSTRUCTIONS_LOCAL_OPT_IN_ENV,
19958
20059
  INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
19959
20060
  INBOX_CONVERSATIONS_MINIMUM_VERSION,
20061
+ HARNESS_DISCOVERY_SCHEMA,
19960
20062
  GLOBAL_AGENT_RULES_STANDARD_SLUG,
19961
20063
  GLOBAL_AGENT_RULES_STANDARD_CONTENT,
20064
+ DISCOVERABLE_HARNESSES,
19962
20065
  DANGEROUS_OPERATION_GUARD_STANDARD_SLUG,
19963
20066
  DANGEROUS_OPERATION_GUARD_STANDARD_CONTENT,
19964
20067
  ConfigVersionConflictError,