@hasna/instructions 0.7.0 → 0.7.2

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 (74) hide show
  1. package/README.md +52 -11
  2. package/dist/chunks/{apply-7svah2qm.js → apply-ps6jjt6k.js} +3 -3
  3. package/dist/chunks/{apply-jp13sa1e.js → apply-wj7wh5km.js} +3 -3
  4. package/dist/chunks/{index-1cnyjnd7.js → index-18j7x3xw.js} +2 -2
  5. package/dist/chunks/{index-3djthcm6.js → index-27az1qb1.js} +28 -1
  6. package/dist/chunks/{index-fwbs6tma.js → index-ape24zc0.js} +2 -2
  7. package/dist/chunks/{index-bzyxvwcd.js → index-ek15201v.js} +2 -2
  8. package/dist/chunks/{index-arrrx0ed.js → index-mvckkf15.js} +39 -20
  9. package/dist/chunks/{index-7m4j8a33.js → index-n6nh6f5r.js} +235 -20
  10. package/dist/chunks/{index-4pfq42t5.js → index-pq18fdbj.js} +146 -32
  11. package/dist/chunks/{index-n3fjspr0.js → index-preyvj8n.js} +28 -1
  12. package/dist/chunks/{index-z3ma4aqk.js → index-y8410ve0.js} +3 -3
  13. package/dist/chunks/{local-5wvv5xv3.js → local-1c1qxzvx.js} +155 -4
  14. package/dist/chunks/{local-zxa5dpf4.js → local-hc67bynj.js} +155 -4
  15. package/dist/chunks/{s3-backup-n2z4fwyh.js → s3-backup-hxe3zpfz.js} +1 -1
  16. package/dist/chunks/{s3-object-store-90tgbh6k.js → s3-object-store-n0s82swt.js} +1 -1
  17. package/dist/chunks/{sync-r8t7zsp3.js → sync-12m3vs6g.js} +4 -4
  18. package/dist/chunks/{sync-2075deve.js → sync-azd9t4p8.js} +4 -4
  19. package/dist/chunks/{template-wgnzdn7h.js → template-e4qrxs8p.js} +1 -1
  20. package/dist/chunks/{template-fsfxpe4p.js → template-nhn124zd.js} +1 -1
  21. package/dist/cli/index.js +860 -181
  22. package/dist/data/config-store.d.ts +22 -6
  23. package/dist/data/config-store.d.ts.map +1 -1
  24. package/dist/db/configs.d.ts +4 -1
  25. package/dist/db/configs.d.ts.map +1 -1
  26. package/dist/db/local.d.ts +1 -1
  27. package/dist/db/local.d.ts.map +1 -1
  28. package/dist/index.d.ts +5 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +940 -134
  31. package/dist/lib/asset-plan.d.ts.map +1 -1
  32. package/dist/lib/codewith-shared-todos-storage-standard.d.ts.map +1 -1
  33. package/dist/lib/compact-output.d.ts +2 -18
  34. package/dist/lib/compact-output.d.ts.map +1 -1
  35. package/dist/lib/dangerous-operation-guard-standard.d.ts.map +1 -1
  36. package/dist/lib/export.d.ts.map +1 -1
  37. package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
  38. package/dist/lib/global-source-coverage.d.ts +12 -7
  39. package/dist/lib/global-source-coverage.d.ts.map +1 -1
  40. package/dist/lib/import.d.ts.map +1 -1
  41. package/dist/lib/instruction-graph.d.ts +1 -1
  42. package/dist/lib/instruction-graph.d.ts.map +1 -1
  43. package/dist/lib/instruction-source-policy.d.ts +6 -0
  44. package/dist/lib/instruction-source-policy.d.ts.map +1 -0
  45. package/dist/lib/platform-profiles.d.ts.map +1 -1
  46. package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
  47. package/dist/lib/session-apply.d.ts +24 -0
  48. package/dist/lib/session-apply.d.ts.map +1 -1
  49. package/dist/lib/session-refresh.d.ts +30 -0
  50. package/dist/lib/session-refresh.d.ts.map +1 -0
  51. package/dist/lib/session-render.d.ts +34 -1
  52. package/dist/lib/session-render.d.ts.map +1 -1
  53. package/dist/lib/transforms.d.ts +1 -0
  54. package/dist/lib/transforms.d.ts.map +1 -1
  55. package/dist/mcp/index.js +23 -22
  56. package/dist/mcp/server.d.ts.map +1 -1
  57. package/dist/sdk/index.js +46 -3
  58. package/dist/sdk/v1-client.d.ts +10 -3
  59. package/dist/sdk/v1-client.d.ts.map +1 -1
  60. package/dist/sdk/v1.generated.d.ts +52 -2
  61. package/dist/sdk/v1.generated.d.ts.map +1 -1
  62. package/dist/server/index.d.ts +1 -1
  63. package/dist/server/index.d.ts.map +1 -1
  64. package/dist/server/index.js +215 -15
  65. package/dist/server/openapi.d.ts +240 -3
  66. package/dist/server/openapi.d.ts.map +1 -1
  67. package/dist/server/v1.d.ts.map +1 -1
  68. package/dist/storage/cloud-store.d.ts +2 -15
  69. package/dist/storage/cloud-store.d.ts.map +1 -1
  70. package/dist/storage/index.js +39 -20
  71. package/dist/storage/s3-object-store.d.ts.map +1 -1
  72. package/dist/types/index.d.ts +49 -2
  73. package/dist/types/index.d.ts.map +1 -1
  74. package/package.json +3 -3
package/dist/cli/index.js CHANGED
@@ -9,31 +9,39 @@ import {
9
9
  syncKnown,
10
10
  syncProject,
11
11
  syncToDisk
12
- } from "../chunks/index-1cnyjnd7.js";
12
+ } from "../chunks/index-18j7x3xw.js";
13
13
  import {
14
14
  applyConfigsWithReport,
15
15
  compactPathForConfigHome,
16
16
  expandPath,
17
- normalizeTargetPath
18
- } from "../chunks/index-z3ma4aqk.js";
17
+ normalizeTargetPath,
18
+ sessionRenderOwnsPath
19
+ } from "../chunks/index-y8410ve0.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-7m4j8a33.js";
72
+ } from "../chunks/index-n6nh6f5r.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,9 @@ 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";
3798
+ import { existsSync as existsSync11, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync5, writeSync } from "fs";
3788
3799
  import { homedir as homedir4 } from "os";
3789
- import { basename, dirname as dirname6, join as join10, resolve as resolve6 } from "path";
3800
+ import { basename as basename2, dirname as dirname7, join as join11, resolve as resolve7 } from "path";
3790
3801
 
3791
3802
  // src/lib/config-target-identity.ts
3792
3803
  function findConfigsByTargetPath(configs, targetPath) {
@@ -3878,7 +3889,12 @@ function logicalAssetLocator(locator, idToSlug) {
3878
3889
  }
3879
3890
  function logicalCollections(domain, options) {
3880
3891
  const configs = [...domain.configs].sort((left, right) => compareText(left.slug, right.slug));
3881
- const snapshots = [...domain.config_snapshots].sort((left, right) => compareText(left.config_slug, right.config_slug) || left.version - right.version || compareText(left.id, right.id));
3892
+ const snapshots = domain.config_snapshots.map((snapshot) => ({
3893
+ config_slug: snapshot.config_slug,
3894
+ version: snapshot.version,
3895
+ content: snapshot.content,
3896
+ ...options.includeOperationalTimestamps ? { created_at: snapshot.created_at } : {}
3897
+ })).sort((left, right) => compareText(left.config_slug, right.config_slug) || left.version - right.version || compareText(left.content, right.content) || compareText("created_at" in left ? left.created_at ?? "" : "", "created_at" in right ? right.created_at ?? "" : ""));
3882
3898
  const profiles = [...domain.profiles].sort((left, right) => compareText(left.slug, right.slug));
3883
3899
  const configBindings = [...domain.profile_config_bindings].sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.config_slug, right.config_slug));
3884
3900
  const assetBindings = [...domain.profile_asset_bindings].sort((left, right) => compareText(left.profile_slug, right.profile_slug) || left.sort_order - right.sort_order || compareText(left.binding.assetKey, right.binding.assetKey));
@@ -3907,12 +3923,7 @@ function logicalCollections(domain, options) {
3907
3923
  synced_at: config.synced_at
3908
3924
  } : {}
3909
3925
  })),
3910
- config_snapshots: snapshots.map((snapshot) => ({
3911
- config_slug: snapshot.config_slug,
3912
- version: snapshot.version,
3913
- content: snapshot.content,
3914
- ...options.includeOperationalTimestamps ? { created_at: snapshot.created_at } : {}
3915
- })),
3926
+ config_snapshots: snapshots,
3916
3927
  profiles: profiles.map((profile) => ({
3917
3928
  slug: profile.slug,
3918
3929
  name: profile.name,
@@ -4036,10 +4047,11 @@ function validateInstructionsDomainArchive(value) {
4036
4047
  assertUnique(typed.configs.map((row) => row.slug), "config slug");
4037
4048
  assertUnique(typed.profiles.map((row) => row.slug), "profile slug");
4038
4049
  assertUnique(typed.machines.map((row) => row.hostname), "machine hostname");
4039
- assertUnique(typed.config_snapshots.map((row) => `${row.config_slug}@${row.version}`), "config snapshot");
4050
+ assertUnique(typed.config_snapshots.map((row) => row.id), "config snapshot id");
4040
4051
  assertUnique(typed.profile_config_bindings.map((row) => `${row.profile_slug}:${row.config_slug}`), "profile config binding");
4041
4052
  assertUnique(typed.profile_asset_bindings.map((row) => `${row.profile_slug}:${row.binding.assetKey}`), "profile asset binding");
4042
- const configSlugs = new Set(typed.configs.map((row) => row.slug));
4053
+ const configsBySlug = new Map(typed.configs.map((row) => [row.slug, row]));
4054
+ const configSlugs = new Set(configsBySlug.keys());
4043
4055
  const profileSlugs = new Set(typed.profiles.map((row) => row.slug));
4044
4056
  for (const config of typed.configs) {
4045
4057
  assertTimestamp(config.created_at, `config ${config.slug} created_at`);
@@ -4047,24 +4059,15 @@ function validateInstructionsDomainArchive(value) {
4047
4059
  assertTimestamp(config.synced_at, `config ${config.slug} synced_at`, true);
4048
4060
  if (!Number.isSafeInteger(config.version) || config.version < 1)
4049
4061
  throw new Error(`Invalid v2 archive: config ${config.slug} has invalid version`);
4050
- const snapshots = typed.config_snapshots.filter((row) => row.config_slug === config.slug).sort((a, b) => a.version - b.version);
4051
- if (snapshots.some((row) => !Number.isSafeInteger(row.version) || row.version < 1 || row.version > config.version)) {
4052
- throw new Error(`Invalid v2 archive: config ${config.slug} has an invalid snapshot version`);
4053
- }
4054
- if (snapshots.length) {
4055
- const first = snapshots[0].version;
4056
- const expected = Array.from({ length: config.version - first + 1 }, (_, index) => first + index);
4057
- if (canonicalDomainJson(snapshots.map((row) => row.version)) !== canonicalDomainJson(expected) || snapshots.at(-1).version !== config.version) {
4058
- throw new Error(`Invalid v2 archive: snapshots for ${config.slug} must be a contiguous suffix ending at version ${config.version}`);
4059
- }
4060
- if (snapshots.at(-1).content !== config.content) {
4061
- throw new Error(`Invalid v2 archive: latest snapshot content differs from config ${config.slug}`);
4062
- }
4063
- }
4064
4062
  }
4065
4063
  for (const row of typed.config_snapshots) {
4066
- if (!configSlugs.has(row.config_slug))
4064
+ const config = configsBySlug.get(row.config_slug);
4065
+ if (!config) {
4067
4066
  throw new Error(`Invalid v2 archive: snapshot references missing config ${row.config_slug}`);
4067
+ }
4068
+ if (!Number.isSafeInteger(row.version) || row.version < 1 || row.version > config.version) {
4069
+ throw new Error(`Invalid v2 archive: config ${row.config_slug} has an invalid snapshot version`);
4070
+ }
4068
4071
  assertTimestamp(row.created_at, `snapshot ${row.config_slug}@${row.version} created_at`);
4069
4072
  }
4070
4073
  for (const profile of typed.profiles) {
@@ -4318,14 +4321,11 @@ function configInput(config, content) {
4318
4321
  };
4319
4322
  }
4320
4323
  async function restoreNewConfig(store, archived, domain) {
4321
- const snapshots = domain.config_snapshots.filter((row) => row.config_slug === archived.slug).sort((left, right) => left.version - right.version);
4322
- const byVersion = new Map(snapshots.map((snapshot) => [snapshot.version, snapshot.content]));
4323
- let content = byVersion.get(1) ?? snapshots[0]?.content ?? archived.content;
4324
- let restored = await store.createConfig(configInput(archived, content));
4324
+ const snapshots = domain.config_snapshots.filter((row) => row.config_slug === archived.slug).sort((left, right) => left.version - right.version || compareText2(left.content, right.content) || compareText2(left.id, right.id));
4325
+ let restored = await store.createConfig(configInput(archived, archived.content));
4325
4326
  for (let version = 2;version <= archived.version; version++) {
4326
- content = byVersion.get(version) ?? (version === archived.version ? archived.content : content);
4327
4327
  restored = await store.updateConfig(restored.id, {
4328
- content,
4328
+ content: archived.content,
4329
4329
  ...version === archived.version ? {
4330
4330
  name: archived.name,
4331
4331
  kind: archived.kind,
@@ -4341,9 +4341,12 @@ async function restoreNewConfig(store, archived, domain) {
4341
4341
  } : {}
4342
4342
  });
4343
4343
  }
4344
- await store.pruneSnapshots(restored.id, snapshots.length);
4345
- const restoredSnapshots = (await store.listSnapshots(restored.id)).map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent })).sort((left, right) => left.version - right.version);
4346
- const expectedSnapshots = snapshots.map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent }));
4344
+ await store.pruneSnapshots(restored.id, 0);
4345
+ for (const snapshot of snapshots) {
4346
+ await store.createSnapshot(restored.id, snapshot.content, snapshot.version);
4347
+ }
4348
+ const restoredSnapshots = (await store.listSnapshots(restored.id)).map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent })).sort((left, right) => left.version - right.version || compareText2(left.content, right.content));
4349
+ const expectedSnapshots = snapshots.map(({ version, content: snapshotContent }) => ({ version, content: snapshotContent })).sort((left, right) => left.version - right.version || compareText2(left.content, right.content));
4347
4350
  if (restored.version !== archived.version || restored.content !== archived.content || canonicalDomainJson(restoredSnapshots) !== canonicalDomainJson(expectedSnapshots)) {
4348
4351
  throw new Error(`restored config or snapshot history did not match archive for ${archived.slug}`);
4349
4352
  }
