heyspex 0.2.0 → 0.2.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 (146) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/bin/hs.mjs +181 -15
  3. package/dist/cli/hs-check.mjs +88 -46
  4. package/dist/cli/hs-claim.mjs +84 -39
  5. package/dist/cli/hs-connect.mjs +583 -129
  6. package/dist/cli/hs-defer.mjs +251 -30
  7. package/dist/cli/hs-dev.mjs +20 -8
  8. package/dist/cli/hs-env.mjs +62 -34
  9. package/dist/cli/hs-index-rebuild.mjs +288 -83
  10. package/dist/cli/hs-index-reconcile.mjs +397 -111
  11. package/dist/cli/hs-init.mjs +2143 -91
  12. package/dist/cli/hs-install.mjs +628 -106
  13. package/dist/cli/hs-join.mjs +686 -236
  14. package/dist/cli/hs-vault-pull.mjs +3189 -179
  15. package/dist/cli/hs-vault-push.mjs +6433 -0
  16. package/dist/cli/hs-vault.mjs +396 -178
  17. package/dist/cli/hs-workspace.mjs +289 -151
  18. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
  19. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
  20. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
  21. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
  22. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
  23. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
  24. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
  25. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
  26. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
  27. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
  28. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
  29. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
  30. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
  31. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
  32. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
  33. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
  34. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
  35. package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
  36. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
  37. package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
  38. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
  39. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
  40. package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
  41. package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
  42. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
  43. package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
  44. package/docs/copilot-plugin-install.md +177 -0
  45. package/docs/driving-heyspex-from-copilot.md +165 -0
  46. package/package.json +21 -2
  47. package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
  48. package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
  49. package/plugins/heyspex/README.md +312 -0
  50. package/plugins/heyspex/bin/run-hook.mjs +20 -2
  51. package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
  52. package/plugins/heyspex/hooks/hooks.json +94 -37
  53. package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
  54. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
  55. package/scripts/claude-hooks/_run.mjs +316 -23
  56. package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
  57. package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
  58. package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
  59. package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
  60. package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
  61. package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
  62. package/scripts/claude-hooks/session-end.mjs +2 -0
  63. package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
  64. package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
  65. package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
  66. package/scripts/cursor-hooks/manifest.mjs +46 -0
  67. package/scripts/heyspex-mode.mjs +20 -0
  68. package/scripts/hs-mode.mjs +1 -1
  69. package/scripts/hs-view.mjs +180 -17
  70. package/scripts/install-claude-code-hooks.mjs +3 -1
  71. package/scripts/install-copilot-hooks.mjs +288 -0
  72. package/scripts/lib/console-url-resolver.mjs +3 -2
  73. package/scripts/lib/customer-kit.mjs +327 -10
  74. package/scripts/lib/dev-mode.mjs +7 -6
  75. package/scripts/lib/directive-obligations.mjs +349 -0
  76. package/scripts/lib/hook-dispositions.mjs +210 -0
  77. package/scripts/lib/hook-vault-header.mjs +30 -0
  78. package/scripts/lib/hs-source-branch.mjs +196 -0
  79. package/scripts/lib/hs-source-log.mjs +2 -1
  80. package/scripts/lib/hs-source-preflight.mjs +18 -4
  81. package/scripts/lib/hs-source-push.mjs +386 -356
  82. package/scripts/lib/window-placement.mjs +109 -0
  83. package/spexharness/src/adapters/claude-code.mjs +11 -3
  84. package/spexharness/src/adapters/copilot.mjs +236 -0
  85. package/spexharness/src/adapters/detect-harness.mjs +34 -0
  86. package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
  87. package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
  88. package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
  89. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
  90. package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
  91. package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
  92. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
  93. package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
  94. package/spexharness/src/intent-classifier.mjs +4 -2
  95. package/spexharness/src/intent-directive.mjs +626 -541
  96. package/spexharness/src/rule-table.mjs +31 -1
  97. package/vendor/heyspex-mcp/account-switch.mjs +6 -5
  98. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
  99. package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
  100. package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
  101. package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
  102. package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
  103. package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
  104. package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
  105. package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
  106. package/vendor/heyspex-mcp/cli.mjs +7 -1
  107. package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
  108. package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
  109. package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
  110. package/vendor/heyspex-mcp/console-transport.mjs +7 -1
  111. package/vendor/heyspex-mcp/credentials.mjs +9 -8
  112. package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
  113. package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
  114. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
  115. package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
  116. package/vendor/heyspex-mcp/http-transport.mjs +2 -0
  117. package/vendor/heyspex-mcp/list-tools.mjs +1 -1
  118. package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
  119. package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
  120. package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
  121. package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
  122. package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
  123. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
  124. package/vendor/heyspex-mcp/plan-push.mjs +3 -2
  125. package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
  126. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
  127. package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
  128. package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
  129. package/vendor/heyspex-mcp/promote-research.mjs +6 -0
  130. package/vendor/heyspex-mcp/read-path.mjs +162 -6
  131. package/vendor/heyspex-mcp/request-context.mjs +25 -1
  132. package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
  133. package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
  134. package/vendor/heyspex-mcp/session-owner.mjs +3 -2
  135. package/vendor/heyspex-mcp/session-tools.mjs +10 -0
  136. package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
  137. package/vendor/heyspex-mcp/split-research.mjs +3 -0
  138. package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
  139. package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
  140. package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
  141. package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
  142. package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
  143. package/vendor/heyspex-mcp/vault-io.mjs +720 -724
  144. package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
  145. package/vendor/heyspex-mcp/whoami.mjs +2 -1
  146. package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
