heyspex 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +2 -1
  2. package/README.md +11 -0
  3. package/bin/hs-vault-binding.mjs +157 -0
  4. package/bin/hs.mjs +83 -79
  5. package/bin/spexcode.mjs +91 -0
  6. package/dev-factory/engine/worktree.mjs +36 -7
  7. package/dev-factory/lib/epic-detect.mjs +2 -2
  8. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  9. package/dev-factory/lib/fleet-state.mjs +2 -2
  10. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  11. package/dist/cli/hs-check.mjs +76 -46
  12. package/dist/cli/hs-claim.mjs +63 -33
  13. package/dist/cli/hs-connect.mjs +520 -214
  14. package/dist/cli/hs-defer.mjs +161 -19
  15. package/dist/cli/hs-dev.mjs +11 -5
  16. package/dist/cli/hs-env.mjs +187 -161
  17. package/dist/cli/hs-index-rebuild.mjs +123 -78
  18. package/dist/cli/hs-index-reconcile.mjs +203 -107
  19. package/dist/cli/hs-init.mjs +426 -179
  20. package/dist/cli/hs-install.mjs +759 -437
  21. package/dist/cli/hs-join.mjs +590 -383
  22. package/dist/cli/hs-vault-pull.mjs +359 -254
  23. package/dist/cli/hs-vault-push.mjs +151 -113
  24. package/dist/cli/hs-vault.mjs +271 -104
  25. package/dist/cli/hs-workspace.mjs +90 -57
  26. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  27. package/docs/copilot-plugin-install.md +200 -177
  28. package/docs/driving-heyspex-from-copilot.md +174 -165
  29. package/docs/mcp-client-config.md +93 -25
  30. package/package.json +257 -250
  31. package/plugins/heyspex/.claude-plugin/plugin.json +13 -12
  32. package/plugins/heyspex/.cursor-plugin/plugin.json +16 -15
  33. package/plugins/heyspex/.mcp.json +3 -4
  34. package/plugins/heyspex/README.md +323 -312
  35. package/plugins/heyspex/assets/heyspex-glyph-128.png +0 -0
  36. package/plugins/heyspex/assets/heyspex-glyph-256.png +0 -0
  37. package/plugins/heyspex/assets/heyspex-glyph.svg +12 -0
  38. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  39. package/plugins/heyspex/mcp.json +12 -13
  40. package/scripts/check-mcp-freshness.mjs +6 -4
  41. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  42. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  43. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  44. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  45. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  46. package/scripts/claude-statusline/statusline-command.mjs +1073 -0
  47. package/scripts/copilot-statusline.mjs +130 -0
  48. package/scripts/dev-server-status.mjs +3 -2
  49. package/scripts/dev-server-stop.mjs +3 -2
  50. package/scripts/hs-capture-cli.mjs +2 -2
  51. package/scripts/hs-settings.mjs +2 -2
  52. package/scripts/hs-view.mjs +3 -2
  53. package/scripts/install-copilot-hooks.mjs +152 -36
  54. package/scripts/lib/copilot-cli.mjs +108 -0
  55. package/scripts/lib/customer-kit.mjs +223 -30
  56. package/scripts/lib/dev-mode.mjs +28 -2
  57. package/scripts/lib/shared-path-helpers.mjs +32 -0
  58. package/scripts/lib/standalone-doctor.mjs +131 -32
  59. package/scripts/wip-ledger-health.mjs +3 -2
  60. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  61. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  62. package/spexharness/src/intent-directive.mjs +839 -626
  63. package/spexharness/src/rule-table.mjs +1 -1
  64. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  65. package/vendor/heyspex-mcp/cli.mjs +20 -4
  66. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  67. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  68. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  69. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  70. package/vendor/heyspex-mcp/http-transport.mjs +6 -1
  71. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  72. package/vendor/heyspex-mcp/server-identity.mjs +13 -0
  73. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  74. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  75. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  76. package/vendor/heyspex-mcp/tool-dispatch.mjs +63 -23
  77. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  78. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  79. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -129,9 +129,9 @@ function resolveBearer({ envToken, credentialsText, accountId: accountId2, legac
129
129
  if (entry?.mcp_token) return { token: entry.mcp_token, source: "credentials-file" };
130
130
  return { token: null, source: null };
131
131
  }
132
- function readTextOrNull(read, path5) {
132
+ function readTextOrNull(read, path6) {
133
133
  try {
134
- const raw = read(path5);
134
+ const raw = read(path6);
135
135
  return typeof raw === "string" ? raw : null;
136
136
  } catch {
137
137
  return null;
@@ -196,13 +196,13 @@ var init_session_owner = __esm({
196
196
  });
197
197
 
198
198
  // spex-console/src/lib/heyspex-home.ts
199
- import { homedir as homedir6 } from "node:os";
199
+ import { homedir as homedir7 } from "node:os";
200
200
  function heyspexHome2(env = process.env) {
201
201
  const override = env?.HEYSPEX_HOME;
202
202
  if (typeof override === "string" && override.trim().length > 0) {
203
203
  return override.trim();
204
204
  }
205
- return homedir6();
205
+ return homedir7();
206
206
  }
207
207
  var init_heyspex_home2 = __esm({
208
208
  "spex-console/src/lib/heyspex-home.ts"() {
@@ -230,7 +230,7 @@ var init_types = __esm({
230
230
  });
231
231
 
232
232
  // spex-console/src/lib/vault-registry/registry-file.ts
233
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
233
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
234
234
  import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
235
235
  function vaultRegistryDir(opts = {}) {
236
236
  return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
@@ -337,9 +337,9 @@ function parseVaultRegistryFile(json) {
337
337
  }
338
338
  function readRegistryJson(opts) {
339
339
  const filePath = vaultRegistryFilePath(opts);
340
- if (!existsSync4(filePath)) return { kind: "missing" };
340
+ if (!existsSync5(filePath)) return { kind: "missing" };
341
341
  try {
342
- const raw = readFileSync6(filePath, "utf8");
342
+ const raw = readFileSync7(filePath, "utf8");
343
343
  const json = JSON.parse(raw);
344
344
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
345
345
  return { kind: "ok", raw, json };
@@ -409,10 +409,10 @@ function assertNotRealHomeRegistryUnderTest(filePath) {
409
409
  function writeVaultRegistry(registry, opts = {}) {
410
410
  const filePath = vaultRegistryFilePath(opts);
411
411
  assertNotRealHomeRegistryUnderTest(filePath);
412
- mkdirSync3(dirname4(filePath), { recursive: true });
412
+ mkdirSync4(dirname4(filePath), { recursive: true });
413
413
  const tmpPath = `${filePath}.tmp`;
414
- writeFileSync3(tmpPath, serializeVaultRegistry(registry), "utf8");
415
- renameSync3(tmpPath, filePath);
414
+ writeFileSync4(tmpPath, serializeVaultRegistry(registry), "utf8");
415
+ renameSync4(tmpPath, filePath);
416
416
  }
417
417
  function isValidId(id) {
418
418
  if (!isNonEmptyString(id)) return false;
@@ -581,10 +581,10 @@ function rekeyVaultRowsToCloudIds(registry, proofs, plan) {
581
581
  };
582
582
  }
583
583
  function planRegistryMigration(registry, opts = {}) {
584
- const pathExists = opts.pathExists ?? ((p) => existsSync4(p));
584
+ const pathExists = opts.pathExists ?? ((p) => existsSync5(p));
585
585
  const readMarker = opts.readMarker ?? ((root) => {
586
586
  try {
587
- return readFileSync6(join7(root, "heyspex.yaml"), "utf8");
587
+ return readFileSync7(join7(root, "heyspex.yaml"), "utf8");
588
588
  } catch {
589
589
  return null;
590
590
  }
@@ -735,8 +735,8 @@ var init_registry_file = __esm({
735
735
  });
736
736
 
737
737
  // scripts/lib/dev-mode.mjs
738
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
739
- import { homedir as homedir7 } from "node:os";
738
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
739
+ import { homedir as homedir8 } from "node:os";
740
740
  import { join as join8 } from "node:path";
741
741
  function devModeFilePath(homeDir = heyspexHome()) {
742
742
  return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
@@ -791,6 +791,11 @@ function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile: readFile2 =
791
791
  return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
792
792
  }
793
793
  }
794
+ function overrideCloudCheck(source, cloudKnown, staleAgainstCloud) {
795
+ if (source !== "local-override") return "not-applicable";
796
+ if (!cloudKnown) return "unverified";
797
+ return staleAgainstCloud ? "stale" : "in-sync";
798
+ }
794
799
  function resolveDevMode({
795
800
  env = process.env,
796
801
  homeDir = heyspexHome(),
@@ -830,6 +835,7 @@ function resolveDevMode({
830
835
  const semantics = deriveSemantics(mode, reliance);
831
836
  const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
832
837
  const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
838
+ const cloudCheck = overrideCloudCheck(source, cloudKnown, staleAgainstCloud);
833
839
  return {
834
840
  mode,
835
841
  reliance: semantics.reliance,
@@ -847,7 +853,8 @@ function resolveDevMode({
847
853
  present: local.record !== null,
848
854
  record: local.record,
849
855
  parseError: local.parseError,
850
- staleAgainstCloud
856
+ staleAgainstCloud,
857
+ cloudCheck
851
858
  },
852
859
  cloudSetting: { known: cloudKnown, value: cloudValue }
853
860
  };
@@ -861,7 +868,7 @@ var init_dev_mode = __esm({
861
868
  CLOUD_CONSOLE_URL = "https://app.heyspex.com";
862
869
  LOCAL_CONSOLE_URL = "http://localhost:3000";
863
870
  DEV_MODE_FILENAME = "dev-mode.json";
864
- defaultReadFile = (p) => readFileSync7(p, "utf8");
871
+ defaultReadFile = (p) => readFileSync8(p, "utf8");
865
872
  }
866
873
  });
867
874
 
@@ -936,12 +943,12 @@ var init_default_workspace = __esm({
936
943
  });
937
944
 
938
945
  // spex-console/src/lib/cli/vault-workspace-shared.ts
939
- import { readFileSync as readFileSync9 } from "node:fs";
946
+ import { readFileSync as readFileSync10 } from "node:fs";
940
947
  function resolveConfiguredAccountId(opts = {}) {
941
948
  return resolveSessionOwnerUncached({
942
949
  env: opts.env ?? process.env,
943
950
  homeDir: opts.homeDir ?? heyspexHome2(),
944
- readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
951
+ readFile: opts.readFile ?? ((p) => readFileSync10(p, "utf-8"))
945
952
  });
946
953
  }
947
954
  function resolveCloudAccountIdOrRefuse(opts = {}) {
@@ -962,10 +969,10 @@ function classifyHttpFailureReason(res) {
962
969
  if (res.status === 401) return "unauthorized";
963
970
  return "server-error";
964
971
  }
965
- async function cloudHttpRequest(http, method, path5, body) {
966
- const scopeHeaders = http.scope ? http.scope.headersFor(method, path5) : {};
972
+ async function cloudHttpRequest(http, method, path6, body) {
973
+ const scopeHeaders = http.scope ? http.scope.headersFor(method, path6) : {};
967
974
  try {
968
- const res = await http.fetchImpl(`${http.consoleUrl}${path5}`, {
975
+ const res = await http.fetchImpl(`${http.consoleUrl}${path6}`, {
969
976
  method,
970
977
  headers: {
971
978
  "content-type": "application/json",
@@ -1036,8 +1043,8 @@ async function classifyWorkspaceConflictHttp(id, conflictJson, http) {
1036
1043
  if (!Array.isArray(rows)) return { written: false, reason: "server-error" };
1037
1044
  return rows.some((w) => w.id === id) ? { written: true } : { written: false, reason: "owned-by-other-account" };
1038
1045
  }
1039
- async function mutateHttp(http, method, path5, body, landed) {
1040
- const res = await cloudHttpRequest(http, method, path5, body);
1046
+ async function mutateHttp(http, method, path6, body, landed) {
1047
+ const res = await cloudHttpRequest(http, method, path6, body);
1041
1048
  if (res.ok) return landed;
1042
1049
  if (res.status === 404) return "not_found";
1043
1050
  if (res.status === 403 && isOwnedByOtherAccountCode(errorCodeOf(res.json))) {
@@ -3432,13 +3439,13 @@ var init_spec_read_service = __esm({
3432
3439
  });
3433
3440
 
3434
3441
  // spex-console/bin/hs-vault.ts
3435
- import { existsSync as existsSync8, readFileSync as readFileSync11 } from "node:fs";
3436
- import path4 from "node:path";
3442
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "node:fs";
3443
+ import path5 from "node:path";
3437
3444
  import process4 from "node:process";
3438
3445
  import { createInterface as createInterface2 } from "node:readline/promises";
3439
3446
 
3440
3447
  // spex-console/src/lib/cli/control-shared.ts
3441
- import path from "node:path";
3448
+ import path2 from "node:path";
3442
3449
 
3443
3450
  // scripts/lib/hs-command.mjs
3444
3451
  import { existsSync } from "node:fs";
@@ -3462,8 +3469,8 @@ var HS_DISPATCHER_PREFIX = HS_DISPATCHER_PATH ? `node ${shellQuote(HS_DISPATCHER
3462
3469
  init_heyspex_home();
3463
3470
  init_credentials();
3464
3471
  init_session_owner();
3465
- import { readFileSync as readFileSync5 } from "node:fs";
3466
- import { homedir as homedir5 } from "node:os";
3472
+ import { readFileSync as readFileSync6 } from "node:fs";
3473
+ import { homedir as homedir6 } from "node:os";
3467
3474
  import { join as join6 } from "node:path";
3468
3475
 
3469
3476
  // vendor/heyspex-mcp/vault-less-endpoints.mjs
@@ -3476,14 +3483,14 @@ var VAULT_LESS_ENDPOINTS = Object.freeze([
3476
3483
  "GET /api/workspaces",
3477
3484
  "* /api/admin/dev-mode"
3478
3485
  ]);
3479
- function pathnameOf(path5) {
3480
- const raw = String(path5 ?? "").split("?")[0].split("#")[0];
3486
+ function pathnameOf(path6) {
3487
+ const raw = String(path6 ?? "").split("?")[0].split("#")[0];
3481
3488
  const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
3482
3489
  return noTrailing || "/";
3483
3490
  }
3484
- function isVaultLessEndpoint(method, path5) {
3491
+ function isVaultLessEndpoint(method, path6) {
3485
3492
  const m = String(method || "GET").toUpperCase();
3486
- const pathname = pathnameOf(path5);
3493
+ const pathname = pathnameOf(path6);
3487
3494
  return VAULT_LESS_ENDPOINTS.some((row) => {
3488
3495
  const [rowMethod, rowPath] = row.split(" ");
3489
3496
  return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
@@ -3503,7 +3510,9 @@ import {
3503
3510
  import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
3504
3511
 
3505
3512
  // scripts/lib/shared-path-helpers.mjs
3513
+ import { realpathSync } from "node:fs";
3506
3514
  import { isAbsolute, resolve as resolve2, sep } from "node:path";
3515
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
3507
3516
  function resolveCodeRoot({
3508
3517
  explicit,
3509
3518
  packageRoot,
@@ -3522,9 +3531,30 @@ function resolveCodeRoot({
3522
3531
 
3523
3532
  // vendor/heyspex-mcp/vault-resolve.mjs
3524
3533
  init_heyspex_home();
3525
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
3526
- import { homedir as homedir4, tmpdir } from "node:os";
3527
- import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
3534
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
3535
+ import { homedir as homedir5, tmpdir } from "node:os";
3536
+
3537
+ // vendor/heyspex-mcp/usable-dir-env.mjs
3538
+ import { statSync } from "node:fs";
3539
+ import { isAbsolute as isAbsolute3 } from "node:path";
3540
+
3541
+ // vendor/heyspex-mcp/vault-resolve.mjs
3542
+ import { basename as basename2, dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
3543
+
3544
+ // vendor/heyspex-mcp/vault-binding.mjs
3545
+ import {
3546
+ copyFileSync,
3547
+ existsSync as existsSync3,
3548
+ mkdirSync as mkdirSync3,
3549
+ readFileSync as readFileSync4,
3550
+ realpathSync as realpathSync2,
3551
+ renameSync as renameSync3,
3552
+ statSync as statSync2,
3553
+ unlinkSync,
3554
+ writeFileSync as writeFileSync3
3555
+ } from "node:fs";
3556
+ import { homedir as homedir4 } from "node:os";
3557
+ import path from "node:path";
3528
3558
 
3529
3559
  // vendor/heyspex-mcp/console-fetch.mjs
3530
3560
  var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMATCH", "VAULT_NOT_REGISTERED"]);
@@ -3628,11 +3658,11 @@ function readPairedBearer(opts = {}) {
3628
3658
  function readPairedToken(opts = {}) {
3629
3659
  return readPairedBearer(opts).token ?? void 0;
3630
3660
  }
3631
- function scopeHeadersFor(scope, method, path5) {
3661
+ function scopeHeadersFor(scope, method, path6) {
3632
3662
  if (scope === "none") {
3633
- if (!isVaultLessEndpoint(method, path5)) {
3663
+ if (!isVaultLessEndpoint(method, path6)) {
3634
3664
  throw new Error(
3635
- `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path5} is vault-scoped and must name { accountId, vaultId }.`
3665
+ `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path6} is vault-scoped and must name { accountId, vaultId }.`
3636
3666
  );
3637
3667
  }
3638
3668
  return {};
@@ -3643,7 +3673,7 @@ function cloudRequestScope(scope) {
3643
3673
  const named = { accountId: scope.accountId, vaultId: scope.vaultId };
3644
3674
  return {
3645
3675
  ...named,
3646
- headersFor: (method, path5) => isVaultLessEndpoint(method, path5) ? {} : scopeHeadersFor(named, method, path5)
3676
+ headersFor: (method, path6) => isVaultLessEndpoint(method, path6) ? {} : scopeHeadersFor(named, method, path6)
3647
3677
  };
3648
3678
  }
3649
3679
  function withCloudScope(deps, accountId2, vaultId) {
@@ -3654,8 +3684,8 @@ function withCloudScope(deps, accountId2, vaultId) {
3654
3684
  // scripts/lib/console-url-resolver.mjs
3655
3685
  init_heyspex_home();
3656
3686
  init_dev_mode();
3657
- import { readFileSync as readFileSync8 } from "node:fs";
3658
- import { homedir as homedir8 } from "node:os";
3687
+ import { readFileSync as readFileSync9 } from "node:fs";
3688
+ import { homedir as homedir9 } from "node:os";
3659
3689
  import { join as join9 } from "node:path";
3660
3690
  var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
3661
3691
  function trimTrailingSlash(url) {
@@ -3693,7 +3723,7 @@ function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile: readFile
3693
3723
  }
3694
3724
  }
3695
3725
  function defaultReadFile2(p) {
3696
- return readFileSync8(p, "utf8");
3726
+ return readFileSync9(p, "utf8");
3697
3727
  }
3698
3728
  function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
3699
3729
  if (!repoRoot) return null;
@@ -3793,7 +3823,7 @@ var HELP = [
3793
3823
  init_heyspex_home2();
3794
3824
  init_registry_file();
3795
3825
  import { spawnSync } from "node:child_process";
3796
- import { existsSync as existsSync5 } from "node:fs";
3826
+ import { existsSync as existsSync6 } from "node:fs";
3797
3827
  import { join as join10 } from "node:path";
3798
3828
  import { createInterface } from "node:readline/promises";
3799
3829
  init_credentials();
@@ -3802,7 +3832,7 @@ init_session_owner();
3802
3832
  // vendor/heyspex-mcp/whoami.mjs
3803
3833
  init_heyspex_home();
3804
3834
  init_credentials();
3805
- import { homedir as homedir9 } from "node:os";
3835
+ import { homedir as homedir10 } from "node:os";
3806
3836
  var SESSION_PATH = "/api/mcp/session";
3807
3837
  async function probeCredentialIdentity({ consoleUrl, token, fetchImpl = fetch } = {}) {
3808
3838
  if (!token) return { kind: "no_credential" };
@@ -3877,12 +3907,15 @@ var HELP2 = [
3877
3907
  "(spex-console/src/lib/cli/template-manifest.ts) \u2014 universal docs",
3878
3908
  "copied verbatim, template-shape docs written as author-prompt skeletons \u2014",
3879
3909
  "plus a generated CLAUDE.md, the kept-principles index, and the template_version",
3880
- "pin; with --git, one initial `git init` + commit. Rerunnable: a <dir> that already",
3881
- "holds heyspex.yaml and an empty/placeholder-only specs/ is accepted, existing",
3882
- "files are never overwritten (an existing CLAUDE.md gets a CLAUDE.md.template",
3883
- "beside it), and the result lists each manifest row as created or kept. Refuses a",
3884
- "non-empty <dir> without heyspex.yaml, or a specs/ holding managed specs.",
3885
- "See init.customer-vault.ts.",
3910
+ "pin; with --git, one initial `git init` + commit. On an EXISTING vault (<dir>",
3911
+ "holds heyspex.yaml with a vault_id) it is a REFRESH \u2014 the repair `hs doctor`'s",
3912
+ "vault-template row names: only missing shipped files are added, template_version",
3913
+ "is written/updated in heyspex.yaml (every other byte and the vault_id kept),",
3914
+ "nothing existing is ever overwritten (an existing CLAUDE.md gets a",
3915
+ "CLAUDE.md.template beside it), the result lists each manifest row as created or",
3916
+ "kept, and a second run writes nothing. <dir> may be any path (a satellite repo",
3917
+ "names its vault, e.g. `hs init --customer-vault ../my-specs`). Refuses only a",
3918
+ "non-empty <dir> that has no heyspex.yaml. See init.customer-vault.ts.",
3886
3919
  "",
3887
3920
  "Flags:",
3888
3921
  " --console-url <url> console_url value (default: the mode-aware console --",
@@ -3955,9 +3988,9 @@ function parseMarkerVaultPath(content) {
3955
3988
  function readPinnedVaultRoot(repoRoot, deps) {
3956
3989
  const pins = [];
3957
3990
  for (const rel of SATELLITE_MANIFEST_RELS) {
3958
- const path5 = deps.joinPath(repoRoot, ...rel);
3959
- if (!deps.fileExists(path5)) continue;
3960
- const raw = deps.readFile(path5);
3991
+ const path6 = deps.joinPath(repoRoot, ...rel);
3992
+ if (!deps.fileExists(path6)) continue;
3993
+ const raw = deps.readFile(path6);
3961
3994
  if (!raw) continue;
3962
3995
  let value;
3963
3996
  try {
@@ -4222,7 +4255,7 @@ function isInteractiveTerminal() {
4222
4255
  }
4223
4256
  function defaultIdentityGateIo(opts) {
4224
4257
  const consoleUrl = opts.consoleUrl.trim().replace(/\/+$/, "");
4225
- const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join10(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync5(p));
4258
+ const loginCli = [opts.platformRoot, resolveCodeRoot({})].filter((r) => Boolean(r && r.trim())).map((r) => join10(r, "vendor", "heyspex-mcp", "cli.mjs")).find((p) => existsSync6(p));
4226
4259
  return {
4227
4260
  consoleUrl,
4228
4261
  interactive: opts.interactive ?? isInteractiveTerminal(),
@@ -4294,7 +4327,7 @@ init_postgres();
4294
4327
  init_neon_probe();
4295
4328
 
4296
4329
  // spex-console/src/lib/vault-registry/repo-identity.ts
4297
- import path2 from "node:path";
4330
+ import path3 from "node:path";
4298
4331
 
4299
4332
  // spex-console/src/lib/vault-registry/vault-workspace-store.ts
4300
4333
  function mapProbeFailureReason(reason) {
@@ -4775,7 +4808,7 @@ function buildCloseNotDestroyConfirm(kind, verb, displayName) {
4775
4808
  }
4776
4809
 
4777
4810
  // spex-console/src/lib/vault-registry/list.ts
4778
- import { existsSync as existsSync6 } from "node:fs";
4811
+ import { existsSync as existsSync7 } from "node:fs";
4779
4812
  init_account_pointer();
4780
4813
  init_registry_file();
4781
4814
  async function defaultNeonReadTierEnabled() {
@@ -4808,7 +4841,7 @@ async function listVaultsForWorkspace(accountId2, workspaceId, deps = {}) {
4808
4841
  const activeId = await getActiveVault(accountId2, sharedPointerDeps) ?? await adoptSole(accountId2, sharedPointerDeps);
4809
4842
  const rows = deps.includeArchived ? allRows : allRows.filter((v) => !v.archived);
4810
4843
  const scoped = workspaceId ? rows.filter((v) => v.workspace_id === workspaceId) : rows;
4811
- const pathExists = deps.pathExists ?? existsSync6;
4844
+ const pathExists = deps.pathExists ?? existsSync7;
4812
4845
  const anyPathUnusable = scoped.some((v) => !v.repo_root_abs || !pathExists(v.repo_root_abs));
4813
4846
  const neonBackedServeable = anyPathUnusable && neonRows !== null ? await (deps.neonReadTierEnabled ?? defaultNeonReadTierEnabled)() : false;
4814
4847
  return {
@@ -4976,9 +5009,9 @@ async function unregisterVault(input, deps = {}) {
4976
5009
  }
4977
5010
 
4978
5011
  // spex-console/src/lib/vault-resolve/index.ts
4979
- import { existsSync as existsSync7, readFileSync as readFileSync10 } from "node:fs";
5012
+ import { existsSync as existsSync8, readFileSync as readFileSync11, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
4980
5013
  import { tmpdir as tmpdir2 } from "node:os";
4981
- import path3 from "node:path";
5014
+ import path4 from "node:path";
4982
5015
  var UNREGISTERED_VAULT_ID2 = "unregistered";
4983
5016
  var NoActiveVaultError = class _NoActiveVaultError extends Error {
4984
5017
  constructor(message2 = "resolveActiveVaultRoot: no active vault found -- no explicit vaultId, no heyspex.yaml or specs/ directory at or above cwd, no HEYSPEX_VAULT_ROOT/HEYSPEX_VAULT_DIR env, and no account active_vault_id. Run `hs vault register` to register a vault.") {
@@ -4986,6 +5019,22 @@ var NoActiveVaultError = class _NoActiveVaultError extends Error {
4986
5019
  this.name = "NoActiveVaultError";
4987
5020
  Object.setPrototypeOf(this, _NoActiveVaultError.prototype);
4988
5021
  }
5022
+ /** Stable machine code ([[HS-TYJA]]), shared with `vault-binding.mjs`. */
5023
+ code = "VAULT_UNBOUND";
5024
+ /** The exact repair command. */
5025
+ fix = "hs vault bind --vault <name|id>";
5026
+ /** The folder resolution walked from (forward slashes), when known. */
5027
+ from = null;
5028
+ };
5029
+ var VaultResolutionFailure = class _VaultResolutionFailure extends NoActiveVaultError {
5030
+ constructor(code, message2, fix, from) {
5031
+ super(message2);
5032
+ this.name = "VaultResolutionError";
5033
+ this.code = code;
5034
+ this.fix = fix;
5035
+ this.from = from ? from.replace(/\\/g, "/") : null;
5036
+ Object.setPrototypeOf(this, _VaultResolutionFailure.prototype);
5037
+ }
4989
5038
  };
4990
5039
  async function defaultRegistryLookup() {
4991
5040
  return null;
@@ -4994,13 +5043,13 @@ async function defaultAccountPointerLookup() {
4994
5043
  return null;
4995
5044
  }
4996
5045
  function finalize(vaultId, repoRootAbs, source) {
4997
- const resolvedRoot = path3.resolve(repoRootAbs);
5046
+ const resolvedRoot = path4.resolve(repoRootAbs);
4998
5047
  return {
4999
5048
  vaultId,
5000
5049
  repoRootAbs: resolvedRoot,
5001
5050
  // Always derived the same way regardless of which layer resolved the
5002
5051
  // root, per this task's acceptance criteria.
5003
- specsDirAbs: path3.join(resolvedRoot, "specs"),
5052
+ specsDirAbs: path4.join(resolvedRoot, "specs"),
5004
5053
  source
5005
5054
  };
5006
5055
  }
@@ -5012,7 +5061,7 @@ function parseMarkerVaultId(content) {
5012
5061
  }
5013
5062
  function readMarkerVaultId(dir) {
5014
5063
  try {
5015
- return parseMarkerVaultId(readFileSync10(path3.join(dir, "heyspex.yaml"), "utf8"));
5064
+ return parseMarkerVaultId(readFileSync11(path4.join(dir, "heyspex.yaml"), "utf8"));
5016
5065
  } catch {
5017
5066
  return null;
5018
5067
  }
@@ -5020,39 +5069,75 @@ function readMarkerVaultId(dir) {
5020
5069
  function readMarkerSatellite(dir) {
5021
5070
  let raw;
5022
5071
  try {
5023
- raw = readFileSync10(path3.join(dir, "heyspex.yaml"), "utf8");
5072
+ raw = readFileSync11(path4.join(dir, "heyspex.yaml"), "utf8");
5024
5073
  } catch {
5025
5074
  return null;
5026
5075
  }
5027
5076
  const vaultPath = parseMarkerVaultPath(raw);
5028
5077
  if (!vaultPath) return null;
5029
5078
  const vaultId = parseMarkerVaultId(raw);
5030
- const candidate = path3.resolve(dir, vaultPath);
5079
+ const candidate = path4.resolve(dir, vaultPath);
5031
5080
  let vaultRootAbs = null;
5032
- if (candidate.toLowerCase() !== path3.resolve(dir).toLowerCase() && isVaultRootDir(candidate)) {
5081
+ if (candidate.toLowerCase() !== path4.resolve(dir).toLowerCase() && isVaultRootDir(candidate)) {
5033
5082
  const theirs = readMarkerVaultId(candidate);
5034
5083
  if (!vaultId || !theirs || theirs === vaultId) vaultRootAbs = candidate;
5035
5084
  }
5036
5085
  return { vaultId, vaultRootAbs };
5037
5086
  }
5038
5087
  function isVaultRootDir(dir) {
5039
- const norm = (p) => path3.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
5088
+ const norm = (p) => path4.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
5040
5089
  if (norm(dir) === norm(tmpdir2())) return false;
5041
- return existsSync7(path3.join(dir, "heyspex.yaml")) || existsSync7(path3.join(dir, "specs"));
5090
+ if (!existsSync8(path4.join(dir, "specs")) && readRepoBinding2(dir)) return false;
5091
+ return existsSync8(path4.join(dir, "heyspex.yaml")) || existsSync8(path4.join(dir, "specs"));
5092
+ }
5093
+ function parseRepoBinding(content) {
5094
+ const src = String(content ?? "");
5095
+ if (/^(vault_id|vault_root):/m.test(src)) return null;
5096
+ const kind = src.match(/^kind:\s*(.+?)\s*$/m)?.[1]?.replace(/^["']|["']$/g, "").trim();
5097
+ if (kind && kind !== "repo-binding") return null;
5098
+ const lines = src.split(/\r?\n/);
5099
+ const start = lines.findIndex((l) => /^vault:(\s|$)/.test(l));
5100
+ if (start === -1) return null;
5101
+ const unq = (v) => v.trim().replace(/\s+#.*$/, "").replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
5102
+ const scalar = lines[start].slice("vault:".length).trim();
5103
+ if (scalar && !scalar.startsWith("#")) {
5104
+ const id = unq(scalar);
5105
+ return id ? { id, name: null, remote: null } : null;
5106
+ }
5107
+ const out = {
5108
+ id: null,
5109
+ name: null,
5110
+ remote: null
5111
+ };
5112
+ for (let i = start + 1; i < lines.length; i += 1) {
5113
+ const l = lines[i];
5114
+ if (!l.trim() || /^\s*#/.test(l)) continue;
5115
+ if (!/^\s+/.test(l)) break;
5116
+ const m = l.match(/^\s+([a-z_]+):\s*(.*)$/);
5117
+ if (m && (m[1] === "id" || m[1] === "name" || m[1] === "remote")) out[m[1]] = unq(m[2]) || null;
5118
+ }
5119
+ return out.id ? { id: out.id, name: out.name, remote: out.remote } : null;
5120
+ }
5121
+ function readRepoBinding2(dir) {
5122
+ try {
5123
+ return parseRepoBinding(readFileSync11(path4.join(dir, "heyspex.yaml"), "utf8"));
5124
+ } catch {
5125
+ return null;
5126
+ }
5042
5127
  }
5043
5128
  function findCwdVaultOrAttachment(startDir, stopAt) {
5044
5129
  const deps = {
5045
- fileExists: (p) => existsSync7(p),
5130
+ fileExists: (p) => existsSync8(p),
5046
5131
  readFile: (p) => {
5047
5132
  try {
5048
- return readFileSync10(p, "utf8");
5133
+ return readFileSync11(p, "utf8");
5049
5134
  } catch {
5050
5135
  return null;
5051
5136
  }
5052
5137
  },
5053
- joinPath: (...segments) => path3.join(...segments)
5138
+ joinPath: (...segments) => path4.join(...segments)
5054
5139
  };
5055
- const boundary = stopAt ? path3.resolve(stopAt) : null;
5140
+ const boundary = stopAt ? path4.resolve(stopAt) : null;
5056
5141
  let dir = startDir;
5057
5142
  for (let i = 0; i < 256; i += 1) {
5058
5143
  const marked = readMarkerSatellite(dir);
@@ -5064,15 +5149,33 @@ function findCwdVaultOrAttachment(startDir, stopAt) {
5064
5149
  };
5065
5150
  }
5066
5151
  const pinned = readPinnedVaultRoot(dir, deps).find((pin) => !isAtOrUnder(pin.value, dir));
5067
- if (pinned) return { repoRootAbs: path3.resolve(pinned.value), satellite: true };
5152
+ if (pinned) return { repoRootAbs: path4.resolve(pinned.value), satellite: true };
5068
5153
  if (isVaultRootDir(dir)) return { repoRootAbs: dir, satellite: false };
5154
+ const binding = readRepoBinding2(dir);
5155
+ if (binding) return { repoRootAbs: dir, satellite: false, binding };
5069
5156
  if (boundary && dir === boundary) return null;
5070
- const parent = path3.dirname(dir);
5157
+ const parent = path4.dirname(dir);
5071
5158
  if (parent === dir) return null;
5072
5159
  dir = parent;
5073
5160
  }
5074
5161
  return null;
5075
5162
  }
5163
+ function isExistingDir(p) {
5164
+ try {
5165
+ return statSync4(p).isDirectory();
5166
+ } catch {
5167
+ return false;
5168
+ }
5169
+ }
5170
+ function envNotVaultError(name, raw, abs, from) {
5171
+ const shown = abs.replace(/\\/g, "/");
5172
+ return new VaultResolutionFailure(
5173
+ "VAULT_NOT_FOUND",
5174
+ `${name}=${raw} does not name a vault checkout (${shown}). Unset ${name} so the vault bound to this repo is used (\`hs vault resolve\` shows it), or point it at a vault checkout.`,
5175
+ `unset ${name}`,
5176
+ from
5177
+ );
5178
+ }
5076
5179
  async function resolveActiveVaultRoot(options = {}) {
5077
5180
  const registryLookup = options.registryLookup ?? defaultRegistryLookup;
5078
5181
  const accountPointerLookup = options.accountPointerLookup ?? defaultAccountPointerLookup;
@@ -5084,7 +5187,7 @@ async function resolveActiveVaultRoot(options = {}) {
5084
5187
  }
5085
5188
  };
5086
5189
  const finalizePathTier = async (repoRootAbs, source) => {
5087
- const resolvedRoot = path3.resolve(repoRootAbs);
5190
+ const resolvedRoot = path4.resolve(repoRootAbs);
5088
5191
  const recovered = await registryLookupByRoot(resolvedRoot);
5089
5192
  if (recovered) return finalize(recovered.id, resolvedRoot, source);
5090
5193
  return finalize(UNREGISTERED_VAULT_ID2, resolvedRoot, source);
@@ -5104,17 +5207,79 @@ async function resolveActiveVaultRoot(options = {}) {
5104
5207
  detail: `explicit vaultId "${options.vaultId}" has no registry.json row and no Neon row`
5105
5208
  });
5106
5209
  }
5107
- const cwdBase = path3.resolve(options.cwd ?? process.cwd());
5210
+ const fromDir = path4.resolve(options.cwd ?? process.cwd());
5211
+ const envRoot = process.env.HEYSPEX_VAULT_ROOT;
5212
+ if (envRoot) {
5213
+ const root = path4.resolve(envRoot.trim());
5214
+ const envIsVault = isExistingDir(root) && isVaultRootDir(root);
5215
+ if (!envIsVault && !await registryLookupByRoot(root)) {
5216
+ onTier({
5217
+ tier: "env-vault-root",
5218
+ outcome: "empty",
5219
+ detail: `${envRoot} is not a vault checkout`
5220
+ });
5221
+ throw envNotVaultError("HEYSPEX_VAULT_ROOT", envRoot, root, fromDir);
5222
+ }
5223
+ onTier({ tier: "env-vault-root", outcome: "resolved", detail: envRoot });
5224
+ return finalizePathTier(root, "env-vault-root");
5225
+ }
5226
+ onTier({ tier: "env-vault-root", outcome: "skipped", detail: "HEYSPEX_VAULT_ROOT unset" });
5227
+ const envDir = process.env.HEYSPEX_VAULT_DIR;
5228
+ if (envDir) {
5229
+ const specsDirFromEnv = path4.resolve(envDir.trim());
5230
+ const envDirIsSpecs = isExistingDir(specsDirFromEnv) && path4.basename(specsDirFromEnv).toLowerCase() === "specs";
5231
+ const envDirRegistered = Boolean(await registryLookupByRoot(path4.dirname(specsDirFromEnv)));
5232
+ if (!envDirIsSpecs && !envDirRegistered) {
5233
+ onTier({
5234
+ tier: "env-vault-dir",
5235
+ outcome: "empty",
5236
+ detail: `${envDir} is not a specs/ folder`
5237
+ });
5238
+ throw envNotVaultError("HEYSPEX_VAULT_DIR", envDir, specsDirFromEnv, fromDir);
5239
+ }
5240
+ onTier({ tier: "env-vault-dir", outcome: "resolved", detail: envDir });
5241
+ return finalizePathTier(path4.dirname(specsDirFromEnv), "env-vault-dir");
5242
+ }
5243
+ onTier({ tier: "env-vault-dir", outcome: "skipped", detail: "HEYSPEX_VAULT_DIR unset" });
5244
+ let cwdBase = path4.resolve(options.cwd ?? process.cwd());
5245
+ try {
5246
+ cwdBase = realpathSync4(cwdBase);
5247
+ } catch {
5248
+ }
5108
5249
  const cwdMatch = findCwdVaultOrAttachment(cwdBase, options.cwdWalkRoot);
5250
+ if (cwdMatch?.binding) {
5251
+ const record = await registryLookup(cwdMatch.binding.id);
5252
+ const root = record ? record.repoRootAbs : null;
5253
+ if (root) {
5254
+ onTier({
5255
+ tier: "repo-binding",
5256
+ outcome: "resolved",
5257
+ detail: `${cwdMatch.repoRootAbs} -> ${cwdMatch.binding.id}`
5258
+ });
5259
+ return finalize(cwdMatch.binding.id, root, "repo-binding");
5260
+ }
5261
+ onTier({
5262
+ tier: "repo-binding",
5263
+ outcome: "no-registry-row",
5264
+ detail: `${cwdMatch.repoRootAbs}/heyspex.yaml binds ${cwdMatch.binding.id}, which has no registry row (or folder) on this machine`
5265
+ });
5266
+ const bindingDir = cwdMatch.repoRootAbs.replace(/\\/g, "/");
5267
+ throw new VaultResolutionFailure(
5268
+ "VAULT_BINDING_BROKEN",
5269
+ `${bindingDir}/heyspex.yaml binds this repo to vault ${cwdMatch.binding.name ?? cwdMatch.binding.id} (${cwdMatch.binding.id}), but this machine has no registered checkout of it. Clone it (or \`hs pull\` in an existing checkout), \`hs vault register <path>\`, then \`hs vault bind --vault ${cwdMatch.binding.id}\` in ${bindingDir}.`,
5270
+ `hs vault bind --vault ${cwdMatch.binding.id}`,
5271
+ cwdBase
5272
+ );
5273
+ }
5109
5274
  if (cwdMatch?.satellite) {
5110
5275
  onTier({ tier: "satellite-pin", outcome: "resolved", detail: cwdMatch.repoRootAbs });
5111
5276
  if (cwdMatch.markerVaultId) {
5112
- return finalize(cwdMatch.markerVaultId, path3.resolve(cwdMatch.repoRootAbs), "satellite-pin");
5277
+ return finalize(cwdMatch.markerVaultId, path4.resolve(cwdMatch.repoRootAbs), "satellite-pin");
5113
5278
  }
5114
5279
  return finalizePathTier(cwdMatch.repoRootAbs, "satellite-pin");
5115
5280
  }
5116
- if (cwdMatch) {
5117
- const root = path3.resolve(cwdMatch.repoRootAbs);
5281
+ if (cwdMatch && !cwdMatch.binding) {
5282
+ const root = path4.resolve(cwdMatch.repoRootAbs);
5118
5283
  const record = await registryLookupByRoot(root);
5119
5284
  const markerVaultId = readMarkerVaultId(root);
5120
5285
  if (markerVaultId) {
@@ -5137,19 +5302,18 @@ async function resolveActiveVaultRoot(options = {}) {
5137
5302
  outcome: "empty",
5138
5303
  detail: `no satellite pin, heyspex.yaml or specs/ at or above ${cwdBase}`
5139
5304
  });
5140
- const envRoot = process.env.HEYSPEX_VAULT_ROOT;
5141
- if (envRoot) {
5142
- onTier({ tier: "env-vault-root", outcome: "resolved", detail: envRoot });
5143
- return finalizePathTier(path3.resolve(envRoot), "env-vault-root");
5144
- }
5145
- onTier({ tier: "env-vault-root", outcome: "skipped", detail: "HEYSPEX_VAULT_ROOT unset" });
5146
- const envDir = process.env.HEYSPEX_VAULT_DIR;
5147
- if (envDir) {
5148
- const specsDirFromEnv = path3.resolve(envDir);
5149
- onTier({ tier: "env-vault-dir", outcome: "resolved", detail: envDir });
5150
- return finalizePathTier(path3.dirname(specsDirFromEnv), "env-vault-dir");
5305
+ if (!options.allowAccountPointer) {
5306
+ onTier({
5307
+ tier: "account-pointer",
5308
+ outcome: "skipped",
5309
+ detail: "account pointer retired for local resolution (HS-TYJA)"
5310
+ });
5311
+ const unbound = new NoActiveVaultError(
5312
+ `No HeySpex vault is bound to ${cwdBase.replace(/\\/g, "/")}, and it is not inside a vault checkout. There is no default "active" vault. Bind this repo: \`hs vault bind --vault <name|id>\` (register a vault checkout first with \`hs vault register <path>\` if \`hs vault list\` shows none).`
5313
+ );
5314
+ unbound.from = cwdBase.replace(/\\/g, "/");
5315
+ throw unbound;
5151
5316
  }
5152
- onTier({ tier: "env-vault-dir", outcome: "skipped", detail: "HEYSPEX_VAULT_DIR unset" });
5153
5317
  const pointerVaultId = await accountPointerLookup(options.accountId);
5154
5318
  if (pointerVaultId) {
5155
5319
  const record = await registryLookup(pointerVaultId);
@@ -5485,11 +5649,11 @@ function readLocalReceiptSide(subject, registryOpts = {}) {
5485
5649
  return unreadSide(`registry.json could not be read: ${message(err)}`);
5486
5650
  }
5487
5651
  }
5488
- async function readList(http, path5, key) {
5489
- const res = await cloudHttpRequest(http, "GET", path5);
5652
+ async function readList(http, path6, key) {
5653
+ const res = await cloudHttpRequest(http, "GET", path6);
5490
5654
  const rows = res.json?.[key];
5491
5655
  if (!res.ok || !Array.isArray(rows)) {
5492
- return { reason: `GET ${path5.split("?")[0]} answered HTTP ${res.status}` };
5656
+ return { reason: `GET ${path6.split("?")[0]} answered HTTP ${res.status}` };
5493
5657
  }
5494
5658
  return { rows: rows.filter((r) => Boolean(r) && typeof r === "object") };
5495
5659
  }
@@ -6034,6 +6198,7 @@ async function readActivePointers(deps) {
6034
6198
  const str2 = (v) => typeof v === "string" && v ? v : null;
6035
6199
  return { local, cloud: { vault: str2(body.vault_id), workspace: str2(body.workspace_id) } };
6036
6200
  }
6201
+ var SWITCH_DEPRECATION_NOTE = "Note (deprecated): local agents, hs, spexcode and the MCP server ignore the active vault -- they write to the vault bound to the repo they run in. Use `hs vault bind --vault <name|id>` in a repo, and `hs vault resolve` to see which vault a folder writes to.";
6037
6202
  async function runSwitch(args, deps) {
6038
6203
  deps = scopedTo(deps, args.id);
6039
6204
  const subject = { accountId: accountId(deps), vaultId: args.id };
@@ -6117,11 +6282,13 @@ async function runSwitch(args, deps) {
6117
6282
  return finishVaultWrite(
6118
6283
  {
6119
6284
  code: EXIT.OK,
6120
- message: `Switched active vault to "${args.id}"${workspaceSwitched ? ` (workspace: ${workspaceId})` : ""}.`,
6285
+ message: `Switched active vault to "${args.id}"${workspaceSwitched ? ` (workspace: ${workspaceId})` : ""}.
6286
+ ` + SWITCH_DEPRECATION_NOTE,
6121
6287
  payload: {
6122
6288
  ok: true,
6123
6289
  vault_id: args.id,
6124
- workspace_id: workspaceSwitched ? workspaceId : null
6290
+ workspace_id: workspaceSwitched ? workspaceId : null,
6291
+ deprecation: SWITCH_DEPRECATION_NOTE
6125
6292
  },
6126
6293
  isError: false
6127
6294
  },
@@ -6556,7 +6723,7 @@ function runEntry(name, main2, options = {}) {
6556
6723
  // spex-console/bin/hs-vault.ts
6557
6724
  function readFile(p) {
6558
6725
  try {
6559
- return readFileSync11(p, "utf8");
6726
+ return readFileSync12(p, "utf8");
6560
6727
  } catch {
6561
6728
  return null;
6562
6729
  }
@@ -6594,7 +6761,7 @@ async function main() {
6594
6761
  return;
6595
6762
  }
6596
6763
  const vaultIdOverride = process4.env.HS_VAULT_ID_OVERRIDE?.trim() || void 0;
6597
- const cwd = process4.env.HS_INVOKED_CWD?.trim() || (process4.env.HS_VAULT_ROOT ? path4.resolve(process4.env.HS_VAULT_ROOT) : process4.cwd());
6764
+ const cwd = process4.env.HS_INVOKED_CWD?.trim() || (process4.env.HS_VAULT_ROOT ? path5.resolve(process4.env.HS_VAULT_ROOT) : process4.cwd());
6598
6765
  const invokedCwd = process4.env.HS_INVOKED_CWD?.trim() || process4.cwd();
6599
6766
  const consoleUrl = resolveSpexConsoleUrl(cwd, readFile);
6600
6767
  const gate = await gateWriteVerb({
@@ -6602,7 +6769,7 @@ async function main() {
6602
6769
  sub: parsed.args.sub,
6603
6770
  writeVerbs: VAULT_WRITE_VERBS,
6604
6771
  flags: identityArgv.flags,
6605
- io: defaultIdentityGateIo({ consoleUrl, platformRoot: path4.resolve(process4.cwd(), "..") })
6772
+ io: defaultIdentityGateIo({ consoleUrl, platformRoot: path5.resolve(process4.cwd(), "..") })
6606
6773
  });
6607
6774
  if (gate && !gate.ok) {
6608
6775
  process4.stderr.write(
@@ -6615,17 +6782,17 @@ async function main() {
6615
6782
  const result = await runVault(
6616
6783
  parsed.args,
6617
6784
  {
6618
- fileExists: (p) => existsSync8(p),
6785
+ fileExists: (p) => existsSync9(p),
6619
6786
  readFile,
6620
- resolvePath: (p) => path4.resolve(invokedCwd, p),
6621
- joinPath: (...s) => path4.join(...s),
6622
- basename: (p) => path4.basename(p),
6787
+ resolvePath: (p) => path5.resolve(invokedCwd, p),
6788
+ joinPath: (...s) => path5.join(...s),
6789
+ basename: (p) => path5.basename(p),
6623
6790
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
6624
6791
  cwd,
6625
6792
  conflictScan: {
6626
- fileExists: (p) => existsSync8(p),
6793
+ fileExists: (p) => existsSync9(p),
6627
6794
  readFile,
6628
- joinPath: (...s) => path4.join(...s)
6795
+ joinPath: (...s) => path5.join(...s)
6629
6796
  },
6630
6797
  confirm,
6631
6798
  neon: { http: { fetchImpl: fetch, consoleUrl, token: readPairedToken() } }