@@ -4538,14 +4541,20 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4538
4541
  const targetHome = assertSafeTargetHome(plan.targetHome);
4539
4542
  assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
4540
4543
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
4541
- const files = [...payloadFiles, plan.manifestFile];
4542
4544
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
4545
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4543
4546
  const previousManifest = readPreviousManifest(manifestPath);
4544
4547
  const previousHashes = previousManifest ? new Map(previousManifest.files.map((file) => [file.relativePath, file.sha256])) : new Map;
4548
+ const adoptions = validateFileAdoptions(plan, targetHome, previousHashes, options);
4549
+ const adoptedHashes = new Map(adoptions.map((entry) => [entry.relativePath, entry.preimageSha256]));
4550
+ const reconciliations = validateFileReconciliations(plan, targetHome, previousManifest, options);
4551
+ const reconciledHashes = new Map(reconciliations.map((entry) => [entry.relativePath, entry.preimageSha256]));
4552
+ const manifestFile = manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations);
4553
+ const files = [...payloadFiles, manifestFile];
4545
4554
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
4546
4555
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
4547
4556
  const results = [
4548
- ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options)),
4557
+ ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
4549
4558
  ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
4550
4559
  ];
4551
4560
  assertNotSilentManagedWipeout(plan, results);
@@ -4568,7 +4577,9 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4568
4577
  skippedSources: plan.manifest.skippedSources,
4569
4578
  files: results,
4570
4579
  conflicts,
4571
- drift
4580
+ drift,
4581
+ adoptions,
4582
+ reconciliations
4572
4583
  };
4573
4584
  }
4574
4585
  let snapshotPath = null;
@@ -4581,10 +4592,12 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4581
4592
  if (!options.dryRun) {
4582
4593
  const allowPortableFallback = coordination === null;
4583
4594
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
4595
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4584
4596
  ensureSessionTargetHome(targetHome);
4585
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps);
4597
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
4586
4598
  snapshotPath = rollback.snapshotPath;
4587
4599
  options.test_hooks?.before_apply_writes?.({ plan, results });
4600
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4588
4601
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
4589
4602
  for (const file of payloadFiles) {
4590
4603
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -4604,7 +4617,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4604
4617
  });
4605
4618
  coordination?.assert_held();
4606
4619
  }
4607
- applyPlannedFile(plan, plan.manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4620
+ applyPlannedFile(plan, manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4608
4621
  }
4609
4622
  return {
4610
4623
  dryRun: options.dryRun ?? false,
@@ -4618,9 +4631,171 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4618
4631
  skippedSources: plan.manifest.skippedSources,
4619
4632
  files: results,
4620
4633
  conflicts,
4621
- drift
4634
+ drift,
4635
+ adoptions,
4636
+ reconciliations
4622
4637
  };
4623
4638
  }
