@hasna/instructions 0.7.1 → 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 (59) hide show
  1. package/README.md +34 -7
  2. package/dist/chunks/{apply-c3sd4agz.js → apply-ps6jjt6k.js} +3 -3
  3. package/dist/chunks/{apply-pnn7a3y7.js → apply-wj7wh5km.js} +3 -3
  4. package/dist/chunks/{index-pdpg8a44.js → index-18j7x3xw.js} +2 -2
  5. package/dist/chunks/{index-3djthcm6.js → index-27az1qb1.js} +28 -1
  6. package/dist/chunks/{index-ypcef3a2.js → index-ape24zc0.js} +2 -2
  7. package/dist/chunks/{index-jg8ptcwh.js → index-ek15201v.js} +2 -2
  8. package/dist/chunks/{index-6jncnnw7.js → index-n6nh6f5r.js} +119 -17
  9. package/dist/chunks/{index-aj66f6xr.js → index-pq18fdbj.js} +29 -6
  10. package/dist/chunks/{index-n3fjspr0.js → index-preyvj8n.js} +28 -1
  11. package/dist/chunks/{index-r7nfef2g.js → index-y8410ve0.js} +3 -3
  12. package/dist/chunks/{local-0a9gja58.js → local-1c1qxzvx.js} +15 -4
  13. package/dist/chunks/{local-969ye538.js → local-hc67bynj.js} +15 -4
  14. package/dist/chunks/{sync-q5hvwq6e.js → sync-12m3vs6g.js} +4 -4
  15. package/dist/chunks/{sync-xtkbm5br.js → sync-azd9t4p8.js} +4 -4
  16. package/dist/chunks/{template-wgnzdn7h.js → template-e4qrxs8p.js} +1 -1
  17. package/dist/chunks/{template-fsfxpe4p.js → template-nhn124zd.js} +1 -1
  18. package/dist/cli/index.js +596 -133
  19. package/dist/data/config-store.d.ts +12 -4
  20. package/dist/data/config-store.d.ts.map +1 -1
  21. package/dist/db/configs.d.ts.map +1 -1
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +658 -98
  25. package/dist/lib/asset-plan.d.ts.map +1 -1
  26. package/dist/lib/codewith-shared-todos-storage-standard.d.ts.map +1 -1
  27. package/dist/lib/dangerous-operation-guard-standard.d.ts.map +1 -1
  28. package/dist/lib/global-agent-rules-standard.d.ts.map +1 -1
  29. package/dist/lib/global-source-coverage.d.ts +12 -7
  30. package/dist/lib/global-source-coverage.d.ts.map +1 -1
  31. package/dist/lib/instruction-graph.d.ts +1 -1
  32. package/dist/lib/instruction-graph.d.ts.map +1 -1
  33. package/dist/lib/instruction-source-policy.d.ts +6 -0
  34. package/dist/lib/instruction-source-policy.d.ts.map +1 -0
  35. package/dist/lib/platform-profiles.d.ts.map +1 -1
  36. package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
  37. package/dist/lib/session-apply.d.ts +24 -0
  38. package/dist/lib/session-apply.d.ts.map +1 -1
  39. package/dist/lib/session-refresh.d.ts +30 -0
  40. package/dist/lib/session-refresh.d.ts.map +1 -0
  41. package/dist/lib/session-render.d.ts +34 -1
  42. package/dist/lib/session-render.d.ts.map +1 -1
  43. package/dist/lib/transforms.d.ts +1 -0
  44. package/dist/lib/transforms.d.ts.map +1 -1
  45. package/dist/mcp/index.js +13 -12
  46. package/dist/mcp/server.d.ts.map +1 -1
  47. package/dist/sdk/index.js +19 -0
  48. package/dist/sdk/v1-client.d.ts +7 -1
  49. package/dist/sdk/v1-client.d.ts.map +1 -1
  50. package/dist/sdk/v1.generated.d.ts +20 -0
  51. package/dist/sdk/v1.generated.d.ts.map +1 -1
  52. package/dist/server/index.js +122 -8
  53. package/dist/server/openapi.d.ts +123 -0
  54. package/dist/server/openapi.d.ts.map +1 -1
  55. package/dist/server/v1.d.ts.map +1 -1
  56. package/dist/storage/cloud-store.d.ts.map +1 -1
  57. package/dist/types/index.d.ts +16 -2
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -9,31 +9,39 @@ import {
9
9
  syncKnown,
10
10
  syncProject,
11
11
  syncToDisk
12
- } from "../chunks/index-pdpg8a44.js";
12
+ } from "../chunks/index-18j7x3xw.js";
13
13
  import {
14
14
  applyConfigsWithReport,
15
15
  compactPathForConfigHome,
16
16
  expandPath,
17
- normalizeTargetPath
18
- } from "../chunks/index-r7nfef2g.js";
17
+ normalizeTargetPath,
18
+ sessionRenderOwnsPath
19
+ } from "../chunks/index-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-6jncnnw7.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) {
@@ -4530,14 +4541,20 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4530
4541
  const targetHome = assertSafeTargetHome(plan.targetHome);
4531
4542
  assertClaudeAuthorityStillClear(plan, targetHome, options.ownedClaudeAuthorities);
4532
4543
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
4533
- const files = [...payloadFiles, plan.manifestFile];
4534
4544
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
4545
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4535
4546
  const previousManifest = readPreviousManifest(manifestPath);
4536
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];
4537
4554
  const currentRelativePaths = new Set(files.map((file) => file.relativePath));
4538
4555
  const drift = checkSessionRenderDrift(targetHome, manifestPath);
4539
4556
  const results = [
4540
- ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options)),
4557
+ ...files.map((file) => planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes)),
4541
4558
  ...planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options)
4542
4559
  ];
4543
4560
  assertNotSilentManagedWipeout(plan, results);
@@ -4560,7 +4577,9 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4560
4577
  skippedSources: plan.manifest.skippedSources,
4561
4578
  files: results,
4562
4579
  conflicts,
4563
- drift
4580
+ drift,
4581
+ adoptions,
4582
+ reconciliations
4564
4583
  };
4565
4584
  }
4566
4585
  let snapshotPath = null;
