@hasna/instructions 0.7.1 → 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 (67) hide show
  1. package/README.md +47 -7
  2. package/dist/chunks/{apply-c3sd4agz.js → apply-1gyr8r0x.js} +3 -3
  3. package/dist/chunks/{apply-pnn7a3y7.js → apply-fyfzqc7s.js} +3 -3
  4. package/dist/chunks/{index-3djthcm6.js → index-27az1qb1.js} +28 -1
  5. package/dist/chunks/{index-r7nfef2g.js → index-6sbmg1vc.js} +3 -3
  6. package/dist/chunks/{index-pdpg8a44.js → index-axs3yftz.js} +2 -2
  7. package/dist/chunks/{index-aj66f6xr.js → index-dwagz53p.js} +92 -8
  8. package/dist/chunks/{index-6jncnnw7.js → index-enh4yv85.js} +274 -31
  9. package/dist/chunks/{index-ypcef3a2.js → index-n9arv47w.js} +2 -2
  10. package/dist/chunks/{index-n3fjspr0.js → index-preyvj8n.js} +28 -1
  11. package/dist/chunks/{index-jg8ptcwh.js → index-z27xp7fh.js} +2 -2
  12. package/dist/chunks/{local-0a9gja58.js → local-5dsm856v.js} +15 -4
  13. package/dist/chunks/{local-969ye538.js → local-yxsbs8x3.js} +15 -4
  14. package/dist/chunks/{sync-xtkbm5br.js → sync-6r1v8hd3.js} +4 -4
  15. package/dist/chunks/{sync-q5hvwq6e.js → sync-k3pab4s6.js} +4 -4
  16. package/dist/chunks/{template-wgnzdn7h.js → template-e4qrxs8p.js} +1 -1
  17. package/dist/chunks/{template-fsfxpe4p.js → template-nhn124zd.js} +1 -1
  18. package/dist/cli/index.js +1018 -584
  19. package/dist/data/config-store.d.ts +12 -4
  20. package/dist/data/config-store.d.ts.map +1 -1
  21. package/dist/db/configs.d.ts.map +1 -1
  22. package/dist/index.d.ts +8 -4
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1264 -601
  25. package/dist/lib/asset-plan.d.ts +4 -1
  26. package/dist/lib/asset-plan.d.ts.map +1 -1
  27. package/dist/lib/codewith-shared-todos-storage-standard.d.ts.map +1 -1
  28. package/dist/lib/dangerous-operation-guard-standard.d.ts.map +1 -1
  29. package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
  30. package/dist/lib/global-source-coverage.d.ts +12 -7
  31. package/dist/lib/global-source-coverage.d.ts.map +1 -1
  32. package/dist/lib/harness-discovery.d.ts +52 -0
  33. package/dist/lib/harness-discovery.d.ts.map +1 -0
  34. package/dist/lib/instruction-graph.d.ts +1 -1
  35. package/dist/lib/instruction-graph.d.ts.map +1 -1
  36. package/dist/lib/instruction-source-policy.d.ts +6 -0
  37. package/dist/lib/instruction-source-policy.d.ts.map +1 -0
  38. package/dist/lib/machine.d.ts.map +1 -1
  39. package/dist/lib/managed-skill-runtimes.d.ts +12 -28
  40. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
  41. package/dist/lib/platform-profiles.d.ts.map +1 -1
  42. package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
  43. package/dist/lib/session-apply.d.ts +35 -0
  44. package/dist/lib/session-apply.d.ts.map +1 -1
  45. package/dist/lib/session-refresh.d.ts +30 -0
  46. package/dist/lib/session-refresh.d.ts.map +1 -0
  47. package/dist/lib/session-render.d.ts +41 -1
  48. package/dist/lib/session-render.d.ts.map +1 -1
  49. package/dist/lib/transforms.d.ts +1 -0
  50. package/dist/lib/transforms.d.ts.map +1 -1
  51. package/dist/mcp/index.js +13 -12
  52. package/dist/mcp/server.d.ts.map +1 -1
  53. package/dist/sdk/index.d.ts +2 -0
  54. package/dist/sdk/index.d.ts.map +1 -1
  55. package/dist/sdk/index.js +172 -0
  56. package/dist/sdk/v1-client.d.ts +7 -1
  57. package/dist/sdk/v1-client.d.ts.map +1 -1
  58. package/dist/sdk/v1.generated.d.ts +26 -0
  59. package/dist/sdk/v1.generated.d.ts.map +1 -1
  60. package/dist/server/index.js +199 -8
  61. package/dist/server/openapi.d.ts +150 -0
  62. package/dist/server/openapi.d.ts.map +1 -1
  63. package/dist/server/v1.d.ts.map +1 -1
  64. package/dist/storage/cloud-store.d.ts.map +1 -1
  65. package/dist/types/index.d.ts +24 -2
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -9,31 +9,39 @@ import {
9
9
  syncKnown,
10
10
  syncProject,
11
11
  syncToDisk
12
- } from "../chunks/index-pdpg8a44.js";
12
+ } from "../chunks/index-axs3yftz.js";
13
13
  import {
14
14
  applyConfigsWithReport,
15
15
  compactPathForConfigHome,
16
16
  expandPath,
17
- normalizeTargetPath
18
- } from "../chunks/index-r7nfef2g.js";
17
+ normalizeTargetPath,
18
+ sessionRenderOwnsPath
19
+ } from "../chunks/index-6sbmg1vc.js";
19
20
  import {
21
+ CODEWITH_NATIVE_IMPORTS_ENV,
20
22
  CloudConfigStore,
21
23
  DEFAULT_LIST_LIMIT,
22
24
  PROJECT_CONTEXT_MAX_INPUT_BYTES,
23
25
  ProjectContextError,
24
26
  SESSION_INSTRUCTION_LAYERS,
25
27
  SESSION_MANAGED_INPUT_MAX_BYTES,
28
+ SESSION_RENDERER_OWNER_ID,
26
29
  SESSION_RENDER_MANAGED_MARKER,
30
+ SESSION_RENDER_MANIFEST_RELATIVE_PATH,
27
31
  SESSION_RENDER_SCHEMA,
28
32
  SESSION_RENDER_TOOLS,
29
33
  applyProjectContext,
34
+ assertInstructionSource,
30
35
  detectClaudeAuthorityConflicts,
31
36
  detectCursorAuthorityConflicts,
32
37
  detectMachineContext,
33
38
  ensureCodewithSharedTodosStorageStandardConfig,
34
39
  ensureGlobalAgentRulesStandardConfig,
40
+ exports_external,
35
41
  formatCliError,
36
42
  getSessionRenderSnapshotDir,
43
+ instructionSourceRejection,
44
+ isRetiredInstructionSource,
37
45
  isRetiredOrUnsupportedConfigAgent,
38
46
  normalizeProfileAssetBinding,
39
47
  normalizeProfileConfigBinding,
@@ -61,11 +69,14 @@ import {
61
69
  truncateText,
62
70
  withProjectContextSessionGuard,
63
71
  writeProjectContextCoordinatedFile
64
- } from "../chunks/index-6jncnnw7.js";
72
+ } from "../chunks/index-enh4yv85.js";
65
73
  import {
74
+ ConfigNotFoundError,
66
75
  INSTRUCTIONS_DOMAIN_ARCHIVE_SCHEMA,
67
- extractTemplateVars
68
- } from "../chunks/index-n3fjspr0.js";
76
+ ProfileNotFoundError,
77
+ extractTemplateVars,
78
+ validateExpectedConfigVersion
79
+ } from "../chunks/index-preyvj8n.js";
69
80
  import {
70
81
  slugify
71
82
  } from "../chunks/index-ma2xmp6c.js";