4639
+ function assertManifestPrecondition(path, targetHome, expected) {
4640
+ if (expected === undefined)
4641
+ return;
4642
+ if (!/^[a-f0-9]{64}$/.test(expected)) {
4643
+ throw new SessionApplyError("Expected session manifest SHA-256 must contain 64 lowercase hexadecimal characters.");
4644
+ }
4645
+ if (currentSessionFileHash(path, targetHome) !== expected) {
4646
+ throw new SessionApplyError("Session manifest SHA-256 precondition failed; reload the manifest and resolve a new plan.");
4647
+ }
4648
+ }
4649
+ function validateFileAdoptions(plan, targetHome, previousHashes, options) {
4650
+ const requests = options.adoptFiles ?? [];
4651
+ if (!Array.isArray(requests))
4652
+ throw new SessionApplyError("Session file adoptions must be an array.");
4653
+ if (requests.length > 0 && options.force) {
4654
+ throw new SessionApplyError("Exact file adoption cannot be combined with force.");
4655
+ }
4656
+ const seen = new Set;
4657
+ return requests.map((request) => {
4658
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
4659
+ throw new SessionApplyError("Each file adoption requires a plan-relative path and a 64-character lowercase SHA-256.");
4660
+ }
4661
+ if (seen.has(request.relativePath)) {
4662
+ throw new SessionApplyError(`Duplicate file adoption target: ${request.relativePath}`);
4663
+ }
4664
+ seen.add(request.relativePath);
4665
+ const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
4666
+ if (candidates.length !== 1 || candidates[0].role === "manifest") {
4667
+ throw new SessionApplyError(`File adoption target is not a unique planned instruction output: ${request.relativePath}`);
4668
+ }
4669
+ const file = candidates[0];
4670
+ const path = resolvePlannedFilePath(plan, file, targetHome);
4671
+ if (previousHashes.has(file.relativePath) || sessionRenderOwnsPath(path)) {
4672
+ throw new SessionApplyError(`File adoption target is already managed: ${request.relativePath}`);
4673
+ }
4674
+ const observedSha256 = readExactFilePreimage(path, targetHome, request, "adoption");
4675
+ return {
4676
+ path,
4677
+ relativePath: file.relativePath,
4678
+ preimageSha256: observedSha256,
4679
+ renderedSha256: file.sha256,
4680
+ sourceIds: [...file.sourceIds]
4681
+ };
4682
+ });
4683
+ }
4684
+ function readExactFilePreimage(path, targetHome, request, operation) {
4685
+ if (currentSessionFileHash(path, targetHome) === null) {
4686
+ throw new SessionApplyError(`File ${operation} target does not exist: ${request.relativePath}`);
4687
+ }
4688
+ const bytes = readFileSync(path);
4689
+ if (!bytes.equals(Buffer.from(bytes.toString("utf8"), "utf8"))) {
4690
+ throw new SessionApplyError(`File ${operation} target must contain losslessly restorable UTF-8: ${request.relativePath}`);
4691
+ }
4692
+ const observedSha256 = createHash3("sha256").update(bytes).digest("hex");
4693
+ if (observedSha256 !== request.sha256) {
4694
+ throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${request.relativePath}`);
4695
+ }
4696
+ return observedSha256;
4697
+ }
4698
+ function validateFileReconciliations(plan, targetHome, previousManifest, options) {
4699
+ const requests = options.reconcileFiles ?? [];
4700
+ if (!Array.isArray(requests))
4701
+ throw new SessionApplyError("Session file reconciliations must be an array.");
4702
+ if (requests.length === 0)
4703
+ return [];
4704
+ if (options.force)
4705
+ throw new SessionApplyError("Exact file reconciliation cannot be combined with force.");
4706
+ if (options.expectedManifestSha256 === undefined) {
4707
+ throw new SessionApplyError("Exact file reconciliation requires an expected manifest SHA-256 precondition.");
4708
+ }
4709
+ if (!previousManifest || previousManifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || previousManifest.tool !== plan.tool || previousManifest.targetHome !== targetHome) {
4710
+ throw new SessionApplyError("File reconciliation requires this renderer's manifest for the same tool and target home.");
4711
+ }
4712
+ const seen = new Set;
4713
+ return requests.map((request) => {
4714
+ if (!request || typeof request.relativePath !== "string" || typeof request.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.sha256)) {
4715
+ throw new SessionApplyError("Each file reconciliation requires a plan-relative path and a 64-character lowercase SHA-256.");
4716
+ }
4717
+ if (seen.has(request.relativePath)) {
4718
+ throw new SessionApplyError(`Duplicate file reconciliation target: ${request.relativePath}`);
4719
+ }
4720
+ seen.add(request.relativePath);
4721
+ const candidates = plan.files.filter((file2) => file2.relativePath === request.relativePath);
4722
+ if (candidates.length !== 1 || candidates[0].role === "manifest") {
4723
+ throw new SessionApplyError(`File reconciliation target is not a unique planned instruction output: ${request.relativePath}`);
4724
+ }
4725
+ const file = candidates[0];
4726
+ const path = resolvePlannedFilePath(plan, file, targetHome);
4727
+ const owned = previousManifest.files.filter((entry) => entry.relativePath === file.relativePath);
4728
+ if (owned.length !== 1 || owned[0].path !== path || owned[0].role !== file.role || !/^[a-f0-9]{64}$/.test(owned[0].sha256)) {
4729
+ throw new SessionApplyError(`File reconciliation target is not owned by the observed manifest: ${request.relativePath}`);
4730
+ }
4731
+ const observedSha256 = readExactFilePreimage(path, targetHome, request, "reconciliation");
4732
+ if (observedSha256 === owned[0].sha256) {
4733
+ throw new SessionApplyError(`File reconciliation target has no managed drift: ${request.relativePath}`);
4734
+ }
4735
+ if (observedSha256 === file.sha256) {
4736
+ throw new SessionApplyError(`File reconciliation target already matches the planned output: ${request.relativePath}`);
4737
+ }
4738
+ return {
4739
+ path,
4740
+ relativePath: file.relativePath,
4741
+ preimageSha256: observedSha256,
4742
+ renderedSha256: file.sha256,
4743
+ previousManagedSha256: owned[0].sha256,
4744
+ sourceIds: [...file.sourceIds]
4745
+ };
4746
+ });
4747
+ }
4748
+ function manifestWithFileProvenance(plan, previousManifest, adoptions, reconciliations) {
4749
+ if (adoptions.length === 0 && previousManifest?.adoptions === undefined && reconciliations.length === 0 && previousManifest?.reconciliations === undefined)
4750
+ return plan.manifestFile;
4751
+ if (previousManifest?.adoptions !== undefined && !Array.isArray(previousManifest.adoptions)) {
4752
+ throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
4753
+ }
4754
+ const entries = new Map;
4755
+ for (const entry of [
4756
+ ...previousManifest?.adoptions ?? [],
4757
+ ...adoptions.map(({ path: _path, ...receipt }) => receipt)
4758
+ ]) {
4759
+ 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")) {
4760
+ throw new SessionApplyError("Previous session manifest adoption provenance is invalid.");
4761
+ }
4762
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
4763
+ const previous = entries.get(entry.relativePath);
4764
+ if (previous && JSON.stringify(previous) !== JSON.stringify(entry)) {
4765
+ throw new SessionApplyError(`Conflicting session file adoption provenance: ${entry.relativePath}`);
4766
+ }
4767
+ entries.set(entry.relativePath, entry);
4768
+ }
4769
+ if (previousManifest?.reconciliations !== undefined && !Array.isArray(previousManifest.reconciliations)) {
4770
+ throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
4771
+ }
4772
+ const reconciledEntries = new Map;
4773
+ for (const entry of [
4774
+ ...previousManifest?.reconciliations ?? [],
4775
+ ...reconciliations.map(({ path: _path, ...receipt }) => receipt)
4776
+ ]) {
4777
+ 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")) {
4778
+ throw new SessionApplyError("Previous session manifest reconciliation provenance is invalid.");
4779
+ }
4780
+ resolveManifestRelativePath(entry.relativePath, plan.targetHome);
4781
+ const canonical = {
4782
+ relativePath: entry.relativePath,
4783
+ preimageSha256: entry.preimageSha256,
4784
+ renderedSha256: entry.renderedSha256,
4785
+ previousManagedSha256: entry.previousManagedSha256,
4786
+ sourceIds: [...entry.sourceIds]
4787
+ };
4788
+ reconciledEntries.set(JSON.stringify(canonical), canonical);
4789
+ }
4790
+ const manifest = {
4791
+ ...plan.manifest,
4792
+ ...adoptions.length > 0 || previousManifest?.adoptions !== undefined ? { adoptions: [...entries.values()] } : {},
4793
+ ...reconciliations.length > 0 || previousManifest?.reconciliations !== undefined ? { reconciliations: [...reconciledEntries.values()] } : {}
4794
+ };
4795
+ const content = `${JSON.stringify(manifest, null, 2)}
4796
+ `;
4797
+ return { ...plan.manifestFile, content, sha256: sha2563(content) };
4798
+ }
4624
4799
  function assertCursorAuthorityUnchanged(plan) {
4625
4800
  if (plan.tool !== "cursor" || plan.targetKind === "blocked")
4626
4801
  return;
@@ -5092,12 +5267,29 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
5092
5267
  }
5093
5268
  return path;
5094
5269
  }
5095
- function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
5270
+ function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
5096
5271
  const target = resolvePlannedFilePath(plan, file, targetHome);
5097
5272
  const previousContent = existsSync5(target) ? readFileSync(target, "utf-8") : null;
5098
5273
  const previousSha256 = previousContent === null ? null : sha2563(previousContent);
5099
5274
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
5100
5275
  const changed = previousContent !== file.content;
5276
+ const exactSha256 = adoptedHashes.get(file.relativePath) ?? reconciledHashes.get(file.relativePath);
5277
+ if (exactSha256 !== undefined) {
5278
+ const operation = adoptedHashes.has(file.relativePath) ? "adoption" : "reconciliation";
5279
+ if (previousSha256 !== exactSha256) {
5280
+ throw new SessionApplyError(`File ${operation} SHA-256 precondition failed: ${file.relativePath}`);
5281
+ }
5282
+ return {
5283
+ path: target,
5284
+ relativePath: file.relativePath,
5285
+ role: file.role,
5286
+ action: "update",
5287
+ changed,
5288
+ previousSha256,
5289
+ newSha256: file.sha256,
5290
+ reason: `exact observed SHA-256 ${operation}`
5291
+ };
5292
+ }
5101
5293
  if (previousContent !== null && !options.force && !previouslyManaged) {
5102
5294
  return {
5103
5295
  path: target,
@@ -5317,9 +5509,13 @@ function requiredPreviousHash(result) {
5317
5509
  }
5318
5510
  return result.previousSha256;
5319
5511
  }
5320
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
5321
- const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync5(result.path)).map((result) => {
5512
+ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations) {
5513
+ const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").map((result) => {
5514
+ assertExpectedSessionFileHash(result.path, targetHome, result.previousSha256);
5322
5515
  const content = readFileSync(result.path, "utf-8");
5516
+ if (sha2563(content) !== result.previousSha256) {
5517
+ throw new SessionApplyError(`Session snapshot preimage changed after planning: ${result.relativePath}`);
5518
+ }
5323
5519
  return {
5324
5520
  path: result.path,
5325
5521
  relativePath: result.relativePath,
@@ -5360,13 +5556,19 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5360
5556
  manifestPath,
5361
5557
  previousManifest,
5362
5558
  files: existingFiles,
5363
- afterFiles
5559
+ afterFiles,
5560
+ ...adoptions.length > 0 ? { adoptions } : {},
5561
+ ...reconciliations.length > 0 ? { reconciliations } : {}
5364
5562
  };
5563
+ const snapshotContent = `${JSON.stringify(snapshot, null, 2)}
5564
+ `;
5565
+ if ((adoptions.length > 0 || reconciliations.length > 0) && Buffer.byteLength(snapshotContent, "utf8") > 32 * 1024 * 1024) {
5566
+ throw new SessionApplyError("Exact file preimage snapshot exceeds the 32 MiB restore limit.");
5567
+ }
5365
5568
  coordination?.assert_held();
5366
5569
  writeProjectContextCoordinatedFile({
5367
5570
  path: snapshotPath,
5368
- content: `${JSON.stringify(snapshot, null, 2)}
5369
- `,
5571
+ content: snapshotContent,
5370
5572
  workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
5371
5573
  default_mode: 384,
5372
5574
  expected_hash: null,
@@ -5424,11 +5626,15 @@ function sha2563(content) {
5424
5626
  return createHash3("sha256").update(content).digest("hex");
5425
5627
  }
5426
5628
 
5629
+ // src/lib/session-refresh.ts
5630
+ import { createHash as createHash4 } from "crypto";
5631
+ import { closeSync, constants, fstatSync, lstatSync as lstatSync3, openSync, readFileSync as readFileSync3 } from "fs";
5632
+ import { dirname as dirname4, join as join7, parse as parse2, resolve as resolve5 } from "path";
5633
+
5427
5634
  // src/lib/global-source-coverage.ts
5428
5635
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
5429
- var RETIRED_GLOBAL_SOURCE_TAG = "retired-global-source";
5430
5636
  function expectedGlobalSourceSlugs(registryConfigs) {
5431
- 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();
5637
+ return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => instructionSourceRejection(c) === null).map((c) => c.slug).sort();
5432
5638
  }
5433
5639
  function accountedGlobalSourceSlugs(manifest) {
5434
5640
  return [...manifest.sources, ...manifest.skippedSources].map((source) => source.id).filter((id) => id.startsWith(GLOBAL_SOURCE_SLUG_PREFIX));
@@ -5441,6 +5647,10 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
5441
5647
  const unexpected = [...configuredSet].filter((slug) => slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX) && !expectedSet.has(slug)).sort();
5442
5648
  return {
5443
5649
  expectedSlugs: expected,
5650
+ excludedSources: registryConfigs.filter((config) => config.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).flatMap((config) => {
5651
+ const reason = instructionSourceRejection(config);
5652
+ return reason ? [{ slug: config.slug, reason }] : [];
5653
+ }).sort((left, right) => left.slug.localeCompare(right.slug)),
5444
5654
  configuredSlugs: [...configuredSet].sort(),
5445
5655
  missingSlugs: missing,
5446
5656
  unexpectedSlugs: unexpected,
@@ -5696,6 +5906,176 @@ function stationProfileSource(env = process.env) {
5696
5906
  };
5697
5907
  }
5698
5908
 
5909
+ // src/lib/session-refresh.ts
5910
+ import { basename } from "path";
5911
+ var nonempty = exports_external.string().min(1).max(4096);
5912
+ var selectorSchema = exports_external.object({
5913
+ schema: exports_external.literal("hasna.instructions.hosted-profile-selector/v1"),
5914
+ authority: nonempty,
5915
+ profileId: nonempty,
5916
+ providerVersion: nonempty,
5917
+ providerVariant: nonempty.optional(),
5918
+ model: nonempty.optional(),
5919
+ path: nonempty.optional(),
5920
+ manual: exports_external.array(nonempty).max(256),
5921
+ codewithNativeImports: exports_external.boolean(),
5922
+ allowEmptySources: exports_external.boolean(),
5923
+ stationProfile: exports_external.boolean(),
5924
+ checkGlobalCoverage: exports_external.boolean(),
5925
+ assetSurface: nonempty.optional(),
5926
+ assetScope: exports_external.enum(["global", "project", "session"]).optional()
5927
+ }).strict();
5928
+ function authority(value) {
5929
+ const url = new URL(value);
5930
+ if (url.username || url.password || url.search || url.hash || !["https:", "http:"].includes(url.protocol)) {
5931
+ throw new Error("SESSION_REFRESH_AUTHORITY_INVALID: expected a credential-free Instructions API authority.");
5932
+ }
5933
+ return url.toString().replace(/\/$/, "");
5934
+ }
5935
+ function normalizeSessionHostedProfileSelector(value) {
5936
+ const parsed = selectorSchema.parse(value);
5937
+ return { ...parsed, authority: authority(parsed.authority) };
5938
+ }
5939
+ function assertHostedProfileBindings(profileId, configs, bindings) {
5940
+ const configIds = new Set(configs.map((config) => config.id));
5941
+ const bindingIds = new Set(bindings.map((binding) => binding.config_id));
5942
+ 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))) {
5943
+ throw new Error("HOSTED_PROFILE_BINDINGS_INCOMPLETE: exact one-to-one config membership and explicit profile bindings are required; refusing legacy activation fallback.");
5944
+ }
5945
+ for (const row of bindings) {
5946
+ const raw = row.binding;
5947
+ 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.");
5948
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
5949
+ throw invalid();
5950
+ const record = raw;
5951
+ if (!["schema", "activation", "required", "fallback"].every((key) => Object.hasOwn(record, key)) || typeof record["required"] !== "boolean")
5952
+ throw invalid();
5953
+ try {
5954
+ normalizeProfileConfigBinding(record);
5955
+ } catch {
5956
+ throw invalid();
5957
+ }
5958
+ }
5959
+ }
5960
+ function readManagedManifest(targetHome) {
5961
+ const path = join7(targetHome, SESSION_RENDER_MANIFEST_RELATIVE_PATH);
5962
+ let ancestor = dirname4(path);
5963
+ while (true) {
5964
+ const stat = lstatSync3(ancestor);
5965
+ if (stat.isSymbolicLink() || !stat.isDirectory())
5966
+ throw new Error("SESSION_REFRESH_PATH_INVALID: managed target uses a symlink or non-directory ancestor.");
5967
+ const parent = dirname4(ancestor);
5968
+ if (parent === ancestor)
5969
+ break;
5970
+ ancestor = parent;
5971
+ }
5972
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
5973
+ let raw;
5974
+ try {
5975
+ const stat = fstatSync(fd);
5976
+ if (!stat.isFile() || stat.size > SESSION_MANAGED_INPUT_MAX_BYTES)
5977
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: expected a bounded regular manifest file.");
5978
+ raw = readFileSync3(fd);
5979
+ if (raw.byteLength > SESSION_MANAGED_INPUT_MAX_BYTES)
5980
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: manifest grew beyond the read limit.");
5981
+ } finally {
5982
+ closeSync(fd);
5983
+ }
5984
+ const manifest = JSON.parse(raw.toString("utf8"));
5985
+ if (manifest.schema !== SESSION_RENDER_SCHEMA || !SESSION_RENDER_TOOLS.includes(manifest.tool) || manifest.targetHome !== targetHome || !manifest.profile?.trim() || !["session-home", "project-root"].includes(manifest.targetKind) || !Array.isArray(manifest.files) || !manifest.refreshSelector || manifest.targetOwner?.writer?.id !== SESSION_RENDERER_OWNER_ID || manifest.targetOwner?.targetHome !== targetHome) {
5986
+ throw new Error("SESSION_REFRESH_MANIFEST_INVALID: target is not an owned hosted profile render; apply a hosted --compile-profile first.");
5987
+ }
5988
+ manifest.refreshSelector = normalizeSessionHostedProfileSelector(manifest.refreshSelector);
5989
+ return { manifest, sha256: createHash4("sha256").update(raw).digest("hex") };
5990
+ }
5991
+ async function refreshSessionRender(input) {
5992
+ const { store } = input;
5993
+ if (store.mode !== "api" || !store.v1BaseUrl)
5994
+ throw new Error("SESSION_REFRESH_HOSTED_REQUIRED: refresh requires the configured hosted Instructions API.");
5995
+ const targetHome = resolve5(input.targetHome);
5996
+ if (targetHome === parse2(targetHome).root)
5997
+ throw new Error("SESSION_REFRESH_PATH_INVALID: filesystem root is not a managed target.");
5998
+ const previous = readManagedManifest(targetHome);
5999
+ const selector = previous.manifest.refreshSelector;
6000
+ if (authority(store.v1BaseUrl) !== selector.authority)
6001
+ throw new Error("SESSION_REFRESH_AUTHORITY_MISMATCH: configured API differs from the recorded profile authority.");
6002
+ const profile = await store.getProfile(selector.profileId);
6003
+ if (profile.id !== selector.profileId)
6004
+ throw new Error("SESSION_REFRESH_PROFILE_MISMATCH: hosted profile identity changed.");
6005
+ const [configs, bindings, assetBindings] = await Promise.all([
6006
+ store.getProfileConfigs(profile.id),
6007
+ store.getProfileConfigBindings(profile.id, { requireExplicit: true }),
6008
+ store.getProfileAssetBindings(profile.id, { requireExplicit: true })
6009
+ ]);
6010
+ assertHostedProfileBindings(profile.id, configs, bindings);
6011
+ if (configs.length > 2048 || bindings.length > 8192 || assetBindings.length > 1024)
6012
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted profile exceeds refresh bounds.");
6013
+ const totalBytes = configs.reduce((sum, config) => sum + Buffer.byteLength(config.content), 0);
6014
+ if (totalBytes > SESSION_MANAGED_INPUT_MAX_BYTES)
6015
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted instruction payload exceeds refresh byte bounds.");
6016
+ const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
6017
+ if (assetConfigs.reduce((sum, config) => sum + Buffer.byteLength(config.content), totalBytes) > SESSION_MANAGED_INPUT_MAX_BYTES)
6018
+ throw new Error("SESSION_REFRESH_INPUT_LIMIT: hosted source and asset payload exceeds refresh byte bounds.");
6019
+ let ownedClaudeAuthorities;
6020
+ if (previous.manifest.tool === "claude") {
6021
+ 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 }));
6022
+ }
6023
+ const station = selector.stationProfile ? stationProfileSource() : null;
6024
+ const plan = planProfileSessionRender({
6025
+ tool: previous.manifest.tool,
6026
+ profile: previous.manifest.profile,
6027
+ profile_id: profile.id,
6028
+ provider_version: selector.providerVersion,
6029
+ targetHome,
6030
+ ...previous.manifest.targetKind === "project-root" ? { projectRoot: targetHome } : {},
6031
+ sessionId: previous.manifest.sessionId ?? undefined,
6032
+ refreshSelector: selector,
6033
+ codewithNativeImports: selector.codewithNativeImports,
6034
+ allowEmptySources: selector.allowEmptySources,
6035
+ configs,
6036
+ bindings,
6037
+ asset_configs: assetConfigs,
6038
+ asset_bindings: assetBindings,
6039
+ asset_plan_mode: input.dryRun ? "dry-run" : "apply",
6040
+ asset_scope: selector.assetScope,
6041
+ asset_surface: selector.assetSurface,
6042
+ extra_sources: station ? [station] : undefined,
6043
+ ownedClaudeAuthorities,
6044
+ graph_context: {
6045
+ ...selector.providerVariant ? { provider_variant: selector.providerVariant } : {},
6046
+ ...selector.model ? { model: selector.model } : {},
6047
+ ...selector.path ? { path: selector.path } : {},
6048
+ ...selector.manual.length ? { manual: selector.manual } : {}
6049
+ }
6050
+ });
6051
+ if (selector.checkGlobalCoverage) {
6052
+ const coverage = computeGlobalSourceCoverage(await store.listConfigs({}), accountedGlobalSourceSlugs(plan.manifest));
6053
+ if (!coverage.complete)
6054
+ throw new Error(`SESSION_REFRESH_COVERAGE_INCOMPLETE: ${coverage.missingSlugs.length} registered global sources are absent.`);
6055
+ }
6056
+ const preview = applySessionRender(plan, { dryRun: true, expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
6057
+ const common = { checkedAt: new Date().toISOString(), targetHome, selector, sourceHash: plan.manifest.sourceHash };
6058
+ if (preview.conflicts.length)
6059
+ return { ...common, status: "blocked", apply: preview };
6060
+ if (!preview.drift.clean)
6061
+ throw new Error("SESSION_REFRESH_DRIFT: generated files changed or disappeared since their last managed render.");
6062
+ const sameSources = previous.manifest.sourceHash === plan.manifest.sourceHash;
6063
+ const samePayloads = preview.files.filter((file) => file.role !== "manifest").every((file) => file.action === "unchanged");
6064
+ if (sameSources && samePayloads) {
6065
+ if (readManagedManifest(targetHome).sha256 !== previous.sha256 || !checkSessionRenderDrift(targetHome).clean)
6066
+ throw new Error("SESSION_REFRESH_DRIFT: managed target changed during refresh.");
6067
+ 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) } };
6068
+ }
6069
+ if (input.dryRun)
6070
+ return { ...common, status: "dry-run", apply: preview };
6071
+ const applied = applySessionRender(plan, { expectedManifestSha256: previous.sha256, ownedClaudeAuthorities });
6072
+ if (applied.conflicts.length)
6073
+ return { ...common, status: "blocked", apply: applied };
6074
+ if (!applied.applied || !checkSessionRenderDrift(targetHome).clean)
6075
+ throw new Error("SESSION_REFRESH_READBACK_FAILED: applied instructions did not verify against their manifest.");
6076
+ return { ...common, status: "updated", apply: applied };
6077
+ }
6078
+
5699
6079
  // src/lib/project-dashboard-standard.ts
5700
6080
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
5701
6081
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
@@ -5788,11 +6168,15 @@ async function ensureProjectDashboardStandardConfig(store = resolveConfigStore()
5788
6168
  };
5789
6169
  try {
5790
6170
  const existing = await store.getConfig(PROJECT_DASHBOARD_STANDARD_SLUG);
6171
+ if (isRetiredInstructionSource(existing))
6172
+ return existing;
5791
6173
  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) {
5792
6174
  return await store.updateConfig(existing.id, input);
5793
6175
  }
5794
6176
  return existing;
5795
- } catch {
6177
+ } catch (error) {
6178
+ if (!(error instanceof ConfigNotFoundError))
6179
+ throw error;
5796
6180
  return await store.createConfig(input);
5797
6181
  }
5798
6182
  }
@@ -5805,10 +6189,9 @@ function profileHasSelectors(profile) {
5805
6189
  var PLATFORM_PROFILE_PRESETS = [
5806
6190
  {
5807
6191
  name: "linux-arm64",
5808
- description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
5809
- selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b", "station01"] },
6192
+ description: "Default reviewed instruction profile for Linux arm64",
6193
+ selectors: { os: ["linux"], arch: ["arm64"] },
5810
6194
  variables: {
5811
- WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
5812
6195
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5813
6196
  BUN_PATH: "{{BUN_BIN_DIR}}/bun",
5814
6197
  PATH_PREFIX: "{{BUN_BIN_DIR}}",
@@ -5817,10 +6200,9 @@ var PLATFORM_PROFILE_PRESETS = [
5817
6200
  },
5818
6201
  {
5819
6202
  name: "macos-arm64",
5820
- description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
5821
- selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
6203
+ description: "Default reviewed instruction profile for macOS arm64",
6204
+ selectors: { os: ["macos"], arch: ["arm64"] },
5822
6205
  variables: {
5823
- WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
5824
6206
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5825
6207
  BUN_PATH: "/opt/homebrew/bin/bun",
5826
6208
  PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}",
@@ -5829,12 +6211,21 @@ var PLATFORM_PROFILE_PRESETS = [
5829
6211
  }
5830
6212
  ];
5831
6213
  async function ensurePlatformProfiles(store = resolveConfigStore()) {
5832
- const configs = await store.listConfigs();
5833
6214
  const ensured = [];
5834
- for (const preset of PLATFORM_PROFILE_PRESETS) {
5835
- let profile;
6215
+ const existingProfiles = await Promise.all(PLATFORM_PROFILE_PRESETS.map(async (preset) => {
5836
6216
  try {
5837
- profile = await store.getProfile(preset.name);
6217
+ return await store.getProfile(preset.name);
6218
+ } catch (error) {
6219
+ if (error instanceof ProfileNotFoundError)
6220
+ return null;
6221
+ throw error;
6222
+ }
6223
+ }));
6224
+ for (const [index, preset] of PLATFORM_PROFILE_PRESETS.entries()) {
6225
+ let profile;
6226
+ const existing = existingProfiles[index];
6227
+ if (existing) {
6228
+ profile = existing;
5838
6229
  const selectors = mergeProfileSelectors(preset.selectors, profile.selectors);
5839
6230
  const variables = mergeProfileVariables(preset.variables, profile.variables);
5840
6231
  if (JSON.stringify(selectors) !== JSON.stringify(profile.selectors) || JSON.stringify(variables) !== JSON.stringify(profile.variables)) {
@@ -5844,12 +6235,9 @@ async function ensurePlatformProfiles(store = resolveConfigStore()) {
5844
6235
  variables
5845
6236
  });
5846
6237
  }
5847
- } catch {
6238
+ } else {
5848
6239
  profile = await store.createProfile(preset);
5849
6240
  }
5850
- for (const config of configs) {
5851
- await store.addConfigToProfile(profile.id, config.id);
5852
- }
5853
6241
  ensured.push(profile);
5854
6242
  }
5855
6243
  return ensured;
@@ -5990,29 +6378,33 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
5990
6378
  };
5991
6379
  try {
5992
6380
  const existing = await store.getConfig(DANGEROUS_OPERATION_GUARD_STANDARD_SLUG);
6381
+ if (isRetiredInstructionSource(existing))
6382
+ return existing;
5993
6383
  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)) {
5994
6384
  return await store.updateConfig(existing.id, input);
5995
6385
  }
5996
6386
  return existing;
5997
- } catch {
6387
+ } catch (error) {
6388
+ if (!(error instanceof ConfigNotFoundError))
6389
+ throw error;
5998
6390
  return await store.createConfig(input);
5999
6391
  }
6000
6392
  }
6001
6393
 
6002
6394
  // src/lib/managed-skill-runtimes.ts
6003
- import { createHash as createHash4 } from "crypto";
6395
+ import { createHash as createHash5 } from "crypto";
6004
6396
  import { spawnSync as spawnSync2 } from "child_process";
6005
6397
  import {
6006
6398
  existsSync as existsSync7,
6007
- lstatSync as lstatSync3,
6399
+ lstatSync as lstatSync4,
6008
6400
  mkdirSync as mkdirSync4,
6009
- readFileSync as readFileSync3,
6401
+ readFileSync as readFileSync4,
6010
6402
  renameSync,
6011
6403
  rmSync as rmSync2,
6012
6404
  writeFileSync as writeFileSync3
6013
6405
  } from "fs";
6014
6406
  import { homedir as homedir3 } from "os";
6015
- import { dirname as dirname4, join as join7, parse as parse2, relative as relative2, resolve as resolve5 } from "path";
6407
+ import { dirname as dirname5, join as join8, parse as parse3, relative as relative2, resolve as resolve6 } from "path";
6016
6408
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
6017
6409
  var INBOX_SKILL_MARKERS = [
6018
6410
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -6023,25 +6415,25 @@ var INBOX_SKILL_MARKERS = [
6023
6415
  ];
6024
6416
  var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
6025
6417
  function sha2564(content) {
6026
- return createHash4("sha256").update(content).digest("hex");
6418
+ return createHash5("sha256").update(content).digest("hex");
6027
6419
  }
6028
6420
  function lstatOrNull(path) {
6029
6421
  try {
6030
- return lstatSync3(path);
6422
+ return lstatSync4(path);
6031
6423
  } catch {
6032
6424
  return null;
6033
6425
  }
6034
6426
  }
6035
6427
  function findSymlinkedAncestor(path) {
6036
- const normalized = resolve5(path);
6037
- const parsed = parse2(normalized);
6428
+ const normalized = resolve6(path);
6429
+ const parsed = parse3(normalized);
6038
6430
  let current = parsed.root;
6039
6431
  const rel = relative2(parsed.root, normalized);
6040
6432
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
6041
- current = join7(current, segment);
6433
+ current = join8(current, segment);
6042
6434
  if (!existsSync7(current))
6043
6435
  return null;
6044
- if (lstatSync3(current).isSymbolicLink())
6436
+ if (lstatSync4(current).isSymbolicLink())
6045
6437
  return current;
6046
6438
  }
6047
6439
  return null;
@@ -6063,7 +6455,7 @@ function readCanonicalSkill(explicitPath) {
6063
6455
  if (!stat?.isFile()) {
6064
6456
  throw new Error("packaged inbox skill contract is not a regular file");
6065
6457
  }
6066
- const content = readFileSync3(assetPath, "utf8");
6458
+ const content = readFileSync4(assetPath, "utf8");
6067
6459
  if (!content.includes("conversations watch --from <agent> --all")) {
6068
6460
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
6069
6461
  }
@@ -6101,7 +6493,7 @@ function compareVersions(left, right) {
6101
6493
  return 0;
6102
6494
  }
6103
6495
  function inspectSkillMarkers(homeDir2) {
6104
- return INBOX_SKILL_MARKERS.map((parts) => join7(homeDir2, ...parts)).map((path) => {
6496
+ return INBOX_SKILL_MARKERS.map((parts) => join8(homeDir2, ...parts)).map((path) => {
6105
6497
  const stat = lstatOrNull(path);
6106
6498
  if (!stat)
6107
6499
  return null;
@@ -6110,7 +6502,7 @@ function inspectSkillMarkers(homeDir2) {
6110
6502
  }
6111
6503
  return {
6112
6504
  path,
6113
- content: readFileSync3(path, "utf8"),
6505
+ content: readFileSync4(path, "utf8"),
6114
6506
  mode: stat.mode & 511,
6115
6507
  regular: true
6116
6508
  };
@@ -6145,7 +6537,7 @@ function inspectInbox(options) {
6145
6537
  let reason = "skill not installed";
6146
6538
  if (skillPresent) {
6147
6539
  const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
6148
- const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname4(path))).find((found) => found !== null);
6540
+ const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname5(path))).find((found) => found !== null);
6149
6541
  if (nonRegular)
6150
6542
  reason = "managed skill target is not a regular file";
6151
6543
  else if (symlinkAncestor)
@@ -6232,10 +6624,10 @@ function cleanup(path) {
6232
6624
  rmSync2(path, { force: true });
6233
6625
  }
6234
6626
  function writeAtomic(path, content, mode) {
6235
- assertNoSymlinkAncestors2(dirname4(path));
6627
+ assertNoSymlinkAncestors2(dirname5(path));
6236
6628
  const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
6237
6629
  try {
6238
- mkdirSync4(dirname4(path), { recursive: true, mode: 493 });
6630
+ mkdirSync4(dirname5(path), { recursive: true, mode: 493 });
6239
6631
  writeFileSync3(tempPath, content, { mode, flag: "wx" });
6240
6632
  renameSync(tempPath, path);
6241
6633
  } finally {
@@ -6244,7 +6636,7 @@ function writeAtomic(path, content, mode) {
6244
6636
  }
6245
6637
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
6246
6638
  lstat: lstatOrNull,
6247
- read: (path) => readFileSync3(path, "utf8"),
6639
+ read: (path) => readFileSync4(path, "utf8"),
6248
6640
  write: writeAtomic
6249
6641
  };
6250
6642
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -6311,7 +6703,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
6311
6703
  dry_run: dryRun
6312
6704
  };
6313
6705
  }
6314
- const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname4(path))).find((found) => found !== null);
6706
+ const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname5(path))).find((found) => found !== null);
6315
6707
  if (symlinkedAncestor) {
6316
6708
  return {
6317
6709
  runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
@@ -6388,28 +6780,28 @@ async function reconcileManagedSkillRuntimes(options = {}) {
6388
6780
  }
6389
6781
 
6390
6782
  // src/status.ts
6391
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
6783
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
6392
6784
 
6393
6785
  // src/lib/package-version.ts
6394
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
6395
- import { dirname as dirname5, join as join8 } from "path";
6786
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6787
+ import { dirname as dirname6, join as join9 } from "path";
6396
6788
  import { fileURLToPath } from "url";
6397
6789
  var cached = null;
6398
6790
  function getPackageVersion() {
6399
6791
  if (cached)
6400
6792
  return cached;
6401
6793
  try {
6402
- let dir = dirname5(fileURLToPath(import.meta.url));
6794
+ let dir = dirname6(fileURLToPath(import.meta.url));
6403
6795
  for (let i = 0;i < 8; i++) {
6404
- const pkgPath = join8(dir, "package.json");
6796
+ const pkgPath = join9(dir, "package.json");
6405
6797
  if (existsSync8(pkgPath)) {
6406
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
6798
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
6407
6799
  if (pkg.name === "@hasna/instructions" && pkg.version) {
6408
6800
  cached = pkg.version;
6409
6801
  return cached;
6410
6802
  }
6411
6803
  }
6412
- const parent = dirname5(dir);
6804
+ const parent = dirname6(dir);
6413
6805
  if (parent === dir)
6414
6806
  break;
6415
6807
  dir = parent;
@@ -6482,7 +6874,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6482
6874
  missingTargets += 1;
6483
6875
  continue;
6484
6876
  }
6485
- const disk = readFileSync5(targetPath, "utf-8");
6877
+ const disk = readFileSync6(targetPath, "utf-8");
6486
6878
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
6487
6879
  if (redactedDisk !== config.content) {
6488
6880
  driftedTargets += 1;
@@ -6574,9 +6966,9 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6574
6966
  }
6575
6967
 
6576
6968
  // src/lib/provider-context.ts
6577
- import { createHash as createHash5 } from "crypto";
6578
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
6579
- import { join as join9 } from "path";
6969
+ import { createHash as createHash6 } from "crypto";
6970
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
6971
+ import { join as join10 } from "path";
6580
6972
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
6581
6973
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
6582
6974
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -6712,7 +7104,7 @@ function renderProviderFragment(entry) {
6712
7104
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
6713
7105
  }
6714
7106
  function sha2565(content) {
6715
- return createHash5("sha256").update(content).digest("hex");
7107
+ return createHash6("sha256").update(content).digest("hex");
6716
7108
  }
6717
7109
  function resolveAndRenderProviderContext(opts) {
6718
7110
  const entry = matchProviderEndpoint(opts.origin);
@@ -6721,18 +7113,18 @@ function resolveAndRenderProviderContext(opts) {
6721
7113
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
6722
7114
  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;
6723
7115
  const content = renderProviderFragment(entry);
6724
- const dir = join9(opts.homeDir, PROVIDER_CONTEXT_DIR);
7116
+ const dir = join10(opts.homeDir, PROVIDER_CONTEXT_DIR);
6725
7117
  if (!existsSync10(dir))
6726
7118
  mkdirSync5(dir, { recursive: true });
6727
7119
  const filename = `${entry ? entry.key : "invariant"}.md`;
6728
- const fragmentPath = join9(dir, filename);
7120
+ const fragmentPath = join10(dir, filename);
6729
7121
  const fragmentSha256 = sha2565(content);
6730
7122
  writeFileSync4(fragmentPath, content, "utf8");
6731
- const manifestPath = join9(dir, PROVIDER_CONTEXT_MANIFEST);
7123
+ const manifestPath = join10(dir, PROVIDER_CONTEXT_MANIFEST);
6732
7124
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
6733
7125
  try {
6734
7126
  if (existsSync10(manifestPath)) {
6735
- const parsed = JSON.parse(readFileSync6(manifestPath, "utf8"));
7127
+ const parsed = JSON.parse(readFileSync7(manifestPath, "utf8"));
6736
7128
  if (parsed && typeof parsed === "object")
6737
7129
  manifest = parsed;
6738
7130
  }
@@ -6798,6 +7190,9 @@ function printLine(text = "") {
6798
7190
  function printJson(value) {
6799
7191
  printLine(JSON.stringify(value, null, 2));
6800
7192
  }
7193
+ function printMachineJson(value, pretty = false) {
7194
+ printLine(JSON.stringify(value, null, pretty ? 2 : undefined));
7195
+ }
6801
7196
  function printManagedSkillRuntimeReport(report) {
6802
7197
  for (const runtime of report.runtimes) {
6803
7198
  if (!runtime.skill_present)
@@ -6819,7 +7214,7 @@ function fmtConfig(c, format) {
6819
7214
  ` ${chalk.cyan("category:")} ${c.category} ${chalk.cyan("agent:")} ${c.agent} ${chalk.cyan("kind:")} ${c.kind}`,
6820
7215
  ` ${chalk.cyan("format:")} ${c.format} ${chalk.cyan("version:")} ${c.version}${c.target_path ? ` ${chalk.cyan("path:")} ${c.target_path}` : ""}`,
6821
7216
  c.description ? ` ${chalk.dim(c.description)}` : "",
6822
- c.tags.length > 0 ? ` ${chalk.dim("tags: " + c.tags.join(", "))}` : ""
7217
+ c.tags?.length ? ` ${chalk.dim("tags: " + c.tags.join(", "))}` : ""
6823
7218
  ].filter(Boolean).join(`
6824
7219
  `);
6825
7220
  }
@@ -6837,7 +7232,7 @@ function printConfigRows(configs) {
6837
7232
  for (const c of configs) {
6838
7233
  const type = `${c.category}/${c.agent}`;
6839
7234
  const path = c.kind === "reference" ? "(ref)" : c.target_path ?? "(no path)";
6840
- console.log(`${pad(c.slug, 32)} ${pad(type, 15)} ${pad(c.format, 8)} ${pad(truncateMiddle(path, 44), 44)} ${String(c.outputs.length).padStart(3)} ${c.version}`);
7235
+ console.log(`${pad(c.slug, 32)} ${pad(type, 15)} ${pad(c.format, 8)} ${pad(truncateMiddle(path, 44), 44)} ${String(c.output_count).padStart(3)} ${c.version}`);
6841
7236
  }
6842
7237
  }
6843
7238
  function splitCsv(value) {
@@ -6879,7 +7274,7 @@ function parseSessionSource(value, order) {
6879
7274
  throw new Error(`Instruction source file not found: ${absPath}`);
6880
7275
  const content = readSessionInstructionSourceFile(absPath);
6881
7276
  const source = sourceFromFilePath(absPath, content, order);
6882
- const resolvedId = id || source.id || basename(absPath);
7277
+ const resolvedId = id || source.id || basename2(absPath);
6883
7278
  return {
6884
7279
  ...source,
6885
7280
  id: resolvedId,
@@ -6919,7 +7314,7 @@ function sessionSourceReplacements(values) {
6919
7314
  return replacements;
6920
7315
  }
6921
7316
  function readSessionInstructionSourceFile(path) {
6922
- const stat = lstatSync4(path);
7317
+ const stat = lstatSync5(path);
6923
7318
  if (stat.isSymbolicLink()) {
6924
7319
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
6925
7320
  }
@@ -6929,7 +7324,7 @@ function readSessionInstructionSourceFile(path) {
6929
7324
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
6930
7325
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
6931
7326
  }
6932
- return readFileSync7(path, "utf-8");
7327
+ return readFileSync8(path, "utf-8");
6933
7328
  }
6934
7329
  function parseLayeredReference(value) {
6935
7330
  const trimmed = value.trim();
@@ -6952,13 +7347,15 @@ async function collectSessionSources(opts, tool, store) {
6952
7347
  const sources = (opts.source ?? []).map((value, index) => parseSessionSource(value, index));
6953
7348
  for (const value of opts.config ?? []) {
6954
7349
  const { layer, id } = parseLayeredReference(value);
6955
- sources.push(sourceFromConfig(await store.getConfig(id), sources.length, layer));
7350
+ const config = await store.getConfig(id);
7351
+ assertInstructionSource(config);
7352
+ sources.push(sourceFromConfig(config, sources.length, layer));
6956
7353
  }
6957
7354
  for (const value of opts.identityExport ?? []) {
6958
7355
  const path = resolveSessionPath(value);
6959
7356
  if (!existsSync11(path))
6960
7357
  throw new Error(`Identity instruction export not found: ${path}`);
6961
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
7358
+ const parsed = JSON.parse(readFileSync8(path, "utf-8"));
6962
7359
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
6963
7360
  }
6964
7361
  return sources.map((source) => {
@@ -6974,7 +7371,7 @@ async function collectSessionSources(opts, tool, store) {
6974
7371
  }
6975
7372
  async function loadOwnedClaudeAuthorities(store) {
6976
7373
  const configs = await store.listConfigs({ category: "rules", agent: "claude", kind: "file" });
6977
- 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 }));
7374
+ 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 }));
6978
7375
  }
6979
7376
  async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-run") {
6980
7377
  const station = opts.stationProfile === false ? null : stationProfileSource();
@@ -7004,8 +7401,10 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
7004
7401
  }
7005
7402
  const profile = await store.getProfile(opts.compileProfile);
7006
7403
  const configs = await store.getProfileConfigs(profile.id);
7007
- const bindings = await store.getProfileConfigBindings(profile.id);
7008
- const assetBindings = await store.getProfileAssetBindings(profile.id);
7404
+ const bindings = await store.getProfileConfigBindings(profile.id, { requireExplicit: store.mode === "api" });
7405
+ if (store.mode === "api")
7406
+ assertHostedProfileBindings(profile.id, configs, bindings);
7407
+ const assetBindings = await store.getProfileAssetBindings(profile.id, { requireExplicit: store.mode === "api" });
7009
7408
  const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
7010
7409
  return planProfileSessionRender({
7011
7410
  profile_id: profile.id,
@@ -7015,6 +7414,24 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
7015
7414
  targetHome: opts.targetHome,
7016
7415
  projectRoot: opts.projectRoot,
7017
7416
  sessionId: opts.sessionId,
7417
+ ...store.mode === "api" && store.v1BaseUrl ? {
7418
+ refreshSelector: normalizeSessionHostedProfileSelector({
7419
+ schema: "hasna.instructions.hosted-profile-selector/v1",
7420
+ authority: store.v1BaseUrl,
7421
+ profileId: profile.id,
7422
+ providerVersion: opts.providerVersion,
7423
+ ...opts.providerVariant ? { providerVariant: opts.providerVariant } : {},
7424
+ ...opts.model ? { model: opts.model } : {},
7425
+ ...opts.path ? { path: opts.path } : {},
7426
+ manual: opts.manual ?? [],
7427
+ codewithNativeImports: opts.codewithNativeImports === true || process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "1" || process.env[CODEWITH_NATIVE_IMPORTS_ENV] === "true",
7428
+ allowEmptySources: opts.allowEmptySources === true,
7429
+ stationProfile: opts.stationProfile !== false,
7430
+ checkGlobalCoverage: opts.checkGlobalCoverage === true,
7431
+ ...opts.assetSurface ? { assetSurface: opts.assetSurface } : {},
7432
+ ...opts.assetScope ? { assetScope: opts.assetScope } : {}
7433
+ })
7434
+ } : {},
7018
7435
  codewithNativeImports: opts.codewithNativeImports,
7019
7436
  allowEmptySources: opts.allowEmptySources,
7020
7437
  configs,
@@ -7095,14 +7512,14 @@ function readProjectContextBundleOption(value, allowMissing = false) {
7095
7512
  return {};
7096
7513
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
7097
7514
  }
7098
- const stat = lstatSync4(path);
7515
+ const stat = lstatSync5(path);
7099
7516
  if (stat.isSymbolicLink() || !stat.isFile()) {
7100
7517
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
7101
7518
  }
7102
7519
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
7103
7520
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
7104
7521
  }
7105
- return { json: readFileSync7(path, "utf8"), sourcePath: path };
7522
+ return { json: readFileSync8(path, "utf8"), sourcePath: path };
7106
7523
  }
7107
7524
  function readBoundedProjectContextStdin() {
7108
7525
  const chunks = [];
@@ -7189,24 +7606,245 @@ async function getMachineProfileContext(opts, store, readOptions = {}) {
7189
7606
  const profile = resolution.profile;
7190
7607
  return { machine, profile, resolution, vars: resolveProfileVariables(profile, machine) };
7191
7608
  }
7192
- program.command("list").alias("ls").description("List stored configs").option("-c, --category <cat>", "filter by category").option("-a, --agent <agent>", "filter by agent").option("-k, --kind <kind>", "filter by kind (file|reference)").option("-t, --tag <tag>", "filter by tag").option("-s, --search <query>", "search name/description/content").option("-f, --format <fmt>", "output format: compact|table|json", "compact").option("--brief", "shorthand for --format compact").option("--verbose", "show expanded metadata for each listed config").option("--json", "output full matching records as JSON").option("--limit <n>", `max rows for human output (default ${DEFAULT_LIST_LIMIT})`).option("--cursor <n>", "zero-based pagination cursor for human output").action(async (opts) => {
7193
- const fmt = opts.json ? "json" : opts.verbose ? "table" : opts.brief ? "compact" : opts.format;
7194
- const configs = await resolveConfigStore().listConfigs({
7609
+ var COMPACT_CONFIG_FIELDS = [
7610
+ "id",
7611
+ "slug",
7612
+ "name",
7613
+ "kind",
7614
+ "category",
7615
+ "agent",
7616
+ "format",
7617
+ "is_template",
7618
+ "version",
7619
+ "updated_at"
7620
+ ];
7621
+ var OPTIONAL_CONFIG_IDENTITY_FIELDS = ["created_at", "synced_at"];
7622
+ var FULL_CONFIG_FIELDS = [
7623
+ "id",
7624
+ "name",
7625
+ "slug",
7626
+ "kind",
7627
+ "category",
7628
+ "agent",
7629
+ "target_path",
7630
+ "outputs",
7631
+ "format",
7632
+ "content",
7633
+ "description",
7634
+ "tags",
7635
+ "is_template",
7636
+ "version",
7637
+ "created_at",
7638
+ "updated_at",
7639
+ "synced_at"
7640
+ ];
7641
+ function parseConfigListDetail(value) {
7642
+ if (value === undefined)
7643
+ return;
7644
+ if (value === "compact" || value === "full")
7645
+ return value;
7646
+ throw new Error('--detail must be "compact" or "full"');
7647
+ }
7648
+ function parseConfigListFields(value, detail) {
7649
+ if (value === undefined)
7650
+ return;
7651
+ const raw = String(value);
7652
+ if (raw.length > 1024)
7653
+ throw new Error("--fields exceeds 1024 characters");
7654
+ const fields = raw.split(",").map((field) => field.trim());
7655
+ if (fields.length === 0 || fields.some((field) => field.length === 0)) {
7656
+ throw new Error("--fields must contain non-empty comma-separated field names");
7657
+ }
7658
+ if (fields.length > 32)
7659
+ throw new Error("--fields accepts at most 32 fields");
7660
+ if (new Set(fields).size !== fields.length)
7661
+ throw new Error("--fields must not contain duplicate names");
7662
+ const allowed = new Set(detail === "full" ? FULL_CONFIG_FIELDS : [...COMPACT_CONFIG_FIELDS, ...OPTIONAL_CONFIG_IDENTITY_FIELDS]);
7663
+ const unknown = fields.filter((field) => !allowed.has(field));
7664
+ if (unknown.length > 0)
7665
+ throw new Error(`Unknown ${detail} config field(s): ${unknown.join(", ")}`);
7666
+ if (!fields.includes("id"))
7667
+ throw new Error("--fields must include the immutable id field");
7668
+ if (detail !== "full" && fields.includes("content")) {
7669
+ throw new Error("Config content requires --detail full");
7670
+ }
7671
+ return fields;
7672
+ }
7673
+ function projectConfigFields(record, fields) {
7674
+ return Object.fromEntries(fields.map((field) => [field, record[field]]));
7675
+ }
7676
+ async function readAllConfigIdentityPages(store, filter) {
7677
+ const items = [];
7678
+ const seen = new Set;
7679
+ let cursor = 0;
7680
+ let expectedTotal = null;
7681
+ let sourceBounded = true;
7682
+ let previousId = null;
7683
+ while (true) {
7684
+ const page = await store.listConfigIdentitiesPage(filter, { limit: 100, cursor });
7685
+ sourceBounded = sourceBounded && page.source_bounded;
7686
+ if (expectedTotal === null)
7687
+ expectedTotal = page.total;
7688
+ else if (page.total !== expectedTotal)
7689
+ throw new Error("Config collection changed while paging");
7690
+ for (const item of page.items) {
7691
+ if (seen.has(item.id))
7692
+ throw new Error(`Config collection repeated ${item.id} while paging`);
7693
+ if (page.source_bounded && previousId !== null && item.id <= previousId)
7694
+ throw new Error("Config collection order changed while paging");
7695
+ seen.add(item.id);
7696
+ previousId = page.source_bounded ? item.id : null;
7697
+ items.push(item);
7698
+ }
7699
+ if (page.next_cursor === null)
7700
+ break;
7701
+ if (page.next_cursor <= cursor)
7702
+ throw new Error("Config identity pagination did not advance");
7703
+ cursor = page.next_cursor;
7704
+ }
7705
+ if (expectedTotal !== null && items.length !== expectedTotal) {
7706
+ throw new Error(`Config collection returned ${items.length} of ${expectedTotal} rows`);
7707
+ }
7708
+ return { items, source_bounded: sourceBounded };
7709
+ }
7710
+ async function readAllConfigPages(store, filter) {
7711
+ const items = [];
7712
+ const seen = new Set;
7713
+ let cursor = 0;
7714
+ let expectedTotal = null;
7715
+ let sourceBounded = true;
7716
+ let previousId = null;
7717
+ while (true) {
7718
+ const page = await store.listConfigsPage(filter, { limit: 100, cursor });
7719
+ sourceBounded = sourceBounded && page.source_bounded;
7720
+ if (expectedTotal === null)
7721
+ expectedTotal = page.total;
7722
+ else if (page.total !== expectedTotal)
7723
+ throw new Error("Config collection changed while paging");
7724
+ for (const item of page.items) {
7725
+ if (seen.has(item.id))
7726
+ throw new Error(`Config collection repeated ${item.id} while paging`);
7727
+ if (page.source_bounded && previousId !== null && item.id <= previousId)
7728
+ throw new Error("Config collection order changed while paging");
7729
+ seen.add(item.id);
7730
+ previousId = page.source_bounded ? item.id : null;
7731
+ items.push(item);
7732
+ }
7733
+ if (page.next_cursor === null)
7734
+ break;
7735
+ if (page.next_cursor <= cursor)
7736
+ throw new Error("Config pagination did not advance");
7737
+ cursor = page.next_cursor;
7738
+ }
7739
+ if (expectedTotal !== null && items.length !== expectedTotal) {
7740
+ throw new Error(`Config collection returned ${items.length} of ${expectedTotal} rows`);
7741
+ }
7742
+ return { items, source_bounded: sourceBounded };
7743
+ }
7744
+ function configListEnvelope(records, page, detail, fields) {
7745
+ const complete = page.cursor === 0 && !page.has_more;
7746
+ const truncated = !complete;
7747
+ const truncationReason = page.cursor > 0 ? page.has_more ? "cursor_and_limit" : "cursor" : page.has_more ? "limit" : null;
7748
+ return {
7749
+ configs: records,
7750
+ _meta: {
7751
+ schema_version: 1,
7752
+ count: records.length,
7753
+ total: page.total,
7754
+ limit: page.limit,
7755
+ cursor: page.cursor,
7756
+ next_cursor: page.next_cursor,
7757
+ has_more: page.has_more,
7758
+ complete,
7759
+ truncated,
7760
+ truncation_reason: truncationReason,
7761
+ detail,
7762
+ fields: [...fields],
7763
+ source_bounded: page.source_bounded
7764
+ }
7765
+ };
7766
+ }
7767
+ program.command("list").alias("ls").description("List stored configs").option("-c, --category <cat>", "filter by category").option("-a, --agent <agent>", "filter by agent").option("-k, --kind <kind>", "filter by kind (file|reference)").option("-t, --tag <tag>", "filter by tag").option("-s, --search <query>", "search name/description/content").option("-f, --format <fmt>", "output format: compact|table|json", "compact").option("--brief", "shorthand for --format compact").option("--verbose", "show expanded metadata for each listed config").option("--json", "output machine-readable records (legacy default: complete full array)").option("--detail <level>", "JSON detail: compact|full; enables a bounded metadata envelope").option("--fields <fields>", "comma-separated JSON fields; implies --detail compact").option("--all", "explicitly read every matching row").option("--pretty", "pretty-print modern JSON output (modern JSON is compact by default)").option("--limit <n>", `max rows (default ${DEFAULT_LIST_LIMIT} for bounded output)`).option("--cursor <n>", "zero-based pagination cursor").action(async (opts) => {
7768
+ const requestedDetail = parseConfigListDetail(opts.detail);
7769
+ const modernJson = requestedDetail !== undefined || opts.fields !== undefined;
7770
+ const jsonRequested = Boolean(opts.json || opts.format === "json");
7771
+ if (modernJson && !jsonRequested)
7772
+ throw new Error("--detail and --fields require JSON output");
7773
+ if (opts.all && !modernJson)
7774
+ throw new Error("--all requires JSON output with --detail or --fields");
7775
+ if (opts.pretty && !jsonRequested)
7776
+ throw new Error("--pretty requires JSON output");
7777
+ const detail = requestedDetail ?? "compact";
7778
+ const fields = parseConfigListFields(opts.fields, detail) ?? (detail === "full" ? [...FULL_CONFIG_FIELDS] : [...COMPACT_CONFIG_FIELDS]);
7779
+ const filter = {
7195
7780
  category: opts.category,
7196
7781
  agent: opts.agent,
7197
7782
  kind: opts.kind,
7198
7783
  tags: opts.tag ? [opts.tag] : undefined,
7199
7784
  search: opts.search
7200
- });
7785
+ };
7786
+ const store = resolveConfigStore();
7787
+ if (modernJson) {
7788
+ if (opts.all && (opts.cursor !== undefined || opts.limit !== undefined)) {
7789
+ throw new Error("--all cannot be combined with --limit or --cursor");
7790
+ }
7791
+ let page2;
7792
+ if (opts.all) {
7793
+ if (detail === "full") {
7794
+ const completeRead = await readAllConfigPages(store, filter);
7795
+ page2 = {
7796
+ items: completeRead.items,
7797
+ total: completeRead.items.length,
7798
+ limit: Math.max(completeRead.items.length, 1),
7799
+ cursor: 0,
7800
+ next_cursor: null,
7801
+ has_more: false,
7802
+ complete: true,
7803
+ truncated: false,
7804
+ source_bounded: completeRead.source_bounded
7805
+ };
7806
+ } else {
7807
+ const completeRead = await readAllConfigIdentityPages(store, filter);
7808
+ page2 = {
7809
+ items: completeRead.items,
7810
+ total: completeRead.items.length,
7811
+ limit: Math.max(completeRead.items.length, 1),
7812
+ cursor: 0,
7813
+ next_cursor: null,
7814
+ has_more: false,
7815
+ complete: true,
7816
+ truncated: false,
7817
+ source_bounded: completeRead.source_bounded
7818
+ };
7819
+ }
7820
+ } else {
7821
+ page2 = detail === "full" ? await store.listConfigsPage(filter, { limit: opts.limit, cursor: opts.cursor }) : await store.listConfigIdentitiesPage(filter, { limit: opts.limit, cursor: opts.cursor });
7822
+ }
7823
+ const records = page2.items.map((item) => projectConfigFields(item, fields));
7824
+ printMachineJson(configListEnvelope(records, {
7825
+ total: page2.total,
7826
+ limit: opts.all ? null : page2.limit,
7827
+ cursor: page2.cursor,
7828
+ next_cursor: page2.next_cursor,
7829
+ has_more: page2.has_more,
7830
+ complete: page2.complete,
7831
+ source_bounded: page2.source_bounded
7832
+ }, detail, fields), Boolean(opts.pretty));
7833
+ return;
7834
+ }
7835
+ const fmt = opts.json ? "json" : opts.verbose ? "table" : opts.brief ? "compact" : opts.format;
7201
7836
  if (fmt === "json") {
7837
+ const configs = opts.limit !== undefined || opts.cursor !== undefined ? (await store.listConfigsPage(filter, { limit: opts.limit, cursor: opts.cursor })).items : await store.listConfigs(filter);
7202
7838
  printJson(configs);
7203
7839
  return;
7204
7840
  }
7205
- if (configs.length === 0) {
7841
+ const page = opts.all ? (() => {
7842
+ throw new Error("--all is only available with --json and --detail");
7843
+ })() : await store.listConfigSummariesPage(filter, { limit: opts.limit, cursor: opts.cursor });
7844
+ if (page.total === 0) {
7206
7845
  console.log(chalk.dim("No configs found."));
7207
7846
  return;
7208
7847
  }
7209
- const page = paginate(configs, { limit: opts.limit, cursor: opts.cursor });
7210
7848
  if (fmt === "compact") {
7211
7849
  printConfigRows(page.items);
7212
7850
  } else {
@@ -7215,7 +7853,7 @@ program.command("list").alias("ls").description("List stored configs").option("-
7215
7853
  console.log();
7216
7854
  }
7217
7855
  }
7218
- pageFooter("configs list", page, "Use --verbose for expanded rows, --json for full records, or `configs show <slug>` for content.");
7856
+ pageFooter("configs list", page, "Use --verbose for expanded rows, --json --detail compact for metadata, or `configs show <slug>` for content.");
7219
7857
  });
7220
7858
  program.command("show <id>").alias("inspect").description("Show a config's content and metadata").option("-f, --format <fmt>", "output format: table|json|content", "table").action(async (id, opts) => {
7221
7859
  try {
@@ -7260,13 +7898,18 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
7260
7898
  }
7261
7899
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
7262
7900
  });
7263
- 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) => {
7264
- const abs = resolve6(filePath);
7901
+ 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) => {
7902
+ const expectedVersion = opts.expectedVersion === undefined ? undefined : Number(opts.expectedVersion);
7903
+ validateExpectedConfigVersion(expectedVersion);
7904
+ if (expectedVersion !== undefined && !opts.update) {
7905
+ throw new Error("--expected-version requires --update");
7906
+ }
7907
+ const abs = resolve7(filePath);
7265
7908
  if (!existsSync11(abs)) {
7266
7909
  console.error(chalk.red(`File not found: ${abs}`));
7267
7910
  process.exit(1);
7268
7911
  }
7269
- const rawContent = readFileSync7(abs, "utf-8");
7912
+ const rawContent = readFileSync8(abs, "utf-8");
7270
7913
  const storedFmt = detectFormat(abs);
7271
7914
  const fmt = redactFormatForTarget(abs, storedFmt);
7272
7915
  const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
@@ -7297,10 +7940,11 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7297
7940
  const exactIndex = isReference ? existingOwners.findIndex((owner) => owner.name === name) : -1;
7298
7941
  const target = exactIndex >= 0 ? existingOwners[exactIndex] : existingOwners[0];
7299
7942
  const rest = existingOwners.filter((owner) => owner.id !== target.id);
7300
- if (content !== target.content) {
7943
+ if (content !== target.content && expectedVersion === undefined) {
7301
7944
  await store.createSnapshot(target.id, target.content, target.version);
7302
7945
  }
7303
7946
  config = await store.updateConfig(target.id, {
7947
+ expected_version: expectedVersion,
7304
7948
  content,
7305
7949
  format: storedFmt,
7306
7950
  is_template: (opts.template ?? false) || isTemplate,
@@ -7317,6 +7961,9 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7317
7961
  }
7318
7962
  return;
7319
7963
  }
7964
+ if (expectedVersion !== undefined) {
7965
+ throw new Error("--expected-version requires an existing config; no config was created");
7966
+ }
7320
7967
  config = await store.createConfig({
7321
7968
  name,
7322
7969
  kind: opts.kind ?? "file",
@@ -7445,7 +8092,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7445
8092
  for (const entry of entries) {
7446
8093
  if (!entry.isDirectory())
7447
8094
  continue;
7448
- const projDir = join10(absDir, entry.name);
8095
+ const projDir = join11(absDir, entry.name);
7449
8096
  const hasAgentConfig = [
7450
8097
  "CLAUDE.md",
7451
8098
  ".mcp.json",
@@ -7458,7 +8105,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7458
8105
  ".aicopilot",
7459
8106
  ".cursor",
7460
8107
  ".agents"
7461
- ].some((marker) => existsSync11(join10(projDir, marker)));
8108
+ ].some((marker) => existsSync11(join11(projDir, marker)));
7462
8109
  if (!hasAgentConfig)
7463
8110
  continue;
7464
8111
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -7506,7 +8153,7 @@ program.command("import <file>").description("Restore a v2 Instructions domain a
7506
8153
  });
7507
8154
  program.command("whoami").description("Show setup summary").action(async () => {
7508
8155
  const store = resolveConfigStore();
7509
- const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db");
8156
+ const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db");
7510
8157
  const stats = await store.getConfigStats();
7511
8158
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
7512
8159
  console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
@@ -7890,7 +8537,7 @@ projectContextCmd.command("apply").description("Atomically write project context
7890
8537
  }
7891
8538
  });
7892
8539
  var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
7893
- 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) => {
8540
+ 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) => {
7894
8541
  try {
7895
8542
  const tool = opts.tool;
7896
8543
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -7945,7 +8592,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
7945
8592
  process.exit(1);
7946
8593
  }
7947
8594
  });
7948
- 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) => {
8595
+ sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "native instruction home; Sumi: resolved config from `sumi debug paths config`; Grok: GROK_HOME; Devin: user config directory (global AGENTS.md unless --project-root is set)").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "verify every registered, non-retired global-* source is in this render (expected read fresh from the registry, independent of this plan); on shortfall warn, exit non-zero, and for apply refuse to write (todos 102d6d0a, O15-00694)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--adopt-file <relativepath=sha256>", "adopt one reviewed unmanaged generated target only at its exact observed SHA256; repeatable", collectOption, []).option("--reconcile-file <relativepath=sha256>", "reconcile one reviewed managed drifted target at its exact observed SHA256; requires --expected-manifest-sha256", collectOption, []).option("--expected-manifest-sha256 <sha256>", "require the previously reviewed manifest preimage to match exactly before applying").option("--json", "output apply JSON").action(async (opts) => {
7949
8596
  try {
7950
8597
  const tool = opts.tool;
7951
8598
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -7971,7 +8618,15 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
7971
8618
  return;
7972
8619
  }
7973
8620
  const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
7974
- const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, ownedClaudeAuthorities });
8621
+ const parsePreimages = (values, option) => values.map((value) => {
8622
+ const separator = value.lastIndexOf("=");
8623
+ if (separator <= 0)
8624
+ throw new Error(`${option} requires relativepath=sha256.`);
8625
+ return { relativePath: value.slice(0, separator), sha256: value.slice(separator + 1) };
8626
+ });
8627
+ const adoptFiles = parsePreimages(opts.adoptFile, "--adopt-file");
8628
+ const reconcileFiles = parsePreimages(opts.reconcileFile, "--reconcile-file");
8629
+ const result = applySessionRender(plan, { dryRun: opts.dryRun, force: opts.force, adoptFiles, reconcileFiles, expectedManifestSha256: opts.expectedManifestSha256, ownedClaudeAuthorities });
7975
8630
  if (opts.json) {
7976
8631
  printJson({
7977
8632
  ...result,
@@ -8012,7 +8667,7 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8012
8667
  console.log(chalk.dim(`global source coverage: ${globalCoverage.expectedSlugs.length}/${globalCoverage.expectedSlugs.length} complete`));
8013
8668
  }
8014
8669
  if (result.conflicts.length > 0) {
8015
- console.error(chalk.red(`Conflicts: ${result.conflicts.length}. Re-run with --force to overwrite unmanaged files.`));
8670
+ console.error(chalk.red(`Conflicts: ${result.conflicts.length}. Review and import existing instructions before adopting each exact preimage with --adopt-file.`));
8016
8671
  process.exitCode = 1;
8017
8672
  }
8018
8673
  } catch (e) {
@@ -8020,6 +8675,29 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8020
8675
  process.exit(1);
8021
8676
  }
8022
8677
  });
8678
+ 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) => {
8679
+ try {
8680
+ const result = await refreshSessionRender({ targetHome: resolveSessionPath(opts.targetHome), store: resolveConfigStore(), dryRun: opts.dryRun });
8681
+ if (opts.json)
8682
+ printJson(result);
8683
+ else {
8684
+ console.log(`${result.status}: ${result.targetHome}`);
8685
+ console.log(`hosted profile: ${result.selector.profileId}`);
8686
+ console.log(`source hash: ${result.sourceHash}`);
8687
+ if (result.apply.snapshotPath)
8688
+ console.log(`snapshot: ${result.apply.snapshotPath}`);
8689
+ }
8690
+ if (result.status === "blocked")
8691
+ process.exitCode = 1;
8692
+ } catch (error) {
8693
+ const message = formatCliError(error);
8694
+ if (opts.json)
8695
+ printJson({ status: "failed", error: message });
8696
+ else
8697
+ console.error(chalk.red(message));
8698
+ process.exitCode = 1;
8699
+ }
8700
+ });
8023
8701
  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) => {
8024
8702
  try {
8025
8703
  const result = restoreSessionRenderSnapshot(resolveSessionPath(snapshot), {
@@ -8214,7 +8892,7 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
8214
8892
  });
8215
8893
  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) => {
8216
8894
  try {
8217
- const { renderTemplate } = await import("../chunks/template-wgnzdn7h.js");
8895
+ const { renderTemplate } = await import("../chunks/template-e4qrxs8p.js");
8218
8896
  const c = await resolveConfigStore().getConfig(id);
8219
8897
  const vars = {};
8220
8898
  if (opts.var) {
@@ -8228,7 +8906,7 @@ templateCmd.command("render <id>").description("Render a template config with va
8228
8906
  }
8229
8907
  }
8230
8908
  if (opts.env) {
8231
- const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-wgnzdn7h.js");
8909
+ const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-e4qrxs8p.js");
8232
8910
  for (const v of extractTemplateVars2(c.content)) {
8233
8911
  if (!(v.name in vars) && process.env[v.name]) {
8234
8912
  vars[v.name] = process.env[v.name];
@@ -8277,7 +8955,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
8277
8955
  } else if (opts.all) {
8278
8956
  configs = await store.listConfigs(opts.category ? { kind: "file", category: opts.category } : { kind: "file" });
8279
8957
  } else {
8280
- const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-2075deve.js");
8958
+ const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-azd9t4p8.js");
8281
8959
  const slugs = [
8282
8960
  ...KNOWN_CONFIGS2.filter((k) => !k.rulesDir).map((k) => k.name)
8283
8961
  ];
@@ -8571,7 +9249,7 @@ command = "${mcpBinary}"
8571
9249
  args = []
8572
9250
  `;
8573
9251
  if (ex(configPath)) {
8574
- const content = readFileSync7(configPath, "utf-8");
9252
+ const content = readFileSync8(configPath, "utf-8");
8575
9253
  if (findTomlTableLines(content.split(`
8576
9254
  `), "mcp_servers.configs")) {
8577
9255
  console.log(chalk.dim("= Already installed in Codex"));
@@ -8633,7 +9311,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8633
9311
  console.log(chalk.dim("= Not installed in Codex (no config.toml)"));
8634
9312
  continue;
8635
9313
  }
8636
- const content = readFileSync7(configPath, "utf-8");
9314
+ const content = readFileSync8(configPath, "utf-8");
8637
9315
  const lines = content.split(`
8638
9316
  `);
8639
9317
  const table = findTomlTableLines(lines, "mcp_servers.configs");
@@ -8652,7 +9330,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8652
9330
  console.log(chalk.green("\u2713") + " Removed from Codex");
8653
9331
  } else if (target === "antigravity") {
8654
9332
  const { existsSync: ex, readFileSync: rf, writeFileSync: wf } = await import("fs");
8655
- const configPath = join10(homedir4(), ".gemini", "config", "mcp_config.json");
9333
+ const configPath = join11(homedir4(), ".gemini", "config", "mcp_config.json");
8656
9334
  if (!ex(configPath)) {
8657
9335
  console.log(chalk.dim("= Not installed in Antigravity (no mcp_config.json)"));
8658
9336
  continue;
@@ -8687,15 +9365,15 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8687
9365
  if (!opts.confirmLocal) {
8688
9366
  throw new Error("migrate-legacy requires --confirm-local");
8689
9367
  }
8690
- const destinationPath = resolve6(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db"));
9368
+ const destinationPath = resolve7(opts.destination || process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db"));
8691
9369
  const database = await import("../chunks/database-wstmr56z.js");
8692
9370
  database.getDatabase(destinationPath);
8693
9371
  database.resetDatabase();
8694
9372
  const { migrateLegacyInstructionsStore } = await import("../chunks/legacy-store-migration-z2m5yzyk.js");
8695
9373
  const result = migrateLegacyInstructionsStore({
8696
- ...opts.source ? { sourcePath: resolve6(opts.source) } : {},
9374
+ ...opts.source ? { sourcePath: resolve7(opts.source) } : {},
8697
9375
  destinationPath,
8698
- ...opts.backup ? { backupPath: resolve6(opts.backup) } : {},
9376
+ ...opts.backup ? { backupPath: resolve7(opts.backup) } : {},
8699
9377
  dryRun: !opts.apply,
8700
9378
  ...opts.mergePreserveDestination ? { mergePolicy: "preserve-destination" } : {},
8701
9379
  operatorConfirmed: true
@@ -8714,7 +9392,7 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8714
9392
  if (result.backupCreated)
8715
9393
  console.log(chalk.dim(" destination backup created before mutation"));
8716
9394
  });
8717
- 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) => {
9395
+ 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) => {
8718
9396
  const store = resolveConfigStore();
8719
9397
  if (opts.force) {
8720
9398
  try {
@@ -8728,26 +9406,28 @@ program.command("init").description("First-time setup: sync all known configs, c
8728
9406
  }
8729
9407
  console.log(chalk.bold(`@hasna/instructions \u2014 initializing
8730
9408
  `));
8731
- const result = await syncKnown({ store });
8732
- console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
8733
- if (result.skipped.length > 0) {
8734
- console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
9409
+ if (store.mode === "local" || opts.importLocal) {
9410
+ const result = await syncKnown({ store });
9411
+ console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
9412
+ if (result.skipped.length > 0)
9413
+ console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
9414
+ } else {
9415
+ console.log(chalk.dim("Hosted authority retained; use --import-local for an intentional local-file migration."));
8735
9416
  }
8736
9417
  const refs = [
8737
9418
  { slug: "workspace-structure", name: "Workspace Structure", category: "workspace", content: `# Workspace Structure
8738
9419
 
8739
- See ~/.claude/rules/workspace.md for full conventions.`, desc: "~/Workspace/ hierarchy and naming" },
8740
- { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# .secrets Schema
8741
-
8742
- Location: ~/.secrets (sourced by ~/.zshrc)
8743
- Format: export KEY_NAME="value"
9420
+ 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" },
9421
+ { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# Credential References
8744
9422
 
8745
- Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`, desc: "Shape of ~/.secrets (no values)" }
9423
+ 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" }
8746
9424
  ];
8747
9425
  for (const ref of refs) {
8748
9426
  try {
8749
9427
  await store.getConfig(ref.slug);
8750
- } catch {
9428
+ } catch (error) {
9429
+ if (!(error instanceof ConfigNotFoundError))
9430
+ throw error;
8751
9431
  await store.createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.desc });
8752
9432
  }
8753
9433
  }
@@ -8757,12 +9437,11 @@ Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`,
8757
9437
  await ensureProjectDashboardStandardConfig(store);
8758
9438
  try {
8759
9439
  await store.getProfile("my-setup");
8760
- } catch {
8761
- const p = await store.createProfile({ name: "my-setup", description: "Default profile with all known configs" });
8762
- const allConfigs = await store.listConfigs();
8763
- for (const c of allConfigs)
8764
- await store.addConfigToProfile(p.id, c.id);
8765
- console.log(chalk.green("\u2713") + ` Created profile "my-setup" with ${allConfigs.length} configs`);
9440
+ } catch (error) {
9441
+ if (!(error instanceof ProfileNotFoundError))
9442
+ throw error;
9443
+ await store.createProfile({ name: "my-setup", description: "Instruction profile; add explicitly reviewed and scoped source bindings" });
9444
+ console.log(chalk.green("\u2713") + ' Created empty profile "my-setup"; bind reviewed instruction sources explicitly.');
8766
9445
  }
8767
9446
  const machineProfiles = await ensurePlatformProfiles(store);
8768
9447
  console.log(chalk.green("\u2713") + ` Ensured ${machineProfiles.length} machine-aware profile(s)`);
@@ -8773,7 +9452,7 @@ DB stats:`));
8773
9452
  if (count > 0)
8774
9453
  console.log(` ${key.padEnd(18)} ${count}`);
8775
9454
  }
8776
- const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join10(getRawStoreRoot(), "instructions.db");
9455
+ const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join11(getRawStoreRoot(), "instructions.db");
8777
9456
  console.log(chalk.dim(`
8778
9457
  ${store.mode === "api" ? "API" : "DB"}: ${location}`));
8779
9458
  });
@@ -8874,13 +9553,13 @@ storageCmd.command("status").description("Show redacted S3 configuration status
8874
9553
  });
8875
9554
  var storageBackupCmd = storageCmd.command("backup").description("Push, pull, and verify immutable Instructions backup objects");
8876
9555
  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) => {
8877
- const abs = resolve6(file);
8878
- if (!existsSync11(abs) || !lstatSync4(abs).isFile())
9556
+ const abs = resolve7(file);
9557
+ if (!existsSync11(abs) || !lstatSync5(abs).isFile())
8879
9558
  throw new Error(`Backup file not found: ${abs}`);
8880
9559
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
8881
9560
  import("../chunks/s3-config-hhght91k.js"),
8882
- import("../chunks/s3-backup-n2z4fwyh.js"),
8883
- import("../chunks/s3-object-store-90tgbh6k.js")
9561
+ import("../chunks/s3-backup-hxe3zpfz.js"),
9562
+ import("../chunks/s3-object-store-n0s82swt.js")
8884
9563
  ]);
8885
9564
  const config = loadInstructionsS3Config(process.env);
8886
9565
  if (!config)
@@ -8927,8 +9606,8 @@ storageBackupCmd.command("verify <backup-id>").description("Verify the immutable
8927
9606
  const versions = normalizeBackupVersionOptions(opts);
8928
9607
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
8929
9608
  import("../chunks/s3-config-hhght91k.js"),
8930
- import("../chunks/s3-backup-n2z4fwyh.js"),
8931
- import("../chunks/s3-object-store-90tgbh6k.js")
9609
+ import("../chunks/s3-backup-hxe3zpfz.js"),
9610
+ import("../chunks/s3-object-store-n0s82swt.js")
8932
9611
  ]);
8933
9612
  const config = loadInstructionsS3Config(process.env);
8934
9613
  if (!config)
@@ -8953,8 +9632,8 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
8953
9632
  const versions = normalizeBackupVersionOptions(opts);
8954
9633
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
8955
9634
  import("../chunks/s3-config-hhght91k.js"),
8956
- import("../chunks/s3-backup-n2z4fwyh.js"),
8957
- import("../chunks/s3-object-store-90tgbh6k.js")
9635
+ import("../chunks/s3-backup-hxe3zpfz.js"),
9636
+ import("../chunks/s3-object-store-n0s82swt.js")
8958
9637
  ]);
8959
9638
  const config = loadInstructionsS3Config(process.env);
8960
9639
  if (!config)
@@ -8965,16 +9644,16 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
8965
9644
  backupId,
8966
9645
  ...versions
8967
9646
  });
8968
- const output = resolve6(opts.output);
9647
+ const output = resolve7(opts.output);
8969
9648
  if (existsSync11(output)) {
8970
9649
  if (!opts.force)
8971
9650
  throw new Error(`Refusing to overwrite existing backup file: ${output}`);
8972
- if (!lstatSync4(output).isFile() || lstatSync4(output).isSymbolicLink()) {
9651
+ if (!lstatSync5(output).isFile() || lstatSync5(output).isSymbolicLink()) {
8973
9652
  throw new Error(`Refusing to replace a non-regular backup destination: ${output}`);
8974
9653
  }
8975
9654
  }
8976
- mkdirSync6(dirname6(output), { recursive: true, mode: 448 });
8977
- const temp = join10(dirname6(output), `.${basename(output)}.tmp-${process.pid}-${Date.now()}`);
9655
+ mkdirSync6(dirname7(output), { recursive: true, mode: 448 });
9656
+ const temp = join11(dirname7(output), `.${basename2(output)}.tmp-${process.pid}-${Date.now()}`);
8978
9657
  try {
8979
9658
  writeFileSync5(temp, pulled.bytes, { flag: "wx", mode: 384 });
8980
9659
  renameSync2(temp, output);
@@ -8999,10 +9678,10 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
8999
9678
  });
9000
9679
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
9001
9680
  const { mkdirSync: mk } = await import("fs");
9002
- const backupDir = join10(getRawStoreRoot(), "backups");
9681
+ const backupDir = join11(getRawStoreRoot(), "backups");
9003
9682
  mk(backupDir, { recursive: true });
9004
9683
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
9005
- const outPath = join10(backupDir, `configs-${ts}.tar.gz`);
9684
+ const outPath = join11(backupDir, `configs-${ts}.tar.gz`);
9006
9685
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
9007
9686
  const { statSync: st } = await import("fs");
9008
9687
  const size = st(outPath).size;
@@ -9176,7 +9855,7 @@ program.command("watch").description("Watch known config files for changes and a
9176
9855
  const store = resolveConfigStore();
9177
9856
  const interval = Number(opts.interval);
9178
9857
  const { statSync: st } = await import("fs");
9179
- const { expandPath: expandPath2 } = await import("../chunks/apply-jp13sa1e.js");
9858
+ const { expandPath: expandPath2 } = await import("../chunks/apply-wj7wh5km.js");
9180
9859
  console.log(chalk.bold("@hasna/instructions watch") + chalk.dim(` \u2014 polling every ${interval}ms`));
9181
9860
  console.log(chalk.dim(`Watching known config files for changes\u2026
9182
9861
  `));
@@ -9188,7 +9867,7 @@ program.command("watch").description("Watch known config files for changes and a
9188
9867
  continue;
9189
9868
  const { readdirSync: readdirSync3 } = await import("fs");
9190
9869
  for (const f of readdirSync3(absDir).filter((f2) => f2.endsWith(".md"))) {
9191
- const abs = join10(absDir, f);
9870
+ const abs = join11(absDir, f);
9192
9871
  mtimes.set(abs, st(abs).mtimeMs);
9193
9872
  }
9194
9873
  } else {
@@ -9216,7 +9895,7 @@ program.command("watch").description("Watch known config files for changes and a
9216
9895
  if (!existsSync11(absDir))
9217
9896
  continue;
9218
9897
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
9219
- const abs = join10(absDir, f);
9898
+ const abs = join11(absDir, f);
9220
9899
  if (!mtimes.has(abs)) {
9221
9900
  mtimes.set(abs, st(abs).mtimeMs);
9222
9901
  changed++;
@@ -9256,7 +9935,7 @@ program.command("report").description("Summary of stored configs, drift, and eco
9256
9935
  missing++;
9257
9936
  continue;
9258
9937
  }
9259
- const disk = readFileSync7(abs, "utf-8");
9938
+ const disk = readFileSync8(abs, "utf-8");
9260
9939
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
9261
9940
  if (redactedDisk !== c.content)
9262
9941
  drifted++;