@@ -5,11 +5,26 @@ var __esm = (fn, res) => function __init() {
5
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
6
  };
7
7
 
8
+ // scripts/lib/heyspex-home.mjs
9
+ import { homedir } from "node:os";
10
+ function heyspexHome(env = process.env) {
11
+ const override = env?.HEYSPEX_HOME;
12
+ if (typeof override === "string" && override.trim().length > 0) {
13
+ return override.trim();
14
+ }
15
+ return homedir();
16
+ }
17
+ var init_heyspex_home = __esm({
18
+ "scripts/lib/heyspex-home.mjs"() {
19
+ "use strict";
20
+ }
21
+ });
22
+
8
23
  // vendor/heyspex-mcp/credentials.mjs
9
24
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
- import os, { homedir } from "node:os";
25
+ import os, { homedir as homedir2 } from "node:os";
11
26
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
12
- function credentialsFilePath(homeDir = homedir()) {
27
+ function credentialsFilePath(homeDir = heyspexHome()) {
13
28
  return join2(homeDir, ".heyspex", "credentials.yaml");
14
29
  }
15
30
  function parseMcpToken(raw) {
@@ -122,14 +137,14 @@ function readLegacyConfigAccountId(read, homeDir) {
122
137
  const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
123
138
  return value || null;
124
139
  }
125
- function readBearer({ env = process.env, homeDir = homedir(), readFile, accountId } = {}) {
140
+ function readBearer({ env = process.env, homeDir = heyspexHome(), readFile, accountId } = {}) {
126
141
  const read = readFile ?? ((p) => readFileSync(p, "utf8"));
127
142
  const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
128
143
  const named = typeof accountId === "string" && accountId.trim() ? accountId.trim() : null;
129
144
  const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
130
145
  return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
131
146
  }
132
- function readCurrentAccountId({ homeDir = homedir(), readFile } = {}) {
147
+ function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
133
148
  const read = readFile ?? ((p) => readFileSync(p, "utf8"));
134
149
  const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
135
150
  if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
@@ -142,6 +157,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
142
157
  var init_credentials = __esm({
143
158
  "vendor/heyspex-mcp/credentials.mjs"() {
144
159
  "use strict";
160
+ init_heyspex_home();
145
161
  MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
146
162
  MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
147
163
  CREDENTIALS_SCHEMA_VERSION = 2;
@@ -153,11 +169,11 @@ var init_credentials = __esm({
153
169
  });
154
170
 
155
171
  // scripts/lib/dev-mode.mjs
156
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
157
- import { homedir as homedir6 } from "node:os";
158
- import { join as join7 } from "node:path";
159
- function devModeFilePath(homeDir = homedir6()) {
160
- return join7(homeDir, ".heyspex", DEV_MODE_FILENAME);
172
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
173
+ import { homedir as homedir7 } from "node:os";
174
+ import { join as join8 } from "node:path";
175
+ function devModeFilePath(homeDir = heyspexHome()) {
176
+ return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
161
177
  }
162
178
  function normalizeMode(raw) {
163
179
  const v = String(raw ?? "").trim().toLowerCase();
@@ -181,7 +197,7 @@ function deriveSemantics(mode, reliance) {
181
197
  vaultWriteMode: local ? "local" : "cloud"
182
198
  };
183
199
  }
184
- function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFile } = {}) {
200
+ function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
185
201
  let raw;
186
202
  try {
187
203
  raw = readFile(devModeFilePath(homeDir));
@@ -211,7 +227,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFi
211
227
  }
212
228
  function resolveDevMode({
213
229
  env = process.env,
214
- homeDir = homedir6(),
230
+ homeDir = heyspexHome(),
215
231
  readFile = defaultReadFile,
216
232
  cloudSetting = void 0
217
233
  } = {}) {
@@ -274,17 +290,18 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
274
290
  var init_dev_mode = __esm({
275
291
  "scripts/lib/dev-mode.mjs"() {
276
292
  "use strict";
293
+ init_heyspex_home();
277
294
  init_credentials();
278
295
  CLOUD_CONSOLE_URL = "https://app.heyspex.com";
279
296
  LOCAL_CONSOLE_URL = "http://localhost:3000";
280
297
  DEV_MODE_FILENAME = "dev-mode.json";
281
- defaultReadFile = (p) => readFileSync6(p, "utf8");
298
+ defaultReadFile = (p) => readFileSync7(p, "utf8");
282
299
  }
283
300
  });
284
301
 
285
302
  // spex-console/bin/hs-check.ts
286
303
  import { spawn, spawnSync } from "node:child_process";
287
- import { existsSync as existsSync4, readFileSync as readFileSync8 } from "node:fs";
304
+ import { existsSync as existsSync5, readFileSync as readFileSync9 } from "node:fs";
288
305
  import path2 from "node:path";
289
306
  import process3 from "node:process";
290
307
 
@@ -822,17 +839,19 @@ function hsCommand(args = "", options = {}) {
822
839
  }
823
840
 
824
841
  // vendor/heyspex-mcp/console-fetch.mjs
842
+ init_heyspex_home();
825
843
  init_credentials();
826
- import { readFileSync as readFileSync4 } from "node:fs";
827
- import { homedir as homedir4 } from "node:os";
828
- import { join as join5 } from "node:path";
844
+ import { readFileSync as readFileSync5 } from "node:fs";
845
+ import { homedir as homedir5 } from "node:os";
846
+ import { join as join6 } from "node:path";
829
847
 
830
848
  // vendor/heyspex-mcp/session-owner.mjs
849
+ init_heyspex_home();
831
850
  init_credentials();
832
851
  import { readFileSync as readFileSync2 } from "node:fs";
833
- import { homedir as homedir2 } from "node:os";
852
+ import { homedir as homedir3 } from "node:os";
834
853
  import { join as join3 } from "node:path";
835
- function resolveSessionOwnerUncached({ env = process.env, homeDir = homedir2(), readFile } = {}) {
854
+ function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
836
855
  const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
837
856
  if (fromEnv) return fromEnv;
838
857
  return readCurrentAccountId({ homeDir, readFile });
@@ -862,10 +881,23 @@ function isVaultLessEndpoint(method, path3) {
862
881
  });
863
882
  }
864
883
 
884
+ // vendor/heyspex-mcp/write-outbox.mjs
885
+ import {
886
+ readdirSync,
887
+ readFileSync as readFileSync3,
888
+ writeFileSync as writeFileSync2,
889
+ mkdirSync as mkdirSync2,
890
+ existsSync as existsSync2,
891
+ renameSync as renameSync2,
892
+ rmSync
893
+ } from "node:fs";
894
+ import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
895
+
865
896
  // vendor/heyspex-mcp/vault-resolve.mjs
866
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
867
- import { homedir as homedir3, tmpdir } from "node:os";
868
- import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
897
+ init_heyspex_home();
898
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
899
+ import { homedir as homedir4, tmpdir } from "node:os";
900
+ import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
869
901
 
870
902
  // vendor/heyspex-mcp/console-fetch.mjs
871
903
  var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
@@ -897,9 +929,9 @@ function isCloudVaultId(value) {
897
929
  var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
898
930
  function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
899
931
  if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
900
- const read = readFile ?? ((p) => readFileSync4(p, "utf8"));
932
+ const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
901
933
  try {
902
- const raw = read(join5(repoRootAbs, "heyspex.yaml"));
934
+ const raw = read(join6(repoRootAbs, "heyspex.yaml"));
903
935
  const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
904
936
  return value || null;
905
937
  } catch {
@@ -919,9 +951,18 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
919
951
  init_credentials();
920
952
 
921
953
  // spex-console/src/lib/vault-registry/registry-file.ts
922
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
923
- import { homedir as homedir5 } from "node:os";
924
- import { dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
954
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
955
+ import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
956
+
957
+ // spex-console/src/lib/heyspex-home.ts
958
+ import { homedir as homedir6 } from "node:os";
959
+ function heyspexHome2(env = process.env) {
960
+ const override = env?.HEYSPEX_HOME;
961
+ if (typeof override === "string" && override.trim().length > 0) {
962
+ return override.trim();
963
+ }
964
+ return homedir6();
965
+ }
925
966
 
926
967
  // spex-console/src/lib/vault-registry/types.ts
927
968
  var LEGACY_REGISTRY_ACCOUNT_ID = "legacy";
@@ -938,10 +979,10 @@ function emptyVaultRegistry() {
938
979
 
939
980
  // spex-console/src/lib/vault-registry/registry-file.ts
940
981
  function vaultRegistryDir(opts = {}) {
941
- return opts.registryDir ?? join6(homedir5(), ".heyspex");
982
+ return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
942
983
  }
943
984
  function vaultRegistryFilePath(opts = {}) {
944
- return join6(vaultRegistryDir(opts), "registry.json");
985
+ return join7(vaultRegistryDir(opts), "registry.json");
945
986
  }
946
987
  function isNonEmptyString(v) {
947
988
  return typeof v === "string" && v.trim().length > 0;
@@ -1042,9 +1083,9 @@ function parseVaultRegistryFile(json) {
1042
1083
  }
1043
1084
  function readRegistryJson(opts) {
1044
1085
  const filePath = vaultRegistryFilePath(opts);
1045
- if (!existsSync3(filePath)) return { kind: "missing" };
1086
+ if (!existsSync4(filePath)) return { kind: "missing" };
1046
1087
  try {
1047
- const raw = readFileSync5(filePath, "utf8");
1088
+ const raw = readFileSync6(filePath, "utf8");
1048
1089
  const json = JSON.parse(raw);
1049
1090
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
1050
1091
  return { kind: "ok", raw, json };
@@ -1319,10 +1360,11 @@ function resolveControlScopeFromMachine(vaultRoot, opts = {}) {
1319
1360
  }
1320
1361
 
1321
1362
  // scripts/lib/console-url-resolver.mjs
1363
+ init_heyspex_home();
1322
1364
  init_dev_mode();
1323
- import { readFileSync as readFileSync7 } from "node:fs";
1324
- import { homedir as homedir7 } from "node:os";
1325
- import { join as join8 } from "node:path";
1365
+ import { readFileSync as readFileSync8 } from "node:fs";
1366
+ import { homedir as homedir8 } from "node:os";
1367
+ import { join as join9 } from "node:path";
1326
1368
  var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
1327
1369
  function trimTrailingSlash(url) {
1328
1370
  return url.replace(/\/+$/, "");
@@ -1348,9 +1390,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
1348
1390
  } catch {
1349
1391
  }
1350
1392
  }
1351
- function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultReadFile2 } = {}) {
1393
+ function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile2 } = {}) {
1352
1394
  try {
1353
- const raw = readFile(join8(homeDir, ".heyspex", "credentials.yaml"));
1395
+ const raw = readFile(join9(homeDir, ".heyspex", "credentials.yaml"));
1354
1396
  const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
1355
1397
  const value = match?.[1]?.trim();
1356
1398
  return value && value.length > 0 ? trimTrailingSlash(value) : null;
@@ -1359,12 +1401,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultRea
1359
1401
  }
1360
1402
  }
1361
1403
  function defaultReadFile2(p) {
1362
- return readFileSync7(p, "utf8");
1404
+ return readFileSync8(p, "utf8");
1363
1405
  }
1364
1406
  function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
1365
1407
  if (!repoRoot) return null;
1366
1408
  try {
1367
- const discovery = String(readDiscovery(join8(repoRoot, ".heyspex", "console-url")) ?? "").trim();
1409
+ const discovery = String(readDiscovery(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
1368
1410
  if (discovery.length > 0) return trimTrailingSlash(discovery);
1369
1411
  } catch {
1370
1412
  }
@@ -1392,7 +1434,7 @@ function resolveModeEnvelope({ env, homeDir }) {
1392
1434
  function resolveConsoleUrl({
1393
1435
  repoRoot,
1394
1436
  env = process.env,
1395
- homeDir = homedir7(),
1437
+ homeDir = heyspexHome(),
1396
1438
  strict = false,
1397
1439
  devMode,
1398
1440
  log = (line) => process.stderr.write(line),
@@ -1489,10 +1531,10 @@ function manifestForSpecId(vaultRoot, specId) {
1489
1531
  }
1490
1532
  function resolveManifestPath(target, vaultRoot, invokedCwd) {
1491
1533
  const literal = path2.resolve(invokedCwd, target);
1492
- if (existsSync4(literal)) return literal;
1534
+ if (existsSync5(literal)) return literal;
1493
1535
  if (target.endsWith(".json")) return null;
1494
1536
  const byId = manifestForSpecId(vaultRoot, target);
1495
- return existsSync4(byId) ? byId : null;
1537
+ return existsSync5(byId) ? byId : null;
1496
1538
  }
1497
1539
  function countTscErrors(output) {
1498
1540
  const matches = output.match(/error TS\d+/g);
@@ -1506,12 +1548,12 @@ function resolveWindowsPosixShell() {
1506
1548
  "C:\\Program Files\\Git\\bin\\bash.exe"
1507
1549
  ].filter((c) => typeof c === "string" && c.length > 0);
1508
1550
  for (const candidate of candidates) {
1509
- if (existsSync4(candidate)) return cachedPosixShell = candidate;
1551
+ if (existsSync5(candidate)) return cachedPosixShell = candidate;
1510
1552
  }
1511
1553
  const where = spawnSync("where", ["bash"], { encoding: "utf8", windowsHide: true });
1512
1554
  if (where.status === 0 && where.stdout) {
1513
1555
  const hit = where.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).find((line) => !/\\system32\\/i.test(line));
1514
- if (hit && existsSync4(hit)) return cachedPosixShell = hit;
1556
+ if (hit && existsSync5(hit)) return cachedPosixShell = hit;
1515
1557
  }
1516
1558
  return cachedPosixShell = null;
1517
1559
  }
@@ -1600,7 +1642,7 @@ async function main() {
1600
1642
  if (process3.argv[3] === "judge") {
1601
1643
  const pkgRoot = resolveCodeRoot({ vaultRoot });
1602
1644
  const judgeScript = path2.join(pkgRoot, "dev-factory", "judge.mjs");
1603
- if (!existsSync4(judgeScript)) {
1645
+ if (!existsSync5(judgeScript)) {
1604
1646
  process3.stderr.write(
1605
1647
  `hs check judge: script not found at ${judgeScript} (source checkout only \u2014 not available in a published install).
1606
1648
  `
@@ -1642,7 +1684,7 @@ async function main() {
1642
1684
  process3.exitCode = EXIT.NOT_FOUND;
1643
1685
  return;
1644
1686
  }
1645
- const { manifest, error } = parseChecksManifest(readFileSync8(manifestPath, "utf-8"));
1687
+ const { manifest, error } = parseChecksManifest(readFileSync9(manifestPath, "utf-8"));
1646
1688
  if (error) {
1647
1689
  process3.stderr.write(`hs check: ${manifestPath}: ${error}
1648
1690
  `);
@@ -1674,7 +1716,7 @@ async function main() {
1674
1716
  process3.exitCode = reportExitCode(report);
1675
1717
  }
1676
1718
  async function reportVerdict(target, report, vaultRoot, unattended = false) {
1677
- if (existsSync4(target) || target.endsWith(".json")) {
1719
+ if (existsSync5(target) || target.endsWith(".json")) {
1678
1720
  process3.stderr.write(
1679
1721
  "hs check: --report needs a spec-id target (to identify the claim), not a manifest path.\n"
1680
1722
  );
@@ -1682,7 +1724,7 @@ async function reportVerdict(target, report, vaultRoot, unattended = false) {
1682
1724
  }
1683
1725
  const consoleUrl = resolveSpexConsoleUrl(vaultRoot, (p) => {
1684
1726
  try {
1685
- return readFileSync8(p, "utf-8");
1727
+ return readFileSync9(p, "utf-8");
1686
1728
  } catch {
1687
1729
  return null;
1688
1730
  }
@@ -5,11 +5,26 @@ var __esm = (fn, res) => function __init() {
5
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
6
  };
7
7
 
8
+ // scripts/lib/heyspex-home.mjs
9
+ import { homedir } from "node:os";
10
+ function heyspexHome(env = process.env) {
11
+ const override = env?.HEYSPEX_HOME;
12
+ if (typeof override === "string" && override.trim().length > 0) {
13
+ return override.trim();
14
+ }
15
+ return homedir();
16
+ }
17
+ var init_heyspex_home = __esm({
18
+ "scripts/lib/heyspex-home.mjs"() {
19
+ "use strict";
20
+ }
21
+ });
22
+
8
23
  // vendor/heyspex-mcp/credentials.mjs
9
24
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
10
- import os, { homedir } from "node:os";
25
+ import os, { homedir as homedir2 } from "node:os";
11
26
  import { dirname as dirname2, join as join2, resolve } from "node:path";
12
- function credentialsFilePath(homeDir = homedir()) {
27
+ function credentialsFilePath(homeDir = heyspexHome()) {
13
28
  return join2(homeDir, ".heyspex", "credentials.yaml");
14
29
  }
15
30
  function parseMcpToken(raw) {
@@ -122,14 +137,14 @@ function readLegacyConfigAccountId(read, homeDir) {
122
137
  const value = raw?.match(CONFIG_ACCOUNT_ID_RE)?.[1]?.trim() ?? "";
123
138
  return value || null;
124
139
  }
125
- function readBearer({ env = process.env, homeDir = homedir(), readFile, accountId } = {}) {
140
+ function readBearer({ env = process.env, homeDir = heyspexHome(), readFile, accountId } = {}) {
126
141
  const read = readFile ?? ((p) => readFileSync(p, "utf8"));
127
142
  const credentialsText = readTextOrNull(read, credentialsFilePath(homeDir));
128
143
  const named = typeof accountId === "string" && accountId.trim() ? accountId.trim() : null;
129
144
  const legacyAccountId = named && parseCredentialsStore(credentialsText).schemaVersion === 1 ? readLegacyConfigAccountId(read, homeDir) : null;
130
145
  return resolveBearer({ envToken: env?.[MCP_TOKEN_ENV], credentialsText, accountId: named, legacyAccountId });
131
146
  }
132
- function readCurrentAccountId({ homeDir = homedir(), readFile } = {}) {
147
+ function readCurrentAccountId({ homeDir = heyspexHome(), readFile } = {}) {
133
148
  const read = readFile ?? ((p) => readFileSync(p, "utf8"));
134
149
  const store = parseCredentialsStore(readTextOrNull(read, credentialsFilePath(homeDir)));
135
150
  if (store.schemaVersion === 2 && store.currentAccountId && store.currentAccountId !== LEGACY_ACCOUNT_ID) {
@@ -142,6 +157,7 @@ var MCP_TOKEN_ENV, MCP_TOKEN_LINE_RE, CREDENTIALS_SCHEMA_VERSION, LEGACY_ACCOUNT
142
157
  var init_credentials = __esm({
143
158
  "vendor/heyspex-mcp/credentials.mjs"() {
144
159
  "use strict";
160
+ init_heyspex_home();
145
161
  MCP_TOKEN_ENV = "HEYSPEX_MCP_TOKEN";
146
162
  MCP_TOKEN_LINE_RE = /^mcp_token:[ \t]*["']?([^"'\r\n#]+)/m;
147
163
  CREDENTIALS_SCHEMA_VERSION = 2;
@@ -153,11 +169,11 @@ var init_credentials = __esm({
153
169
  });
154
170
 
155
171
  // scripts/lib/dev-mode.mjs
156
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
157
- import { homedir as homedir6 } from "node:os";
158
- import { join as join7 } from "node:path";
159
- function devModeFilePath(homeDir = homedir6()) {
160
- return join7(homeDir, ".heyspex", DEV_MODE_FILENAME);
172
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
173
+ import { homedir as homedir7 } from "node:os";
174
+ import { join as join8 } from "node:path";
175
+ function devModeFilePath(homeDir = heyspexHome()) {
176
+ return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
161
177
  }
162
178
  function normalizeMode(raw) {
163
179
  const v = String(raw ?? "").trim().toLowerCase();
@@ -181,7 +197,7 @@ function deriveSemantics(mode, reliance) {
181
197
  vaultWriteMode: local ? "local" : "cloud"
182
198
  };
183
199
  }
184
- function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFile } = {}) {
200
+ function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
185
201
  let raw;
186
202
  try {
187
203
  raw = readFile(devModeFilePath(homeDir));
@@ -211,7 +227,7 @@ function readLocalDevModeRecord({ homeDir = homedir6(), readFile = defaultReadFi
211
227
  }
212
228
  function resolveDevMode({
213
229
  env = process.env,
214
- homeDir = homedir6(),
230
+ homeDir = heyspexHome(),
215
231
  readFile = defaultReadFile,
216
232
  cloudSetting = void 0
217
233
  } = {}) {
@@ -274,16 +290,17 @@ var CLOUD_CONSOLE_URL, LOCAL_CONSOLE_URL, DEV_MODE_FILENAME, defaultReadFile;
274
290
  var init_dev_mode = __esm({
275
291
  "scripts/lib/dev-mode.mjs"() {
276
292
  "use strict";
293
+ init_heyspex_home();
277
294
  init_credentials();
278
295
  CLOUD_CONSOLE_URL = "https://app.heyspex.com";
279
296
  LOCAL_CONSOLE_URL = "http://localhost:3000";
280
297
  DEV_MODE_FILENAME = "dev-mode.json";
281
- defaultReadFile = (p) => readFileSync6(p, "utf8");
298
+ defaultReadFile = (p) => readFileSync7(p, "utf8");
282
299
  }
283
300
  });
284
301
 
285
302
  // spex-console/bin/hs-claim.ts
286
- import { readFileSync as readFileSync8 } from "node:fs";
303
+ import { readFileSync as readFileSync9 } from "node:fs";
287
304
  import process3 from "node:process";
288
305
 
289
306
  // spex-console/src/lib/work-stages.ts
@@ -415,17 +432,19 @@ function hsCommand(args = "", options = {}) {
415
432
  }
416
433
 
417
434
  // vendor/heyspex-mcp/console-fetch.mjs
435
+ init_heyspex_home();
418
436
  init_credentials();
419
- import { readFileSync as readFileSync4 } from "node:fs";
420
- import { homedir as homedir4 } from "node:os";
421
- import { join as join5 } from "node:path";
437
+ import { readFileSync as readFileSync5 } from "node:fs";
438
+ import { homedir as homedir5 } from "node:os";
439
+ import { join as join6 } from "node:path";
422
440
 
423
441
  // vendor/heyspex-mcp/session-owner.mjs
442
+ init_heyspex_home();
424
443
  init_credentials();
425
444
  import { readFileSync as readFileSync2 } from "node:fs";
426
- import { homedir as homedir2 } from "node:os";
445
+ import { homedir as homedir3 } from "node:os";
427
446
  import { join as join3 } from "node:path";
428
- function resolveSessionOwnerUncached({ env = process.env, homeDir = homedir2(), readFile } = {}) {
447
+ function resolveSessionOwnerUncached({ env = process.env, homeDir = heyspexHome(), readFile } = {}) {
429
448
  const fromEnv = typeof env?.HS_ACCOUNT_ID === "string" ? env.HS_ACCOUNT_ID.trim() : "";
430
449
  if (fromEnv) return fromEnv;
431
450
  return readCurrentAccountId({ homeDir, readFile });
@@ -455,10 +474,26 @@ function isVaultLessEndpoint(method, path2) {
455
474
  });
456
475
  }
457
476
 
477
+ // vendor/heyspex-mcp/write-outbox.mjs
478
+ import {
479
+ readdirSync,
480
+ readFileSync as readFileSync3,
481
+ writeFileSync as writeFileSync2,
482
+ mkdirSync as mkdirSync2,
483
+ existsSync as existsSync2,
484
+ renameSync as renameSync2,
485
+ rmSync
486
+ } from "node:fs";
487
+ import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
488
+
489
+ // scripts/lib/shared-path-helpers.mjs
490
+ import { isAbsolute, resolve as resolve2, sep } from "node:path";
491
+
458
492
  // vendor/heyspex-mcp/vault-resolve.mjs
459
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
460
- import { homedir as homedir3, tmpdir } from "node:os";
461
- import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
493
+ init_heyspex_home();
494
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
495
+ import { homedir as homedir4, tmpdir } from "node:os";
496
+ import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
462
497
 
463
498
  // vendor/heyspex-mcp/console-fetch.mjs
464
499
  var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
@@ -490,9 +525,9 @@ function isCloudVaultId(value) {
490
525
  var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
491
526
  function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
492
527
  if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
493
- const read = readFile ?? ((p) => readFileSync4(p, "utf8"));
528
+ const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
494
529
  try {
495
- const raw = read(join5(repoRootAbs, "heyspex.yaml"));
530
+ const raw = read(join6(repoRootAbs, "heyspex.yaml"));
496
531
  const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
497
532
  return value || null;
498
533
  } catch {
@@ -512,9 +547,18 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
512
547
  init_credentials();
513
548
 
514
549
  // spex-console/src/lib/vault-registry/registry-file.ts
515
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
516
- import { homedir as homedir5 } from "node:os";
517
- import { dirname as dirname4, join as join6, resolve as resolve3 } from "node:path";
550
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
551
+ import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
552
+
553
+ // spex-console/src/lib/heyspex-home.ts
554
+ import { homedir as homedir6 } from "node:os";
555
+ function heyspexHome2(env = process.env) {
556
+ const override = env?.HEYSPEX_HOME;
557
+ if (typeof override === "string" && override.trim().length > 0) {
558
+ return override.trim();
559
+ }
560
+ return homedir6();
561
+ }
518
562
 
519
563
  // spex-console/src/lib/vault-registry/types.ts
520
564
  var LEGACY_REGISTRY_ACCOUNT_ID = "legacy";
@@ -531,10 +575,10 @@ function emptyVaultRegistry() {
531
575
 
532
576
  // spex-console/src/lib/vault-registry/registry-file.ts
533
577
  function vaultRegistryDir(opts = {}) {
534
- return opts.registryDir ?? join6(homedir5(), ".heyspex");
578
+ return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
535
579
  }
536
580
  function vaultRegistryFilePath(opts = {}) {
537
- return join6(vaultRegistryDir(opts), "registry.json");
581
+ return join7(vaultRegistryDir(opts), "registry.json");
538
582
  }
539
583
  function isNonEmptyString(v) {
540
584
  return typeof v === "string" && v.trim().length > 0;
@@ -635,9 +679,9 @@ function parseVaultRegistryFile(json) {
635
679
  }
636
680
  function readRegistryJson(opts) {
637
681
  const filePath = vaultRegistryFilePath(opts);
638
- if (!existsSync3(filePath)) return { kind: "missing" };
682
+ if (!existsSync4(filePath)) return { kind: "missing" };
639
683
  try {
640
- const raw = readFileSync5(filePath, "utf8");
684
+ const raw = readFileSync6(filePath, "utf8");
641
685
  const json = JSON.parse(raw);
642
686
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
643
687
  return { kind: "ok", raw, json };
@@ -1199,10 +1243,11 @@ var HELP = [
1199
1243
  ].join("\n");
1200
1244
 
1201
1245
  // scripts/lib/console-url-resolver.mjs
1246
+ init_heyspex_home();
1202
1247
  init_dev_mode();
1203
- import { readFileSync as readFileSync7 } from "node:fs";
1204
- import { homedir as homedir7 } from "node:os";
1205
- import { join as join8 } from "node:path";
1248
+ import { readFileSync as readFileSync8 } from "node:fs";
1249
+ import { homedir as homedir8 } from "node:os";
1250
+ import { join as join9 } from "node:path";
1206
1251
  var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
1207
1252
  function trimTrailingSlash(url) {
1208
1253
  return url.replace(/\/+$/, "");
@@ -1228,9 +1273,9 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
1228
1273
  } catch {
1229
1274
  }
1230
1275
  }
1231
- function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultReadFile2 } = {}) {
1276
+ function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile2 } = {}) {
1232
1277
  try {
1233
- const raw = readFile(join8(homeDir, ".heyspex", "credentials.yaml"));
1278
+ const raw = readFile(join9(homeDir, ".heyspex", "credentials.yaml"));
1234
1279
  const match = String(raw ?? "").match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
1235
1280
  const value = match?.[1]?.trim();
1236
1281
  return value && value.length > 0 ? trimTrailingSlash(value) : null;
@@ -1239,12 +1284,12 @@ function readCredentialsConsoleUrl({ homeDir = homedir7(), readFile = defaultRea
1239
1284
  }
1240
1285
  }
1241
1286
  function defaultReadFile2(p) {
1242
- return readFileSync7(p, "utf8");
1287
+ return readFileSync8(p, "utf8");
1243
1288
  }
1244
1289
  function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery: readDiscovery2 = defaultReadFile2 } = {}) {
1245
1290
  if (!repoRoot) return null;
1246
1291
  try {
1247
- const discovery = String(readDiscovery2(join8(repoRoot, ".heyspex", "console-url")) ?? "").trim();
1292
+ const discovery = String(readDiscovery2(join9(repoRoot, ".heyspex", "console-url")) ?? "").trim();
1248
1293
  if (discovery.length > 0) return trimTrailingSlash(discovery);
1249
1294
  } catch {
1250
1295
  }
@@ -1272,7 +1317,7 @@ function resolveModeEnvelope({ env, homeDir }) {
1272
1317
  function resolveConsoleUrl({
1273
1318
  repoRoot,
1274
1319
  env = process.env,
1275
- homeDir = homedir7(),
1320
+ homeDir = heyspexHome(),
1276
1321
  strict = false,
1277
1322
  devMode,
1278
1323
  log = (line) => process.stderr.write(line),
@@ -1366,7 +1411,7 @@ function runEntry(name, main2, options = {}) {
1366
1411
  // spex-console/bin/hs-claim.ts
1367
1412
  function readDiscovery(p) {
1368
1413
  try {
1369
- return readFileSync8(p, "utf-8");
1414
+ return readFileSync9(p, "utf-8");
1370
1415
  } catch {
1371
1416
  return null;
1372
1417
  }