@@ -3784,9 +3795,162 @@ var {
3784
3795
 
3785
3796
  // src/cli/index.tsx
3786
3797
  import chalk from "chalk";
3787
- import { existsSync as existsSync11, lstatSync as lstatSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync5, writeSync } from "fs";
3788
- import { homedir as homedir4 } from "os";
3789
- import { basename, dirname as dirname6, join as join10, resolve as resolve6 } 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";
3790
3954
 
3791
3955
  // src/lib/config-target-identity.ts
3792
3956
  function findConfigsByTargetPath(configs, targetPath) {
@@ -3827,7 +3991,7 @@ function findDuplicateTargetPathGroups(configs) {
3827
3991
  // src/lib/export.ts
3828
3992
  import { createHash } from "crypto";
3829
3993
  import { existsSync as existsSync3, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
3830
- import { dirname, join as join3, resolve as resolve2 } from "path";
3994
+ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
3831
3995
  import { tmpdir } from "os";
3832
3996
  var ARCHIVE_EXCLUSIONS = [
3833
3997
  {
@@ -4163,13 +4327,13 @@ async function exportConfigs(outputPath, opts = {}) {
4163
4327
  integrity,
4164
4328
  exclusions: ARCHIVE_EXCLUSIONS
4165
4329
  };
4166
- const absOutput = resolve2(outputPath);
4167
- mkdirSync(dirname(absOutput), { recursive: true });
4168
- 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-"));
4169
4333
  try {
4170
- writeFileSync(join3(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4334
+ writeFileSync(join5(stagingDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
4171
4335
  `, "utf-8");
4172
- writeFileSync(join3(stagingDir, "domain.json"), payload, "utf-8");
4336
+ writeFileSync(join5(stagingDir, "domain.json"), payload, "utf-8");
4173
4337
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", stagingDir, "manifest.json", "domain.json"], {
4174
4338
  stdout: "pipe",
4175
4339
  stderr: "pipe"
@@ -4188,7 +4352,7 @@ async function exportConfigs(outputPath, opts = {}) {
4188
4352
 
4189
4353
  // src/lib/import.ts
4190
4354
  import { createHash as createHash2 } from "crypto";
4191
- import { resolve as resolve3 } from "path";
4355
+ import { resolve as resolve4 } from "path";
4192
4356
  function compareText2(left, right) {
4193
4357
  return left < right ? -1 : left > right ? 1 : 0;
4194
4358
  }
@@ -4476,7 +4640,7 @@ async function importV1(reader, manifest, store, conflict) {
4476
4640
  async function importConfigs(bundlePath, opts = {}) {
4477
4641
  const store = opts.store ?? resolveConfigStore();
4478
4642
  const conflict = opts.conflict ?? "skip";
4479
- const reader = await openArchive(resolve3(bundlePath));
4643
+ const reader = await openArchive(resolve4(bundlePath));
4480
4644
  const manifestText = await reader.read("manifest.json");
4481
4645
  let manifest;
4482
4646
  try {
@@ -4502,13 +4666,13 @@ async function importConfigs(bundlePath, opts = {}) {
4502
4666
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
4503
4667
  import {
4504
4668
  existsSync as existsSync5,
4505
- lstatSync,
4669
+ lstatSync as lstatSync2,
4506
4670
  mkdirSync as mkdirSync2,
4507
4671
  readFileSync,
4508
4672
  readdirSync,
4509
- statSync
4673
+ statSync as statSync2
4510
4674
  } from "fs";
4511
- 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";
4512
4676
  class SessionApplyError extends Error {
4513
4677
  constructor(message) {
4514
4678
  super(message);
@@ -4530,15 +4694,23 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4530
4694
  const targetHome = assertSafeTargetHome(plan.targetHome);
4531
4695
  assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
4532
4696
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
4533
- const files = [...payloadFiles, plan.manifestFile];
4534
4697
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
4698
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4535
4699
  const previousManifest = readPreviousManifest(manifestPath);
4536
4700
  const previousHashes = previousManifest ? new Map(previousManifest.files.map((file) => [file.relativePath, file.sha256])) : new Map;
4701
+ const adoptions = validateFileAdoptions(plan, targetHome, previousHashes, options);
4702
+ const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
4703
+ const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
4704
+ const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
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);
4708
+ const files = [...payloadFiles, manifestFile];
4537
4709
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
4538
4710
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
4539
4711
  const results = [
4540
- ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options)),
4541
- ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
4712
+ ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
4713
+ ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes)
4542
4714
  ];
4543
4715
  assertNotSilentManagedWipeout(plan, results);
4544
4716
  const conflicts = results.filter((result) => result.action === "conflict");
@@ -4560,7 +4732,10 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4560
4732
  skippedSources: plan.manifest.skippedSources,
4561
4733
  files: results,
4562
4734
  conflicts,
4563
- drift
4735
+ drift,
4736
+ adoptions,
4737
+ reconciliations,
4738
+ retirements
4564
4739
  };
4565
4740
  }
4566
4741
  let snapshotPath = null;
@@ -4573,10 +4748,16 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4573
4748
  if (!options.dryRun) {
4574
4749
  const allowPortableFallback = coordination === null;
4575
4750
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
4751
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4576
4752
  ensureSessionTargetHome(targetHome);
4577
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps);
4753
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements);
4578
4754
  snapshotPath = rollback.snapshotPath;
4579
4755
  options.test_hooks?.before_apply_writes?.({ plan, results });
4756
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4757
+ for (const result of results) {
4758
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
4759
+ coordination?.assert_held();
4760
+ }
4580
4761
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
4581
4762
  for (const file of payloadFiles) {
4582
4763
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -4596,7 +4777,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4596
4777
  });
4597
4778
  coordination?.assert_held();
4598
4779
  }
4599
- applyPlannedFile(plan, plan.manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4780
+ applyPlannedFile(plan, manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4600
4781
  }
4601
4782
  return {
4602
4783
  dryRun: options.dryRun ?? false,
@@ -4610,9 +4791,233 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4610
4791
  skippedSources: plan.manifest.skippedSources,
4611
4792
  files: results,
4612
4793
  conflicts,
4613
- drift
4794
+ drift,
4795
+ adoptions,
4796
+ reconciliations,
4797
+ retirements
4614
4798
  };
4615
4799
  }
4800
+ function assertManifestPrecondition(path, targetHome, expected) {
4801
+ if (expected === undefined)
4802
+ return;
4803
+ if (!/^[a-f0-9]{64}$/.test(expected)) {
4804
+ throw new SessionApplyError("Expected session manifest SHA-256 must contain 64 lowercase hexadecimal characters.");
4805
+ }
4806
+ if (currentSessionFileHash(path, targetHome) !== expected) {
4807
+ throw new SessionApplyError("Session manifest SHA-256 precondition failed; reload the manifest and resolve a new plan.");
4808
+ }
4809
+ }
4810
+ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
4811
+ const requests = options.adoptFiles ?? [];
4812
+ if (!Array.isArray(requests))
4813
+ throw new SessionApplyError("Session file adoptions must be an array.");
4814
+ if (requests.length > 0 && options.force) {
4815
+ throw new SessionApplyError("Exact file adoption cannot be combined with force.");
4816
+ }
4817
+ const seen = new Set;
4818
+ return requests.map((request) => {
4819
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
4820
+ throw new SessionApplyError("Each file adoption requires a plan-relative path and a 64-character lowercase SHA-256.");
4821
+ }
4822
+ if (seen.has(request.relativePath)) {
4823
+ throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
4824
+ }
4825
+ seen.add(request.relativePath);
4826
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
4827
+ if (candidates.length !== 1 || candidates[0].role === "manifest") {
4828
+ throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
4829
+ }
4830
+ const file = candidates[0];
4831
+ const path = resolvePlannedFilePath(plan, file, targetHome);
4832
+ if (previousHashes.has(file.relativePath) || sessionRenderOwnsPath(path)) {
4833
+ throw new SessionApplyError(`File adoption target is already managed: ${request.relativePath}`);
4834
+ }
4835
+ const observedSha256 = readExactFilePreimage(path, targetHome, request, "adoption");
4836
+ return {
4837
+ path,
4838
+ relativePath: file.relativePath,
4839
+ preimageSha256: observedSha256,
4840
+ renderedSha256: file.sha256,
4841
+ sourceIds: [...file.sourceIds]
4842
+ };
4843
+ });
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
+ }
4849
+ function readExactFilePreimage(path, targetHome, request, operation) {
4850
+ if (currentSessionFileHash(path, targetHome) === null) {
4851
+ throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
4852
+ }
4853
+ const bytes = readFileSync(path);
4854
+ if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
4855
+ throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
4856
+ }
4857
+ const observedSha256 = createHash3("sha256").update(bytes).digest("hex");
4858
+ if (observedSha256 !== request.sha256) {
4859
+ throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
4860
+ }
4861
+ return observedSha256;
4862
+ }
4863
+ function validateFileReconciliations(plan, targetHome, previousManifest, options) {
4864
+ const requests = options.reconcileFiles ?? [];
4865
+ if (!Array.isArray(requests))
4866
+ throw new SessionApplyError("Session file reconciliations must be an array.");
4867
+ if (requests.length === 0)
4868
+ return [];
4869
+ if (options.force)
4870
+ throw new SessionApplyError("Exact file reconciliation cannot be combined with force.");
4871
+ if (options.expectedManifestSha256 === undefined) {
4872
+ throw new SessionApplyError("Exact file reconciliation requires an expected manifest SHA-256 precondition.");
4873
+ }
4874
+ if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
4875
+ throw new SessionApplyError("File reconciliation requires this renderer's manifest for the same tool and target home.");
4876
+ }
4877
+ const seen = new Set;
4878
+ return requests.map((request) => {
4879
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
4880
+ throw new SessionApplyError("Each file reconciliation requires a plan-relative path and a 64-character lowercase SHA-256.");
4881
+ }
4882
+ if (seen.has(request.relativePath)) {
4883
+ throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
4884
+ }
4885
+ seen.add(request.relativePath);
4886
+ const candidates = exactPreimageCandidates(plan).filter((file2) => file2.relativePath === request.relativePath);
4887
+ if (candidates.length !== 1 || candidates[0].role === "manifest") {
4888
+ throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
4889
+ }
4890
+ const file = candidates[0];
4891
+ const path = resolvePlannedFilePath(plan, file, targetHome);
4892
+ const owned = previousManifest.files.filter((entry) => entry.relativePath === file.relativePath);
4893
+ if (owned.length !== 1 || owned[0].path !== path || owned[0].role !== file.role || !/^[a-f0-9]{64}$/.test(owned[0].sha256)) {
4894
+ throw new SessionApplyError(`File reconciliation target is not owned by the observed manifest: ${request.relativePath}`);
4895
+ }
4896
+ const observedSha256 = readExactFilePreimage(path, targetHome, request, "reconciliation");
4897
+ if (observedSha256 === owned[0].sha256) {
4898
+ throw new SessionApplyError(`File reconciliation target has no managed drift: ${request.relativePath}`);
4899
+ }
4900
+ if (observedSha256 === file.sha256) {
4901
+ throw new SessionApplyError(`File reconciliation target already matches the planned output: ${request.relativePath}`);
4902
+ }
4903
+ return {
4904
+ path,
4905
+ relativePath: file.relativePath,
4906
+ preimageSha256: observedSha256,
4907
+ renderedSha256: file.sha256,
4908
+ previousManagedSha256: owned[0].sha256,
4909
+ sourceIds: [...file.sourceIds]
4910
+ };
4911
+ });
4912
+ }
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)
4954
+ return plan.manifestFile;
4955
+ if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
4956
+ throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
4957
+ }
4958
+ const entries = new Map;
4959
+ for (const entry of [
4960
+ ...previousManifest?.adoptions ?? [],
4961
+ ...adoptions.map(({ path: _path, ...receipt }) => receipt)
4962
+ ]) {
4963
+ if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.renderedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
4964
+ throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
4965
+ }
4966
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
4967
+ const previous = entries.get(entry.relativePath);
4968
+ if (previous && JSON.stringify(previous) !== JSON.stringify(entry)) {
4969
+ throw new SessionApplyError(`Conflicting session file adoption provenance: ${entry.relativePath}`);
4970
+ }
4971
+ entries.set(entry.relativePath, entry);
4972
+ }
4973
+ if (previousManifest?.reconciliations !== undefined && !Array.isArray(previousManifest.reconciliations)) {
4974
+ throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
4975
+ }
4976
+ const reconciledEntries = new Map;
4977
+ for (const entry of [
4978
+ ...previousManifest?.reconciliations ?? [],
4979
+ ...reconciliations.map(({ path: _path, ...receipt }) => receipt)
4980
+ ]) {
4981
+ if (!entry || typeof entry.relativePath !== "string" || !/^[a-f0-9]{64}$/.test(entry.preimageSha256) || !/^[a-f0-9]{64}$/.test(entry.renderedSha256) || !/^[a-f0-9]{64}$/.test(entry.previousManagedSha256) || !Array.isArray(entry.sourceIds) || entry.sourceIds.some((id) => typeof id !== "string")) {
4982
+ throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
4983
+ }
4984
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
4985
+ const canonical = {
4986
+ relativePath: entry.relativePath,
4987
+ preimageSha256: entry.preimageSha256,
4988
+ renderedSha256: entry.renderedSha256,
4989
+ previousManagedSha256: entry.previousManagedSha256,
4990
+ sourceIds: [...entry.sourceIds]
4991
+ };
4992
+ reconciledEntries.set(JSON.stringify(canonical), canonical);
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
+ }
5011
+ const manifest = {
5012
+ ...plan.manifest,
5013
+ ...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
5014
+ ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {},
5015
+ ...retirements.length > 0 || previousManifest?.retirements !== undefined ? { retirements: [...retiredEntries.values()] } : {}
5016
+ };
5017
+ const content = `${JSON.stringify(manifest, null, 2)}
5018
+ `;
5019
+ return { ...plan.manifestFile, content, sha256: sha2563(content) };
5020
+ }
4616
5021
  function assertCursorAuthorityUnchanged(plan) {
4617
5022
  if (plan.tool !== "cursor" || plan.targetKind === "blocked")
4618
5023
  return;
@@ -4645,7 +5050,7 @@ function ensureSessionTargetHome(targetHome) {
4645
5050
  }
4646
5051
  function checkSessionRenderDrift(targetHome, manifestPath) {
4647
5052
  const safeTargetHome = assertSafeTargetHome(targetHome);
4648
- 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");
4649
5054
  const checkedAt = new Date().toISOString();
4650
5055
  const previousManifest = readPreviousManifest(resolvedManifestPath);
4651
5056
  if (!previousManifest) {
@@ -4695,13 +5100,13 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
4695
5100
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
4696
5101
  const snapshot = readSessionRenderSnapshot(snapshotPath);
4697
5102
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
4698
- const resolvedSnapshotPath = resolve4(snapshotPath);
5103
+ const resolvedSnapshotPath = resolve5(snapshotPath);
4699
5104
  const snapshotDir = getSessionRenderSnapshotDir(targetHome);
4700
5105
  const snapshotDirRelative = relative(snapshotDir, resolvedSnapshotPath);
4701
- const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute(snapshotDirRelative);
5106
+ const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute2(snapshotDirRelative);
4702
5107
  if (!insideSnapshotDir) {
4703
5108
  const snapshotRelativePath = relative(targetHome, resolvedSnapshotPath);
4704
- if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute(snapshotRelativePath)) {
5109
+ if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute2(snapshotRelativePath)) {
4705
5110
  throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
4706
5111
  }
4707
5112
  }
@@ -4818,14 +5223,14 @@ function requiredRestoreHash(file) {
4818
5223
  return file.previousSha256;
4819
5224
  }
4820
5225
  function readSessionRenderSnapshot(snapshotPath) {
4821
- const resolved = resolve4(snapshotPath);
5226
+ const resolved = resolve5(snapshotPath);
4822
5227
  if (!existsSync5(resolved))
4823
5228
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
4824
- const stat = lstatSync(resolved);
5229
+ const stat = lstatSync2(resolved);
4825
5230
  if (stat.isSymbolicLink() || !stat.isFile()) {
4826
5231
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
4827
5232
  }
4828
- if (statSync(resolved).size > 32 * 1024 * 1024) {
5233
+ if (statSync2(resolved).size > 32 * 1024 * 1024) {
4829
5234
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
4830
5235
  }
4831
5236
  let parsed;
@@ -4899,7 +5304,7 @@ function readSessionRenderSnapshot(snapshotPath) {
4899
5304
  }
4900
5305
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
4901
5306
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
4902
- const manifestPath = resolve4(snapshot.manifestPath);
5307
+ const manifestPath = resolve5(snapshot.manifestPath);
4903
5308
  const manifestRelativePath = relative(targetHome, manifestPath).replaceAll("\\", "/");
4904
5309
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
4905
5310
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
@@ -4916,7 +5321,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
4916
5321
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
4917
5322
  }
4918
5323
  const appliedManifest = parsedManifest;
4919
- 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)) {
4920
5325
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
4921
5326
  }
4922
5327
  const afterFiles = [];
@@ -5000,17 +5405,17 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
5000
5405
  if (!Number.isFinite(createdAtMs)) {
5001
5406
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
5002
5407
  }
5003
- for (const entry of readdirSync(dirname2(snapshotPath))) {
5004
- const candidatePath = resolve4(dirname2(snapshotPath), entry);
5005
- 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"))
5006
5411
  continue;
5007
- const candidateStat = lstatSync(candidatePath);
5412
+ const candidateStat = lstatSync2(candidatePath);
5008
5413
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
5009
5414
  continue;
5010
5415
  try {
5011
5416
  const candidate = JSON.parse(readFileSync(candidatePath, "utf8"));
5012
5417
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
5013
- 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) {
5014
5419
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
5015
5420
  }
5016
5421
  } catch (error) {
@@ -5079,17 +5484,34 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
5079
5484
  }
5080
5485
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
5081
5486
  const path = resolveManifestRelativePath(relativePath, targetHome);
5082
- if (resolve4(recordedPath) !== path) {
5487
+ if (resolve5(recordedPath) !== path) {
5083
5488
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
5084
5489
  }
5085
5490
  return path;
5086
5491
  }
5087
- function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
5492
+ function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
5088
5493
  const target = resolvePlannedFilePath(plan, file, targetHome);
5089
5494
  const previousContent = existsSync5(target) ? readFileSync(target, "utf-8") : null;
5090
5495
  const previousSha256 = previousContent === null ? null : sha2563(previousContent);
5091
5496
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
5092
5497
  const changed = previousContent !== file.content;
5498
+ const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
5499
+ if (exactSha256 !== undefined) {
5500
+ const operation = adoptedHashes.has(file.relativePath) ? "adoption" : "reconciliation";
5501
+ if (previousSha256 !== exactSha256) {
5502
+ throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${file.relativePath}`);
5503
+ }
5504
+ return {
5505
+ path: target,
5506
+ relativePath: file.relativePath,
5507
+ role: file.role,
5508
+ action: "update",
5509
+ changed,
5510
+ previousSha256,
5511
+ newSha256: file.sha256,
5512
+ reason: `exact observed SHA-256 ${operation}`
5513
+ };
5514
+ }
5093
5515
  if (previousContent !== null && !options.force && !previouslyManaged) {
5094
5516
  return {
5095
5517
  path: target,
@@ -5178,17 +5600,55 @@ function isPlanManagedFile(plan, relativePath, role) {
5178
5600
  return true;
5179
5601
  return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
5180
5602
  }
5181
- function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
5603
+ function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options, retiredHashes) {
5182
5604
  if (!previousManifest)
5183
5605
  return [];
5184
- 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);
5185
5607
  }
5186
- function planStaleFileResult(file, targetHome, options) {
5608
+ function planStaleFileResult(file, targetHome, options, retiredHash) {
5187
5609
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
5188
- if (!existsSync5(target))
5189
- 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
+ }
5190
5624
  const previousContent = readFileSync(target, "utf-8");
5191
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
+ }
5192
5652
  if (!options.force && previousSha256 !== file.sha256) {
5193
5653
  return {
5194
5654
  path: target,
@@ -5232,21 +5692,21 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
5232
5692
  return previousHashes.get(file.relativePath) === previousSha256;
5233
5693
  }
5234
5694
  function resolvePlannedFilePath(plan, file, targetHome) {
5235
- const target = resolve4(targetHome, ...file.relativePath.split("/"));
5695
+ const target = resolve5(targetHome, ...file.relativePath.split("/"));
5236
5696
  const rel = relative(targetHome, target);
5237
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
5697
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
5238
5698
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
5239
5699
  }
5240
- if (resolve4(file.path) !== target) {
5700
+ if (resolve5(file.path) !== target) {
5241
5701
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
5242
5702
  }
5243
5703
  assertNoSymlinkSegments(targetHome, target);
5244
5704
  return target;
5245
5705
  }
5246
5706
  function resolveManifestRelativePath(relativePath, targetHome) {
5247
- const target = resolve4(targetHome, ...relativePath.split(/[\\/]+/));
5707
+ const target = resolve5(targetHome, ...relativePath.split(/[\\/]+/));
5248
5708
  const rel = relative(targetHome, target);
5249
- if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
5709
+ if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute2(rel)) {
5250
5710
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
5251
5711
  }
5252
5712
  assertNoSymlinkSegments(targetHome, target);
@@ -5297,7 +5757,7 @@ function currentSessionFileHash(path, targetHome) {
5297
5757
  assertNoSymlinkSegments(targetHome, path);
5298
5758
  if (!existsSync5(path))
5299
5759
  return null;
5300
- const stat = lstatSync(path);
5760
+ const stat = lstatSync2(path);
5301
5761
  if (stat.isSymbolicLink() || !stat.isFile()) {
5302
5762
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
5303
5763
  }
@@ -5309,9 +5769,13 @@ function requiredPreviousHash(result) {
5309
5769
  }
5310
5770
  return result.previousSha256;
5311
5771
  }
5312
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
5313
- const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync5(result.path)).map((result) => {
5772
+ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations, retirements) {
5773
+ const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
5774
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
5314
5775
  const content = readFileSync(result.path, "utf-8");
5776
+ if (sha2563(content) !== result.previousSha256) {
5777
+ throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
5778
+ }
5315
5779
  return {
5316
5780
  path: result.path,
5317
5781
  relativePath: result.relativePath,
@@ -5329,7 +5793,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5329
5793
  };
5330
5794
  }
5331
5795
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
5332
- const snapshotPath = join5(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID3()}.json`);
5796
+ const snapshotPath = join6(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID3()}.json`);
5333
5797
  const afterFiles = results.map((result) => {
5334
5798
  if (result.action === "conflict") {
5335
5799
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -5352,13 +5816,20 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5352
5816
  manifestPath,
5353
5817
  previousManifest,
5354
5818
  files: existingFiles,
5355
- afterFiles
5819
+ afterFiles,
5820
+ ...adoptions.length > 0 ? { adoptions } : {},
5821
+ ...reconciliations.length > 0 ? { reconciliations } : {},
5822
+ ...retirements.length > 0 ? { retirements } : {}
5356
5823
  };
5824
+ const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
5825
+ `;
5826
+ if ((adoptions.length > 0 || reconciliations.length > 0 || retirements.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
5827
+ throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
5828
+ }
5357
5829
  coordination?.assert_held();
5358
5830
  writeProjectContextCoordinatedFile({
5359
5831
  path: snapshotPath,
5360
- content: `${JSON.stringify(snapshot, null, 2)}
5361
- `,
5832
+ content: snapshotContent,
5362
5833
  workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
5363
5834
  default_mode: 384,
5364
5835
  expected_hash: null,
@@ -5375,14 +5846,14 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5375
5846
  };
5376
5847
  }
5377
5848
  function assertSafeTargetHome(targetHome) {
5378
- if (!isAbsolute(targetHome))
5849
+ if (!isAbsolute2(targetHome))
5379
5850
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
5380
- const normalized = resolve4(targetHome);
5851
+ const normalized = resolve5(targetHome);
5381
5852
  if (normalized === parse(normalized).root) {
5382
5853
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
5383
5854
  }
5384
5855
  assertNoSymlinkAncestors(normalized);
5385
- if (existsSync5(normalized) && lstatSync(normalized).isSymbolicLink()) {
5856
+ if (existsSync5(normalized) && lstatSync2(normalized).isSymbolicLink()) {
5386
5857
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
5387
5858
  }
5388
5859
  return normalized;
@@ -5392,22 +5863,22 @@ function assertNoSymlinkSegments(root, target) {
5392
5863
  const rel = relative(root, target);
5393
5864
  let current = root;
5394
5865
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
5395
- current = join5(current, segment);
5396
- if (existsSync5(current) && lstatSync(current).isSymbolicLink()) {
5866
+ current = join6(current, segment);
5867
+ if (existsSync5(current) && lstatSync2(current).isSymbolicLink()) {
5397
5868
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
5398
5869
  }
5399
5870
  }
5400
5871
  }
5401
5872
  function assertNoSymlinkAncestors(path) {
5402
- const normalized = resolve4(path);
5873
+ const normalized = resolve5(path);
5403
5874
  const parsed = parse(normalized);
5404
5875
  let current = parsed.root;
5405
5876
  const rel = relative(parsed.root, normalized);
5406
5877
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
5407
- current = join5(current, segment);
5878
+ current = join6(current, segment);
5408
5879
  if (!existsSync5(current))
5409
5880
  return;
5410
- if (lstatSync(current).isSymbolicLink()) {
5881
+ if (lstatSync2(current).isSymbolicLink()) {
5411
5882
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
5412
5883
  }
5413
5884
  }
@@ -5416,11 +5887,15 @@ function sha2563(content) {
5416
5887
  return createHash3("sha256").update(content).digest("hex");
5417
5888
  }
5418
5889
 
5890
+ // src/lib/session-refresh.ts
5891
+ import { createHash as createHash4 } from "crypto";
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";
5894
+
5419
5895
  // src/lib/global-source-coverage.ts
5420
5896
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
5421
- var RETIRED_GLOBAL_SOURCE_TAG = "retired-global-source";
5422
5897
  function expectedGlobalSourceSlugs(registryConfigs) {
5423
- return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => !(c.tags ?? []).includes(RETIRED_GLOBAL_SOURCE_TAG)).map((c) => c.slug).sort();
5898
+ return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => instructionSourceRejection(c) === null).map((c) => c.slug).sort();
5424
5899
  }
5425
5900
  function accountedGlobalSourceSlugs(manifest) {
5426
5901
  return [...manifest.sources, ...manifest.skippedSources].map((source) => source.id).filter((id) => id.startsWith(GLOBAL_SOURCE_SLUG_PREFIX));
@@ -5433,6 +5908,10 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
5433
5908
  const unexpected = [...configuredSet].filter((slug) => slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX) && !expectedSet.has(slug)).sort();
5434
5909
  return {
5435
5910
  expectedSlugs: expected,
5911
+ excludedSources: registryConfigs.filter((config) => config.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).flatMap((config) => {
5912
+ const reason = instructionSourceRejection(config);
5913
+ return reason ? [{ slug: config.slug, reason }] : [];
5914
+ }).sort((left, right) => left.slug.localeCompare(right.slug)),
5436
5915
  configuredSlugs: [...configuredSet].sort(),
5437
5916
  missingSlugs: missing,
5438
5917
  unexpectedSlugs: unexpected,
@@ -5449,9 +5928,9 @@ function formatGlobalSourceCoverageWarnings(result) {
5449
5928
 
5450
5929
  // src/lib/station-profile.ts
5451
5930
  import { spawnSync } from "child_process";
5452
- import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
5453
- import { arch as osArch, homedir as homedir2, hostname as osHostname, platform as osPlatform, userInfo as osUserInfo } from "os";
5454
- 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";
5455
5934
  var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
5456
5935
  var STATION_PROFILE_SOURCE_ID = "station-profile";
5457
5936
  var STATION_PROFILE_LAYER = "machine";
@@ -5462,16 +5941,16 @@ var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
5462
5941
  var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
5463
5942
  var BUN_INSTALL_ENV = "BUN_INSTALL";
5464
5943
  function homeDir(env = process.env) {
5465
- return env["HOME"] || env["USERPROFILE"] || homedir2();
5944
+ return env["HOME"] || env["USERPROFILE"] || homedir3();
5466
5945
  }
5467
5946
  function getStationProfileCachePath(env = process.env) {
5468
- return join6(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
5947
+ return join7(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
5469
5948
  }
5470
5949
  function getMachinesManifestPath(env = process.env) {
5471
- 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");
5472
5951
  }
5473
5952
  function getBunGlobalModulesDir(env = process.env) {
5474
- 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");
5475
5954
  }
5476
5955
  function readMachinesManifest(path) {
5477
5956
  try {
@@ -5533,7 +6012,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
5533
6012
  const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname);
5534
6013
  const home = homeDir(env);
5535
6014
  const platform = stringField(record, "platform") ?? osPlatform();
5536
- const workspacePath = stringField(record, "workspacePath") ?? join6(home, platform === "darwin" ? "Workspace" : "workspace");
6015
+ const workspacePath = stringField(record, "workspacePath");
5537
6016
  const machine = {
5538
6017
  id: stringField(record, "id") ?? hostname,
5539
6018
  hostname: stringField(record, "hostname") ?? hostname,
@@ -5550,7 +6029,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
5550
6029
  return machine;
5551
6030
  }
5552
6031
  function scopedPackageNames(modulesDir, scope) {
5553
- const scopeDir = join6(modulesDir, scope);
6032
+ const scopeDir = join7(modulesDir, scope);
5554
6033
  try {
5555
6034
  if (!existsSync6(scopeDir))
5556
6035
  return null;
@@ -5562,7 +6041,7 @@ function scopedPackageNames(modulesDir, scope) {
5562
6041
  function readdirNames(dir) {
5563
6042
  return readdirSync2(dir).filter((name) => {
5564
6043
  try {
5565
- return lstatSync2(join6(dir, name)).isDirectory();
6044
+ return lstatSync3(join7(dir, name)).isDirectory();
5566
6045
  } catch {
5567
6046
  return false;
5568
6047
  }
@@ -5649,7 +6128,7 @@ function refreshStationProfile(options = {}) {
5649
6128
  if (!options.dryRun) {
5650
6129
  const existing = existsSync6(path) ? readFileSync2(path, "utf8") : null;
5651
6130
  if (existing !== content) {
5652
- mkdirSync3(dirname3(path), { recursive: true });
6131
+ mkdirSync3(dirname4(path), { recursive: true });
5653
6132
  writeFileSync2(path, content, "utf8");
5654
6133
  }
5655
6134
  }
@@ -5688,6 +6167,188 @@ function stationProfileSource(env = process.env) {
5688
6167
  };
5689
6168
  }
5690
6169
 
6170
+ // src/lib/session-refresh.ts
6171
+ var nonempty = exports_external.string().min(1).max(4096);
6172
+ var selectorSchema = exports_external.object({
6173
+ schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
6174
+ authority: nonempty,
6175
+ profileId: nonempty,
6176
+ providerVersion: nonempty,
6177
+ providerVariant: nonempty.optional(),
6178
+ model: nonempty.optional(),
6179
+ path: nonempty.optional(),
6180
+ manual: exports_external.array(nonempty).max(256),
6181
+ codewithNativeImports: exports_external.boolean(),
6182
+ allowEmptySources: exports_external.boolean(),
6183
+ stationProfile: exports_external.boolean(),
6184
+ checkGlobalCoverage: exports_external.boolean(),
6185
+ assetSurface: nonempty.optional(),
6186
+ assetScope: exports_external.enum(["global", "project", "session"]).optional()
6187
+ }).strict();
6188
+ function authority(value) {
6189
+ const url = new URL(value);
6190
+ if (url.username || url.password || url.search || url.hash || !["https:", "http:"].includes(url.protocol)) {
6191
+ throw new Error("SESSION_REFRESH_AUTHORITY_INVALID: expected a credential-free Instructions API authority.");
6192
+ }
6193
+ return url.toString().replace(/\/$/, "");
6194
+ }
6195
+ function normalizeSessionHostedProfileSelector(value) {
6196
+ const parsed = selectorSchema.parse(value);
6197
+ return { ...parsed, authority: authority(parsed.authority) };
6198
+ }
6199
+ function assertHostedProfileBindings(profileId, configs, bindings) {
6200
+ const configIds = new Set(configs.map((config) => config.id));
6201
+ const bindingIds = new Set(bindings.map((binding) => binding.config_id));
6202
+ if (configIds.size !== configs.length || bindingIds.size !== bindings.length || bindings.some((binding) => binding.profile_id !== profileId || !configIds.has(binding.config_id)) || [...configIds].some((id) => !bindingIds.has(id))) {
6203
+ throw new Error("HOSTED_PROFILE_BINDINGS_INCOMPLETE: exact one-to-one config membership and explicit profile bindings are required; refusing legacy activation fallback.");
6204
+ }
6205
+ for (const row of bindings) {
6206
+ const raw = row.binding;
6207
+ const invalid = () => new Error("HOSTED_PROFILE_BINDINGS_INVALID: each hosted binding must contain a complete explicit schema, activation, required flag and fallback; refusing legacy activation defaults.");
6208
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
6209
+ throw invalid();
6210
+ const record = raw;
6211
+ if (!["schema", "activation", "required", "fallback"].every((key) => Object.hasOwn(record, key)) || typeof record["required"] !== "boolean")
6212
+ throw invalid();
6213
+ try {
6214
+ normalizeProfileConfigBinding(record);
6215
+ } catch {
6216
+ throw invalid();
6217
+ }
6218
+ }
6219
+ }
6220
+ function readManagedManifest(targetHome) {
6221
+ const path = join8(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
6222
+ let ancestor = dirname5(path);
6223
+ while (true) {
6224
+ const stat = lstatSync4(ancestor);
6225
+ if (stat.isSymbolicLink() || !stat.isDirectory())
6226
+ throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
6227
+ const parent = dirname5(ancestor);
6228
+ if (parent === ancestor)
6229
+ break;
6230
+ ancestor = parent;
6231
+ }
6232
+ const fd = openSync(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
6233
+ let raw;
6234
+ try {
6235
+ const stat = fstatSync(fd);
6236
+ if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
6237
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
6238
+ raw = readFileSync3(fd);
6239
+ if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
6240
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
6241
+ } finally {
6242
+ closeSync(fd);
6243
+ }
6244
+ const manifest = JSON.parse(raw.toString("utf8"));
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) {
6246
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
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
+ }
6261
+ manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
6262
+ return { manifest, sha256: createHash4("sha256").update(raw).digest("hex") };
6263
+ }
6264
+ async function refreshSessionRender(input) {
6265
+ const { store } = input;
6266
+ if (store.mode !== "api" || !store.v1BaseUrl)
6267
+ throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
6268
+ const targetHome = resolve6(input.targetHome);
6269
+ if (targetHome === parse2(targetHome).root)
6270
+ throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
6271
+ const previous = readManagedManifest(targetHome);
6272
+ const selector = previous.manifest.refreshSelector;
6273
+ if (authority(store.v1BaseUrl) !== selector.authority)
6274
+ throw new Error("SESSION_REFRESH_AUTHORITY_MISMATCH: configured API differs from the recorded profile authority.");
6275
+ const profile = await store.getProfile(selector.profileId);
6276
+ if (profile.id !== selector.profileId)
6277
+ throw new Error("SESSION_REFRESH_PROFILE_MISMATCH: hosted profile identity changed.");
6278
+ const [configs, bindings, assetBindings] = await Promise.all([
6279
+ store.getProfileConfigs(profile.id),
6280
+ store.getProfileConfigBindings(profile.id, { requireExplicit: true }),
6281
+ store.getProfileAssetBindings(profile.id, { requireExplicit: true })
6282
+ ]);
6283
+ assertHostedProfileBindings(profile.id, configs, bindings);
6284
+ if (configs.length > 2048 || bindings.length > 8192 || assetBindings.length > 1024)
6285
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted profile exceeds refresh bounds.");
6286
+ const totalBytes = configs.reduce((sum, config) => sum + Buffer.byteLength(config.content), 0);
6287
+ if (totalBytes > SESSION_MANAGED_INPUT_MAX_BYTES)
6288
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted instruction payload exceeds refresh byte bounds.");
6289
+ const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
6290
+ if (assetConfigs.reduce((sum, config) => sum + Buffer.byteLength(config.content), totalBytes) > SESSION_MANAGED_INPUT_MAX_BYTES)
6291
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted source and asset payload exceeds refresh byte bounds.");
6292
+ let ownedClaudeAuthorities;
6293
+ if (previous.manifest.tool === "claude") {
6294
+ ownedClaudeAuthorities = (await store.listConfigs({ category: "rules", agent: "claude", kind: "file" })).filter((config) => config.target_path && basename(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
6295
+ }
6296
+ const station = selector.stationProfile ? stationProfileSource() : null;
6297
+ const plan = planProfileSessionRender({
6298
+ tool: previous.manifest.tool,
6299
+ profile: previous.manifest.profile,
6300
+ profile_id: profile.id,
6301
+ provider_version: selector.providerVersion,
6302
+ targetHome,
6303
+ ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : previous.manifest.targetOwner.projectRoot ? { projectRoot: previous.manifest.targetOwner.projectRoot } : {},
6304
+ sessionId: previous.manifest.sessionId ?? undefined,
6305
+ refreshSelector: selector,
6306
+ codewithNativeImports: selector.codewithNativeImports,
6307
+ allowEmptySources: selector.allowEmptySources,
6308
+ configs,
6309
+ bindings,
6310
+ asset_configs: assetConfigs,
6311
+ asset_bindings: assetBindings,
6312
+ asset_plan_mode: "apply",
6313
+ asset_scope: selector.assetScope,
6314
+ asset_surface: selector.assetSurface,
6315
+ extra_sources: station ? [station] : undefined,
6316
+ ownedClaudeAuthorities,
6317
+ graph_context: {
6318
+ ...selector.providerVariant ? { provider_variant: selector.providerVariant } : {},
6319
+ ...selector.model ? { model: selector.model } : {},
6320
+ ...selector.path ? { path: selector.path } : {},
6321
+ ...selector.manual.length ? { manual: selector.manual } : {}
6322
+ }
6323
+ });
6324
+ if (selector.checkGlobalCoverage) {
6325
+ const coverage = computeGlobalSourceCoverage(await store.listConfigs({}), accountedGlobalSourceSlugs(plan.manifest));
6326
+ if (!coverage.complete)
6327
+ throw new Error(`SESSION_REFRESH_COVERAGE_INCOMPLETE: ${coverage.missingSlugs.length} registered global sources are absent.`);
6328
+ }
6329
+ const preview = applySessionRender(plan, { dryRun: true, expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
6330
+ const common = { checkedAt: new Date().toISOString(), targetHome, selector, sourceHash: plan.manifest.sourceHash };
6331
+ if (preview.conflicts.length)
6332
+ return { ...common, status: "blocked", apply: preview };
6333
+ if (!preview.drift.clean)
6334
+ throw new Error("SESSION_REFRESH_DRIFT: generated files changed or disappeared since their last managed render.");
6335
+ const sameSources = previous.manifest.sourceHash === plan.manifest.sourceHash;
6336
+ const samePayloads = preview.files.filter((file) => file.role !== "manifest").every((file) => file.action === "unchanged");
6337
+ if (sameSources && samePayloads) {
6338
+ if (readManagedManifest(targetHome).sha256 !== previous.sha256 || !checkSessionRenderDrift(targetHome).clean)
6339
+ throw new Error("SESSION_REFRESH_DRIFT: managed target changed during refresh.");
6340
+ return { ...common, status: "unchanged", apply: { ...preview, files: preview.files.map((file) => file.role === "manifest" ? { ...file, action: "unchanged", changed: false, newSha256: previous.sha256, reason: "hosted sources and generated payloads unchanged" } : file) } };
6341
+ }
6342
+ if (input.dryRun)
6343
+ return { ...common, status: "dry-run", apply: preview };
6344
+ const applied = applySessionRender(plan, { expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
6345
+ if (applied.conflicts.length)
6346
+ return { ...common, status: "blocked", apply: applied };
6347
+ if (!applied.applied || !checkSessionRenderDrift(targetHome).clean)
6348
+ throw new Error("SESSION_REFRESH_READBACK_FAILED: applied instructions did not verify against their manifest.");
6349
+ return { ...common, status: "updated", apply: applied };
6350
+ }
6351
+
5691
6352
  // src/lib/project-dashboard-standard.ts
5692
6353
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
5693
6354
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
@@ -5780,11 +6441,15 @@ async function ensureProjectDashboardStandardConfig(store = resolveConfigStore()
5780
6441
  };
5781
6442
  try {
5782
6443
  const existing = await store.getConfig(PROJECT_DASHBOARD_STANDARD_SLUG);
6444
+ if (isRetiredInstructionSource(existing))
6445
+ return existing;
5783
6446
  if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind) {
5784
6447
  return await store.updateConfig(existing.id, input);
5785
6448
  }
5786
6449
  return existing;
5787
- } catch {
6450
+ } catch (error) {
6451
+ if (!(error instanceof ConfigNotFoundError))
6452
+ throw error;
5788
6453
  return await store.createConfig(input);
5789
6454
  }
5790
6455
  }
@@ -5797,10 +6462,9 @@ function profileHasSelectors(profile) {
5797
6462
  var PLATFORM_PROFILE_PRESETS = [
5798
6463
  {
5799
6464
  name: "linux-arm64",
5800
- description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
5801
- selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b", "station01"] },
6465
+ description: "Default reviewed instruction profile for Linux arm64",
6466
+ selectors: { os: ["linux"], arch: ["arm64"] },
5802
6467
  variables: {
5803
- WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
5804
6468
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5805
6469
  BUN_PATH: "{{BUN_BIN_DIR}}/bun",
5806
6470
  PATH_PREFIX: "{{BUN_BIN_DIR}}",
@@ -5809,10 +6473,9 @@ var PLATFORM_PROFILE_PRESETS = [
5809
6473
  },
5810
6474
  {
5811
6475
  name: "macos-arm64",
5812
- description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
5813
- selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
6476
+ description: "Default reviewed instruction profile for macOS arm64",
6477
+ selectors: { os: ["macos"], arch: ["arm64"] },
5814
6478
  variables: {
5815
- WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
5816
6479
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5817
6480
  BUN_PATH: "/opt/homebrew/bin/bun",
5818
6481
  PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}",
@@ -5821,12 +6484,21 @@ var PLATFORM_PROFILE_PRESETS = [
5821
6484
  }
5822
6485
  ];
5823
6486
  async function ensurePlatformProfiles(store = resolveConfigStore()) {
5824
- const configs = await store.listConfigs();
5825
6487
  const ensured = [];
5826
- for (const preset of PLATFORM_PROFILE_PRESETS) {
5827
- let profile;
6488
+ const existingProfiles = await Promise.all(PLATFORM_PROFILE_PRESETS.map(async (preset) => {
5828
6489
  try {
5829
- profile = await store.getProfile(preset.name);
6490
+ return await store.getProfile(preset.name);
6491
+ } catch (error) {
6492
+ if (error instanceof ProfileNotFoundError)
6493
+ return null;
6494
+ throw error;
6495
+ }
6496
+ }));
6497
+ for (const [index, preset] of PLATFORM_PROFILE_PRESETS.entries()) {
6498
+ let profile;
6499
+ const existing = existingProfiles[index];
6500
+ if (existing) {
6501
+ profile = existing;
5830
6502
  const selectors = mergeProfileSelectors(preset.selectors, profile.selectors);
5831
6503
  const variables = mergeProfileVariables(preset.variables, profile.variables);
5832
6504
  if (JSON.stringify(selectors) !== JSON.stringify(profile.selectors) || JSON.stringify(variables) !== JSON.stringify(profile.variables)) {
@@ -5836,12 +6508,9 @@ async function ensurePlatformProfiles(store = resolveConfigStore()) {
5836
6508
  variables
5837
6509
  });
5838
6510
  }
5839
- } catch {
6511
+ } else {
5840
6512
  profile = await store.createProfile(preset);
5841
6513
  }
5842
- for (const config of configs) {
5843
- await store.addConfigToProfile(profile.id, config.id);
5844
- }
5845
6514
  ensured.push(profile);
5846
6515
  }
5847
6516
  return ensured;
@@ -5982,29 +6651,23 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
5982
6651
  };
5983
6652
  try {
5984
6653
  const existing = await store.getConfig(DANGEROUS_OPERATION_GUARD_STANDARD_SLUG);
6654
+ if (isRetiredInstructionSource(existing))
6655
+ return existing;
5985
6656
  if (existing.content !== input.content || existing.description !== input.description || existing.category !== input.category || existing.agent !== input.agent || existing.format !== input.format || existing.kind !== input.kind || JSON.stringify(existing.tags) !== JSON.stringify(input.tags)) {
5986
6657
  return await store.updateConfig(existing.id, input);
5987
6658
  }
5988
6659
  return existing;
5989
- } catch {
6660
+ } catch (error) {
6661
+ if (!(error instanceof ConfigNotFoundError))
6662
+ throw error;
5990
6663
  return await store.createConfig(input);
5991
6664
  }
5992
6665
  }
5993
6666
 
5994
6667
  // src/lib/managed-skill-runtimes.ts
5995
- import { createHash as createHash4 } from "crypto";
5996
- import { spawnSync as spawnSync2 } from "child_process";
5997
- import {
5998
- existsSync as existsSync7,
5999
- lstatSync as lstatSync3,
6000
- mkdirSync as mkdirSync4,
6001
- readFileSync as readFileSync3,
6002
- renameSync,
6003
- rmSync as rmSync2,
6004
- writeFileSync as writeFileSync3
6005
- } from "fs";
6006
- import { homedir as homedir3 } from "os";
6007
- import { dirname as dirname4, join as join7, parse as parse2, relative as relative2, resolve as resolve5 } 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";
6008
6671
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
6009
6672
  var INBOX_SKILL_MARKERS = [
6010
6673
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -6013,395 +6676,93 @@ var INBOX_SKILL_MARKERS = [
6013
6676
  [".config", "opencode", "skills", "inbox", "SKILL.md"],
6014
6677
  [".cursor", "skills", "inbox", "SKILL.md"]
6015
6678
  ];
6016
- var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
6017
- function sha2564(content) {
6018
- return createHash4("sha256").update(content).digest("hex");
6019
- }
6020
- function lstatOrNull(path) {
6021
- try {
6022
- return lstatSync3(path);
6023
- } catch {
6024
- return null;
6025
- }
6026
- }
6027
- function findSymlinkedAncestor(path) {
6028
- const normalized = resolve5(path);
6029
- const parsed = parse2(normalized);
6030
- let current = parsed.root;
6031
- const rel = relative2(parsed.root, normalized);
6032
- for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
6033
- current = join7(current, segment);
6034
- if (!existsSync7(current))
6035
- return null;
6036
- if (lstatSync3(current).isSymbolicLink())
6037
- return current;
6038
- }
6039
- return null;
6040
- }
6041
- function assertNoSymlinkAncestors2(path) {
6042
- const found = findSymlinkedAncestor(path);
6043
- if (found !== null) {
6044
- throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
6045
- }
6046
- }
6047
- function packagedInboxSkillPath(explicitPath) {
6048
- if (explicitPath)
6049
- return explicitPath;
6050
- throw new Error("Bundled skill contracts are retired; use the Skills CLI to manage private skills");
6051
- }
6052
- function readCanonicalSkill(explicitPath) {
6053
- const assetPath = packagedInboxSkillPath(explicitPath);
6054
- const stat = lstatOrNull(assetPath);
6055
- if (!stat?.isFile()) {
6056
- throw new Error("packaged inbox skill contract is not a regular file");
6057
- }
6058
- const content = readFileSync3(assetPath, "utf8");
6059
- if (!content.includes("conversations watch --from <agent> --all")) {
6060
- throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
6061
- }
6062
- if (!content.includes("There is no separate")) {
6063
- throw new Error("packaged inbox skill contract does not retire the legacy executable");
6064
- }
6065
- return { content, sha256: sha2564(content) };
6066
- }
6067
- function runProbe(command, args) {
6068
- const result = spawnSync2(command, args, {
6069
- encoding: "utf8",
6070
- timeout: 5000,
6071
- stdio: ["ignore", "pipe", "pipe"]
6072
- });
6073
- if (result.error || result.status !== 0) {
6074
- return { ok: false, output: "" };
6075
- }
6076
- return {
6077
- ok: true,
6078
- output: `${result.stdout ?? ""}
6079
- ${result.stderr ?? ""}`.trim()
6080
- };
6081
- }
6082
- function parseVersion(output) {
6083
- return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
6084
- }
6085
- function compareVersions(left, right) {
6086
- const a = left.split(".").map(Number);
6087
- const b = right.split(".").map(Number);
6088
- for (let i = 0;i < Math.max(a.length, b.length); i++) {
6089
- const delta = (a[i] ?? 0) - (b[i] ?? 0);
6090
- if (delta !== 0)
6091
- return delta;
6092
- }
6093
- return 0;
6094
- }
6095
- function inspectSkillMarkers(homeDir2) {
6096
- return INBOX_SKILL_MARKERS.map((parts) => join7(homeDir2, ...parts)).map((path) => {
6097
- const stat = lstatOrNull(path);
6098
- if (!stat)
6099
- return null;
6100
- if (!stat.isFile()) {
6101
- 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;
6102
6691
  }
6103
- return {
6104
- path,
6105
- content: readFileSync3(path, "utf8"),
6106
- mode: stat.mode & 511,
6107
- regular: true
6108
- };
6109
- }).filter((snapshot) => snapshot !== null);
6110
- }
6111
- function inspectInbox(options) {
6112
- const homeDir2 = options.homeDir ?? homedir3();
6113
- const runtimeCommand = options.conversationsCommand ?? "conversations";
6114
- const snapshots = inspectSkillMarkers(homeDir2);
6115
- const skillPresent = snapshots.length > 0;
6116
- let canonicalContent = null;
6117
- let canonicalSha256 = null;
6118
- let assetError = null;
6119
- try {
6120
- const canonical = readCanonicalSkill(options.assetPath);
6121
- canonicalContent = canonical.content;
6122
- canonicalSha256 = canonical.sha256;
6123
- } catch (error) {
6124
- assetError = error instanceof Error ? error.message : String(error);
6125
- }
6126
- const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
6127
- const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
6128
- const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
6129
- const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
6130
- const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
6131
- const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
6132
- const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
6133
- const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
6134
- const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
6135
- const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
6136
- const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
6137
- let reason = "skill not installed";
6138
- if (skillPresent) {
6139
- const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
6140
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname4(path))).find((found) => found !== null);
6141
- if (nonRegular)
6142
- reason = "managed skill target is not a regular file";
6143
- else if (symlinkAncestor)
6144
- reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
6145
- else if (assetError)
6146
- reason = assetError;
6147
- else if (!versionProbe.ok)
6148
- reason = "conversations command unavailable";
6149
- else if (!runtimeVersion)
6150
- reason = "conversations version is unreadable";
6151
- else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
6152
- reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
6153
- } else if (!helpProbe.ok)
6154
- reason = "conversations watch help is unavailable";
6155
- else if (!supportsFrom || !supportsAll || !supportsFullContent) {
6156
- const missing = [
6157
- !supportsFrom ? "--from" : null,
6158
- !supportsAll ? "--all" : null,
6159
- !supportsFullContent ? "--full-content" : null
6160
- ].filter((flag) => flag !== null);
6161
- reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
6162
- } else if (staleMarkers.length > 0)
6163
- reason = "skill contract stale";
6164
- else if (hostedHeartbeat === "failed")
6165
- reason = "hosted heartbeat failed; manual fallback required";
6166
- else if (hostedHeartbeat === "unverified")
6167
- reason = "hosted heartbeat unverified; manual fallback required";
6168
- else if (!deliveryVerified)
6169
- reason = "hosted heartbeat passed; channel and DM delivery verification required";
6170
- else
6171
- reason = "ready";
6692
+ if (stat.isSymbolicLink() || current !== absolute && !stat.isDirectory())
6693
+ return true;
6172
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;
6173
6701
  return {
6174
- status: {
6175
- skill: "inbox",
6176
- runtime: "conversations watch",
6177
- minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
6178
- skill_present: skillPresent,
6179
- skill_markers: snapshots.map((snapshot) => snapshot.path),
6180
- skill_contracts_current: snapshots.length - staleMarkers.length,
6181
- stale_skill_markers: staleMarkers,
6182
- expected_skill_sha256: canonicalSha256,
6183
- runtime_command: runtimeCommand,
6184
- runtime_present: versionProbe.ok,
6185
- runtime_version: runtimeVersion,
6186
- watch_supports_from: supportsFrom,
6187
- watch_supports_all: supportsAll,
6188
- watch_supports_full_content: supportsFullContent,
6189
- hosted_heartbeat: hostedHeartbeat,
6190
- delivery_verified: deliveryVerified,
6191
- manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
6192
- healthy: !skillPresent || reason === "ready",
6193
- reason
6194
- },
6195
- canonicalContent,
6196
- 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"
6197
6721
  };
6198
6722
  }
6199
6723
  function inspectManagedSkillRuntimes(options = {}) {
6200
- const runtime = inspectInbox(options).status;
6201
- const installed = runtime.skill_present ? [runtime] : [];
6724
+ const runtime = inspectLegacyInbox(options);
6202
6725
  return {
6203
6726
  runtimes: [runtime],
6204
- skills_present: installed.length,
6205
- healthy: installed.filter((item) => item.healthy).length,
6206
- 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
6207
6730
  };
6208
6731
  }
6209
- function runtimeReadyForWrite(status) {
6210
- 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;
6211
- }
6212
- function projectUpdatedStatus(status, contractCount) {
6213
- const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
6214
- return {
6215
- ...status,
6216
- skill_contracts_current: contractCount,
6217
- stale_skill_markers: [],
6218
- manual_fallback_ready: true,
6219
- healthy,
6220
- 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"
6221
- };
6222
- }
6223
- function cleanup(path) {
6224
- rmSync2(path, { force: true });
6225
- }
6226
- function writeAtomic(path, content, mode) {
6227
- assertNoSymlinkAncestors2(dirname4(path));
6228
- const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
6229
- try {
6230
- mkdirSync4(dirname4(path), { recursive: true, mode: 493 });
6231
- writeFileSync3(tempPath, content, { mode, flag: "wx" });
6232
- renameSync(tempPath, path);
6233
- } finally {
6234
- cleanup(tempPath);
6235
- }
6236
- }
6237
- var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
6238
- lstat: lstatOrNull,
6239
- read: (path) => readFileSync3(path, "utf8"),
6240
- write: writeAtomic
6241
- };
6242
- function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
6243
- const written = [];
6244
- try {
6245
- for (const snapshot of snapshots) {
6246
- const currentStat = fileOperations.lstat(snapshot.path);
6247
- if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
6248
- throw new Error("managed skill changed after inspection; refusing a stale write");
6249
- }
6250
- fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
6251
- written.push(snapshot);
6252
- }
6253
- return { ok: true, error: null, rollback_conflicts: [] };
6254
- } catch (error) {
6255
- const rollbackConflicts = [];
6256
- for (const snapshot of written.reverse()) {
6257
- const currentStat = fileOperations.lstat(snapshot.path);
6258
- if (!currentStat?.isFile()) {
6259
- rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
6260
- continue;
6261
- }
6262
- let currentContent;
6263
- try {
6264
- currentContent = fileOperations.read(snapshot.path);
6265
- } catch {
6266
- rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
6267
- continue;
6268
- }
6269
- if (currentContent !== canonicalContent) {
6270
- rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
6271
- continue;
6272
- }
6273
- try {
6274
- fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
6275
- } catch {
6276
- rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
6277
- }
6278
- }
6279
- return {
6280
- ok: false,
6281
- error: error instanceof Error ? error.message : String(error),
6282
- rollback_conflicts: rollbackConflicts
6283
- };
6284
- }
6285
- }
6286
6732
  async function reconcileManagedSkillRuntimes(options = {}) {
6733
+ const runtime = inspectLegacyInbox(options);
6287
6734
  const dryRun = options.dryRun ?? false;
6288
- const before = inspectInbox(options);
6289
- const status = before.status;
6290
- if (!status.skill_present) {
6291
- return {
6292
- runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
6293
- changed: 0,
6294
- failed: 0,
6295
- dry_run: dryRun
6296
- };
6297
- }
6298
- if (before.snapshots.some((snapshot) => !snapshot.regular)) {
6299
- return {
6300
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6301
- changed: 0,
6302
- failed: 1,
6303
- dry_run: dryRun
6304
- };
6305
- }
6306
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname4(path))).find((found) => found !== null);
6307
- if (symlinkedAncestor) {
6308
- return {
6309
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6310
- changed: 0,
6311
- failed: 1,
6312
- dry_run: dryRun
6313
- };
6314
- }
6315
- if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
6316
- return {
6317
- runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
6318
- changed: 0,
6319
- failed: 1,
6320
- dry_run: dryRun
6321
- };
6322
- }
6323
- const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
6324
- if (staleSnapshots.length === 0) {
6325
- return {
6326
- runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
6327
- changed: 0,
6328
- failed: 0,
6329
- dry_run: dryRun
6330
- };
6331
- }
6332
- if (dryRun) {
6333
- const projected = projectUpdatedStatus(status, before.snapshots.length);
6334
- return {
6335
- runtimes: [{
6336
- ...projected,
6337
- action: "update",
6338
- dry_run: true,
6339
- skill_contracts_changed: staleSnapshots.length
6340
- }],
6341
- changed: 1,
6342
- failed: 0,
6343
- dry_run: true
6344
- };
6345
- }
6346
- const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
6347
- path: snapshot.path,
6348
- content: snapshot.content,
6349
- mode: snapshot.mode ?? 420
6350
- })), before.canonicalContent);
6351
- if (!transaction.ok) {
6352
- const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
6353
- const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
6354
- return {
6355
- runtimes: [{
6356
- ...status,
6357
- action: "failed",
6358
- dry_run: false,
6359
- skill_contracts_changed: 0,
6360
- reason
6361
- }],
6362
- changed: 0,
6363
- failed: 1,
6364
- dry_run: false
6365
- };
6366
- }
6367
- const after = inspectInbox(options).status;
6368
- const accepted = after.healthy || after.manual_fallback_ready;
6369
6735
  return {
6370
- runtimes: [{
6371
- ...after,
6372
- action: accepted ? "update" : "failed",
6373
- dry_run: false,
6374
- skill_contracts_changed: accepted ? staleSnapshots.length : 0
6375
- }],
6376
- changed: accepted ? 1 : 0,
6377
- failed: accepted ? 0 : 1,
6378
- 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
6379
6740
  };
6380
6741
  }
6381
6742
 
6382
6743
  // src/status.ts
6383
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
6744
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6384
6745
 
6385
6746
  // src/lib/package-version.ts
6386
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
6387
- import { dirname as dirname5, join as join8 } from "path";
6747
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
6748
+ import { dirname as dirname6, join as join10 } from "path";
6388
6749
  import { fileURLToPath } from "url";
6389
6750
  var cached = null;
6390
6751
  function getPackageVersion() {
6391
6752
  if (cached)
6392
6753
  return cached;
6393
6754
  try {
6394
- let dir = dirname5(fileURLToPath(import.meta.url));
6755
+ let dir = dirname6(fileURLToPath(import.meta.url));
6395
6756
  for (let i = 0;i < 8; i++) {
6396
- const pkgPath = join8(dir, "package.json");
6397
- if (existsSync8(pkgPath)) {
6757
+ const pkgPath = join10(dir, "package.json");
6758
+ if (existsSync7(pkgPath)) {
6398
6759
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
6399
6760
  if (pkg.name === "@hasna/instructions" && pkg.version) {
6400
6761
  cached = pkg.version;
6401
6762
  return cached;
6402
6763
  }
6403
6764
  }
6404
- const parent = dirname5(dir);
6765
+ const parent = dirname6(dir);
6405
6766
  if (parent === dir)
6406
6767
  break;
6407
6768
  dir = parent;
@@ -6470,7 +6831,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6470
6831
  continue;
6471
6832
  knownTargets += 1;
6472
6833
  const targetPath = expandPath(config.target_path);
6473
- if (!existsSync9(targetPath)) {
6834
+ if (!existsSync8(targetPath)) {
6474
6835
  missingTargets += 1;
6475
6836
  continue;
6476
6837
  }
@@ -6567,8 +6928,8 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6567
6928
 
6568
6929
  // src/lib/provider-context.ts
6569
6930
  import { createHash as createHash5 } from "crypto";
6570
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
6571
- import { join as join9 } from "path";
6931
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
6932
+ import { join as join11 } from "path";
6572
6933
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
6573
6934
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
6574
6935
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -6703,7 +7064,7 @@ function renderPerEndpointFragment(entry) {
6703
7064
  function renderProviderFragment(entry) {
6704
7065
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
6705
7066
  }
6706
- function sha2565(content) {
7067
+ function sha2564(content) {
6707
7068
  return createHash5("sha256").update(content).digest("hex");
6708
7069
  }
6709
7070
  function resolveAndRenderProviderContext(opts) {
@@ -6713,17 +7074,17 @@ function resolveAndRenderProviderContext(opts) {
6713
7074
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
6714
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;
6715
7076
  const content = renderProviderFragment(entry);
6716
- const dir = join9(opts.homeDir, PROVIDER_CONTEXT_DIR);
6717
- if (!existsSync10(dir))
6718
- mkdirSync5(dir, { recursive: true });
7077
+ const dir = join11(opts.homeDir, PROVIDER_CONTEXT_DIR);
7078
+ if (!existsSync9(dir))
7079
+ mkdirSync4(dir, { recursive: true });
6719
7080
  const filename = `${entry ? entry.key : "invariant"}.md`;
6720
- const fragmentPath = join9(dir, filename);
6721
- const fragmentSha256 = sha2565(content);
6722
- writeFileSync4(fragmentPath, content, "utf8");
6723
- const manifestPath = join9(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);
6724
7085
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
6725
7086
  try {
6726
- if (existsSync10(manifestPath)) {
7087
+ if (existsSync9(manifestPath)) {
6727
7088
  const parsed = JSON.parse(readFileSync6(manifestPath, "utf8"));
6728
7089
  if (parsed && typeof parsed === "object")
6729
7090
  manifest = parsed;
@@ -6739,7 +7100,7 @@ function resolveAndRenderProviderContext(opts) {
6739
7100
  rawModel: opts.rawModel || null
6740
7101
  };
6741
7102
  manifest.fragments = fragmentsObj;
6742
- writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
7103
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
6743
7104
  return {
6744
7105
  entry,
6745
7106
  rawEndpoint: opts.rawEndpoint,
@@ -6870,11 +7231,11 @@ function parseSessionSource(value, order) {
6870
7231
  if (!path)
6871
7232
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
6872
7233
  const absPath = resolveSessionPath(path);
6873
- if (!existsSync11(absPath))
7234
+ if (!existsSync10(absPath))
6874
7235
  throw new Error(`Instruction source file not found: ${absPath}`);
6875
7236
  const content = readSessionInstructionSourceFile(absPath);
6876
7237
  const source = sourceFromFilePath(absPath, content, order);
6877
- const resolvedId = id || source.id || basename(absPath);
7238
+ const resolvedId = id || source.id || basename2(absPath);
6878
7239
  return {
6879
7240
  ...source,
6880
7241
  id: resolvedId,
@@ -6914,7 +7275,7 @@ function sessionSourceReplacements(values) {
6914
7275
  return replacements;
6915
7276
  }
6916
7277
  function readSessionInstructionSourceFile(path) {
6917
- const stat = lstatSync4(path);
7278
+ const stat = lstatSync6(path);
6918
7279
  if (stat.isSymbolicLink()) {
6919
7280
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
6920
7281
  }
@@ -6947,11 +7308,13 @@ async function collectSessionSources(opts, tool, store) {
6947
7308
  const sources = (opts.source ?? []).map((value, index) => parseSessionSource(value, index));
6948
7309
  for (const value of opts.config ?? []) {
6949
7310
  const { layer, id } = parseLayeredReference(value);
6950
- sources.push(sourceFromConfig(await store.getConfig(id), sources.length, layer));
7311
+ const config = await store.getConfig(id);
7312
+ assertInstructionSource(config);
7313
+ sources.push(sourceFromConfig(config, sources.length, layer));
6951
7314
  }
6952
7315
  for (const value of opts.identityExport ?? []) {
6953
7316
  const path = resolveSessionPath(value);
6954
- if (!existsSync11(path))
7317
+ if (!existsSync10(path))
6955
7318
  throw new Error(`Identity instruction export not found: ${path}`);
6956
7319
  const parsed = JSON.parse(readFileSync7(path, "utf-8"));
6957
7320
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
@@ -6969,7 +7332,7 @@ async function collectSessionSources(opts, tool, store) {
6969
7332
  }
6970
7333
  async function loadOwnedClaudeAuthorities(store) {
6971
7334
  const configs = await store.listConfigs({ category: "rules", agent: "claude", kind: "file" });
6972
- return configs.filter((config) => config.target_path && basename(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
7335
+ return configs.filter((config) => config.target_path && basename2(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
6973
7336
  }
6974
7337
  async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-run") {
6975
7338
  const station = opts.stationProfile === false ? null : stationProfileSource();
@@ -6999,8 +7362,10 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
6999
7362
  }
7000
7363
  const profile = await store.getProfile(opts.compileProfile);
7001
7364
  const configs = await store.getProfileConfigs(profile.id);
7002
- const bindings = await store.getProfileConfigBindings(profile.id);
7003
- const assetBindings = await store.getProfileAssetBindings(profile.id);
7365
+ const bindings = await store.getProfileConfigBindings(profile.id, { requireExplicit: store.mode === "api" });
7366
+ if (store.mode === "api")
7367
+ assertHostedProfileBindings(profile.id, configs, bindings);
7368
+ const assetBindings = await store.getProfileAssetBindings(profile.id, { requireExplicit: store.mode === "api" });
7004
7369
  const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
7005
7370
  return planProfileSessionRender({
7006
7371
  profile_id: profile.id,
@@ -7010,6 +7375,24 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
7010
7375
  targetHome: opts.targetHome,
7011
7376
  projectRoot: opts.projectRoot,
7012
7377
  sessionId: opts.sessionId,
7378
+ ...store.mode === "api" && store.v1BaseUrl ? {
7379
+ refreshSelector: normalizeSessionHostedProfileSelector({
7380
+ schema: "hasna.instructions.hosted-profile-selector/v1",
7381
+ authority: store.v1BaseUrl,
7382
+ profileId: profile.id,
7383
+ providerVersion: opts.providerVersion,
7384
+ ...opts.providerVariant ? { providerVariant: opts.providerVariant } : {},
7385
+ ...opts.model ? { model: opts.model } : {},
7386
+ ...opts.path ? { path: opts.path } : {},
7387
+ manual: opts.manual ?? [],
7388
+ codewithNativeImports: opts.codewithNativeImports === true || process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "1" || process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "true",
7389
+ allowEmptySources: opts.allowEmptySources === true,
7390
+ stationProfile: opts.stationProfile !== false,
7391
+ checkGlobalCoverage: opts.checkGlobalCoverage === true,
7392
+ ...opts.assetSurface ? { assetSurface: opts.assetSurface } : {},
7393
+ ...opts.assetScope ? { assetScope: opts.assetScope } : {}
7394
+ })
7395
+ } : {},
7013
7396
  codewithNativeImports: opts.codewithNativeImports,
7014
7397
  allowEmptySources: opts.allowEmptySources,
7015
7398
  configs,
@@ -7085,12 +7468,12 @@ function readProjectContextBundleOption(value, allowMissing = false) {
7085
7468
  if (value === "-")
7086
7469
  return { json: readBoundedProjectContextStdin() };
7087
7470
  const path = resolveSessionPath(value);
7088
- if (!existsSync11(path)) {
7471
+ if (!existsSync10(path)) {
7089
7472
  if (allowMissing)
7090
7473
  return {};
7091
7474
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
7092
7475
  }
7093
- const stat = lstatSync4(path);
7476
+ const stat = lstatSync6(path);
7094
7477
  if (stat.isSymbolicLink() || !stat.isFile()) {
7095
7478
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
7096
7479
  }
@@ -7476,9 +7859,14 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
7476
7859
  }
7477
7860
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
7478
7861
  });
7479
- program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
7480
- const abs = resolve6(filePath);
7481
- if (!existsSync11(abs)) {
7862
+ program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").option("--expected-version <version>", "with --update, require the stored version to match atomically").action(async (filePath, opts) => {
7863
+ const expectedVersion = opts.expectedVersion === undefined ? undefined : Number(opts.expectedVersion);
7864
+ validateExpectedConfigVersion(expectedVersion);
7865
+ if (expectedVersion !== undefined && !opts.update) {
7866
+ throw new Error("--expected-version requires --update");
7867
+ }
7868
+ const abs = resolve8(filePath);
7869
+ if (!existsSync10(abs)) {
7482
7870
  console.error(chalk.red(`File not found: ${abs}`));
7483
7871
  process.exit(1);
7484
7872
  }
@@ -7513,10 +7901,11 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7513
7901
  const exactIndex = isReference ? existingOwners.findIndex((owner) => owner.name === name) : -1;
7514
7902
  const target = exactIndex >= 0 ? existingOwners[exactIndex] : existingOwners[0];
7515
7903
  const rest = existingOwners.filter((owner) => owner.id !== target.id);
7516
- if (content !== target.content) {
7904
+ if (content !== target.content && expectedVersion === undefined) {
7517
7905
  await store.createSnapshot(target.id, target.content, target.version);
7518
7906
  }
7519
7907
  config = await store.updateConfig(target.id, {
7908
+ expected_version: expectedVersion,
7520
7909
  content,
7521
7910
  format: storedFmt,
7522
7911
  is_template: (opts.template ?? false) || isTemplate,
@@ -7533,6 +7922,9 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7533
7922
  }
7534
7923
  return;
7535
7924
  }
7925
+ if (expectedVersion !== undefined) {
7926
+ throw new Error("--expected-version requires an existing config; no config was created");
7927
+ }
7536
7928
  config = await store.createConfig({
7537
7929
  name,
7538
7930
  kind: opts.kind ?? "file",
@@ -7661,7 +8053,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7661
8053
  for (const entry of entries) {
7662
8054
  if (!entry.isDirectory())
7663
8055
  continue;
7664
- const projDir = join10(absDir, entry.name);
8056
+ const projDir = join12(absDir, entry.name);
7665
8057
  const hasAgentConfig = [
7666
8058
  "CLAUDE.md",
7667
8059
  ".mcp.json",
@@ -7674,7 +8066,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7674
8066
  ".aicopilot",
7675
8067
  ".cursor",
7676
8068
  ".agents"
7677
- ].some((marker) => existsSync11(join10(projDir, marker)));
8069
+ ].some((marker) => existsSync10(join12(projDir, marker)));
7678
8070
  if (!hasAgentConfig)
7679
8071
  continue;
7680
8072
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -7722,7 +8114,7 @@ program.command("import <file>").description("Restore a v2 Instructions domain a
7722
8114
  });
7723
8115
  program.command("whoami").description("Show setup summary").action(async () => {
7724
8116
  const store = resolveConfigStore();
7725
- const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db");
8117
+ const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db");
7726
8118
  const stats = await store.getConfigStats();
7727
8119
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
7728
8120
  console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
@@ -7945,7 +8337,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
7945
8337
  process.exit(1);
7946
8338
  }
7947
8339
  });
7948
- 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) => {
7949
8341
  try {
7950
8342
  const store = resolveConfigStore();
7951
8343
  const { machine, profile } = await getMachineProfileContext(opts, store);
@@ -7982,14 +8374,6 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
7982
8374
  }
7983
8375
  if (report.failures.length > 0)
7984
8376
  process.exitCode = 1;
7985
- const runtimeReport = await reconcileManagedSkillRuntimes({
7986
- dryRun: opts.dryRun,
7987
- agent: opts.from,
7988
- deliveryVerified: opts.deliveryVerified
7989
- });
7990
- printManagedSkillRuntimeReport(runtimeReport);
7991
- if (runtimeReport.failed > 0)
7992
- process.exitCode = 1;
7993
8377
  console.log(chalk.dim(`
7994
8378
  ${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
7995
8379
  } catch (e) {
@@ -8106,7 +8490,7 @@ projectContextCmd.command("apply").description("Atomically write project context
8106
8490
  }
8107
8491
  });
8108
8492
  var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
8109
- sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").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; 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("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").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("--json", "output dry-run JSON").action(async (opts) => {
8493
+ sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").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("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").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("--json", "output dry-run JSON").action(async (opts) => {
8110
8494
  try {
8111
8495
  const tool = opts.tool;
8112
8496
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -8161,7 +8545,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
8161
8545
  process.exit(1);
8162
8546
  }
8163
8547
  });
8164
- 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; 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("--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) => {
8165
8549
  try {
8166
8550
  const tool = opts.tool;
8167
8551
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -8187,7 +8571,16 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8187
8571
  return;
8188
8572
  }
8189
8573
  const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
8190
- const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, ownedClaudeAuthorities });
8574
+ const parsePreimages = (values, option) => values.map((value) => {
8575
+ const separator = value.lastIndexOf("=");
8576
+ if (separator <= 0)
8577
+ throw new Error(`${option} requires relativepath=sha256.`);
8578
+ return { relativePath: value.slice(0, separator), sha256: value.slice(separator + 1) };
8579
+ });
8580
+ const adoptFiles = parsePreimages(opts.adoptFile, "--adopt-file");
8581
+ const reconcileFiles = parsePreimages(opts.reconcileFile, "--reconcile-file");
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 });
8191
8584
  if (opts.json) {
8192
8585
  printJson({
8193
8586
  ...result,
@@ -8228,7 +8621,7 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8228
8621
  console.log(chalk.dim(`global source coverage: ${globalCoverage.expectedSlugs.length}/${globalCoverage.expectedSlugs.length} complete`));
8229
8622
  }
8230
8623
  if (result.conflicts.length > 0) {
8231
- console.error(chalk.red(`Conflicts: ${result.conflicts.length}. Re-run with --force to overwrite unmanaged files.`));
8624
+ console.error(chalk.red(`Conflicts: ${result.conflicts.length}. Review and import existing instructions before adopting each exact preimage with --adopt-file.`));
8232
8625
  process.exitCode = 1;
8233
8626
  }
8234
8627
  } catch (e) {
@@ -8236,6 +8629,29 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8236
8629
  process.exit(1);
8237
8630
  }
8238
8631
  });
8632
+ sessionCmd.command("refresh").description("Fetch the recorded hosted profile, recompile and safely update a managed target; unchanged output is not rewritten").requiredOption("--target-home <path>", "existing managed global or project target containing its hosted profile selector").option("--dry-run", "fetch authoritative sources and preview without writing").option("--json", "output refresh receipt without instruction content").action(async (opts) => {
8633
+ try {
8634
+ const result = await refreshSessionRender({ targetHome: resolveSessionPath(opts.targetHome), store: resolveConfigStore(), dryRun: opts.dryRun });
8635
+ if (opts.json)
8636
+ printJson(result);
8637
+ else {
8638
+ console.log(`${result.status}: ${result.targetHome}`);
8639
+ console.log(`hosted profile: ${result.selector.profileId}`);
8640
+ console.log(`source hash: ${result.sourceHash}`);
8641
+ if (result.apply.snapshotPath)
8642
+ console.log(`snapshot: ${result.apply.snapshotPath}`);
8643
+ }
8644
+ if (result.status === "blocked")
8645
+ process.exitCode = 1;
8646
+ } catch (error) {
8647
+ const message = formatCliError(error);
8648
+ if (opts.json)
8649
+ printJson({ status: "failed", error: message });
8650
+ else
8651
+ console.error(chalk.red(message));
8652
+ process.exitCode = 1;
8653
+ }
8654
+ });
8239
8655
  sessionCmd.command("restore <snapshot>").description("Restore a session render snapshot only when applied files have not drifted").option("--dry-run", "preview restore actions and conflicts without writing").option("--json", "output restore JSON").action(async (snapshot, opts) => {
8240
8656
  try {
8241
8657
  const result = restoreSessionRenderSnapshot(resolveSessionPath(snapshot), {
@@ -8264,6 +8680,36 @@ sessionCmd.command("restore <snapshot>").description("Restore a session render s
8264
8680
  process.exit(1);
8265
8681
  }
8266
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
+ });
8267
8713
  var stationProfileCmd = program.command("station-profile").description("Generate and inject the compact station profile block into every session render");
8268
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) => {
8269
8715
  try {
@@ -8430,7 +8876,7 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
8430
8876
  });
8431
8877
  templateCmd.command("render <id>").description("Render a template config with variables and optionally apply to disk").option("--var <vars...>", "set variables as KEY=VALUE pairs").option("--env", "use environment variables to fill template vars").option("--apply", "write rendered output to target_path").option("--dry-run", "preview rendered output without writing").action(async (id, opts) => {
8432
8878
  try {
8433
- const { renderTemplate } = await import("../chunks/template-wgnzdn7h.js");
8879
+ const { renderTemplate } = await import("../chunks/template-e4qrxs8p.js");
8434
8880
  const c = await resolveConfigStore().getConfig(id);
8435
8881
  const vars = {};
8436
8882
  if (opts.var) {
@@ -8444,7 +8890,7 @@ templateCmd.command("render <id>").description("Render a template config with va
8444
8890
  }
8445
8891
  }
8446
8892
  if (opts.env) {
8447
- const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-wgnzdn7h.js");
8893
+ const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-e4qrxs8p.js");
8448
8894
  for (const v of extractTemplateVars2(c.content)) {
8449
8895
  if (!(v.name in vars) && process.env[v.name]) {
8450
8896
  vars[v.name] = process.env[v.name];
@@ -8493,7 +8939,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
8493
8939
  } else if (opts.all) {
8494
8940
  configs = await store.listConfigs(opts.category ? { kind: "file", category: opts.category } : { kind: "file" });
8495
8941
  } else {
8496
- const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-xtkbm5br.js");
8942
+ const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-6r1v8hd3.js");
8497
8943
  const slugs = [
8498
8944
  ...KNOWN_CONFIGS2.filter((k) => !k.rulesDir).map((k) => k.name)
8499
8945
  ];
@@ -8780,7 +9226,7 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
8780
9226
  } else if (target === "codex") {
8781
9227
  const { appendFileSync, existsSync: ex, mkdirSync: md } = await import("fs");
8782
9228
  const { dirname: dn, join: j } = await import("path");
8783
- const configPath = j(homedir4(), ".codex", "config.toml");
9229
+ const configPath = j(homedir5(), ".codex", "config.toml");
8784
9230
  const block = `
8785
9231
  [mcp_servers.configs]
8786
9232
  command = "${mcpBinary}"
@@ -8800,7 +9246,7 @@ args = []
8800
9246
  } else if (target === "antigravity") {
8801
9247
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
8802
9248
  const { dirname: dn, join: j } = await import("path");
8803
- const configPath = j(homedir4(), ".gemini", "config", "mcp_config.json");
9249
+ const configPath = j(homedir5(), ".gemini", "config", "mcp_config.json");
8804
9250
  let settings = {};
8805
9251
  if (ex(configPath)) {
8806
9252
  try {
@@ -8844,7 +9290,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8844
9290
  } else if (target === "codex") {
8845
9291
  const { existsSync: ex, writeFileSync: wf } = await import("fs");
8846
9292
  const { join: j } = await import("path");
8847
- const configPath = j(homedir4(), ".codex", "config.toml");
9293
+ const configPath = j(homedir5(), ".codex", "config.toml");
8848
9294
  if (!ex(configPath)) {
8849
9295
  console.log(chalk.dim("= Not installed in Codex (no config.toml)"));
8850
9296
  continue;
@@ -8868,7 +9314,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8868
9314
  console.log(chalk.green("\u2713") + " Removed from Codex");
8869
9315
  } else if (target === "antigravity") {
8870
9316
  const { existsSync: ex, readFileSync: rf, writeFileSync: wf } = await import("fs");
8871
- const configPath = join10(homedir4(), ".gemini", "config", "mcp_config.json");
9317
+ const configPath = join12(homedir5(), ".gemini", "config", "mcp_config.json");
8872
9318
  if (!ex(configPath)) {
8873
9319
  console.log(chalk.dim("= Not installed in Antigravity (no mcp_config.json)"));
8874
9320
  continue;
@@ -8903,15 +9349,15 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8903
9349
  if (!opts.confirmLocal) {
8904
9350
  throw new Error("migrate-legacy requires --confirm-local");
8905
9351
  }
8906
- const destinationPath = resolve6(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db"));
9352
+ const destinationPath = resolve8(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db"));
8907
9353
  const database = await import("../chunks/database-wstmr56z.js");
8908
9354
  database.getDatabase(destinationPath);
8909
9355
  database.resetDatabase();
8910
9356
  const { migrateLegacyInstructionsStore } = await import("../chunks/legacy-store-migration-z2m5yzyk.js");
8911
9357
  const result = migrateLegacyInstructionsStore({
8912
- ...opts.source ? { sourcePath: resolve6(opts.source) } : {},
9358
+ ...opts.source ? { sourcePath: resolve8(opts.source) } : {},
8913
9359
  destinationPath,
8914
- ...opts.backup ? { backupPath: resolve6(opts.backup) } : {},
9360
+ ...opts.backup ? { backupPath: resolve8(opts.backup) } : {},
8915
9361
  dryRun: !opts.apply,
8916
9362
  ...opts.mergePreserveDestination ? { mergePolicy: "preserve-destination" } : {},
8917
9363
  operatorConfirmed: true
@@ -8930,7 +9376,7 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8930
9376
  if (result.backupCreated)
8931
9377
  console.log(chalk.dim(" destination backup created before mutation"));
8932
9378
  });
8933
- program.command("init").description("First-time setup: sync all known configs, create default profile").option("--force", "delete existing DB and start fresh").action(async (opts) => {
9379
+ program.command("init").description("First-time setup: create default profiles; local file import into a hosted authority is explicit").option("--force", "delete existing DB and start fresh").option("--import-local", "explicitly import known local config files into the configured hosted authority").action(async (opts) => {
8934
9380
  const store = resolveConfigStore();
8935
9381
  if (opts.force) {
8936
9382
  try {
@@ -8944,26 +9390,28 @@ program.command("init").description("First-time setup: sync all known configs, c
8944
9390
  }
8945
9391
  console.log(chalk.bold(`@hasna/instructions \u2014 initializing
8946
9392
  `));
8947
- const result = await syncKnown({ store });
8948
- console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
8949
- if (result.skipped.length > 0) {
8950
- console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
9393
+ if (store.mode === "local" || opts.importLocal) {
9394
+ const result = await syncKnown({ store });
9395
+ console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
9396
+ if (result.skipped.length > 0)
9397
+ console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
9398
+ } else {
9399
+ console.log(chalk.dim("Hosted authority retained; use --import-local for an intentional local-file migration."));
8951
9400
  }
8952
9401
  const refs = [
8953
9402
  { slug: "workspace-structure", name: "Workspace Structure", category: "workspace", content: `# Workspace Structure
8954
9403
 
8955
- See ~/.claude/rules/workspace.md for full conventions.`, desc: "~/Workspace/ hierarchy and naming" },
8956
- { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# .secrets Schema
8957
-
8958
- Location: ~/.secrets (sourced by ~/.zshrc)
8959
- Format: export KEY_NAME="value"
9404
+ Resolve repositories and task worktrees through the configured repository manager. Use the configured scratch workspace for temporary execution artifacts. Keep durable project records in their owning application and follow the current project conventions.`, desc: "Repository, worktree, and execution scratch responsibilities" },
9405
+ { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# Credential References
8960
9406
 
8961
- Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`, desc: "Shape of ~/.secrets (no values)" }
9407
+ Store credential values in the configured Secrets authority. Application configuration records contain credential names and supported resolver references only. Resolve values through the approved consuming application at execution time; never embed values in instruction content, generated prompts, or shell profiles.`, desc: "Secret reference and runtime resolution conventions; no credential values" }
8962
9408
  ];
8963
9409
  for (const ref of refs) {
8964
9410
  try {
8965
9411
  await store.getConfig(ref.slug);
8966
- } catch {
9412
+ } catch (error) {
9413
+ if (!(error instanceof ConfigNotFoundError))
9414
+ throw error;
8967
9415
  await store.createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.desc });
8968
9416
  }
8969
9417
  }
@@ -8973,12 +9421,11 @@ Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`,
8973
9421
  await ensureProjectDashboardStandardConfig(store);
8974
9422
  try {
8975
9423
  await store.getProfile("my-setup");
8976
- } catch {
8977
- const p = await store.createProfile({ name: "my-setup", description: "Default profile with all known configs" });
8978
- const allConfigs = await store.listConfigs();
8979
- for (const c of allConfigs)
8980
- await store.addConfigToProfile(p.id, c.id);
8981
- console.log(chalk.green("\u2713") + ` Created profile "my-setup" with ${allConfigs.length} configs`);
9424
+ } catch (error) {
9425
+ if (!(error instanceof ProfileNotFoundError))
9426
+ throw error;
9427
+ await store.createProfile({ name: "my-setup", description: "Instruction profile; add explicitly reviewed and scoped source bindings" });
9428
+ console.log(chalk.green("\u2713") + ' Created empty profile "my-setup"; bind reviewed instruction sources explicitly.');
8982
9429
  }
8983
9430
  const machineProfiles = await ensurePlatformProfiles(store);
8984
9431
  console.log(chalk.green("\u2713") + ` Ensured ${machineProfiles.length} machine-aware profile(s)`);
@@ -8989,7 +9436,7 @@ DB stats:`));
8989
9436
  if (count > 0)
8990
9437
  console.log(` ${key.padEnd(18)} ${count}`);
8991
9438
  }
8992
- const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db");
9439
+ const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join12(getRawStoreRoot(), "instructions.db");
8993
9440
  console.log(chalk.dim(`
8994
9441
  ${store.mode === "api" ? "API" : "DB"}: ${location}`));
8995
9442
  });
@@ -9007,11 +9454,11 @@ program.command("status").description("Health check: total configs, drift from d
9007
9454
  console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
9008
9455
  console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
9009
9456
  console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
9010
- 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)`);
9011
9458
  console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
9012
9459
  });
9013
- var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
9014
- 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) => {
9015
9462
  const report = inspectManagedSkillRuntimes({
9016
9463
  agent: opts.from,
9017
9464
  deliveryVerified: opts.deliveryVerified
@@ -9023,7 +9470,7 @@ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted C
9023
9470
  return;
9024
9471
  }
9025
9472
  if (report.skills_present === 0) {
9026
- 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."));
9027
9474
  return;
9028
9475
  }
9029
9476
  for (const runtime of report.runtimes) {
@@ -9035,7 +9482,7 @@ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted C
9035
9482
  if (report.missing > 0)
9036
9483
  process.exitCode = 1;
9037
9484
  });
9038
- 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) => {
9039
9486
  const report = await reconcileManagedSkillRuntimes({
9040
9487
  dryRun: opts.dryRun,
9041
9488
  agent: opts.from,
@@ -9090,8 +9537,8 @@ storageCmd.command("status").description("Show redacted S3 configuration status
9090
9537
  });
9091
9538
  var storageBackupCmd = storageCmd.command("backup").description("Push, pull, and verify immutable Instructions backup objects");
9092
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) => {
9093
- const abs = resolve6(file);
9094
- if (!existsSync11(abs) || !lstatSync4(abs).isFile())
9540
+ const abs = resolve8(file);
9541
+ if (!existsSync10(abs) || !lstatSync6(abs).isFile())
9095
9542
  throw new Error(`Backup file not found: ${abs}`);
9096
9543
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
9097
9544
  import("../chunks/s3-config-hhght91k.js"),
@@ -9181,19 +9628,19 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9181
9628
  backupId,
9182
9629
  ...versions
9183
9630
  });
9184
- const output = resolve6(opts.output);
9185
- if (existsSync11(output)) {
9631
+ const output = resolve8(opts.output);
9632
+ if (existsSync10(output)) {
9186
9633
  if (!opts.force)
9187
9634
  throw new Error(`Refusing to overwrite existing backup file: ${output}`);
9188
- if (!lstatSync4(output).isFile() || lstatSync4(output).isSymbolicLink()) {
9635
+ if (!lstatSync6(output).isFile() || lstatSync6(output).isSymbolicLink()) {
9189
9636
  throw new Error(`Refusing to replace a non-regular backup destination: ${output}`);
9190
9637
  }
9191
9638
  }
9192
- mkdirSync6(dirname6(output), { recursive: true, mode: 448 });
9193
- const temp = join10(dirname6(output), `.${basename(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()}`);
9194
9641
  try {
9195
- writeFileSync5(temp, pulled.bytes, { flag: "wx", mode: 384 });
9196
- renameSync2(temp, output);
9642
+ writeFileSync4(temp, pulled.bytes, { flag: "wx", mode: 384 });
9643
+ renameSync(temp, output);
9197
9644
  } catch (error) {
9198
9645
  try {
9199
9646
  unlinkSync(temp);
@@ -9215,10 +9662,10 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9215
9662
  });
9216
9663
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
9217
9664
  const { mkdirSync: mk } = await import("fs");
9218
- const backupDir = join10(getRawStoreRoot(), "backups");
9665
+ const backupDir = join12(getRawStoreRoot(), "backups");
9219
9666
  mk(backupDir, { recursive: true });
9220
9667
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
9221
- const outPath = join10(backupDir, `configs-${ts}.tar.gz`);
9668
+ const outPath = join12(backupDir, `configs-${ts}.tar.gz`);
9222
9669
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
9223
9670
  const { statSync: st } = await import("fs");
9224
9671
  const size = st(outPath).size;
@@ -9244,9 +9691,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
9244
9691
  console.log(chalk.cyan("Known files on disk:"));
9245
9692
  for (const k of KNOWN_CONFIGS) {
9246
9693
  if (k.rulesDir) {
9247
- 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`);
9248
9695
  } else {
9249
- 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`);
9250
9697
  }
9251
9698
  }
9252
9699
  const allConfigs = await store.listConfigs();
@@ -9392,7 +9839,7 @@ program.command("watch").description("Watch known config files for changes and a
9392
9839
  const store = resolveConfigStore();
9393
9840
  const interval = Number(opts.interval);
9394
9841
  const { statSync: st } = await import("fs");
9395
- const { expandPath: expandPath2 } = await import("../chunks/apply-pnn7a3y7.js");
9842
+ const { expandPath: expandPath2 } = await import("../chunks/apply-fyfzqc7s.js");
9396
9843
  console.log(chalk.bold("@hasna/instructions watch") + chalk.dim(` \u2014 polling every ${interval}ms`));
9397
9844
  console.log(chalk.dim(`Watching known config files for changes\u2026
9398
9845
  `));
@@ -9400,16 +9847,16 @@ program.command("watch").description("Watch known config files for changes and a
9400
9847
  for (const k of KNOWN_CONFIGS) {
9401
9848
  if (k.rulesDir) {
9402
9849
  const absDir = expandPath2(k.rulesDir);
9403
- if (!existsSync11(absDir))
9850
+ if (!existsSync10(absDir))
9404
9851
  continue;
9405
9852
  const { readdirSync: readdirSync3 } = await import("fs");
9406
9853
  for (const f of readdirSync3(absDir).filter((f2) => f2.endsWith(".md"))) {
9407
- const abs = join10(absDir, f);
9854
+ const abs = join12(absDir, f);
9408
9855
  mtimes.set(abs, st(abs).mtimeMs);
9409
9856
  }
9410
9857
  } else {
9411
9858
  const abs = expandPath2(k.path);
9412
- if (existsSync11(abs))
9859
+ if (existsSync10(abs))
9413
9860
  mtimes.set(abs, st(abs).mtimeMs);
9414
9861
  }
9415
9862
  }
@@ -9417,7 +9864,7 @@ program.command("watch").description("Watch known config files for changes and a
9417
9864
  const tick = async () => {
9418
9865
  let changed = 0;
9419
9866
  for (const [abs, oldMtime] of mtimes) {
9420
- if (!existsSync11(abs))
9867
+ if (!existsSync10(abs))
9421
9868
  continue;
9422
9869
  const newMtime = st(abs).mtimeMs;
9423
9870
  if (newMtime !== oldMtime) {
@@ -9429,10 +9876,10 @@ program.command("watch").description("Watch known config files for changes and a
9429
9876
  for (const k of KNOWN_CONFIGS) {
9430
9877
  if (k.rulesDir) {
9431
9878
  const absDir = expandPath2(k.rulesDir);
9432
- if (!existsSync11(absDir))
9879
+ if (!existsSync10(absDir))
9433
9880
  continue;
9434
9881
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
9435
- const abs = join10(absDir, f);
9882
+ const abs = join12(absDir, f);
9436
9883
  if (!mtimes.has(abs)) {
9437
9884
  mtimes.set(abs, st(abs).mtimeMs);
9438
9885
  changed++;
@@ -9440,7 +9887,7 @@ program.command("watch").description("Watch known config files for changes and a
9440
9887
  }
9441
9888
  } else {
9442
9889
  const abs = expandPath2(k.path);
9443
- if (existsSync11(abs) && !mtimes.has(abs)) {
9890
+ if (existsSync10(abs) && !mtimes.has(abs)) {
9444
9891
  mtimes.set(abs, st(abs).mtimeMs);
9445
9892
  changed++;
9446
9893
  }
@@ -9468,7 +9915,7 @@ program.command("report").description("Summary of stored configs, drift, and eco
9468
9915
  if (!c.target_path)
9469
9916
  continue;
9470
9917
  const abs = expandPath(c.target_path);
9471
- if (!existsSync11(abs)) {
9918
+ if (!existsSync10(abs)) {
9472
9919
  missing++;
9473
9920
  continue;
9474
9921
  }
@@ -9539,7 +9986,7 @@ program.command("clean").description("Remove configs from DB whose target files
9539
9986
  if (!c.target_path)
9540
9987
  continue;
9541
9988
  const abs = expandPath(c.target_path);
9542
- if (!existsSync11(abs)) {
9989
+ if (!existsSync10(abs)) {
9543
9990
  if (printed < maxPrinted) {
9544
9991
  if (opts.dryRun) {
9545
9992
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -9563,7 +10010,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
9563
10010
  console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
9564
10011
  }
9565
10012
  });
9566
- 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) => {
9567
10014
  const store = resolveConfigStore();
9568
10015
  const packages = [
9569
10016
  { name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
@@ -9571,7 +10018,6 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
9571
10018
  { name: "@hasna/conversations", bin: "conversations", mcp: "conversations-mcp" },
9572
10019
  { name: "@hasna/skills", bin: "skills", mcp: "skills-mcp" },
9573
10020
  { name: "@hasna/economy", bin: "economy", mcp: "economy-mcp" },
9574
- { name: "@hasna/attachments", bin: "attachments", mcp: "attachments-mcp" },
9575
10021
  { name: "@hasna/emails", bin: "emails", mcp: "emails-mcp" },
9576
10022
  { name: "@hasna/recordings", bin: "recordings", mcp: "recordings-mcp" },
9577
10023
  { name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" }
@@ -9623,20 +10069,8 @@ Initializing configs:`));
9623
10069
  } else {
9624
10070
  console.log(chalk.dim(" would run: configs init"));
9625
10071
  }