@@ -4573,10 +4592,12 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4573
4592
  if (!options.dryRun) {
4574
4593
  const allowPortableFallback = coordination === null;
4575
4594
  const forcePortableFileOps = options.test_hooks?.force_portable_file_ops ?? false;
4595
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4576
4596
  ensureSessionTargetHome(targetHome);
4577
- rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps);
4597
+ rollback = writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps, adoptions, reconciliations);
4578
4598
  snapshotPath = rollback.snapshotPath;
4579
4599
  options.test_hooks?.before_apply_writes?.({ plan, results });
4600
+ assertManifestPrecondition(manifestPath, targetHome, options.expectedManifestSha256);
4580
4601
  const resultsByPath = new Map(results.map((result) => [result.path, result]));
4581
4602
  for (const file of payloadFiles) {
4582
4603
  applyPlannedFile(plan, file, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
@@ -4596,7 +4617,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4596
4617
  });
4597
4618
  coordination?.assert_held();
4598
4619
  }
4599
- applyPlannedFile(plan, plan.manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4620
+ applyPlannedFile(plan, manifestFile, targetHome, resultsByPath, coordination, allowPortableFallback, forcePortableFileOps);
4600
4621
  }
4601
4622
  return {
4602
4623
  dryRun: options.dryRun ?? false,
@@ -4610,9 +4631,171 @@ function applySessionRenderUnlocked(plan, options, coordination) {
4610
4631
  skippedSources: plan.manifest.skippedSources,
4611
4632
  files: results,
4612
4633
  conflicts,
4613
- drift
4634
+ drift,
4635
+ adoptions,
4636
+ reconciliations
4614
4637
  };
4615
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
+ }
4616
4799
  function assertCursorAuthorityUnchanged(plan) {
4617
4800
  if (plan.tool !== "cursor" || plan.targetKind === "blocked")
4618
4801
  return;
@@ -5084,12 +5267,29 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
5084
5267
  }
5085
5268
  return path;
5086
5269
  }
5087
- function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
5270
+ function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options, adoptedHashes, reconciledHashes) {
5088
5271
  const target = resolvePlannedFilePath(plan, file, targetHome);
5089
5272
  const previousContent = existsSync5(target) ? readFileSync(target, "utf-8") : null;
5090
5273
  const previousSha256 = previousContent === null ? null : sha2563(previousContent);
5091
5274
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
5092
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
+ }
5093
5293
  if (previousContent !== null && !options.force && !previouslyManaged) {
5094
5294
  return {
5095
5295
  path: target,
@@ -5309,9 +5509,13 @@ function requiredPreviousHash(result) {
5309
5509
  }
5310
5510
  return result.previousSha256;
5311
5511
  }
5312
- function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
5313
- const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync5(result.path)).map((result) => {
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);
5314
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
+ }
5315
5519
  return {
5316
5520
  path: result.path,
5317
5521
  relativePath: result.relativePath,
@@ -5352,13 +5556,19 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
5352
5556
  manifestPath,
5353
5557
  previousManifest,
5354
5558
  files: existingFiles,
5355
- afterFiles
5559
+ afterFiles,
5560
+ ...adoptions.length > 0 ? { adoptions } : {},
5561
+ ...reconciliations.length > 0 ? { reconciliations } : {}
5356
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
+ }
5357
5568
  coordination?.assert_held();
5358
5569
  writeProjectContextCoordinatedFile({
5359
5570
  path: snapshotPath,
5360
- content: `${JSON.stringify(snapshot, null, 2)}
5361
- `,
5571
+ content: snapshotContent,
5362
5572
  workspace_root: sessionRenderSnapshotWorkspaceRoot(targetHome),
5363
5573
  default_mode: 384,
5364
5574
  expected_hash: null,
@@ -5416,11 +5626,15 @@ function sha2563(content) {
5416
5626
  return createHash3("sha256").update(content).digest("hex");
5417
5627
  }
5418
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
+
5419
5634
  // src/lib/global-source-coverage.ts
5420
5635
  var GLOBAL_SOURCE_SLUG_PREFIX = "global-";
5421
- var RETIRED_GLOBAL_SOURCE_TAG = "retired-global-source";
5422
5636
  function expectedGlobalSourceSlugs(registryConfigs) {
5423
- return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => !(c.tags ?? []).includes(RETIRED_GLOBAL_SOURCE_TAG)).map((c) => c.slug).sort();
5637
+ return registryConfigs.filter((c) => c.slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX)).filter((c) => instructionSourceRejection(c) === null).map((c) => c.slug).sort();
5424
5638
  }
5425
5639
  function accountedGlobalSourceSlugs(manifest) {
5426
5640
  return [...manifest.sources, ...manifest.skippedSources].map((source) => source.id).filter((id) => id.startsWith(GLOBAL_SOURCE_SLUG_PREFIX));
@@ -5433,6 +5647,10 @@ function computeGlobalSourceCoverage(registryConfigs, configuredSlugs) {
5433
5647
  const unexpected = [...configuredSet].filter((slug) => slug.startsWith(GLOBAL_SOURCE_SLUG_PREFIX) && !expectedSet.has(slug)).sort();
5434
5648
  return {
5435
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)),
5436
5654
  configuredSlugs: [...configuredSet].sort(),
5437
5655
  missingSlugs: missing,
5438
5656
  unexpectedSlugs: unexpected,
@@ -5688,6 +5906,176 @@ function stationProfileSource(env = process.env) {
5688
5906
  };
5689
5907
  }
5690
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
+
5691
6079
  // src/lib/project-dashboard-standard.ts
5692
6080
  var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
5693
6081
  var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
@@ -5780,11 +6168,15 @@ async function ensureProjectDashboardStandardConfig(store = resolveConfigStore()
5780
6168
  };
5781
6169
  try {
5782
6170
  const existing = await store.getConfig(PROJECT_DASHBOARD_STANDARD_SLUG);
6171
+ if (isRetiredInstructionSource(existing))
6172
+ return existing;
5783
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) {
5784
6174
  return await store.updateConfig(existing.id, input);
5785
6175
  }
5786
6176
  return existing;
5787
- } catch {
6177
+ } catch (error) {
6178
+ if (!(error instanceof ConfigNotFoundError))
6179
+ throw error;
5788
6180
  return await store.createConfig(input);
5789
6181
  }
5790
6182
  }
