@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/cli/index.js CHANGED
@@ -9,14 +9,14 @@ import {
9
9
  syncKnown,
10
10
  syncProject,
11
11
  syncToDisk
12
- } from "../chunks/index-18j7x3xw.js";
12
+ } from "../chunks/index-axs3yftz.js";
13
13
  import {
14
14
  applyConfigsWithReport,
15
15
  compactPathForConfigHome,
16
16
  expandPath,
17
17
  normalizeTargetPath,
18
18
  sessionRenderOwnsPath
19
- } from "../chunks/index-y8410ve0.js";
19
+ } from "../chunks/index-6sbmg1vc.js";
20
20
  import {
21
21
  CODEWITH_NATIVE_IMPORTS_ENV,
22
22
  CloudConfigStore,
@@ -69,7 +69,7 @@ import {
69
69
  truncateText,
70
70
  withProjectContextSessionGuard,
71
71
  writeProjectContextCoordinatedFile
72
- } from "../chunks/index-n6nh6f5r.js";
72
+ } from "../chunks/index-enh4yv85.js";
73
73
  import {
74
74
  ConfigNotFoundError,
75
75
  INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
@@ -3795,9 +3795,162 @@ var {
3795
3795
 
3796
3796
  // src/cli/index.tsx
3797
3797
  import chalk from "chalk";
3798
- import { existsSync as existsSync11, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync5, writeSync } from "fs";
3799
- import { homedir as homedir4 } from "os";
3800
- import { basename as basename2, dirname as dirname7, join as join11, resolve as resolve7 } from "path";
3798
+ import { existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readSync, renameSync, unlinkSync, writeFileSync as writeFileSync4, writeSync } from "fs";
3799
+ import { homedir as homedir5 } from "os";
3800
+
3801
+ // src/lib/harness-discovery.ts
3802
+ import { accessSync, constants, lstatSync, realpathSync, statSync } from "fs";
3803
+ import { homedir as homedir2 } from "os";
3804
+ import { delimiter, dirname, isAbsolute, join as join3, resolve as resolve2 } from "path";
3805
+ var HARNESS_DISCOVERY_SCHEMA = "hasna.instructions.harness-discovery/v1";
3806
+ var DISCOVERABLE_HARNESSES = ["claude", "codex", "opencode", "sumi"];
3807
+ function pathInput(value, home) {
3808
+ if (!value || value !== value.trim() || /[\x00-\x1f\x7f]/.test(value))
3809
+ throw new Error("Harness paths must be nonempty and contain no surrounding whitespace or control characters.");
3810
+ if (value.split("/").some((segment) => segment === "." || segment === ".."))
3811
+ throw new Error("Harness paths must not contain dot segments; normalizing them can change a symlink destination.");
3812
+ let path = value;
3813
+ if (home) {
3814
+ for (const prefix of ["~", "{{HOME}}", "{{HOME_DIR}}", "${HOME}"]) {
3815
+ if (path === prefix)
3816
+ path = home;
3817
+ else if (path.startsWith(`${prefix}/`))
3818
+ path = join3(home, path.slice(prefix.length + 1));
3819
+ }
3820
+ }
3821
+ if (!isAbsolute(path))
3822
+ throw new Error("Harness paths must be absolute or explicitly relative to the owner home (~/ or {{HOME_DIR}}/).");
3823
+ return resolve2(path);
3824
+ }
3825
+ function observe(path) {
3826
+ let symlink = false;
3827
+ let viaSymlink = false;
3828
+ for (let parent = dirname(path);parent !== dirname(parent); parent = dirname(parent)) {
3829
+ try {
3830
+ if (lstatSync(parent).isSymbolicLink()) {
3831
+ viaSymlink = true;
3832
+ break;
3833
+ }
3834
+ } catch {}
3835
+ }
3836
+ try {
3837
+ symlink = lstatSync(path).isSymbolicLink();
3838
+ const stat = statSync(path);
3839
+ return { path, state: "present", realPath: realpathSync(path), symlink, viaSymlink: viaSymlink || symlink, type: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other" };
3840
+ } catch (error) {
3841
+ const missing = error.code === "ENOENT";
3842
+ return { path, state: missing ? symlink ? "dangling-symlink" : "missing" : "unreadable", realPath: null, symlink, viaSymlink: viaSymlink || symlink, type: null };
3843
+ }
3844
+ }
3845
+ function executableAt(path) {
3846
+ const observation = observe(path);
3847
+ if (observation.type !== "file")
3848
+ return null;
3849
+ try {
3850
+ accessSync(path, constants.X_OK);
3851
+ return observation;
3852
+ } catch {
3853
+ return null;
3854
+ }
3855
+ }
3856
+ function configSelection(tool, home, env, override) {
3857
+ if (override !== undefined)
3858
+ return { path: pathInput(override, home), source: "override" };
3859
+ const key = { claude: "CLAUDE_CONFIG_DIR", codex: "CODEX_HOME", opencode: "OPENCODE_CONFIG_DIR", sumi: "SUMI_CONFIG_DIR" }[tool];
3860
+ const nativePath = (value, selector) => {
3861
+ try {
3862
+ return pathInput(value);
3863
+ } catch {
3864
+ throw new Error(`${selector} is present but is not an unambiguous absolute native path; supply the reviewed runtime path explicitly.`);
3865
+ }
3866
+ };
3867
+ if (env[key] !== undefined && !(tool === "codex" && env[key] === ""))
3868
+ return { path: nativePath(env[key], key), source: key };
3869
+ if (tool === "claude" || tool === "codex")
3870
+ return { path: join3(home, `.${tool}`), source: "native-default" };
3871
+ if (env.XDG_CONFIG_HOME !== undefined)
3872
+ return { path: join3(nativePath(env.XDG_CONFIG_HOME, "XDG_CONFIG_HOME"), tool), source: "XDG_CONFIG_HOME" };
3873
+ if (tool === "sumi") {
3874
+ if (env.SUMI_HOME !== undefined)
3875
+ return { path: join3(nativePath(env.SUMI_HOME, "SUMI_HOME"), "config"), source: "SUMI_HOME" };
3876
+ return null;
3877
+ }
3878
+ return { path: join3(home, ".config", tool), source: "native-default" };
3879
+ }
3880
+ function discoverHarnesses(options = {}) {
3881
+ const env = options.env ?? process.env;
3882
+ const home = pathInput(options.ownerHome ?? env.HOME ?? env.USERPROFILE ?? homedir2());
3883
+ const project = options.projectRoot === undefined ? null : observe(pathInput(options.projectRoot, home));
3884
+ const variables = { HOME_DIR: home };
3885
+ if (project)
3886
+ variables.PROJECT_ROOT = project.path;
3887
+ const tools = DISCOVERABLE_HARNESSES.map((tool) => {
3888
+ const diagnostics = [];
3889
+ const override = options.overrides?.[tool];
3890
+ let executable = null;
3891
+ let executableSource = null;
3892
+ if (override?.executable !== undefined) {
3893
+ executable = executableAt(pathInput(override.executable, home));
3894
+ executableSource = "override";
3895
+ if (!executable)
3896
+ diagnostics.push("Explicit executable is missing, unreadable, not a file, or not executable; PATH fallback is disabled.");
3897
+ } else {
3898
+ for (const directory of (env.PATH ?? "").split(delimiter)) {
3899
+ if (!isAbsolute(directory)) {
3900
+ const warning = "Relative or empty PATH entries were ignored; this is an absolute-path candidate inventory, not proof of the launcher's selected executable.";
3901
+ if (!diagnostics.includes(warning))
3902
+ diagnostics.push(warning);
3903
+ continue;
3904
+ }
3905
+ let validated;
3906
+ try {
3907
+ validated = pathInput(directory);
3908
+ } catch {
3909
+ diagnostics.push("An ambiguous PATH directory was ignored; use an explicit executable override after reviewing its native path.");
3910
+ continue;
3911
+ }
3912
+ executable = executableAt(join3(validated, tool));
3913
+ if (executable) {
3914
+ executableSource = "PATH";
3915
+ break;
3916
+ }
3917
+ }
3918
+ }
3919
+ let selected = null;
3920
+ let configError = false;
3921
+ try {
3922
+ selected = configSelection(tool, home, env, override?.configDir);
3923
+ } catch (error) {
3924
+ if (override?.configDir !== undefined)
3925
+ throw error;
3926
+ configError = true;
3927
+ diagnostics.push(`Config root unresolved: ${error.message}`);
3928
+ }
3929
+ const config = selected ? { ...observe(selected.path), source: selected.source } : null;
3930
+ const filename = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
3931
+ const globalPrompt = config ? observe(join3(config.path, filename)) : null;
3932
+ const promptOverrides = tool === "codex" ? [...new Set([config?.path, project?.path].filter((path) => !!path))].map((path) => observe(join3(path, "AGENTS.override.md"))) : [];
3933
+ if (!config && !configError)
3934
+ diagnostics.push("Config root unresolved: supply the actual `sumi debug paths config` result as a configDir override; no runtime or migration was invoked.");
3935
+ if (executable) {
3936
+ variables[`${tool.toUpperCase()}_EXECUTABLE`] = executable.path;
3937
+ if (config)
3938
+ variables[`${tool.toUpperCase()}_CONFIG_DIR`] = config.path;
3939
+ }
3940
+ const linkedGlobal = !!globalPrompt?.viaSymlink || !!config?.viaSymlink;
3941
+ const hasOverride = promptOverrides.some((path) => path.state !== "missing" || path.viaSymlink);
3942
+ const scopeReviewRequired = linkedGlobal || hasOverride;
3943
+ if (linkedGlobal)
3944
+ diagnostics.push("Global config or prompt resolves through a symlink; preserve its lexical path and review the destination scope before planning writes.");
3945
+ if (hasOverride)
3946
+ diagnostics.push("A Codex AGENTS.override.md candidate may take precedence; review its native loader behavior and scope before applying AGENTS.md.");
3947
+ return { tool, status: executable ? "executable-found" : "absent", executable, executableSource, versionEvidence: "not-probed", config, globalPrompt, projectPrompt: project ? observe(join3(project.path, filename)) : null, promptOverrides, scopeReviewRequired, diagnostics };
3948
+ });
3949
+ return { schema: HARNESS_DISCOVERY_SCHEMA, ownerHome: observe(home), projectRoot: project, tools, templateVariables: variables };
3950
+ }
3951
+
3952
+ // src/cli/index.tsx
3953
+ import { basename as basename2, dirname as dirname7, join as join12, resolve as resolve8 } from "path";
3801
3954
 
3802
3955
  // src/lib/config-target-identity.ts
3803
3956
  function findConfigsByTargetPath(configs, targetPath) {
@@ -3838,7 +3991,7 @@ function findDuplicateTargetPathGroups(configs) {
3838
3991
  // src/lib/export.ts
3839
3992
  import { createHash } from "crypto";
3840
3993
  import { existsSync as existsSync3, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
3841
- import { dirname, join as join3, resolve as resolve2 } from "path";
3994
+ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
3842
3995
  import { tmpdir } from "os";
3843
3996
  var ARCHIVE_EXCLUSIONS = [
3844
3997
  {
@@ -4174,13 +4327,13 @@ async function exportConfigs(outputPath, opts = {}) {
4174
4327
  integrity,
4175
4328
  exclusions: ARCHIVE_EXCLUSIONS
4176
4329
  };
4177
- const absOutput = resolve2(outputPath);
4178
- mkdirSync(dirname(absOutput), { recursive: true });
4179
- const stagingDir = mkdtempSync(join3(tmpdir(), "instructions-domain-export-"));
4330
+ const absOutput = resolve3(outputPath);
4331
+ mkdirSync(dirname2(absOutput), { recursive: true });
4332
+ const stagingDir = mkdtempSync(join5(tmpdir(), "instructions-domain-export-"));
4180
4333
  try {
4181
- writeFileSync(join3(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4334
+ writeFileSync(join5(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4182
4335
  `, "utf-8");
4183
- writeFileSync(join3(stagingDir, "domain.json"), payload, "utf-8");
4336
+ writeFileSync(join5(stagingDir, "domain.json"), payload, "utf-8");
4184
4337
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
4185
4338
  stdout: "pipe",
4186
4339
  stderr: "pipe"
@@ -4199,7 +4352,7 @@ async function exportConfigs(outputPath, opts = {}) {
4199
4352
 
4200
4353
  // src/lib/import.ts
4201
4354
  import { createHash as createHash2 } from "crypto";
4202
- import { resolve as resolve3 } from "path";
4355
+ import { resolve as resolve4 } from "path";
4203
4356
  function compareText2(left, right) {
4204
4357
  return left < right ? -1 : left > right ? 1 : 0;
4205
4358
  }
@@ -4487,7 +4640,7 @@ async function importV1(reader, manifest, store, conflict) {
4487
4640
  async function importConfigs(bundlePath, opts = {}) {
4488
4641
  const store = opts.store ?? resolveConfigStore();
4489
4642
  const conflict = opts.conflict ?? "skip";
4490
- const reader = await openArchive(resolve3(bundlePath));
4643
+ const reader = await openArchive(resolve4(bundlePath));
4491
4644
  const manifestText = await reader.read("manifest.json");
4492
4645
  let manifest;
4493
4646
  try {
@@ -4513,13 +4666,13 @@ async function importConfigs(bundlePath, opts = {}) {
4513
4666
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
4514
4667
  import {
4515
4668
  existsSync as existsSync5,
4516
- lstatSync,
4669
+ lstatSync as lstatSync2,
4517
4670
  mkdirSync as mkdirSync2,
4518
4671
  readFileSync,
4519
4672
  readdirSync,
4520
- statSync
4673
+ statSync as statSync2
4521
4674
  } from "fs";
4522
- import { dirname as dirname2, isAbsolute, join as join5, parse, relative, resolve as resolve4 } from "path";
4675
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, parse, posix, relative, resolve as resolve5 } from "path";
4523
4676
  class SessionApplyError extends Error {
4524
4677
  constructor(message) {
4525
4678
  super(message);
@@ -4549,13 +4702,15 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4549
4702
  const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
4550
4703
  const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
4551
4704
  const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
4552
- const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations);
4705
+ const retirements = validateFileRetirements(plan, targetHome, previousManifest, options);
4706
+ const retiredHashes = new Map(retirements.map((entry) => [entry.relativePath, entry.preimageSha256]));
4707
+ const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements);
4553
4708
  const files = [...payloadFiles, manifestFile];
4554
4709
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
4555
4710
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
4556
4711
  const results = [
4557
4712
  ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
4558
- ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
4713
+ ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
4559
4714
  ];
4560
4715
  assertNotSilentManagedWipeout(plan, results);
4561
4716
  const conflicts = results.filter((result) => result.action === "conflict");
@@ -4579,7 +4734,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4579
4734
  conflicts,
4580
4735
  drift,
4581
4736
  adoptions,
4582
- reconciliations
4737
+ reconciliations,
4738
+ retirements
4583
4739
  };
4584
4740
  }
4585
4741
  let snapshotPath = null;
@@ -4594,10 +4750,14 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4594
4750
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
4595
4751
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4596
4752
  ensureSessionTargetHome(targetHome);
4597
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
4753
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
4598
4754
  snapshotPath = rollback.snapshotPath;
4599
4755
  options.test_hooks?.before_apply_writes?.({ plan, results });
4600
4756
  assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4757
+ for (const result of results) {
4758
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
4759
+ coordination?.assert_held();
4760
+ }
4601
4761
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
4602
4762
  for (const file of payloadFiles) {
4603
4763
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -4633,7 +4793,8 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4633
4793
  conflicts,
4634
4794
  drift,
4635
4795
  adoptions,
4636
- reconciliations
4796
+ reconciliations,
4797
+ retirements
4637
4798
  };
4638
4799
  }
4639
4800
  function assertManifestPrecondition(path, targetHome, expected) {
@@ -4662,7 +4823,7 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
4662
4823
  throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
4663
4824
  }
4664
4825
  seen.add(request.relativePath);
4665
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
4826
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
4666
4827
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
4667
4828
  throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
4668
4829
  }
@@ -4681,6 +4842,10 @@ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
4681
4842
  };
4682
4843
  });
4683
4844
  }
4845
+ function exactPreimageCandidates(plan) {
4846
+ 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) => posix.normalize(asset.destination.relativePath)));
4847
+ return [...plan.files, ...(plan.assetFiles ?? []).filter((file) => customAgentPaths.has(file.relativePath))];
4848
+ }
4684
4849
  function readExactFilePreimage(path, targetHome, request, operation) {
4685
4850
  if (currentSessionFileHash(path, targetHome) === null) {
4686
4851
  throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
@@ -4718,7 +4883,7 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
4718
4883
  throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
4719
4884
  }
4720
4885
  seen.add(request.relativePath);
4721
- const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
4886
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
4722
4887
  if (candidates.length !== 1 || candidates[0].role === "manifest") {
4723
4888
  throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
4724
4889
  }
@@ -4745,8 +4910,47 @@ function validateFileReconciliations(plan, targetHome, previousManifest, options
4745
4910
  };
4746
4911
  });
4747
4912
  }
4748
- function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations) {
4749
- if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined)
4913
+ function validateFileRetirements(plan, targetHome, previousManifest, options) {
4914
+ const requests = options.retireFiles ?? [];
4915
+ if (!Array.isArray(requests))
4916
+ throw new SessionApplyError("Session file retirements must be an array.");
4917
+ if (requests.length === 0)
4918
+ return [];
4919
+ if (options.force)
4920
+ throw new SessionApplyError("Exact file retirement cannot be combined with force.");
4921
+ if (options.expectedManifestSha256 === undefined) {
4922
+ throw new SessionApplyError("Exact file retirement requires an expected manifest SHA-256 precondition.");
4923
+ }
4924
+ if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
4925
+ throw new SessionApplyError("File retirement requires this renderer's manifest for the same tool and target home.");
4926
+ }
4927
+ const seen = new Set;
4928
+ return requests.map((request) => {
4929
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
4930
+ throw new SessionApplyError("Each file retirement requires a plan-relative path and a 64-character lowercase SHA-256.");
4931
+ }
4932
+ if (seen.has(request.relativePath))
4933
+ throw new SessionApplyError(`Duplicate file retirement target: ${request.relativePath}`);
4934
+ seen.add(request.relativePath);
4935
+ if (plan.allFiles.some((file) => file.relativePath === request.relativePath)) {
4936
+ throw new SessionApplyError(`File retirement target is retained by the new plan: ${request.relativePath}`);
4937
+ }
4938
+ const owned = previousManifest.files.filter((entry) => entry.relativePath === request.relativePath);
4939
+ const path = resolveManifestRelativePath(request.relativePath, targetHome);
4940
+ 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") {
4941
+ throw new SessionApplyError(`File retirement target is not an obsolete managed fragment, rule, or asset: ${request.relativePath}`);
4942
+ }
4943
+ return {
4944
+ path,
4945
+ relativePath: request.relativePath,
4946
+ preimageSha256: readExactFilePreimage(path, targetHome, request, "retirement"),
4947
+ previousManagedSha256: owned[0].sha256,
4948
+ sourceIds: [...owned[0].sourceIds]
4949
+ };
4950
+ });
4951
+ }
4952
+ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations, retirements) {
4953
+ if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined && retirements.length === 0 && previousManifest?.retirements === undefined)
4750
4954
  return plan.manifestFile;
4751
4955
  if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
4752
4956
  throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
@@ -4787,10 +4991,28 @@ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconcili
4787
4991
  };
4788
4992
  reconciledEntries.set(JSON.stringify(canonical), canonical);
4789
4993
  }
4994
+ if (previousManifest?.retirements !== undefined && !Array.isArray(previousManifest.retirements)) {
4995
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
4996
+ }
4997
+ const retiredEntries = new Map;
4998
+ for (const entry of [...previousManifest?.retirements ?? [], ...retirements.map(({ path: _path, ...receipt }) => receipt)]) {
4999
+ 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")) {
5000
+ throw new SessionApplyError("Previous session manifest retirement provenance is invalid.");
5001
+ }
5002
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
5003
+ const canonical = {
5004
+ relativePath: entry.relativePath,
5005
+ preimageSha256: entry.preimageSha256,
5006
+ previousManagedSha256: entry.previousManagedSha256,
5007
+ sourceIds: [...entry.sourceIds]
5008
+ };
5009
+ retiredEntries.set(JSON.stringify(canonical), canonical);
5010
+ }
4790
5011
  const manifest = {
4791
5012
  ...plan.manifest,
4792
5013
  ...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
4793
- ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
5014
+ ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
5015
+ ...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
4794
5016
  };
4795
5017
  const content = `${JSON.stringify(manifest, null, 2)}
4796
5018
  `;
@@ -4828,7 +5050,7 @@ function ensureSessionTargetHome(targetHome) {
4828
5050
  }
4829
5051
  function checkSessionRenderDrift(targetHome, manifestPath) {
4830
5052
  const safeTargetHome = assertSafeTargetHome(targetHome);
4831
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative(safeTargetHome, resolve4(manifestPath)), safeTargetHome) : resolve4(safeTargetHome, ".hasna", "session-render-manifest.json");
5053
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative(safeTargetHome, resolve5(manifestPath)), safeTargetHome) : resolve5(safeTargetHome, ".hasna", "session-render-manifest.json");
4832
5054
  const checkedAt = new Date().toISOString();
4833
5055
  const previousManifest = readPreviousManifest(resolvedManifestPath);
4834
5056
  if (!previousManifest) {
@@ -4878,13 +5100,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
4878
5100
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
4879
5101
  const snapshot = readSessionRenderSnapshot(snapshotPath);
4880
5102
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
4881
- const resolvedSnapshotPath = resolve4(snapshotPath);
5103
+ const resolvedSnapshotPath = resolve5(snapshotPath);
4882
5104
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
4883
5105
  const snapshotDirRelative = relative(snapshotDir, resolvedSnapshotPath);
4884
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute(snapshotDirRelative);
5106
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute2(snapshotDirRelative);
4885
5107
  if (!insideSnapshotDir) {
4886
5108
  const snapshotRelativePath = relative(targetHome, resolvedSnapshotPath);
4887
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute(snapshotRelativePath)) {
5109
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute2(snapshotRelativePath)) {
4888
5110
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
4889
5111
  }
4890
5112
  }
@@ -5001,14 +5223,14 @@ function requiredRestoreHash(file) {
5001
5223
  return file.previousSha256;
5002
5224
  }
5003
5225
  function readSessionRenderSnapshot(snapshotPath) {
5004
- const resolved = resolve4(snapshotPath);
5226
+ const resolved = resolve5(snapshotPath);
5005
5227
  if (!existsSync5(resolved))
5006
5228
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
5007
- const stat = lstatSync(resolved);
5229
+ const stat = lstatSync2(resolved);
5008
5230
  if (stat.isSymbolicLink() || !stat.isFile()) {
5009
5231
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
5010
5232
  }
5011
- if (statSync(resolved).size > 32 * 1024 * 1024) {
5233
+ if (statSync2(resolved).size > 32 * 1024 * 1024) {
5012
5234
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
5013
5235
  }
5014
5236
  let parsed;
@@ -5082,7 +5304,7 @@ function readSessionRenderSnapshot(snapshotPath) {
5082
5304
  }
5083
5305
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
5084
5306
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
5085
- const manifestPath = resolve4(snapshot.manifestPath);
5307
+ const manifestPath = resolve5(snapshot.manifestPath);
5086
5308
  const manifestRelativePath = relative(targetHome, manifestPath).replaceAll("\\", "/");
5087
5309
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
5088
5310
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
@@ -5099,7 +5321,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
5099
5321
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
5100
5322
  }
5101
5323
  const appliedManifest = parsedManifest;
5102
- if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve4(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
5324
+ if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve5(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
5103
5325
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
5104
5326
  }
5105
5327
  const afterFiles = [];
@@ -5183,17 +5405,17 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
5183
5405
  if (!Number.isFinite(createdAtMs)) {
5184
5406
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
5185
5407
  }
5186
- for (const entry of readdirSync(dirname2(snapshotPath))) {
5187
- const candidatePath = resolve4(dirname2(snapshotPath), entry);
5188
- if (candidatePath === resolve4(snapshotPath) || !entry.endsWith(".json"))
5408
+ for (const entry of readdirSync(dirname3(snapshotPath))) {
5409
+ const candidatePath = resolve5(dirname3(snapshotPath), entry);
5410
+ if (candidatePath === resolve5(snapshotPath) || !entry.endsWith(".json"))
5189
5411
  continue;
5190
- const candidateStat = lstatSync(candidatePath);
5412
+ const candidateStat = lstatSync2(candidatePath);
5191
5413
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
5192
5414
  continue;
5193
5415
  try {
5194
5416
  const candidate = JSON.parse(readFileSync(candidatePath, "utf8"));
5195
5417
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
5196
- if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve4(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
5418
+ if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve5(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
5197
5419
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
5198
5420
  }
5199
5421
  } catch (error) {
@@ -5262,7 +5484,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
5262
5484
  }
5263
5485
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
5264
5486
  const path = resolveManifestRelativePath(relativePath, targetHome);
5265
- if (resolve4(recordedPath) !== path) {
5487
+ if (resolve5(recordedPath) !== path) {
5266
5488
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
5267
5489
  }
5268
5490
  return path;
@@ -5378,17 +5600,55 @@ function isPlanManagedFile(plan, relativePath, role) {
5378
5600
  return true;
5379
5601
  return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
5380
5602
  }
5381
- function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
5603
+ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
5382
5604
  if (!previousManifest)
5383
5605
  return [];
5384
- 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);
5606
+ 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);
5385
5607
  }
5386
- function planStaleFileResult(file, targetHome, options) {
5608
+ function planStaleFileResult(file, targetHome, options, retiredHash) {
5387
5609
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
5388
- if (!existsSync5(target))
5389
- return null;
5610
+ if (!existsSync5(target)) {
5611
+ if (file.role !== "asset")
5612
+ return null;
5613
+ return {
5614
+ path: target,
5615
+ relativePath: file.relativePath,
5616
+ role: file.role,
5617
+ action: "conflict",
5618
+ changed: true,
5619
+ previousSha256: null,
5620
+ newSha256: "",
5621
+ reason: "obsolete managed asset is missing; preserve manifest ownership until its reviewed preimage is restored and retired exactly"
5622
+ };
5623
+ }
5390
5624
  const previousContent = readFileSync(target, "utf-8");
5391
5625
  const previousSha256 = sha2563(previousContent);
5626
+ if (retiredHash !== undefined) {
5627
+ if (previousSha256 !== retiredHash)
5628
+ throw new SessionApplyError(`File retirement preimage changed after validation: ${file.relativePath}`);
5629
+ return {
5630
+ path: target,
5631
+ relativePath: file.relativePath,
5632
+ role: file.role,
5633
+ action: "delete",
5634
+ changed: true,
5635
+ previousSha256,
5636
+ newSha256: "",
5637
+ reason: "exact reviewed obsolete managed file retired"
5638
+ };
5639
+ }
5640
+ if (file.role === "asset") {
5641
+ return {
5642
+ path: target,
5643
+ relativePath: file.relativePath,
5644
+ role: file.role,
5645
+ action: "conflict",
5646
+ changed: true,
5647
+ previousSha256,
5648
+ newSha256: "",
5649
+ reason: "obsolete managed asset requires exact retirement; preserve ownership until --retire-file and --expected-manifest-sha256 are supplied"
5650
+ };
5651
+ }
5392
5652
  if (!options.force && previousSha256 !== file.sha256) {
5393
5653
  return {
5394
5654
  path: target,
@@ -5432,21 +5692,21 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
5432
5692
  return previousHashes.get(file.relativePath) === previousSha256;
5433
5693
  }
5434
5694
  function resolvePlannedFilePath(plan, file, targetHome) {
5435
- const target = resolve4(targetHome, ...file.relativePath.split("/"));
5695
+ const target = resolve5(targetHome, ...file.relativePath.split("/"));
5436
5696
  const rel = relative(targetHome, target);
5437
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
5697
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
5438
5698
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
5439
5699
  }
5440
- if (resolve4(file.path) !== target) {
5700
+ if (resolve5(file.path) !== target) {
5441
5701
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
5442
5702
  }
5443
5703
  assertNoSymlinkSegments(targetHome, target);
5444
5704
  return target;
5445
5705
  }
5446
5706
  function resolveManifestRelativePath(relativePath, targetHome) {
5447
- const target = resolve4(targetHome, ...relativePath.split(/[\\/]+/));
5707
+ const target = resolve5(targetHome, ...relativePath.split(/[\\/]+/));
5448
5708
  const rel = relative(targetHome, target);
5449
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
5709
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
5450
5710
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
5451
5711
  }
5452
5712
  assertNoSymlinkSegments(targetHome, target);
@@ -5497,7 +5757,7 @@ function currentSessionFileHash(path, targetHome) {
5497
5757
  assertNoSymlinkSegments(targetHome, path);
5498
5758
  if (!existsSync5(path))
5499
5759
  return null;
5500
- const stat = lstatSync(path);
5760
+ const stat = lstatSync2(path);
5501
5761
  if (stat.isSymbolicLink() || !stat.isFile()) {
5502
5762
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
5503
5763
  }
@@ -5509,7 +5769,7 @@ function requiredPreviousHash(result) {
5509
5769
  }
5510
5770
  return result.previousSha256;
5511
5771
  }
5512
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
5772
+ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
5513
5773
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
5514
5774
  assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
5515
5775
  const content = readFileSync(result.path, "utf-8");
@@ -5533,7 +5793,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5533
5793
  };
5534
5794
  }
5535
5795
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
5536
- const snapshotPath = join5(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID3()}.json`);
5796
+ const snapshotPath = join6(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID3()}.json`);
5537
5797
  const afterFiles = results.map((result) => {
5538
5798
  if (result.action === "conflict") {
5539
5799
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -5558,11 +5818,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5558
5818
  files: existingFiles,
5559
5819
  afterFiles,
5560
5820
  ...adoptions.length > 0 ? { adoptions } : {},
5561
- ...reconciliations.length > 0 ? { reconciliations } : {}
5821
+ ...reconciliations.length > 0 ? { reconciliations } : {},
5822
+ ...retirements.length > 0 ? { retirements } : {}
5562
5823
  };
5563
5824
  const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
5564
5825
  `;
5565
- if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
5826
+ if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
5566
5827
  throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
5567
5828
  }
5568
5829
  coordination?.assert_held();
@@ -5585,14 +5846,14 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5585
5846
  };
5586
5847
  }
5587
5848
  function assertSafeTargetHome(targetHome) {
5588
- if (!isAbsolute(targetHome))
5849
+ if (!isAbsolute2(targetHome))
5589
5850
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
5590
- const normalized = resolve4(targetHome);
5851
+ const normalized = resolve5(targetHome);
5591
5852
  if (normalized === parse(normalized).root) {
5592
5853
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
5593
5854
  }
5594
5855
  assertNoSymlinkAncestors(normalized);
5595
- if (existsSync5(normalized) && lstatSync(normalized).isSymbolicLink()) {
5856
+ if (existsSync5(normalized) && lstatSync2(normalized).isSymbolicLink()) {
5596
5857
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
5597
5858
  }
5598
5859
  return normalized;
@@ -5602,22 +5863,22 @@ function assertNoSymlinkSegments(root, target) {
5602
5863
  const rel = relative(root, target);
5603
5864
  let current = root;
5604
5865
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
5605
- current = join5(current, segment);
5606
- if (existsSync5(current) && lstatSync(current).isSymbolicLink()) {
5866
+ current = join6(current, segment);
5867
+ if (existsSync5(current) && lstatSync2(current).isSymbolicLink()) {
5607
5868
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
5608
5869
  }
5609
5870
  }
5610
5871
  }
5611
5872
  function assertNoSymlinkAncestors(path) {
5612
- const normalized = resolve4(path);
5873
+ const normalized = resolve5(path);
5613
5874
  const parsed = parse(normalized);
5614
5875
  let current = parsed.root;
5615
5876
  const rel = relative(parsed.root, normalized);
5616
5877
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
5617
- current = join5(current, segment);
5878
+ current = join6(current, segment);
5618
5879
  if (!existsSync5(current))
5619
5880
  return;
5620
- if (lstatSync(current).isSymbolicLink()) {
5881
+ if (lstatSync2(current).isSymbolicLink()) {
5621
5882
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
5622
5883
  }
5623
5884
  }
@@ -5628,8 +5889,8 @@ function sha2563(content) {
5628
5889
 
5629
5890
  // src/lib/session-refresh.ts
5630
5891
  import { createHash as createHash4 } from "crypto";
5631
- import { closeSync, constants, fstatSync, lstatSync as lstatSync3, openSync, readFileSync as readFileSync3 } from "fs";
5632
- import { dirname as dirname4, join as join7, parse as parse2, resolve as resolve5 } from "path";
5892
+ import { closeSync, constants as constants2, fstatSync, lstatSync as lstatSync4, openSync, readFileSync as readFileSync3 } from "fs";
5893
+ import { basename, dirname as dirname5, isAbsolute as isAbsolute3, join as join8, parse as parse2, resolve as resolve6 } from "path";
5633
5894
 
5634
5895
  // src/lib/global-source-coverage.ts
5635
5896
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
@@ -5667,9 +5928,9 @@ function formatGlobalSourceCoverageWarnings(result) {
5667
5928
 
5668
5929
  // src/lib/station-profile.ts
5669
5930
  import { spawnSync } from "child_process";
5670
- import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
5671
- import { arch as osArch, homedir as homedir2, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
5672
- import { dirname as dirname3, join as join6 } from "path";
5931
+ import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
5932
+ import { arch as osArch, homedir as homedir3, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
5933
+ import { dirname as dirname4, join as join7 } from "path";
5673
5934
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
5674
5935
  var STATION_PROFILE_SOURCE_ID = "station-profile";
5675
5936
  var STATION_PROFILE_LAYER = "machine";
@@ -5680,16 +5941,16 @@ var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
5680
5941
  var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
5681
5942
  var BUN_INSTALL_ENV = "BUN_INSTALL";
5682
5943
  function homeDir(env = process.env) {
5683
- return env["HOME"] || env["USERPROFILE"] || homedir2();
5944
+ return env["HOME"] || env["USERPROFILE"] || homedir3();
5684
5945
  }
5685
5946
  function getStationProfileCachePath(env = process.env) {
5686
- return join6(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
5947
+ return join7(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
5687
5948
  }
5688
5949
  function getMachinesManifestPath(env = process.env) {
5689
- return env[MACHINES_MANIFEST_PATH_ENV] || join6(homeDir(env), ".hasna", "machines", "machines.json");
5950
+ return env[MACHINES_MANIFEST_PATH_ENV] || join7(homeDir(env), ".hasna", "machines", "machines.json");
5690
5951
  }
5691
5952
  function getBunGlobalModulesDir(env = process.env) {
5692
- return join6(env[BUN_INSTALL_ENV] || join6(homeDir(env), ".bun"), "install", "global", "node_modules");
5953
+ return join7(env[BUN_INSTALL_ENV] || join7(homeDir(env), ".bun"), "install", "global", "node_modules");
5693
5954
  }
5694
5955
  function readMachinesManifest(path) {
5695
5956
  try {
@@ -5751,7 +6012,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
5751
6012
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname);
5752
6013
  const home = homeDir(env);
5753
6014
  const platform = stringField(record, "platform") ?? osPlatform();
5754
- const workspacePath = stringField(record, "workspacePath") ?? join6(home, platform === "darwin" ? "Workspace" : "workspace");
6015
+ const workspacePath = stringField(record, "workspacePath");
5755
6016
  const machine = {
5756
6017
  id: stringField(record, "id") ?? hostname,
5757
6018
  hostname: stringField(record, "hostname") ?? hostname,
@@ -5768,7 +6029,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
5768
6029
  return machine;
5769
6030
  }
5770
6031
  function scopedPackageNames(modulesDir, scope) {
5771
- const scopeDir = join6(modulesDir, scope);
6032
+ const scopeDir = join7(modulesDir, scope);
5772
6033
  try {
5773
6034
  if (!existsSync6(scopeDir))
5774
6035
  return null;
@@ -5780,7 +6041,7 @@ function scopedPackageNames(modulesDir, scope) {
5780
6041
  function readdirNames(dir) {
5781
6042
  return readdirSync2(dir).filter((name) => {
5782
6043
  try {
5783
- return lstatSync2(join6(dir, name)).isDirectory();
6044
+ return lstatSync3(join7(dir, name)).isDirectory();
5784
6045
  } catch {
5785
6046
  return false;
5786
6047
  }
@@ -5867,7 +6128,7 @@ function refreshStationProfile(options = {}) {
5867
6128
  if (!options.dryRun) {
5868
6129
  const existing = existsSync6(path) ? readFileSync2(path, "utf8") : null;
5869
6130
  if (existing !== content) {
5870
- mkdirSync3(dirname3(path), { recursive: true });
6131
+ mkdirSync3(dirname4(path), { recursive: true });
5871
6132
  writeFileSync2(path, content, "utf8");
5872
6133
  }
5873
6134
  }
@@ -5907,7 +6168,6 @@ function stationProfileSource(env = process.env) {
5907
6168
  }
5908
6169
 
5909
6170
  // src/lib/session-refresh.ts
5910
- import { basename } from "path";
5911
6171
  var nonempty = exports_external.string().min(1).max(4096);
5912
6172
  var selectorSchema = exports_external.object({
5913
6173
  schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
@@ -5958,18 +6218,18 @@ function assertHostedProfileBindings(profileId, configs, bindings) {
5958
6218
  }
5959
6219
  }
5960
6220
  function readManagedManifest(targetHome) {
5961
- const path = join7(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
5962
- let ancestor = dirname4(path);
6221
+ const path = join8(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
6222
+ let ancestor = dirname5(path);
5963
6223
  while (true) {
5964
- const stat = lstatSync3(ancestor);
6224
+ const stat = lstatSync4(ancestor);
5965
6225
  if (stat.isSymbolicLink() || !stat.isDirectory())
5966
6226
  throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
5967
- const parent = dirname4(ancestor);
6227
+ const parent = dirname5(ancestor);
5968
6228
  if (parent === ancestor)
5969
6229
  break;
5970
6230
  ancestor = parent;
5971
6231
  }
5972
- const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
6232
+ const fd = openSync(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
5973
6233
  let raw;
5974
6234
  try {
5975
6235
  const stat = fstatSync(fd);
@@ -5985,6 +6245,19 @@ function readManagedManifest(targetHome) {
5985
6245
  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) {
5986
6246
  throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
5987
6247
  }
6248
+ const recordedProjectRoot = manifest.targetOwner.projectRoot;
6249
+ if (recordedProjectRoot !== null && recordedProjectRoot !== undefined) {
6250
+ if (typeof recordedProjectRoot !== "string" || !isAbsolute3(recordedProjectRoot)) {
6251
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root must be an absolute path.");
6252
+ }
6253
+ const normalizedProjectRoot = resolve6(recordedProjectRoot);
6254
+ if (normalizedProjectRoot === parse2(normalizedProjectRoot).root || manifest.targetKind === "project-root" && normalizedProjectRoot !== targetHome || manifest.targetKind === "session-home" && manifest.tool !== "opencode") {
6255
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: recorded project root does not match the managed target.");
6256
+ }
6257
+ manifest.targetOwner.projectRoot = normalizedProjectRoot;
6258
+ } else if (manifest.targetKind === "project-root") {
6259
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: project-scoped render is missing its recorded project root.");
6260
+ }
5988
6261
  manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
5989
6262
  return { manifest, sha256: createHash4("sha256").update(raw).digest("hex") };
5990
6263
  }
@@ -5992,7 +6265,7 @@ async function refreshSessionRender(input) {
5992
6265
  const { store } = input;
5993
6266
  if (store.mode !== "api" || !store.v1BaseUrl)
5994
6267
  throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
5995
- const targetHome = resolve5(input.targetHome);
6268
+ const targetHome = resolve6(input.targetHome);
5996
6269
  if (targetHome === parse2(targetHome).root)
5997
6270
  throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
5998
6271
  const previous = readManagedManifest(targetHome);
@@ -6027,7 +6300,7 @@ async function refreshSessionRender(input) {
6027
6300
  profile_id: profile.id,
6028
6301
  provider_version: selector.providerVersion,
6029
6302
  targetHome,
6030
- ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
6303
+ ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
6031
6304
  sessionId: previous.manifest.sessionId ?? undefined,
6032
6305
  refreshSelector: selector,
6033
6306
  codewithNativeImports: selector.codewithNativeImports,
@@ -6036,7 +6309,7 @@ async function refreshSessionRender(input) {
6036
6309
  bindings,
6037
6310
  asset_configs: assetConfigs,
6038
6311
  asset_bindings: assetBindings,
6039
- asset_plan_mode: input.dryRun ? "dry-run" : "apply",
6312
+ asset_plan_mode: "apply",
6040
6313
  asset_scope: selector.assetScope,
6041
6314
  asset_surface: selector.assetSurface,
6042
6315
  extra_sources: station ? [station] : undefined,
@@ -6392,19 +6665,9 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
6392
6665
  }
6393
6666
 
6394
6667
  // src/lib/managed-skill-runtimes.ts
6395
- import { createHash as createHash5 } from "crypto";
6396
- import { spawnSync as spawnSync2 } from "child_process";
6397
- import {
6398
- existsSync as existsSync7,
6399
- lstatSync as lstatSync4,
6400
- mkdirSync as mkdirSync4,
6401
- readFileSync as readFileSync4,
6402
- renameSync,
6403
- rmSync as rmSync2,
6404
- writeFileSync as writeFileSync3
6405
- } from "fs";
6406
- import { homedir as homedir3 } from "os";
6407
- import { dirname as dirname5, join as join8, parse as parse3, relative as relative2, resolve as resolve6 } from "path";
6668
+ import { lstatSync as lstatSync5 } from "fs";
6669
+ import { homedir as homedir4 } from "os";
6670
+ import { join as join9, resolve as resolve7 } from "path";
6408
6671
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
6409
6672
  var INBOX_SKILL_MARKERS = [
6410
6673
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -6413,378 +6676,76 @@ var INBOX_SKILL_MARKERS = [
6413
6676
  [".config", "opencode", "skills", "inbox", "SKILL.md"],
6414
6677
  [".cursor", "skills", "inbox", "SKILL.md"]
6415
6678
  ];
6416
- var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
6417
- function sha2564(content) {
6418
- return createHash5("sha256").update(content).digest("hex");
6419
- }
6420
- function lstatOrNull(path) {
6421
- try {
6422
- return lstatSync4(path);
6423
- } catch {
6424
- return null;
6425
- }
6426
- }
6427
- function findSymlinkedAncestor(path) {
6428
- const normalized = resolve6(path);
6429
- const parsed = parse3(normalized);
6430
- let current = parsed.root;
6431
- const rel = relative2(parsed.root, normalized);
6432
- for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
6433
- current = join8(current, segment);
6434
- if (!existsSync7(current))
6435
- return null;
6436
- if (lstatSync4(current).isSymbolicLink())
6437
- return current;
6438
- }
6439
- return null;
6440
- }
6441
- function assertNoSymlinkAncestors2(path) {
6442
- const found = findSymlinkedAncestor(path);
6443
- if (found !== null) {
6444
- throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
6445
- }
6446
- }
6447
- function packagedInboxSkillPath(explicitPath) {
6448
- if (explicitPath)
6449
- return explicitPath;
6450
- throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
6451
- }
6452
- function readCanonicalSkill(explicitPath) {
6453
- const assetPath = packagedInboxSkillPath(explicitPath);
6454
- const stat = lstatOrNull(assetPath);
6455
- if (!stat?.isFile()) {
6456
- throw new Error("packaged inbox skill contract is not a regular file");
6457
- }
6458
- const content = readFileSync4(assetPath, "utf8");
6459
- if (!content.includes("conversations watch --from <agent> --all")) {
6460
- throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
6461
- }
6462
- if (!content.includes("There is no separate")) {
6463
- throw new Error("packaged inbox skill contract does not retire the legacy executable");
6464
- }
6465
- return { content, sha256: sha2564(content) };
6466
- }
6467
- function runProbe(command, args) {
6468
- const result = spawnSync2(command, args, {
6469
- encoding: "utf8",
6470
- timeout: 5000,
6471
- stdio: ["ignore", "pipe", "pipe"]
6472
- });
6473
- if (result.error || result.status !== 0) {
6474
- return { ok: false, output: "" };
6475
- }
6476
- return {
6477
- ok: true,
6478
- output: `${result.stdout ?? ""}
6479
- ${result.stderr ?? ""}`.trim()
6480
- };
6481
- }
6482
- function parseVersion(output) {
6483
- return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
6484
- }
6485
- function compareVersions(left, right) {
6486
- const a = left.split(".").map(Number);
6487
- const b = right.split(".").map(Number);
6488
- for (let i = 0;i < Math.max(a.length, b.length); i++) {
6489
- const delta = (a[i] ?? 0) - (b[i] ?? 0);
6490
- if (delta !== 0)
6491
- return delta;
6492
- }
6493
- return 0;
6494
- }
6495
- function inspectSkillMarkers(homeDir2) {
6496
- return INBOX_SKILL_MARKERS.map((parts) => join8(homeDir2, ...parts)).map((path) => {
6497
- const stat = lstatOrNull(path);
6498
- if (!stat)
6499
- return null;
6500
- if (!stat.isFile()) {
6501
- return { path, content: null, mode: null, regular: false };
6679
+ function needsMigrationReview(homeDir2, parts) {
6680
+ let current = resolve7(homeDir2);
6681
+ const absolute = join9(current, ...parts);
6682
+ for (const segment of parts) {
6683
+ current = join9(current, segment);
6684
+ let stat;
6685
+ try {
6686
+ stat = lstatSync5(current);
6687
+ } catch (error) {
6688
+ if (error.code === "ENOENT")
6689
+ return false;
6690
+ throw error;
6502
6691
  }
6503
- return {
6504
- path,
6505
- content: readFileSync4(path, "utf8"),
6506
- mode: stat.mode & 511,
6507
- regular: true
6508
- };
6509
- }).filter((snapshot) => snapshot !== null);
6510
- }
6511
- function inspectInbox(options) {
6512
- const homeDir2 = options.homeDir ?? homedir3();
6513
- const runtimeCommand = options.conversationsCommand ?? "conversations";
6514
- const snapshots = inspectSkillMarkers(homeDir2);
6515
- const skillPresent = snapshots.length > 0;
6516
- let canonicalContent = null;
6517
- let canonicalSha256 = null;
6518
- let assetError = null;
6519
- try {
6520
- const canonical = readCanonicalSkill(options.assetPath);
6521
- canonicalContent = canonical.content;
6522
- canonicalSha256 = canonical.sha256;
6523
- } catch (error) {
6524
- assetError = error instanceof Error ? error.message : String(error);
6525
- }
6526
- const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
6527
- const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
6528
- const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
6529
- const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
6530
- const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
6531
- const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
6532
- const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
6533
- const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
6534
- const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
6535
- const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
6536
- const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
6537
- let reason = "skill not installed";
6538
- if (skillPresent) {
6539
- const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
6540
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname5(path))).find((found) => found !== null);
6541
- if (nonRegular)
6542
- reason = "managed skill target is not a regular file";
6543
- else if (symlinkAncestor)
6544
- reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
6545
- else if (assetError)
6546
- reason = assetError;
6547
- else if (!versionProbe.ok)
6548
- reason = "conversations command unavailable";
6549
- else if (!runtimeVersion)
6550
- reason = "conversations version is unreadable";
6551
- else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
6552
- reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
6553
- } else if (!helpProbe.ok)
6554
- reason = "conversations watch help is unavailable";
6555
- else if (!supportsFrom || !supportsAll || !supportsFullContent) {
6556
- const missing = [
6557
- !supportsFrom ? "--from" : null,
6558
- !supportsAll ? "--all" : null,
6559
- !supportsFullContent ? "--full-content" : null
6560
- ].filter((flag) => flag !== null);
6561
- reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
6562
- } else if (staleMarkers.length > 0)
6563
- reason = "skill contract stale";
6564
- else if (hostedHeartbeat === "failed")
6565
- reason = "hosted heartbeat failed; manual fallback required";
6566
- else if (hostedHeartbeat === "unverified")
6567
- reason = "hosted heartbeat unverified; manual fallback required";
6568
- else if (!deliveryVerified)
6569
- reason = "hosted heartbeat passed; channel and DM delivery verification required";
6570
- else
6571
- reason = "ready";
6692
+ if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
6693
+ return true;
6572
6694
  }
6695
+ return true;
6696
+ }
6697
+ function inspectLegacyInbox(options) {
6698
+ const homeDir2 = options.homeDir ?? homedir4();
6699
+ const markers = INBOX_SKILL_MARKERS.filter((parts) => needsMigrationReview(homeDir2, parts)).map((parts) => join9(homeDir2, ...parts));
6700
+ const present = markers.length > 0;
6573
6701
  return {
6574
- status: {
6575
- skill: "inbox",
6576
- runtime: "conversations watch",
6577
- minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
6578
- skill_present: skillPresent,
6579
- skill_markers: snapshots.map((snapshot) => snapshot.path),
6580
- skill_contracts_current: snapshots.length - staleMarkers.length,
6581
- stale_skill_markers: staleMarkers,
6582
- expected_skill_sha256: canonicalSha256,
6583
- runtime_command: runtimeCommand,
6584
- runtime_present: versionProbe.ok,
6585
- runtime_version: runtimeVersion,
6586
- watch_supports_from: supportsFrom,
6587
- watch_supports_all: supportsAll,
6588
- watch_supports_full_content: supportsFullContent,
6589
- hosted_heartbeat: hostedHeartbeat,
6590
- delivery_verified: deliveryVerified,
6591
- manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
6592
- healthy: !skillPresent || reason === "ready",
6593
- reason
6594
- },
6595
- canonicalContent,
6596
- snapshots
6702
+ skill: "inbox",
6703
+ runtime: "conversations watch",
6704
+ minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
6705
+ skill_present: present,
6706
+ skill_markers: markers,
6707
+ skill_contracts_current: 0,
6708
+ stale_skill_markers: markers,
6709
+ expected_skill_sha256: null,
6710
+ runtime_command: options.conversationsCommand ?? "conversations",
6711
+ runtime_present: false,
6712
+ runtime_version: null,
6713
+ watch_supports_from: false,
6714
+ watch_supports_all: false,
6715
+ watch_supports_full_content: false,
6716
+ hosted_heartbeat: "unverified",
6717
+ delivery_verified: false,
6718
+ manual_fallback_ready: false,
6719
+ healthy: !present,
6720
+ 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"
6597
6721
  };
6598
6722
  }
6599
6723
  function inspectManagedSkillRuntimes(options = {}) {
6600
- const runtime = inspectInbox(options).status;
6601
- const installed = runtime.skill_present ? [runtime] : [];
6724
+ const runtime = inspectLegacyInbox(options);
6602
6725
  return {
6603
6726
  runtimes: [runtime],
6604
- skills_present: installed.length,
6605
- healthy: installed.filter((item) => item.healthy).length,
6606
- missing: installed.filter((item) => !item.healthy).length
6727
+ skills_present: runtime.skill_present ? 1 : 0,
6728
+ healthy: 0,
6729
+ missing: runtime.skill_present ? 1 : 0
6607
6730
  };
6608
6731
  }
6609
- function runtimeReadyForWrite(status) {
6610
- 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;
6611
- }
6612
- function projectUpdatedStatus(status, contractCount) {
6613
- const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
6614
- return {
6615
- ...status,
6616
- skill_contracts_current: contractCount,
6617
- stale_skill_markers: [],
6618
- manual_fallback_ready: true,
6619
- healthy,
6620
- 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"
6621
- };
6622
- }
6623
- function cleanup(path) {
6624
- rmSync2(path, { force: true });
6625
- }
6626
- function writeAtomic(path, content, mode) {
6627
- assertNoSymlinkAncestors2(dirname5(path));
6628
- const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
6629
- try {
6630
- mkdirSync4(dirname5(path), { recursive: true, mode: 493 });
6631
- writeFileSync3(tempPath, content, { mode, flag: "wx" });
6632
- renameSync(tempPath, path);
6633
- } finally {
6634
- cleanup(tempPath);
6635
- }
6636
- }
6637
- var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
6638
- lstat: lstatOrNull,
6639
- read: (path) => readFileSync4(path, "utf8"),
6640
- write: writeAtomic
6641
- };
6642
- function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
6643
- const written = [];
6644
- try {
6645
- for (const snapshot of snapshots) {
6646
- const currentStat = fileOperations.lstat(snapshot.path);
6647
- if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
6648
- throw new Error("managed skill changed after inspection; refusing a stale write");
6649
- }
6650
- fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
6651
- written.push(snapshot);
6652
- }
6653
- return { ok: true, error: null, rollback_conflicts: [] };
6654
- } catch (error) {
6655
- const rollbackConflicts = [];
6656
- for (const snapshot of written.reverse()) {
6657
- const currentStat = fileOperations.lstat(snapshot.path);
6658
- if (!currentStat?.isFile()) {
6659
- rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
6660
- continue;
6661
- }
6662
- let currentContent;
6663
- try {
6664
- currentContent = fileOperations.read(snapshot.path);
6665
- } catch {
6666
- rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
6667
- continue;
6668
- }
6669
- if (currentContent !== canonicalContent) {
6670
- rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
6671
- continue;
6672
- }
6673
- try {
6674
- fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
6675
- } catch {
6676
- rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
6677
- }
6678
- }
6679
- return {
6680
- ok: false,
6681
- error: error instanceof Error ? error.message : String(error),
6682
- rollback_conflicts: rollbackConflicts
6683
- };
6684
- }
6685
- }
6686
6732
  async function reconcileManagedSkillRuntimes(options = {}) {
6733
+ const runtime = inspectLegacyInbox(options);
6687
6734
  const dryRun = options.dryRun ?? false;
6688
- const before = inspectInbox(options);
6689
- const status = before.status;
6690
- if (!status.skill_present) {
6691
- return {
6692
- runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
6693
- changed: 0,
6694
- failed: 0,
6695
- dry_run: dryRun
6696
- };
6697
- }
6698
- if (before.snapshots.some((snapshot) => !snapshot.regular)) {
6699
- return {
6700
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6701
- changed: 0,
6702
- failed: 1,
6703
- dry_run: dryRun
6704
- };
6705
- }
6706
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname5(path))).find((found) => found !== null);
6707
- if (symlinkedAncestor) {
6708
- return {
6709
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6710
- changed: 0,
6711
- failed: 1,
6712
- dry_run: dryRun
6713
- };
6714
- }
6715
- if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
6716
- return {
6717
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6718
- changed: 0,
6719
- failed: 1,
6720
- dry_run: dryRun
6721
- };
6722
- }
6723
- const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
6724
- if (staleSnapshots.length === 0) {
6725
- return {
6726
- runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
6727
- changed: 0,
6728
- failed: 0,
6729
- dry_run: dryRun
6730
- };
6731
- }
6732
- if (dryRun) {
6733
- const projected = projectUpdatedStatus(status, before.snapshots.length);
6734
- return {
6735
- runtimes: [{
6736
- ...projected,
6737
- action: "update",
6738
- dry_run: true,
6739
- skill_contracts_changed: staleSnapshots.length
6740
- }],
6741
- changed: 1,
6742
- failed: 0,
6743
- dry_run: true
6744
- };
6745
- }
6746
- const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
6747
- path: snapshot.path,
6748
- content: snapshot.content,
6749
- mode: snapshot.mode ?? 420
6750
- })), before.canonicalContent);
6751
- if (!transaction.ok) {
6752
- const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
6753
- const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
6754
- return {
6755
- runtimes: [{
6756
- ...status,
6757
- action: "failed",
6758
- dry_run: false,
6759
- skill_contracts_changed: 0,
6760
- reason
6761
- }],
6762
- changed: 0,
6763
- failed: 1,
6764
- dry_run: false
6765
- };
6766
- }
6767
- const after = inspectInbox(options).status;
6768
- const accepted = after.healthy || after.manual_fallback_ready;
6769
6735
  return {
6770
- runtimes: [{
6771
- ...after,
6772
- action: accepted ? "update" : "failed",
6773
- dry_run: false,
6774
- skill_contracts_changed: accepted ? staleSnapshots.length : 0
6775
- }],
6776
- changed: accepted ? 1 : 0,
6777
- failed: accepted ? 0 : 1,
6778
- dry_run: false
6736
+ runtimes: [{ ...runtime, action: runtime.skill_present ? "failed" : "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
6737
+ changed: 0,
6738
+ failed: runtime.skill_present ? 1 : 0,
6739
+ dry_run: dryRun
6779
6740
  };
6780
6741
  }
6781
6742
 
6782
6743
  // src/status.ts
6783
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
6744
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6784
6745
 
6785
6746
  // src/lib/package-version.ts
6786
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6787
- import { dirname as dirname6, join as join9 } from "path";
6747
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
6748
+ import { dirname as dirname6, join as join10 } from "path";
6788
6749
  import { fileURLToPath } from "url";
6789
6750
  var cached = null;
6790
6751
  function getPackageVersion() {
@@ -6793,9 +6754,9 @@ function getPackageVersion() {
6793
6754
  try {
6794
6755
  let dir = dirname6(fileURLToPath(import.meta.url));
6795
6756
  for (let i = 0;i < 8; i++) {
6796
- const pkgPath = join9(dir, "package.json");
6797
- if (existsSync8(pkgPath)) {
6798
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
6757
+ const pkgPath = join10(dir, "package.json");
6758
+ if (existsSync7(pkgPath)) {
6759
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
6799
6760
  if (pkg.name === "@hasna/instructions" && pkg.version) {
6800
6761
  cached = pkg.version;
6801
6762
  return cached;
@@ -6870,11 +6831,11 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6870
6831
  continue;
6871
6832
  knownTargets += 1;
6872
6833
  const targetPath = expandPath(config.target_path);
6873
- if (!existsSync9(targetPath)) {
6834
+ if (!existsSync8(targetPath)) {
6874
6835
  missingTargets += 1;
6875
6836
  continue;
6876
6837
  }
6877
- const disk = readFileSync6(targetPath, "utf-8");
6838
+ const disk = readFileSync5(targetPath, "utf-8");
6878
6839
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
6879
6840
  if (redactedDisk !== config.content) {
6880
6841
  driftedTargets += 1;
@@ -6966,9 +6927,9 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6966
6927
  }
6967
6928
 
6968
6929
  // src/lib/provider-context.ts
6969
- import { createHash as createHash6 } from "crypto";
6970
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
6971
- import { join as join10 } from "path";
6930
+ import { createHash as createHash5 } from "crypto";
6931
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
6932
+ import { join as join11 } from "path";
6972
6933
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
6973
6934
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
6974
6935
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -7103,8 +7064,8 @@ function renderPerEndpointFragment(entry) {
7103
7064
  function renderProviderFragment(entry) {
7104
7065
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
7105
7066
  }
7106
- function sha2565(content) {
7107
- return createHash6("sha256").update(content).digest("hex");
7067
+ function sha2564(content) {
7068
+ return createHash5("sha256").update(content).digest("hex");
7108
7069
  }
7109
7070
  function resolveAndRenderProviderContext(opts) {
7110
7071
  const entry = matchProviderEndpoint(opts.origin);
@@ -7113,18 +7074,18 @@ function resolveAndRenderProviderContext(opts) {
7113
7074
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
7114
7075
  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;
7115
7076
  const content = renderProviderFragment(entry);
7116
- const dir = join10(opts.homeDir, PROVIDER_CONTEXT_DIR);
7117
- if (!existsSync10(dir))
7118
- mkdirSync5(dir, { recursive: true });
7077
+ const dir = join11(opts.homeDir, PROVIDER_CONTEXT_DIR);
7078
+ if (!existsSync9(dir))
7079
+ mkdirSync4(dir, { recursive: true });
7119
7080
  const filename = `${entry ? entry.key : "invariant"}.md`;
7120
- const fragmentPath = join10(dir, filename);
7121
- const fragmentSha256 = sha2565(content);
7122
- writeFileSync4(fragmentPath, content, "utf8");
7123
- const manifestPath = join10(dir, PROVIDER_CONTEXT_MANIFEST);
7081
+ const fragmentPath = join11(dir, filename);
7082
+ const fragmentSha256 = sha2564(content);
7083
+ writeFileSync3(fragmentPath, content, "utf8");
7084
+ const manifestPath = join11(dir, PROVIDER_CONTEXT_MANIFEST);
7124
7085
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
7125
7086
  try {
7126
- if (existsSync10(manifestPath)) {
7127
- const parsed = JSON.parse(readFileSync7(manifestPath, "utf8"));
7087
+ if (existsSync9(manifestPath)) {
7088
+ const parsed = JSON.parse(readFileSync6(manifestPath, "utf8"));
7128
7089
  if (parsed && typeof parsed === "object")
7129
7090
  manifest = parsed;
7130
7091
  }
@@ -7139,7 +7100,7 @@ function resolveAndRenderProviderContext(opts) {
7139
7100
  rawModel: opts.rawModel || null
7140
7101
  };
7141
7102
  manifest.fragments = fragmentsObj;
7142
- writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
7103
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
7143
7104
  return {
7144
7105
  entry,
7145
7106
  rawEndpoint: opts.rawEndpoint,
@@ -7270,7 +7231,7 @@ function parseSessionSource(value, order) {
7270
7231
  if (!path)
7271
7232
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
7272
7233
  const absPath = resolveSessionPath(path);
7273
- if (!existsSync11(absPath))
7234
+ if (!existsSync10(absPath))
7274
7235
  throw new Error(`Instruction source file not found: ${absPath}`);
7275
7236
  const content = readSessionInstructionSourceFile(absPath);
7276
7237
  const source = sourceFromFilePath(absPath, content, order);
@@ -7314,7 +7275,7 @@ function sessionSourceReplacements(values) {
7314
7275
  return replacements;
7315
7276
  }
7316
7277
  function readSessionInstructionSourceFile(path) {
7317
- const stat = lstatSync5(path);
7278
+ const stat = lstatSync6(path);
7318
7279
  if (stat.isSymbolicLink()) {
7319
7280
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
7320
7281
  }
@@ -7324,7 +7285,7 @@ function readSessionInstructionSourceFile(path) {
7324
7285
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
7325
7286
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
7326
7287
  }
7327
- return readFileSync8(path, "utf-8");
7288
+ return readFileSync7(path, "utf-8");
7328
7289
  }
7329
7290
  function parseLayeredReference(value) {
7330
7291
  const trimmed = value.trim();
@@ -7353,9 +7314,9 @@ async function collectSessionSources(opts, tool, store) {
7353
7314
  }
7354
7315
  for (const value of opts.identityExport ?? []) {
7355
7316
  const path = resolveSessionPath(value);
7356
- if (!existsSync11(path))
7317
+ if (!existsSync10(path))
7357
7318
  throw new Error(`Identity instruction export not found: ${path}`);
7358
- const parsed = JSON.parse(readFileSync8(path, "utf-8"));
7319
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
7359
7320
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
7360
7321
  }
7361
7322
  return sources.map((source) => {
@@ -7507,19 +7468,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
7507
7468
  if (value === "-")
7508
7469
  return { json: readBoundedProjectContextStdin() };
7509
7470
  const path = resolveSessionPath(value);
7510
- if (!existsSync11(path)) {
7471
+ if (!existsSync10(path)) {
7511
7472
  if (allowMissing)
7512
7473
  return {};
7513
7474
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
7514
7475
  }
7515
- const stat = lstatSync5(path);
7476
+ const stat = lstatSync6(path);
7516
7477
  if (stat.isSymbolicLink() || !stat.isFile()) {
7517
7478
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
7518
7479
  }
7519
7480
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
7520
7481
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
7521
7482
  }
7522
- return { json: readFileSync8(path, "utf8"), sourcePath: path };
7483
+ return { json: readFileSync7(path, "utf8"), sourcePath: path };
7523
7484
  }
7524
7485
  function readBoundedProjectContextStdin() {
7525
7486
  const chunks = [];
@@ -7904,12 +7865,12 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7904
7865
  if (expectedVersion !== undefined && !opts.update) {
7905
7866
  throw new Error("--expected-version requires --update");
7906
7867
  }
7907
- const abs = resolve7(filePath);
7908
- if (!existsSync11(abs)) {
7868
+ const abs = resolve8(filePath);
7869
+ if (!existsSync10(abs)) {
7909
7870
  console.error(chalk.red(`File not found: ${abs}`));
7910
7871
  process.exit(1);
7911
7872
  }
7912
- const rawContent = readFileSync8(abs, "utf-8");
7873
+ const rawContent = readFileSync7(abs, "utf-8");
7913
7874
  const storedFmt = detectFormat(abs);
7914
7875
  const fmt = redactFormatForTarget(abs, storedFmt);
7915
7876
  const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
@@ -8092,7 +8053,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
8092
8053
  for (const entry of entries) {
8093
8054
  if (!entry.isDirectory())
8094
8055
  continue;
8095
- const projDir = join11(absDir, entry.name);
8056
+ const projDir = join12(absDir, entry.name);
8096
8057
  const hasAgentConfig = [
8097
8058
  "CLAUDE.md",
8098
8059
  ".mcp.json",
@@ -8105,7 +8066,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
8105
8066
  ".aicopilot",
8106
8067
  ".cursor",
8107
8068
  ".agents"
8108
- ].some((marker) => existsSync11(join11(projDir, marker)));
8069
+ ].some((marker) => existsSync10(join12(projDir, marker)));
8109
8070
  if (!hasAgentConfig)
8110
8071
  continue;
8111
8072
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -8153,7 +8114,7 @@ program.command("import <file>").description("Restore a v2 Instructions domain a
8153
8114
  });
8154
8115
  program.command("whoami").description("Show setup summary").action(async () => {
8155
8116
  const store = resolveConfigStore();
8156
- const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db");
8117
+ const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db");
8157
8118
  const stats = await store.getConfigStats();
8158
8119
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
8159
8120
  console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
@@ -8376,7 +8337,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
8376
8337
  process.exit(1);
8377
8338
  }
8378
8339
  });
8379
- profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
8340
+ profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--from <agent>", "deprecated compatibility option; no heartbeat is sent").option("--delivery-verified", "deprecated compatibility option; Skills delivery is not verified here").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
8380
8341
  try {
8381
8342
  const store = resolveConfigStore();
8382
8343
  const { machine, profile } = await getMachineProfileContext(opts, store);
@@ -8413,14 +8374,6 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
8413
8374
  }
8414
8375
  if (report.failures.length > 0)
8415
8376
  process.exitCode = 1;
8416
- const runtimeReport = await reconcileManagedSkillRuntimes({
8417
- dryRun: opts.dryRun,
8418
- agent: opts.from,
8419
- deliveryVerified: opts.deliveryVerified
8420
- });
8421
- printManagedSkillRuntimeReport(runtimeReport);
8422
- if (runtimeReport.failed > 0)
8423
- process.exitCode = 1;
8424
8377
  console.log(chalk.dim(`
8425
8378
  ${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
8426
8379
  } catch (e) {
@@ -8592,7 +8545,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
8592
8545
  process.exit(1);
8593
8546
  }
8594
8547
  });
8595
- sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "native instruction home; Sumi: resolved config from `sumi debug paths config`; Grok: GROK_HOME; Devin: user config directory (global AGENTS.md unless --project-root is set)").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "verify every registered, non-retired global-* source is in this render (expected read fresh from the registry, independent of this plan); on shortfall warn, exit non-zero, and for apply refuse to write (todos 102d6d0a, O15-00694)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--adopt-file <relativepath=sha256>", "adopt one reviewed unmanaged generated target only at its exact observed SHA256; repeatable", collectOption, []).option("--reconcile-file <relativepath=sha256>", "reconcile one reviewed managed drifted target at its exact observed SHA256; requires --expected-manifest-sha256", collectOption, []).option("--expected-manifest-sha256 <sha256>", "require the previously reviewed manifest preimage to match exactly before applying").option("--json", "output apply JSON").action(async (opts) => {
8548
+ sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "native instruction home; Sumi: resolved config from `sumi debug paths config`; Grok: GROK_HOME; Devin: user config directory (global AGENTS.md unless --project-root is set)").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "verify every registered, non-retired global-* source is in this render (expected read fresh from the registry, independent of this plan); on shortfall warn, exit non-zero, and for apply refuse to write (todos 102d6d0a, O15-00694)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--adopt-file <relativepath=sha256>", "adopt one reviewed unmanaged generated target only at its exact observed SHA256; repeatable", collectOption, []).option("--reconcile-file <relativepath=sha256>", "reconcile one reviewed managed drifted target at its exact observed SHA256; requires --expected-manifest-sha256", collectOption, []).option("--retire-file <relativepath=sha256>", "retire one reviewed obsolete managed fragment, rule, or asset at its exact SHA256; requires --expected-manifest-sha256", collectOption, []).option("--expected-manifest-sha256 <sha256>", "require the previously reviewed manifest preimage to match exactly before applying").option("--json", "output apply JSON").action(async (opts) => {
8596
8549
  try {
8597
8550
  const tool = opts.tool;
8598
8551
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -8626,7 +8579,8 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8626
8579
  });
8627
8580
  const adoptFiles = parsePreimages(opts.adoptFile, "--adopt-file");
8628
8581
  const reconcileFiles = parsePreimages(opts.reconcileFile, "--reconcile-file");
8629
- const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, adoptFiles, reconcileFiles, expectedManifestSha256: opts.expectedManifestSha256, ownedClaudeAuthorities });
8582
+ const retireFiles = parsePreimages(opts.retireFile, "--retire-file");
8583
+ const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, adoptFiles, reconcileFiles, retireFiles, expectedManifestSha256: opts.expectedManifestSha256, ownedClaudeAuthorities });
8630
8584
  if (opts.json) {
8631
8585
  printJson({
8632
8586
  ...result,
@@ -8726,6 +8680,36 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
8726
8680
  process.exit(1);
8727
8681
  }
8728
8682
  });
8683
+ program.command("harness").description("Inspect native harness installations and portable path inputs").command("discover").description("Read paths without running harnesses, reading prompt bodies, or changing configuration").option("--owner-home <path>", "explicit owner home; otherwise HOME, USERPROFILE, then OS home").option("--project-root <path>", "separate repository scope; never redirects global configuration").option("--executable <tool=path>", "explicit executable path; repeat per harness", (value, previous) => [...previous, value], []).option("--config-dir <tool=path>", "reviewed native config root; repeat per harness", (value, previous) => [...previous, value], []).option("--json", "output the versioned discovery receipt").action((opts) => {
8684
+ try {
8685
+ const overrides = {};
8686
+ for (const [key, inputs] of [["executable", opts.executable], ["configDir", opts.configDir]]) {
8687
+ for (const input of inputs) {
8688
+ const separator = input.indexOf("=");
8689
+ const tool = input.slice(0, separator);
8690
+ if (separator < 1 || !DISCOVERABLE_HARNESSES.includes(tool))
8691
+ throw new Error("Expected claude|codex|opencode|sumi=path.");
8692
+ const entry = overrides[tool] ??= {};
8693
+ if (entry[key] !== undefined)
8694
+ throw new Error(`Duplicate ${key} for ${tool}.`);
8695
+ entry[key] = input.slice(separator + 1);
8696
+ }
8697
+ }
8698
+ const result = discoverHarnesses({ ownerHome: opts.ownerHome, projectRoot: opts.projectRoot, overrides });
8699
+ if (opts.json) {
8700
+ printJson(result);
8701
+ return;
8702
+ }
8703
+ for (const entry of result.tools) {
8704
+ printLine(`${entry.tool}: ${entry.status}; executable=${entry.executable?.path ?? "none"}; config=${entry.config?.path ?? "unresolved"}; version=not-probed`);
8705
+ for (const diagnostic of entry.diagnostics)
8706
+ printLine(` ${diagnostic}`);
8707
+ }
8708
+ } catch (error) {
8709
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
8710
+ process.exitCode = 1;
8711
+ }
8712
+ });
8729
8713
  var stationProfileCmd = program.command("station-profile").description("Generate and inject the compact station profile block into every session render");
8730
8714
  stationProfileCmd.command("refresh").description("Regenerate the cached station profile block (idempotent; writes only when changed)").option("--dry-run", "build and print the block without writing the cache").option("--json", "output JSON").action((opts) => {
8731
8715
  try {
@@ -8955,7 +8939,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
8955
8939
  } else if (opts.all) {
8956
8940
  configs = await store.listConfigs(opts.category ? { kind: "file", category: opts.category } : { kind: "file" });
8957
8941
  } else {
8958
- const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-azd9t4p8.js");
8942
+ const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-6r1v8hd3.js");
8959
8943
  const slugs = [
8960
8944
  ...KNOWN_CONFIGS2.filter((k) => !k.rulesDir).map((k) => k.name)
8961
8945
  ];
@@ -9242,14 +9226,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
9242
9226
  } else if (target === "codex") {
9243
9227
  const { appendFileSync, existsSync: ex, mkdirSync: md } = await import("fs");
9244
9228
  const { dirname: dn, join: j } = await import("path");
9245
- const configPath = j(homedir4(), ".codex", "config.toml");
9229
+ const configPath = j(homedir5(), ".codex", "config.toml");
9246
9230
  const block = `
9247
9231
  [mcp_servers.configs]
9248
9232
  command = "${mcpBinary}"
9249
9233
  args = []
9250
9234
  `;
9251
9235
  if (ex(configPath)) {
9252
- const content = readFileSync8(configPath, "utf-8");
9236
+ const content = readFileSync7(configPath, "utf-8");
9253
9237
  if (findTomlTableLines(content.split(`
9254
9238
  `), "mcp_servers.configs")) {
9255
9239
  console.log(chalk.dim("= Already installed in Codex"));
@@ -9262,7 +9246,7 @@ args = []
9262
9246
  } else if (target === "antigravity") {
9263
9247
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
9264
9248
  const { dirname: dn, join: j } = await import("path");
9265
- const configPath = j(homedir4(), ".gemini", "config", "mcp_config.json");
9249
+ const configPath = j(homedir5(), ".gemini", "config", "mcp_config.json");
9266
9250
  let settings = {};
9267
9251
  if (ex(configPath)) {
9268
9252
  try {
@@ -9306,12 +9290,12 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
9306
9290
  } else if (target === "codex") {
9307
9291
  const { existsSync: ex, writeFileSync: wf } = await import("fs");
9308
9292
  const { join: j } = await import("path");
9309
- const configPath = j(homedir4(), ".codex", "config.toml");
9293
+ const configPath = j(homedir5(), ".codex", "config.toml");
9310
9294
  if (!ex(configPath)) {
9311
9295
  console.log(chalk.dim("= Not installed in Codex (no config.toml)"));
9312
9296
  continue;
9313
9297
  }
9314
- const content = readFileSync8(configPath, "utf-8");
9298
+ const content = readFileSync7(configPath, "utf-8");
9315
9299
  const lines = content.split(`
9316
9300
  `);
9317
9301
  const table = findTomlTableLines(lines, "mcp_servers.configs");
@@ -9330,7 +9314,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
9330
9314
  console.log(chalk.green("\u2713") + " Removed from Codex");
9331
9315
  } else if (target === "antigravity") {
9332
9316
  const { existsSync: ex, readFileSync: rf, writeFileSync: wf } = await import("fs");
9333
- const configPath = join11(homedir4(), ".gemini", "config", "mcp_config.json");
9317
+ const configPath = join12(homedir5(), ".gemini", "config", "mcp_config.json");
9334
9318
  if (!ex(configPath)) {
9335
9319
  console.log(chalk.dim("= Not installed in Antigravity (no mcp_config.json)"));
9336
9320
  continue;
@@ -9365,15 +9349,15 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
9365
9349
  if (!opts.confirmLocal) {
9366
9350
  throw new Error("migrate-legacy requires --confirm-local");
9367
9351
  }
9368
- const destinationPath = resolve7(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db"));
9352
+ const destinationPath = resolve8(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db"));
9369
9353
  const database = await import("../chunks/database-wstmr56z.js");
9370
9354
  database.getDatabase(destinationPath);
9371
9355
  database.resetDatabase();
9372
9356
  const { migrateLegacyInstructionsStore } = await import("../chunks/legacy-store-migration-z2m5yzyk.js");
9373
9357
  const result = migrateLegacyInstructionsStore({
9374
- ...opts.source ? { sourcePath: resolve7(opts.source) } : {},
9358
+ ...opts.source ? { sourcePath: resolve8(opts.source) } : {},
9375
9359
  destinationPath,
9376
- ...opts.backup ? { backupPath: resolve7(opts.backup) } : {},
9360
+ ...opts.backup ? { backupPath: resolve8(opts.backup) } : {},
9377
9361
  dryRun: !opts.apply,
9378
9362
  ...opts.mergePreserveDestination ? { mergePolicy: "preserve-destination" } : {},
9379
9363
  operatorConfirmed: true
@@ -9452,7 +9436,7 @@ DB stats:`));
9452
9436
  if (count > 0)
9453
9437
  console.log(` ${key.padEnd(18)} ${count}`);
9454
9438
  }
9455
- const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db");
9439
+ const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db");
9456
9440
  console.log(chalk.dim(`
9457
9441
  ${store.mode === "api" ? "API" : "DB"}: ${location}`));
9458
9442
  });
@@ -9470,11 +9454,11 @@ program.command("status").description("Health check: total configs, drift from d
9470
9454
  console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
9471
9455
  console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
9472
9456
  console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
9473
- console.log(chalk.cyan("Skill runtimes:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green(`${status.counts.managedSkillRuntimes.healthy} ready`) : chalk.yellow(`${status.health.missingManagedSkillRuntimes} missing`)} (${status.counts.managedSkillRuntimes.skillsPresent} managed skill(s) present)`);
9457
+ console.log(chalk.cyan("Legacy Inbox migration:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green("no legacy paths found") : chalk.yellow("native paths need Skills CLI review")} (Skills availability is not verified here)`);
9474
9458
  console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
9475
9459
  });
9476
- var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
9477
- managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the full local runtime status as JSON").action((opts) => {
9460
+ var managedSkillsCmd = program.command("managed-skills").description("Report retired native Inbox skill paths; manage skills through the Skills CLI");
9461
+ managedSkillsCmd.command("status").option("--from <agent>", "deprecated compatibility option; no heartbeat is sent").option("--delivery-verified", "deprecated compatibility option; Skills delivery is not verified here").option("--json", "output the read-only legacy migration status as JSON").action((opts) => {
9478
9462
  const report = inspectManagedSkillRuntimes({
9479
9463
  agent: opts.from,
9480
9464
  deliveryVerified: opts.deliveryVerified
@@ -9486,7 +9470,7 @@ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted C
9486
9470
  return;
9487
9471
  }
9488
9472
  if (report.skills_present === 0) {
9489
- console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
9473
+ console.log(chalk.dim("No legacy native Inbox skill found. Use the Skills CLI to inspect selected skills."));
9490
9474
  return;
9491
9475
  }
9492
9476
  for (const runtime of report.runtimes) {
@@ -9498,7 +9482,7 @@ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted C
9498
9482
  if (report.missing > 0)
9499
9483
  process.exitCode = 1;
9500
9484
  });
9501
- managedSkillsCmd.command("apply").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the reconcile report as JSON").action(async (opts) => {
9485
+ managedSkillsCmd.command("apply").description("Deprecated: report migration requirements without repairing native skill payloads").option("--dry-run", "preview without writing").option("--from <agent>", "deprecated compatibility option; no heartbeat is sent").option("--delivery-verified", "deprecated compatibility option; Skills delivery is not verified here").option("--json", "output the read-only retirement report as JSON").action(async (opts) => {
9502
9486
  const report = await reconcileManagedSkillRuntimes({
9503
9487
  dryRun: opts.dryRun,
9504
9488
  agent: opts.from,
@@ -9553,8 +9537,8 @@ storageCmd.command("status").description("Show redacted S3 configuration status
9553
9537
  });
9554
9538
  var storageBackupCmd = storageCmd.command("backup").description("Push, pull, and verify immutable Instructions backup objects");
9555
9539
  storageBackupCmd.command("push <file>").description("Upload an immutable backup archive and integrity manifest").requiredOption("--id <backup-id>", "stable immutable backup identifier").option("--content-type <type>", "payload content type", "application/gzip").option("--dry-run", "plan keys and hashes without contacting S3").option("--json", "output JSON").action(async (file, opts) => {
9556
- const abs = resolve7(file);
9557
- if (!existsSync11(abs) || !lstatSync5(abs).isFile())
9540
+ const abs = resolve8(file);
9541
+ if (!existsSync10(abs) || !lstatSync6(abs).isFile())
9558
9542
  throw new Error(`Backup file not found: ${abs}`);
9559
9543
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
9560
9544
  import("../chunks/s3-config-hhght91k.js"),
@@ -9644,19 +9628,19 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9644
9628
  backupId,
9645
9629
  ...versions
9646
9630
  });
9647
- const output = resolve7(opts.output);
9648
- if (existsSync11(output)) {
9631
+ const output = resolve8(opts.output);
9632
+ if (existsSync10(output)) {
9649
9633
  if (!opts.force)
9650
9634
  throw new Error(`Refusing to overwrite existing backup file: ${output}`);
9651
- if (!lstatSync5(output).isFile() || lstatSync5(output).isSymbolicLink()) {
9635
+ if (!lstatSync6(output).isFile() || lstatSync6(output).isSymbolicLink()) {
9652
9636
  throw new Error(`Refusing to replace a non-regular backup destination: ${output}`);
9653
9637
  }
9654
9638
  }
9655
- mkdirSync6(dirname7(output), { recursive: true, mode: 448 });
9656
- const temp = join11(dirname7(output), `.${basename2(output)}.tmp-${process.pid}-${Date.now()}`);
9639
+ mkdirSync5(dirname7(output), { recursive: true, mode: 448 });
9640
+ const temp = join12(dirname7(output), `.${basename2(output)}.tmp-${process.pid}-${Date.now()}`);
9657
9641
  try {
9658
- writeFileSync5(temp, pulled.bytes, { flag: "wx", mode: 384 });
9659
- renameSync2(temp, output);
9642
+ writeFileSync4(temp, pulled.bytes, { flag: "wx", mode: 384 });
9643
+ renameSync(temp, output);
9660
9644
  } catch (error) {
9661
9645
  try {
9662
9646
  unlinkSync(temp);
@@ -9678,10 +9662,10 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9678
9662
  });
9679
9663
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
9680
9664
  const { mkdirSync: mk } = await import("fs");
9681
- const backupDir = join11(getRawStoreRoot(), "backups");
9665
+ const backupDir = join12(getRawStoreRoot(), "backups");
9682
9666
  mk(backupDir, { recursive: true });
9683
9667
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
9684
- const outPath = join11(backupDir, `configs-${ts}.tar.gz`);
9668
+ const outPath = join12(backupDir, `configs-${ts}.tar.gz`);
9685
9669
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
9686
9670
  const { statSync: st } = await import("fs");
9687
9671
  const size = st(outPath).size;
@@ -9707,9 +9691,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
9707
9691
  console.log(chalk.cyan("Known files on disk:"));
9708
9692
  for (const k of KNOWN_CONFIGS) {
9709
9693
  if (k.rulesDir) {
9710
- existsSync11(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
9694
+ existsSync10(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
9711
9695
  } else {
9712
- existsSync11(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
9696
+ existsSync10(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
9713
9697
  }
9714
9698
  }
9715
9699
  const allConfigs = await store.listConfigs();
@@ -9855,7 +9839,7 @@ program.command("watch").description("Watch known config files for changes and a
9855
9839
  const store = resolveConfigStore();
9856
9840
  const interval = Number(opts.interval);
9857
9841
  const { statSync: st } = await import("fs");
9858
- const { expandPath: expandPath2 } = await import("../chunks/apply-wj7wh5km.js");
9842
+ const { expandPath: expandPath2 } = await import("../chunks/apply-fyfzqc7s.js");
9859
9843
  console.log(chalk.bold("@hasna/instructions watch") + chalk.dim(` \u2014 polling every ${interval}ms`));
9860
9844
  console.log(chalk.dim(`Watching known config files for changes\u2026
9861
9845
  `));
@@ -9863,16 +9847,16 @@ program.command("watch").description("Watch known config files for changes and a
9863
9847
  for (const k of KNOWN_CONFIGS) {
9864
9848
  if (k.rulesDir) {
9865
9849
  const absDir = expandPath2(k.rulesDir);
9866
- if (!existsSync11(absDir))
9850
+ if (!existsSync10(absDir))
9867
9851
  continue;
9868
9852
  const { readdirSync: readdirSync3 } = await import("fs");
9869
9853
  for (const f of readdirSync3(absDir).filter((f2) => f2.endsWith(".md"))) {
9870
- const abs = join11(absDir, f);
9854
+ const abs = join12(absDir, f);
9871
9855
  mtimes.set(abs, st(abs).mtimeMs);
9872
9856
  }
9873
9857
  } else {
9874
9858
  const abs = expandPath2(k.path);
9875
- if (existsSync11(abs))
9859
+ if (existsSync10(abs))
9876
9860
  mtimes.set(abs, st(abs).mtimeMs);
9877
9861
  }
9878
9862
  }
@@ -9880,7 +9864,7 @@ program.command("watch").description("Watch known config files for changes and a
9880
9864
  const tick = async () => {
9881
9865
  let changed = 0;
9882
9866
  for (const [abs, oldMtime] of mtimes) {
9883
- if (!existsSync11(abs))
9867
+ if (!existsSync10(abs))
9884
9868
  continue;
9885
9869
  const newMtime = st(abs).mtimeMs;
9886
9870
  if (newMtime !== oldMtime) {
@@ -9892,10 +9876,10 @@ program.command("watch").description("Watch known config files for changes and a
9892
9876
  for (const k of KNOWN_CONFIGS) {
9893
9877
  if (k.rulesDir) {
9894
9878
  const absDir = expandPath2(k.rulesDir);
9895
- if (!existsSync11(absDir))
9879
+ if (!existsSync10(absDir))
9896
9880
  continue;
9897
9881
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
9898
- const abs = join11(absDir, f);
9882
+ const abs = join12(absDir, f);
9899
9883
  if (!mtimes.has(abs)) {
9900
9884
  mtimes.set(abs, st(abs).mtimeMs);
9901
9885
  changed++;
@@ -9903,7 +9887,7 @@ program.command("watch").description("Watch known config files for changes and a
9903
9887
  }
9904
9888
  } else {
9905
9889
  const abs = expandPath2(k.path);
9906
- if (existsSync11(abs) && !mtimes.has(abs)) {
9890
+ if (existsSync10(abs) && !mtimes.has(abs)) {
9907
9891
  mtimes.set(abs, st(abs).mtimeMs);
9908
9892
  changed++;
9909
9893
  }
@@ -9931,11 +9915,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
9931
9915
  if (!c.target_path)
9932
9916
  continue;
9933
9917
  const abs = expandPath(c.target_path);
9934
- if (!existsSync11(abs)) {
9918
+ if (!existsSync10(abs)) {
9935
9919
  missing++;
9936
9920
  continue;
9937
9921
  }
9938
- const disk = readFileSync8(abs, "utf-8");
9922
+ const disk = readFileSync7(abs, "utf-8");
9939
9923
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
9940
9924
  if (redactedDisk !== c.content)
9941
9925
  drifted++;
@@ -10002,7 +9986,7 @@ program.command("clean").description("Remove configs from DB whose target files
10002
9986
  if (!c.target_path)
10003
9987
  continue;
10004
9988
  const abs = expandPath(c.target_path);
10005
- if (!existsSync11(abs)) {
9989
+ if (!existsSync10(abs)) {
10006
9990
  if (printed < maxPrinted) {
10007
9991
  if (opts.dryRun) {
10008
9992
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -10026,7 +10010,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
10026
10010
  console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
10027
10011
  }
10028
10012
  });
10029
- program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
10013
+ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--from <agent>", "deprecated compatibility option; no heartbeat is sent").option("--delivery-verified", "deprecated compatibility option; Skills delivery is not verified here").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
10030
10014
  const store = resolveConfigStore();
10031
10015
  const packages = [
10032
10016
  { name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
@@ -10034,7 +10018,6 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
10034
10018
  { name: "@hasna/conversations", bin: "conversations", mcp: "conversations-mcp" },
10035
10019
  { name: "@hasna/skills", bin: "skills", mcp: "skills-mcp" },
10036
10020
  { name: "@hasna/economy", bin: "economy", mcp: "economy-mcp" },
10037
- { name: "@hasna/attachments", bin: "attachments", mcp: "attachments-mcp" },
10038
10021
  { name: "@hasna/emails", bin: "emails", mcp: "emails-mcp" },
10039
10022
  { name: "@hasna/recordings", bin: "recordings", mcp: "recordings-mcp" },
10040
10023
  { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" }
@@ -10086,20 +10069,8 @@ Initializing configs:`));
10086
10069
  } else {
10087
10070
  console.log(chalk.dim(" would run: configs init"));
10088
10071
  }
10089
- console.log(chalk.cyan(`
10090
- Reconciling managed skill runtimes:`));
10091
- const runtimeReport = await reconcileManagedSkillRuntimes({
10092
- dryRun: opts.dryRun,
10093
- agent: opts.from,
10094
- deliveryVerified: opts.deliveryVerified
10095
- });
10096
- printManagedSkillRuntimeReport(runtimeReport);
10097
- if (runtimeReport.failed > 0) {
10098
- console.error(chalk.red(`
10099
- Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
10100
- process.exitCode = 1;
10101
- return;
10102
- }
10072
+ console.log(chalk.dim(`
10073
+ Manage skill publication, selection and agent hooks through the Skills CLI.`));
10103
10074
  console.log(chalk.bold(`
10104
10075
  \u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
10105
10076
  });
@@ -10145,7 +10116,7 @@ providerContextCmd.command("resolve").description("Resolve the endpoint to a pro
10145
10116
  try {
10146
10117
  const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
10147
10118
  const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
10148
- const homeDir2 = opts.home ?? homedir4();
10119
+ const homeDir2 = opts.home ?? homedir5();
10149
10120
  const origin = normalizeEndpointOrigin(rawEndpoint);
10150
10121
  const resolution = resolveAndRenderProviderContext({
10151
10122
  origin,