9626
- console.log(chalk.cyan(`
9627
- Reconciling managed skill runtimes:`));
9628
- const runtimeReport = await reconcileManagedSkillRuntimes({
9629
- dryRun: opts.dryRun,
9630
- agent: opts.from,
9631
- deliveryVerified: opts.deliveryVerified
9632
- });
9633
- printManagedSkillRuntimeReport(runtimeReport);
9634
- if (runtimeReport.failed > 0) {
9635
- console.error(chalk.red(`
9636
- Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
9637
- process.exitCode = 1;
9638
- return;
9639
- }
10072
+ console.log(chalk.dim(`
10073
+ Manage skill publication, selection and agent hooks through the Skills CLI.`));
9640
10074
  console.log(chalk.bold(`
9641
10075
  \u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
9642
10076
  });
@@ -9682,7 +10116,7 @@ providerContextCmd.command("resolve").description("Resolve the endpoint to a pro
9682
10116
  try {
9683
10117
  const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
9684
10118
  const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
9685
- const homeDir2 = opts.home ?? homedir4();
10119
+ const homeDir2 = opts.home ?? homedir5();
9686
10120
  const origin = normalizeEndpointOrigin(rawEndpoint);
9687
10121
  const resolution = resolveAndRenderProviderContext({
9688
10122
  origin,