@@ -5797,10 +6189,9 @@ function profileHasSelectors(profile) {
5797
6189
  var PLATFORM_PROFILE_PRESETS = [
5798
6190
  {
5799
6191
  name: "linux-arm64",
5800
- description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
5801
- selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b", "station01"] },
6192
+ description: "Default reviewed instruction profile for Linux arm64",
6193
+ selectors: { os: ["linux"], arch: ["arm64"] },
5802
6194
  variables: {
5803
- WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
5804
6195
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5805
6196
  BUN_PATH: "{{BUN_BIN_DIR}}/bun",
5806
6197
  PATH_PREFIX: "{{BUN_BIN_DIR}}",
@@ -5809,10 +6200,9 @@ var PLATFORM_PROFILE_PRESETS = [
5809
6200
  },
5810
6201
  {
5811
6202
  name: "macos-arm64",
5812
- description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
5813
- selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
6203
+ description: "Default reviewed instruction profile for macOS arm64",
6204
+ selectors: { os: ["macos"], arch: ["arm64"] },
5814
6205
  variables: {
5815
- WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
5816
6206
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
5817
6207
  BUN_PATH: "/opt/homebrew/bin/bun",
5818
6208
  PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}",
@@ -5821,12 +6211,21 @@ var PLATFORM_PROFILE_PRESETS = [
5821
6211
  }
5822
6212
  ];
5823
6213
  async function ensurePlatformProfiles(store = resolveConfigStore()) {
5824
- const configs = await store.listConfigs();
5825
6214
  const ensured = [];
5826
- for (const preset of PLATFORM_PROFILE_PRESETS) {
5827
- let profile;
6215
+ const existingProfiles = await Promise.all(PLATFORM_PROFILE_PRESETS.map(async (preset) => {
5828
6216
  try {
5829
- 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;
5830
6229
  const selectors = mergeProfileSelectors(preset.selectors, profile.selectors);
5831
6230
  const variables = mergeProfileVariables(preset.variables, profile.variables);
5832
6231
  if (JSON.stringify(selectors) !== JSON.stringify(profile.selectors) || JSON.stringify(variables) !== JSON.stringify(profile.variables)) {
@@ -5836,12 +6235,9 @@ async function ensurePlatformProfiles(store = resolveConfigStore()) {
5836
6235
  variables
5837
6236
  });
5838
6237
  }
5839
- } catch {
6238
+ } else {
5840
6239
  profile = await store.createProfile(preset);
5841
6240
  }
5842
- for (const config of configs) {
5843
- await store.addConfigToProfile(profile.id, config.id);
5844
- }
5845
6241
  ensured.push(profile);
5846
6242
  }
5847
6243
  return ensured;
@@ -5982,29 +6378,33 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
5982
6378
  };
5983
6379
  try {
5984
6380
  const existing = await store.getConfig(DANGEROUS_OPERATION_GUARD_STANDARD_SLUG);
6381
+ if (isRetiredInstructionSource(existing))
6382
+ return existing;
5985
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)) {
5986
6384
  return await store.updateConfig(existing.id, input);
5987
6385
  }
5988
6386
  return existing;
5989
- } catch {
6387
+ } catch (error) {
6388
+ if (!(error instanceof ConfigNotFoundError))
6389
+ throw error;
5990
6390
  return await store.createConfig(input);
5991
6391
  }
5992
6392
  }
5993
6393
 
5994
6394
  // src/lib/managed-skill-runtimes.ts
5995
- import { createHash as createHash4 } from "crypto";
6395
+ import { createHash as createHash5 } from "crypto";
5996
6396
  import { spawnSync as spawnSync2 } from "child_process";
5997
6397
  import {
5998
6398
  existsSync as existsSync7,
5999
- lstatSync as lstatSync3,
6399
+ lstatSync as lstatSync4,
6000
6400
  mkdirSync as mkdirSync4,
6001
- readFileSync as readFileSync3,
6401
+ readFileSync as readFileSync4,
6002
6402
  renameSync,
6003
6403
  rmSync as rmSync2,
6004
6404
  writeFileSync as writeFileSync3
6005
6405
  } from "fs";
6006
6406
  import { homedir as homedir3 } from "os";
6007
- import { dirname as dirname4, join as join7, parse as parse2, relative as relative2, resolve as resolve5 } from "path";
6407
+ import { dirname as dirname5, join as join8, parse as parse3, relative as relative2, resolve as resolve6 } from "path";
6008
6408
  var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
6009
6409
  var INBOX_SKILL_MARKERS = [
6010
6410
  [".claude", "skills", "inbox", "SKILL.md"],
@@ -6015,25 +6415,25 @@ var INBOX_SKILL_MARKERS = [
6015
6415
  ];
6016
6416
  var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
6017
6417
  function sha2564(content) {
6018
- return createHash4("sha256").update(content).digest("hex");
6418
+ return createHash5("sha256").update(content).digest("hex");
6019
6419
  }
6020
6420
  function lstatOrNull(path) {
6021
6421
  try {
6022
- return lstatSync3(path);
6422
+ return lstatSync4(path);
6023
6423
  } catch {
6024
6424
  return null;
6025
6425
  }
6026
6426
  }
6027
6427
  function findSymlinkedAncestor(path) {
6028
- const normalized = resolve5(path);
6029
- const parsed = parse2(normalized);
6428
+ const normalized = resolve6(path);
6429
+ const parsed = parse3(normalized);
6030
6430
  let current = parsed.root;
6031
6431
  const rel = relative2(parsed.root, normalized);
6032
6432
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
6033
- current = join7(current, segment);
6433
+ current = join8(current, segment);
6034
6434
  if (!existsSync7(current))
6035
6435
  return null;
6036
- if (lstatSync3(current).isSymbolicLink())
6436
+ if (lstatSync4(current).isSymbolicLink())
6037
6437
  return current;
6038
6438
  }
6039
6439
  return null;
@@ -6055,7 +6455,7 @@ function readCanonicalSkill(explicitPath) {
6055
6455
  if (!stat?.isFile()) {
6056
6456
  throw new Error("packaged inbox skill contract is not a regular file");
6057
6457
  }
6058
- const content = readFileSync3(assetPath, "utf8");
6458
+ const content = readFileSync4(assetPath, "utf8");
6059
6459
  if (!content.includes("conversations watch --from <agent> --all")) {
6060
6460
  throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
6061
6461
  }
@@ -6093,7 +6493,7 @@ function compareVersions(left, right) {
6093
6493
  return 0;
6094
6494
  }
6095
6495
  function inspectSkillMarkers(homeDir2) {
6096
- return INBOX_SKILL_MARKERS.map((parts) => join7(homeDir2, ...parts)).map((path) => {
6496
+ return INBOX_SKILL_MARKERS.map((parts) => join8(homeDir2, ...parts)).map((path) => {
6097
6497
  const stat = lstatOrNull(path);
6098
6498
  if (!stat)
6099
6499
  return null;
@@ -6102,7 +6502,7 @@ function inspectSkillMarkers(homeDir2) {
6102
6502
  }
6103
6503
  return {
6104
6504
  path,
6105
- content: readFileSync3(path, "utf8"),
6505
+ content: readFileSync4(path, "utf8"),
6106
6506
  mode: stat.mode & 511,
6107
6507
  regular: true
6108
6508
  };
@@ -6137,7 +6537,7 @@ function inspectInbox(options) {
6137
6537
  let reason = "skill not installed";
6138
6538
  if (skillPresent) {
6139
6539
  const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
6140
- 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);
6141
6541
  if (nonRegular)
6142
6542
  reason = "managed skill target is not a regular file";
6143
6543
  else if (symlinkAncestor)
@@ -6224,10 +6624,10 @@ function cleanup(path) {
6224
6624
  rmSync2(path, { force: true });
6225
6625
  }
6226
6626
  function writeAtomic(path, content, mode) {
6227
- assertNoSymlinkAncestors2(dirname4(path));
6627
+ assertNoSymlinkAncestors2(dirname5(path));
6228
6628
  const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
6229
6629
  try {
6230
- mkdirSync4(dirname4(path), { recursive: true, mode: 493 });
6630
+ mkdirSync4(dirname5(path), { recursive: true, mode: 493 });
6231
6631
  writeFileSync3(tempPath, content, { mode, flag: "wx" });
6232
6632
  renameSync(tempPath, path);
6233
6633
  } finally {
@@ -6236,7 +6636,7 @@ function writeAtomic(path, content, mode) {
6236
6636
  }
6237
6637
  var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
6238
6638
  lstat: lstatOrNull,
6239
- read: (path) => readFileSync3(path, "utf8"),
6639
+ read: (path) => readFileSync4(path, "utf8"),
6240
6640
  write: writeAtomic
6241
6641
  };
6242
6642
  function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
@@ -6303,7 +6703,7 @@ async function reconcileManagedSkillRuntimes(options = {}) {
6303
6703
  dry_run: dryRun
6304
6704
  };
6305
6705
  }
6306
- 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);
6307
6707
  if (symlinkedAncestor) {
6308
6708
  return {
6309
6709
  runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
@@ -6380,28 +6780,28 @@ async function reconcileManagedSkillRuntimes(options = {}) {
6380
6780
  }
6381
6781
 
6382
6782
  // src/status.ts
6383
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
6783
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
6384
6784
 
6385
6785
  // src/lib/package-version.ts
6386
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
6387
- import { dirname as dirname5, join as join8 } from "path";
6786
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6787
+ import { dirname as dirname6, join as join9 } from "path";
6388
6788
  import { fileURLToPath } from "url";
6389
6789
  var cached = null;
6390
6790
  function getPackageVersion() {
6391
6791
  if (cached)
6392
6792
  return cached;
6393
6793
  try {
6394
- let dir = dirname5(fileURLToPath(import.meta.url));
6794
+ let dir = dirname6(fileURLToPath(import.meta.url));
6395
6795
  for (let i = 0;i < 8; i++) {
6396
- const pkgPath = join8(dir, "package.json");
6796
+ const pkgPath = join9(dir, "package.json");
6397
6797
  if (existsSync8(pkgPath)) {
6398
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
6798
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
6399
6799
  if (pkg.name === "@hasna/instructions" && pkg.version) {
6400
6800
  cached = pkg.version;
6401
6801
  return cached;
6402
6802
  }
6403
6803
  }
6404
- const parent = dirname5(dir);
6804
+ const parent = dirname6(dir);
6405
6805
  if (parent === dir)
6406
6806
  break;
6407
6807
  dir = parent;
@@ -6474,7 +6874,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6474
6874
  missingTargets += 1;
6475
6875
  continue;
6476
6876
  }
6477
- const disk = readFileSync5(targetPath, "utf-8");
6877
+ const disk = readFileSync6(targetPath, "utf-8");
6478
6878
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
6479
6879
  if (redactedDisk !== config.content) {
6480
6880
  driftedTargets += 1;
@@ -6566,9 +6966,9 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
6566
6966
  }
6567
6967
 
6568
6968
  // src/lib/provider-context.ts
6569
- import { createHash as createHash5 } from "crypto";
6570
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
6571
- import { join as join9 } from "path";
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";
6572
6972
  var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
6573
6973
  var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
6574
6974
  var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
@@ -6704,7 +7104,7 @@ function renderProviderFragment(entry) {
6704
7104
  return entry ? renderPerEndpointFragment(entry) : INVARIANT_FRAGMENT;
6705
7105
  }
6706
7106
  function sha2565(content) {
6707
- return createHash5("sha256").update(content).digest("hex");
7107
+ return createHash6("sha256").update(content).digest("hex");
6708
7108
  }
6709
7109
  function resolveAndRenderProviderContext(opts) {
6710
7110
  const entry = matchProviderEndpoint(opts.origin);
@@ -6713,18 +7113,18 @@ function resolveAndRenderProviderContext(opts) {
6713
7113
  const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
6714
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;
6715
7115
  const content = renderProviderFragment(entry);
6716
- const dir = join9(opts.homeDir, PROVIDER_CONTEXT_DIR);
7116
+ const dir = join10(opts.homeDir, PROVIDER_CONTEXT_DIR);
6717
7117
  if (!existsSync10(dir))
6718
7118
  mkdirSync5(dir, { recursive: true });
6719
7119
  const filename = `${entry ? entry.key : "invariant"}.md`;
6720
- const fragmentPath = join9(dir, filename);
7120
+ const fragmentPath = join10(dir, filename);
6721
7121
  const fragmentSha256 = sha2565(content);
6722
7122
  writeFileSync4(fragmentPath, content, "utf8");
6723
- const manifestPath = join9(dir, PROVIDER_CONTEXT_MANIFEST);
7123
+ const manifestPath = join10(dir, PROVIDER_CONTEXT_MANIFEST);
6724
7124
  let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
6725
7125
  try {
6726
7126
  if (existsSync10(manifestPath)) {
6727
- const parsed = JSON.parse(readFileSync6(manifestPath, "utf8"));
7127
+ const parsed = JSON.parse(readFileSync7(manifestPath, "utf8"));
6728
7128
  if (parsed && typeof parsed === "object")
6729
7129
  manifest = parsed;
6730
7130
  }
@@ -6874,7 +7274,7 @@ function parseSessionSource(value, order) {
6874
7274
  throw new Error(`Instruction source file not found: ${absPath}`);
6875
7275
  const content = readSessionInstructionSourceFile(absPath);
6876
7276
  const source = sourceFromFilePath(absPath, content, order);
6877
- const resolvedId = id || source.id || basename(absPath);
7277
+ const resolvedId = id || source.id || basename2(absPath);
6878
7278
  return {
6879
7279
  ...source,
6880
7280
  id: resolvedId,
@@ -6914,7 +7314,7 @@ function sessionSourceReplacements(values) {
6914
7314
  return replacements;
6915
7315
  }
6916
7316
  function readSessionInstructionSourceFile(path) {
6917
- const stat = lstatSync4(path);
7317
+ const stat = lstatSync5(path);
6918
7318
  if (stat.isSymbolicLink()) {
6919
7319
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
6920
7320
  }
@@ -6924,7 +7324,7 @@ function readSessionInstructionSourceFile(path) {
6924
7324
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
6925
7325
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
6926
7326
  }
6927
- return readFileSync7(path, "utf-8");
7327
+ return readFileSync8(path, "utf-8");
6928
7328
  }
6929
7329
  function parseLayeredReference(value) {
6930
7330
  const trimmed = value.trim();
@@ -6947,13 +7347,15 @@ async function collectSessionSources(opts, tool, store) {
6947
7347
  const sources = (opts.source ?? []).map((value, index) => parseSessionSource(value, index));
6948
7348
  for (const value of opts.config ?? []) {
6949
7349
  const { layer, id } = parseLayeredReference(value);
6950
- 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));
6951
7353
  }
6952
7354
  for (const value of opts.identityExport ?? []) {
6953
7355
  const path = resolveSessionPath(value);
6954
7356
  if (!existsSync11(path))
6955
7357
  throw new Error(`Identity instruction export not found: ${path}`);
6956
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
7358
+ const parsed = JSON.parse(readFileSync8(path, "utf-8"));
6957
7359
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
6958
7360
  }
6959
7361
  return sources.map((source) => {
@@ -6969,7 +7371,7 @@ async function collectSessionSources(opts, tool, store) {
6969
7371
  }
6970
7372
  async function loadOwnedClaudeAuthorities(store) {
6971
7373
  const configs = await store.listConfigs({ category: "rules", agent: "claude", kind: "file" });
6972
- return configs.filter((config) => config.target_path && basename(normalizeTargetPath(config.target_path)) === "AGENTS.md").map((config) => ({ slug: config.slug, targetPath: config.target_path, content: config.content }));
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 }));
6973
7375
  }
6974
7376
  async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-run") {
6975
7377
  const station = opts.stationProfile === false ? null : stationProfileSource();
@@ -6999,8 +7401,10 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
6999
7401
  }
7000
7402
  const profile = await store.getProfile(opts.compileProfile);
7001
7403
  const configs = await store.getProfileConfigs(profile.id);
7002
- const bindings = await store.getProfileConfigBindings(profile.id);
7003
- 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" });
7004
7408
  const assetConfigs = await Promise.all([...new Set(assetBindings.map((binding) => binding.source_config_id))].map((id) => store.getConfigById(id)));
7005
7409
  return planProfileSessionRender({
7006
7410
  profile_id: profile.id,
@@ -7010,6 +7414,24 @@ async function buildSessionRenderPlan(opts, tool, store, assetPlanMode = "dry-ru
7010
7414
  targetHome: opts.targetHome,
7011
7415
  projectRoot: opts.projectRoot,
7012
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
+ } : {},
7013
7435
  codewithNativeImports: opts.codewithNativeImports,
7014
7436
  allowEmptySources: opts.allowEmptySources,
7015
7437
  configs,
@@ -7090,14 +7512,14 @@ function readProjectContextBundleOption(value, allowMissing = false) {
7090
7512
  return {};
7091
7513
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
7092
7514
  }
7093
- const stat = lstatSync4(path);
7515
+ const stat = lstatSync5(path);
7094
7516
  if (stat.isSymbolicLink() || !stat.isFile()) {
7095
7517
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
7096
7518
  }
7097
7519
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
7098
7520
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
7099
7521
  }
7100
- return { json: readFileSync7(path, "utf8"), sourcePath: path };
7522
+ return { json: readFileSync8(path, "utf8"), sourcePath: path };
7101
7523
  }
7102
7524
  function readBoundedProjectContextStdin() {
7103
7525
  const chunks = [];
@@ -7476,13 +7898,18 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
7476
7898
  }
7477
7899
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
7478
7900
  });
7479
- program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
7480
- const abs = resolve6(filePath);
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);
7481
7908
  if (!existsSync11(abs)) {
7482
7909
  console.error(chalk.red(`File not found: ${abs}`));
7483
7910
  process.exit(1);
7484
7911
  }
7485
- const rawContent = readFileSync7(abs, "utf-8");
7912
+ const rawContent = readFileSync8(abs, "utf-8");
7486
7913
  const storedFmt = detectFormat(abs);
7487
7914
  const fmt = redactFormatForTarget(abs, storedFmt);
7488
7915
  const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
@@ -7513,10 +7940,11 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7513
7940
  const exactIndex = isReference ? existingOwners.findIndex((owner) => owner.name === name) : -1;
7514
7941
  const target = exactIndex >= 0 ? existingOwners[exactIndex] : existingOwners[0];
7515
7942
  const rest = existingOwners.filter((owner) => owner.id !== target.id);
7516
- if (content !== target.content) {
7943
+ if (content !== target.content && expectedVersion === undefined) {
7517
7944
  await store.createSnapshot(target.id, target.content, target.version);
7518
7945
  }
7519
7946
  config = await store.updateConfig(target.id, {
7947
+ expected_version: expectedVersion,
7520
7948
  content,
7521
7949
  format: storedFmt,
7522
7950
  is_template: (opts.template ?? false) || isTemplate,
@@ -7533,6 +7961,9 @@ program.command("add <path>").description("Ingest a file into the config DB").op
7533
7961
  }
7534
7962
  return;
7535
7963
  }
7964
+ if (expectedVersion !== undefined) {
7965
+ throw new Error("--expected-version requires an existing config; no config was created");
7966
+ }
7536
7967
  config = await store.createConfig({
7537
7968
  name,
7538
7969
  kind: opts.kind ?? "file",
@@ -7661,7 +8092,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7661
8092
  for (const entry of entries) {
7662
8093
  if (!entry.isDirectory())
7663
8094
  continue;
7664
- const projDir = join10(absDir, entry.name);
8095
+ const projDir = join11(absDir, entry.name);
7665
8096
  const hasAgentConfig = [
7666
8097
  "CLAUDE.md",
7667
8098
  ".mcp.json",
@@ -7674,7 +8105,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
7674
8105
  ".aicopilot",
7675
8106
  ".cursor",
7676
8107
  ".agents"
7677
- ].some((marker) => existsSync11(join10(projDir, marker)));
8108
+ ].some((marker) => existsSync11(join11(projDir, marker)));
7678
8109
  if (!hasAgentConfig)
7679
8110
  continue;
7680
8111
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -7722,7 +8153,7 @@ program.command("import <file>").description("Restore a v2 Instructions domain a
7722
8153
  });
7723
8154
  program.command("whoami").description("Show setup summary").action(async () => {
7724
8155
  const store = resolveConfigStore();
7725
- 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");
7726
8157
  const stats = await store.getConfigStats();
7727
8158
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
7728
8159
  console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
@@ -8106,7 +8537,7 @@ projectContextCmd.command("apply").description("Atomically write project context
8106
8537
  }
8107
8538
  });
8108
8539
  var sessionCmd = program.command("session").description("Plan and apply session-scoped agent instruction files");
8109
- sessionCmd.command("plan").description("Produce a dry-run render plan for profile-scoped instruction injection").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "native instruction home; Grok: GROK_HOME, Devin: user config directory (global AGENTS.md unless --project-root is set)").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--allow-asset-installers", "opt in to planned provider installers; planning never invokes them").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render plan").option("--check-global-coverage", "verify every registered, non-retired global-* source is in this render (expected read fresh from the registry, independent of this plan); on shortfall warn, exit non-zero, and for apply refuse to write (todos 102d6d0a, O15-00694)").option("--no-station-profile", "do not inject the cached station-profile source").option("--json", "output dry-run JSON").action(async (opts) => {
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) => {
8110
8541
  try {
8111
8542
  const tool = opts.tool;
8112
8543
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -8161,7 +8592,7 @@ sessionCmd.command("plan").description("Produce a dry-run render plan for profil
8161
8592
  process.exit(1);
8162
8593
  }
8163
8594
  });
8164
- sessionCmd.command("apply").description("Write a session render plan to its managed target home or explicit project root").requiredOption("--tool <tool>", `target tool (${SESSION_RENDER_TOOLS.join("|")})`).requiredOption("--profile <profile>", "account/profile name that owns the rendered instruction home").option("--target-home <path>", "native instruction home; Grok: GROK_HOME, Devin: user config directory (global AGENTS.md unless --project-root is set)").option("--project-root <path>", "repository root for project-scoped adapters such as Cursor").option("--session-id <id>", "session id to include in the manifest").option("--source <layer:id=path>", `instruction source file; layers: ${SESSION_SOURCE_LAYER_HELP}`, collectOption, []).option("--config <layer:id-or-slug>", "stored config source by id/slug; repeatable; layer aliases match --source", collectOption, []).option("--identity-export <path>", "OpenIdentities configs instruction export JSON; repeatable", collectOption, []).option("--replace-source <replacer-id>[=<target-source-id>]", "source id that broadly replaces earlier layers, or targets one earlier source", collectOption, []).option("--compile-profile <id-or-slug>", "compile persisted config bindings from this Instructions profile").option("--provider-version <semver>", "installed provider version used for capability matching").option("--provider-variant <variant>", "explicit provider capability variant (for example OpenCode v2-agents)").option("--model <model>", "active model used for model activation").option("--path <path>", "active path recorded in the graph context").option("--manual <config-id-or-slug>", "manually activate a binding; repeatable", collectOption, []).option("--asset-surface <surface>", "explicit provider asset surface (for example cline cli or ide)").option("--asset-scope <scope>", "asset scope (global|project|session)").option("--codewith-native-imports", "select the gated Codewith native @ import adapter").option("--allow-empty-sources", "allow an explicit empty render").option("--check-global-coverage", "verify every registered, non-retired global-* source is in this render (expected read fresh from the registry, independent of this plan); on shortfall warn, exit non-zero, and for apply refuse to write (todos 102d6d0a, O15-00694)").option("--no-station-profile", "do not inject the cached station-profile source").option("--dry-run", "preview writes and conflicts without writing").option("--force", "overwrite existing unmanaged files").option("--json", "output apply JSON").action(async (opts) => {
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) => {
8165
8596
  try {
8166
8597
  const tool = opts.tool;
8167
8598
  if (!SESSION_RENDER_TOOLS.includes(tool)) {
@@ -8187,7 +8618,15 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8187
8618
  return;
8188
8619
  }
8189
8620
  const ownedClaudeAuthorities = tool === "claude" ? await loadOwnedClaudeAuthorities(store) : undefined;
8190
- 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 });
8191
8630
  if (opts.json) {
8192
8631
  printJson({
8193
8632
  ...result,
@@ -8228,7 +8667,7 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8228
8667
  console.log(chalk.dim(`global source coverage: ${globalCoverage.expectedSlugs.length}/${globalCoverage.expectedSlugs.length} complete`));
8229
8668
  }
8230
8669
  if (result.conflicts.length > 0) {
8231
- 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.`));
8232
8671
  process.exitCode = 1;
8233
8672
  }
8234
8673
  } catch (e) {
@@ -8236,6 +8675,29 @@ sessionCmd.command("apply").description("Write a session render plan to its mana
8236
8675
  process.exit(1);
8237
8676
  }
8238
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
+ });
8239
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) => {
8240
8702
  try {
8241
8703
  const result = restoreSessionRenderSnapshot(resolveSessionPath(snapshot), {
@@ -8430,7 +8892,7 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
8430
8892
  });
8431
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) => {
8432
8894
  try {
8433
- const { renderTemplate } = await import("../chunks/template-wgnzdn7h.js");
8895
+ const { renderTemplate } = await import("../chunks/template-e4qrxs8p.js");
8434
8896
  const c = await resolveConfigStore().getConfig(id);
8435
8897
  const vars = {};
8436
8898
  if (opts.var) {
@@ -8444,7 +8906,7 @@ templateCmd.command("render <id>").description("Render a template config with va
8444
8906
  }
8445
8907
  }
8446
8908
  if (opts.env) {
8447
- const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-wgnzdn7h.js");
8909
+ const { extractTemplateVars: extractTemplateVars2 } = await import("../chunks/template-e4qrxs8p.js");
8448
8910
  for (const v of extractTemplateVars2(c.content)) {
8449
8911
  if (!(v.name in vars) && process.env[v.name]) {
8450
8912
  vars[v.name] = process.env[v.name];
@@ -8493,7 +8955,7 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
8493
8955
  } else if (opts.all) {
8494
8956
  configs = await store.listConfigs(opts.category ? { kind: "file", category: opts.category } : { kind: "file" });
8495
8957
  } else {
8496
- const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-xtkbm5br.js");
8958
+ const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await import("../chunks/sync-azd9t4p8.js");
8497
8959
  const slugs = [
8498
8960
  ...KNOWN_CONFIGS2.filter((k) => !k.rulesDir).map((k) => k.name)
8499
8961
  ];
@@ -8787,7 +9249,7 @@ command = "${mcpBinary}"
8787
9249
  args = []
8788
9250
  `;
8789
9251
  if (ex(configPath)) {
8790
- const content = readFileSync7(configPath, "utf-8");
9252
+ const content = readFileSync8(configPath, "utf-8");
8791
9253
  if (findTomlTableLines(content.split(`
8792
9254
  `), "mcp_servers.configs")) {
8793
9255
  console.log(chalk.dim("= Already installed in Codex"));
@@ -8849,7 +9311,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8849
9311
  console.log(chalk.dim("= Not installed in Codex (no config.toml)"));
8850
9312
  continue;
8851
9313
  }
8852
- const content = readFileSync7(configPath, "utf-8");
9314
+ const content = readFileSync8(configPath, "utf-8");
8853
9315
  const lines = content.split(`
8854
9316
  `);
8855
9317
  const table = findTomlTableLines(lines, "mcp_servers.configs");
@@ -8868,7 +9330,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
8868
9330
  console.log(chalk.green("\u2713") + " Removed from Codex");
8869
9331
  } else if (target === "antigravity") {
8870
9332
  const { existsSync: ex, readFileSync: rf, writeFileSync: wf } = await import("fs");
8871
- const configPath = join10(homedir4(), ".gemini", "config", "mcp_config.json");
9333
+ const configPath = join11(homedir4(), ".gemini", "config", "mcp_config.json");
8872
9334
  if (!ex(configPath)) {
8873
9335
  console.log(chalk.dim("= Not installed in Antigravity (no mcp_config.json)"));
8874
9336
  continue;
@@ -8903,15 +9365,15 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8903
9365
  if (!opts.confirmLocal) {
8904
9366
  throw new Error("migrate-legacy requires --confirm-local");
8905
9367
  }
8906
- 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"));
8907
9369
  const database = await import("../chunks/database-wstmr56z.js");
8908
9370
  database.getDatabase(destinationPath);
8909
9371
  database.resetDatabase();
8910
9372
  const { migrateLegacyInstructionsStore } = await import("../chunks/legacy-store-migration-z2m5yzyk.js");
8911
9373
  const result = migrateLegacyInstructionsStore({
8912
- ...opts.source ? { sourcePath: resolve6(opts.source) } : {},
9374
+ ...opts.source ? { sourcePath: resolve7(opts.source) } : {},
8913
9375
  destinationPath,
8914
- ...opts.backup ? { backupPath: resolve6(opts.backup) } : {},
9376
+ ...opts.backup ? { backupPath: resolve7(opts.backup) } : {},
8915
9377
  dryRun: !opts.apply,
8916
9378
  ...opts.mergePreserveDestination ? { mergePolicy: "preserve-destination" } : {},
8917
9379
  operatorConfirmed: true
@@ -8930,7 +9392,7 @@ program.command("migrate-legacy").description("Migrate the historical ~/.hasna/c
8930
9392
  if (result.backupCreated)
8931
9393
  console.log(chalk.dim(" destination backup created before mutation"));
8932
9394
  });
8933
- program.command("init").description("First-time setup: sync all known configs, create default profile").option("--force", "delete existing DB and start fresh").action(async (opts) => {
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) => {
8934
9396
  const store = resolveConfigStore();
8935
9397
  if (opts.force) {
8936
9398
  try {
@@ -8944,26 +9406,28 @@ program.command("init").description("First-time setup: sync all known configs, c
8944
9406
  }
8945
9407
  console.log(chalk.bold(`@hasna/instructions \u2014 initializing
8946
9408
  `));
8947
- const result = await syncKnown({ store });
8948
- console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
8949
- if (result.skipped.length > 0) {
8950
- console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
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."));
8951
9416
  }
8952
9417
  const refs = [
8953
9418
  { slug: "workspace-structure", name: "Workspace Structure", category: "workspace", content: `# Workspace Structure
8954
9419
 
8955
- See ~/.claude/rules/workspace.md for full conventions.`, desc: "~/Workspace/ hierarchy and naming" },
8956
- { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# .secrets Schema
8957
-
8958
- Location: ~/.secrets (sourced by ~/.zshrc)
8959
- Format: export KEY_NAME="value"
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
8960
9422
 
8961
- 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" }
8962
9424
  ];
8963
9425
  for (const ref of refs) {
8964
9426
  try {
8965
9427
  await store.getConfig(ref.slug);
8966
- } catch {
9428
+ } catch (error) {
9429
+ if (!(error instanceof ConfigNotFoundError))
9430
+ throw error;
8967
9431
  await store.createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.desc });
8968
9432
  }
8969
9433
  }
@@ -8973,12 +9437,11 @@ Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`,
8973
9437
  await ensureProjectDashboardStandardConfig(store);
8974
9438
  try {
8975
9439
  await store.getProfile("my-setup");
8976
- } catch {
8977
- const p = await store.createProfile({ name: "my-setup", description: "Default profile with all known configs" });
8978
- const allConfigs = await store.listConfigs();
8979
- for (const c of allConfigs)
8980
- await store.addConfigToProfile(p.id, c.id);
8981
- console.log(chalk.green("\u2713") + ` Created profile "my-setup" with ${allConfigs.length} configs`);
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.');
8982
9445
  }
8983
9446
  const machineProfiles = await ensurePlatformProfiles(store);
8984
9447
  console.log(chalk.green("\u2713") + ` Ensured ${machineProfiles.length} machine-aware profile(s)`);
@@ -8989,7 +9452,7 @@ DB stats:`));
8989
9452
  if (count > 0)
8990
9453
  console.log(` ${key.padEnd(18)} ${count}`);
8991
9454
  }
8992
- 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");
8993
9456
  console.log(chalk.dim(`
8994
9457
  ${store.mode === "api" ? "API" : "DB"}: ${location}`));
8995
9458
  });
@@ -9090,8 +9553,8 @@ storageCmd.command("status").description("Show redacted S3 configuration status
9090
9553
  });
9091
9554
  var storageBackupCmd = storageCmd.command("backup").description("Push, pull, and verify immutable Instructions backup objects");
9092
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) => {
9093
- const abs = resolve6(file);
9094
- if (!existsSync11(abs) || !lstatSync4(abs).isFile())
9556
+ const abs = resolve7(file);
9557
+ if (!existsSync11(abs) || !lstatSync5(abs).isFile())
9095
9558
  throw new Error(`Backup file not found: ${abs}`);
9096
9559
  const [{ loadInstructionsS3Config }, backup, objectStore] = await Promise.all([
9097
9560
  import("../chunks/s3-config-hhght91k.js"),
@@ -9181,16 +9644,16 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9181
9644
  backupId,
9182
9645
  ...versions
9183
9646
  });
9184
- const output = resolve6(opts.output);
9647
+ const output = resolve7(opts.output);
9185
9648
  if (existsSync11(output)) {
9186
9649
  if (!opts.force)
9187
9650
  throw new Error(`Refusing to overwrite existing backup file: ${output}`);
9188
- if (!lstatSync4(output).isFile() || lstatSync4(output).isSymbolicLink()) {
9651
+ if (!lstatSync5(output).isFile() || lstatSync5(output).isSymbolicLink()) {
9189
9652
  throw new Error(`Refusing to replace a non-regular backup destination: ${output}`);
9190
9653
  }
9191
9654
  }
9192
- mkdirSync6(dirname6(output), { recursive: true, mode: 448 });
9193
- 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()}`);
9194
9657
  try {
9195
9658
  writeFileSync5(temp, pulled.bytes, { flag: "wx", mode: 384 });
9196
9659
  renameSync2(temp, output);
@@ -9215,10 +9678,10 @@ storageBackupCmd.command("pull <backup-id>").description("Download and verify an
9215
9678
  });
9216
9679
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
9217
9680
  const { mkdirSync: mk } = await import("fs");
9218
- const backupDir = join10(getRawStoreRoot(), "backups");
9681
+ const backupDir = join11(getRawStoreRoot(), "backups");
9219
9682
  mk(backupDir, { recursive: true });
9220
9683
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
9221
- const outPath = join10(backupDir, `configs-${ts}.tar.gz`);
9684
+ const outPath = join11(backupDir, `configs-${ts}.tar.gz`);
9222
9685
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
9223
9686
  const { statSync: st } = await import("fs");
9224
9687
  const size = st(outPath).size;
@@ -9392,7 +9855,7 @@ program.command("watch").description("Watch known config files for changes and a
9392
9855
  const store = resolveConfigStore();
9393
9856
  const interval = Number(opts.interval);
9394
9857
  const { statSync: st } = await import("fs");
9395
- const { expandPath: expandPath2 } = await import("../chunks/apply-pnn7a3y7.js");
9858
+ const { expandPath: expandPath2 } = await import("../chunks/apply-wj7wh5km.js");
9396
9859
  console.log(chalk.bold("@hasna/instructions watch") + chalk.dim(` \u2014 polling every ${interval}ms`));
9397
9860
  console.log(chalk.dim(`Watching known config files for changes\u2026
9398
9861
  `));
@@ -9404,7 +9867,7 @@ program.command("watch").description("Watch known config files for changes and a
9404
9867
  continue;
9405
9868
  const { readdirSync: readdirSync3 } = await import("fs");
9406
9869
  for (const f of readdirSync3(absDir).filter((f2) => f2.endsWith(".md"))) {
9407
- const abs = join10(absDir, f);
9870
+ const abs = join11(absDir, f);
9408
9871
  mtimes.set(abs, st(abs).mtimeMs);
9409
9872
  }
9410
9873
  } else {
@@ -9432,7 +9895,7 @@ program.command("watch").description("Watch known config files for changes and a
9432
9895
  if (!existsSync11(absDir))
9433
9896
  continue;
9434
9897
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
9435
- const abs = join10(absDir, f);
9898
+ const abs = join11(absDir, f);
9436
9899
  if (!mtimes.has(abs)) {
9437
9900
  mtimes.set(abs, st(abs).mtimeMs);
9438
9901
  changed++;
@@ -9472,7 +9935,7 @@ program.command("report").description("Summary of stored configs, drift, and eco
9472
9935
  missing++;
9473
9936
  continue;
9474
9937
  }
9475
- const disk = readFileSync7(abs, "utf-8");
9938
+ const disk = readFileSync8(abs, "utf-8");
9476
9939
  const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
9477
9940
  if (redactedDisk !== c.content)
9478
9941
  drifted++;