heyspex 0.2.1 → 0.2.3

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 (70) hide show
  1. package/README.md +10 -0
  2. package/bin/hs-vault-binding.mjs +157 -0
  3. package/bin/hs.mjs +99 -81
  4. package/bin/spexcode.mjs +91 -0
  5. package/dev-factory/engine/worktree.mjs +36 -7
  6. package/dev-factory/lib/epic-detect.mjs +2 -2
  7. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  8. package/dev-factory/lib/fleet-state.mjs +2 -2
  9. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  10. package/dist/cli/hs-check.mjs +76 -46
  11. package/dist/cli/hs-claim.mjs +63 -33
  12. package/dist/cli/hs-connect.mjs +520 -214
  13. package/dist/cli/hs-defer.mjs +161 -19
  14. package/dist/cli/hs-dev.mjs +11 -5
  15. package/dist/cli/hs-env.mjs +187 -161
  16. package/dist/cli/hs-index-rebuild.mjs +123 -78
  17. package/dist/cli/hs-index-reconcile.mjs +203 -107
  18. package/dist/cli/hs-init.mjs +426 -179
  19. package/dist/cli/hs-install.mjs +759 -437
  20. package/dist/cli/hs-join.mjs +590 -383
  21. package/dist/cli/hs-vault-pull.mjs +359 -254
  22. package/dist/cli/hs-vault-push.mjs +151 -113
  23. package/dist/cli/hs-vault.mjs +271 -104
  24. package/dist/cli/hs-workspace.mjs +90 -57
  25. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  26. package/docs/copilot-plugin-install.md +189 -162
  27. package/docs/driving-heyspex-from-copilot.md +174 -165
  28. package/docs/mcp-client-config.md +93 -25
  29. package/package.json +255 -249
  30. package/plugins/heyspex/.mcp.json +3 -4
  31. package/plugins/heyspex/README.md +323 -309
  32. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  33. package/plugins/heyspex/mcp.json +12 -13
  34. package/scripts/check-mcp-freshness.mjs +6 -4
  35. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  36. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  37. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  38. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  39. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  40. package/scripts/claude-statusline/statusline-command.mjs +1058 -0
  41. package/scripts/dev-server-status.mjs +3 -2
  42. package/scripts/dev-server-stop.mjs +3 -2
  43. package/scripts/hs-capture-cli.mjs +2 -2
  44. package/scripts/hs-settings.mjs +2 -2
  45. package/scripts/hs-view.mjs +3 -2
  46. package/scripts/install-copilot-hooks.mjs +318 -0
  47. package/scripts/lib/copilot-cli.mjs +108 -0
  48. package/scripts/lib/customer-kit.mjs +223 -30
  49. package/scripts/lib/dev-mode.mjs +28 -2
  50. package/scripts/lib/shared-path-helpers.mjs +32 -0
  51. package/scripts/lib/standalone-doctor.mjs +131 -32
  52. package/scripts/wip-ledger-health.mjs +3 -2
  53. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  54. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  55. package/spexharness/src/intent-directive.mjs +839 -626
  56. package/spexharness/src/rule-table.mjs +1 -1
  57. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  58. package/vendor/heyspex-mcp/cli.mjs +20 -4
  59. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  60. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  61. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  62. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  63. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  64. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  65. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  66. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  67. package/vendor/heyspex-mcp/tool-dispatch.mjs +59 -23
  68. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  69. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  70. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -34,6 +34,8 @@ import { spawnSync } from "node:child_process";
34
34
  import { join, resolve, dirname } from "node:path";
35
35
  import { fileURLToPath } from "node:url";
36
36
  import { tmpdir } from "node:os";
37
+ import { toRunnerRelativePath } from "../lib/runner-path.mjs";
38
+ import { isNoTestFilesOutput } from "../lib/dispatch-guard.mjs";
37
39
 
38
40
  const __dirname = dirname(fileURLToPath(import.meta.url));
39
41
 
@@ -97,13 +99,24 @@ export function runTsc(ctx, worktree) {
97
99
  return { count: (out.match(/error TS\d+/g) || []).length, status: r.status, output: out.trim().slice(0, 1200), timedOut: r.error?.code === "ETIMEDOUT" };
98
100
  }
99
101
 
100
- /** Run a scoped vitest file in <worktree>/<spex>. Returns { pass, output, timedOut }. */
102
+ /** The vitest filter for `testRel` as seen from vitest's cwd (<worktree>/<spex>). Callers pass
103
+ * the test REPO-relative ("spex-console/src/x.test.ts", same convention as task.target) or
104
+ * already spex-relative ("src/x.test.ts"); vitest only matches the latter from its cwd —
105
+ * the repo-relative form printed "No test files found" and was misread as a red baseline
106
+ * (HS-7BYJ, 2026-10-15). */
107
+ export function vitestFilter(ctx, worktree, testRel) {
108
+ return toRunnerRelativePath(testRel, { cwdRel: ctx.spex, roots: [worktree, ctx.repo] });
109
+ }
110
+
111
+ /** Run a scoped vitest file in <worktree>/<spex>. Returns { pass, output, timedOut, cwd, filter }. */
101
112
  export function runVitest(ctx, worktree, testRel) {
102
- const r = spawnSync(`npx --no-install vitest run ${testRel}`, {
103
- cwd: join(worktree, ctx.spex), shell: true, encoding: "utf8", timeout: ctx.testTimeout,
113
+ const cwd = join(worktree, ctx.spex);
114
+ const filter = vitestFilter(ctx, worktree, testRel);
115
+ const r = spawnSync(`npx --no-install vitest run ${filter}`, {
116
+ cwd, shell: true, encoding: "utf8", timeout: ctx.testTimeout,
104
117
  });
105
118
  const out = ((r.stdout || "") + (r.stderr || ""));
106
- return { pass: r.status === 0 && !r.error, output: out.trim().slice(-1200), timedOut: r.error?.code === "ETIMEDOUT" };
119
+ return { pass: r.status === 0 && !r.error, output: out.trim().slice(-1200), timedOut: r.error?.code === "ETIMEDOUT", cwd, filter };
107
120
  }
108
121
 
109
122
  // HS-AY5K: the deterministic gate used to run EVERY test file through scoped
@@ -191,7 +204,10 @@ export function runPlainSelfTestScript(ctx, worktree, testRel) {
191
204
  * runGate and baselineTest both call through, so the routing rule lives in exactly
192
205
  * one place. Order matters: node:test and the plain-self-test conventions are both
193
206
  * narrow, cheap-to-check positives; vitest is the default fallback for everything else. */
194
- function runScopedTest(ctx, worktree, testRel) {
207
+ function runScopedTest(ctx, worktree, testPath) {
208
+ // Root-relative for the node runners (cwd = worktree root); an absolute or backslashed
209
+ // path is re-rooted here, and runVitest re-relativizes it to its own spex cwd.
210
+ const testRel = toRunnerRelativePath(testPath, { cwdRel: "", roots: [worktree, ctx.repo] });
195
211
  const abs = join(worktree, testRel);
196
212
  if (isNodeTestFile(abs)) return runNodeTest(ctx, worktree, testRel);
197
213
  if (isPlainSelfTestScript(abs)) return runPlainSelfTestScript(ctx, worktree, testRel);
@@ -290,7 +306,8 @@ export function runLanguageGate(ctx, worktree, task, code, { buildCmd, testCmd,
290
306
  // relative against `worktree`; vitest resolves it spex-relative against
291
307
  // `worktree/ctx.spex`) — a NEW-FILE test could land under either once the builder
292
308
  // creates it.
293
- function testExistsAtBaseline(ctx, worktree, testRel) {
309
+ function testExistsAtBaseline(ctx, worktree, testPath) {
310
+ const testRel = toRunnerRelativePath(testPath, { cwdRel: "", roots: [worktree, ctx.repo] });
294
311
  return existsSync(join(worktree, testRel)) || existsSync(join(worktree, ctx.spex, testRel));
295
312
  }
296
313
 
@@ -302,7 +319,19 @@ export function baselineTest(ctx, worktree, testRel) {
302
319
  output: `test file does not exist at baseline (${testRel}) — expected NEW-FILE condition, not a gate/environment fault`,
303
320
  };
304
321
  }
305
- return runScopedTest(ctx, worktree, testRel);
322
+ const r = runScopedTest(ctx, worktree, testRel);
323
+ // The file EXISTS (checked above), so a runner that still reports "No test files found"
324
+ // is a path/filter mismatch between the caller and the runner's cwd — never a silent
325
+ // environment fault. Name the cwd and the filter so the fix is obvious (HS-7BYJ).
326
+ if (!r.pass && isNoTestFilesOutput(r.output)) {
327
+ return {
328
+ ...r,
329
+ runnerPathMismatch: true,
330
+ output: `runner found no test files although ${testRel} exists — runner cwd: ${r.cwd ?? worktree}, filter: ${r.filter ?? testRel}. `
331
+ + `The test path is not relative to the runner's cwd (or the runner's include globs exclude it); fix the path convention, not the code.\n${r.output}`,
332
+ };
333
+ }
334
+ return r;
306
335
  }
307
336
 
308
337
  // HS-L9BX: `setupWorktree` builds the gate's worktree from `git worktree add --detach
@@ -26,7 +26,7 @@
26
26
  * exact -EPIC-suffix regression that motivated the module.
27
27
  */
28
28
 
29
- import { pathToFileURL } from "node:url";
29
+ import { isMainModule } from "../../scripts/lib/shared-path-helpers.mjs";
30
30
 
31
31
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
32
32
 
@@ -185,6 +185,6 @@ function runSelfTest() {
185
185
  return ok ? 0 : 1;
186
186
  }
187
187
 
188
- if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
188
+ if (isMainModule(import.meta.url)) {
189
189
  if (process.argv.includes("--self-test")) process.exit(runSelfTest());
190
190
  }
@@ -14,7 +14,8 @@
14
14
  import { loadFleetState } from "./fleet-state.mjs";
15
15
  import { readFileSync } from "node:fs";
16
16
  import path from "node:path";
17
- import { fileURLToPath, pathToFileURL } from "node:url";
17
+ import { fileURLToPath } from "node:url";
18
+ import { isMainModule } from "../../scripts/lib/shared-path-helpers.mjs";
18
19
 
19
20
  export const DEFAULT_FLEET_FRESHNESS_MS = 15 * 60 * 1000; // 15 minutes
20
21
 
@@ -266,9 +267,6 @@ function runSelfTest() {
266
267
  return ok ? 0 : 1;
267
268
  }
268
269
 
269
- if (
270
- process.argv[1] &&
271
- pathToFileURL(process.argv[1]).href === import.meta.url
272
- ) {
270
+ if (isMainModule(import.meta.url)) {
273
271
  if (process.argv.includes("--self-test")) process.exit(runSelfTest());
274
272
  }
@@ -30,7 +30,7 @@
30
30
 
31
31
  import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
32
32
  import { join } from "node:path";
33
- import { pathToFileURL } from "node:url";
33
+ import { isMainModule } from "../../scripts/lib/shared-path-helpers.mjs";
34
34
  import { tmpdir } from "node:os";
35
35
  import { codeRepoRoot } from "./code-repo-root.mjs";
36
36
  import { OLLAMA } from "./model-catalog.mjs";
@@ -232,7 +232,7 @@ function runSelfTest() {
232
232
  }
233
233
 
234
234
  // ── Main ─────────────────────────────────────────────────────────────────────────
235
- if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
235
+ if (isMainModule(import.meta.url)) {
236
236
  if (process.argv.includes("--self-test")) process.exit(runSelfTest());
237
237
  console.log([
238
238
  "lib/fleet-state.mjs — per-machine runtime state sidecar (.heyspex/fleet-state.json).",
@@ -42,7 +42,8 @@
42
42
  * `node dev-factory/lib/sufficiency-score.mjs --self-test`).
43
43
  */
44
44
 
45
- import { fileURLToPath, pathToFileURL } from "node:url";
45
+ import { fileURLToPath } from "node:url";
46
+ import { isMainModule } from "../../scripts/lib/shared-path-helpers.mjs";
46
47
  import { readFileSync, existsSync } from "node:fs";
47
48
  import { join, dirname } from "node:path";
48
49
 
@@ -439,9 +440,6 @@ function runSelfTest() {
439
440
  return ok ? 0 : 1;
440
441
  }
441
442
 
442
- if (
443
- process.argv[1] &&
444
- pathToFileURL(process.argv[1]).href === import.meta.url
445
- ) {
443
+ if (isMainModule(import.meta.url)) {
446
444
  if (process.argv.includes("--self-test")) process.exit(runSelfTest());
447
445
  }
@@ -124,9 +124,9 @@ function resolveBearer({ envToken, credentialsText, accountId, legacyAccountId }
124
124
  if (entry?.mcp_token) return { token: entry.mcp_token, source: "credentials-file" };
125
125
  return { token: null, source: null };
126
126
  }
127
- function readTextOrNull(read, path3) {
127
+ function readTextOrNull(read, path4) {
128
128
  try {
129
- const raw = read(path3);
129
+ const raw = read(path4);
130
130
  return typeof raw === "string" ? raw : null;
131
131
  } catch {
132
132
  return null;
@@ -169,8 +169,8 @@ var init_credentials = __esm({
169
169
  });
170
170
 
171
171
  // scripts/lib/dev-mode.mjs
172
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
173
- import { homedir as homedir7 } from "node:os";
172
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
173
+ import { homedir as homedir8 } from "node:os";
174
174
  import { join as join8 } from "node:path";
175
175
  function devModeFilePath(homeDir = heyspexHome()) {
176
176
  return join8(homeDir, ".heyspex", DEV_MODE_FILENAME);
@@ -225,6 +225,11 @@ function readLocalDevModeRecord({ homeDir = heyspexHome(), readFile = defaultRea
225
225
  return { record: null, parseError: `unparseable dev-mode.json: ${String(err?.message ?? err)}` };
226
226
  }
227
227
  }
228
+ function overrideCloudCheck(source, cloudKnown, staleAgainstCloud) {
229
+ if (source !== "local-override") return "not-applicable";
230
+ if (!cloudKnown) return "unverified";
231
+ return staleAgainstCloud ? "stale" : "in-sync";
232
+ }
228
233
  function resolveDevMode({
229
234
  env = process.env,
230
235
  homeDir = heyspexHome(),
@@ -264,6 +269,7 @@ function resolveDevMode({
264
269
  const semantics = deriveSemantics(mode, reliance);
265
270
  const consoleUrl = source === "local-override" && local.record?.console_url ? local.record.console_url.replace(/\/+$/, "") : semantics.consoleUrl;
266
271
  const staleAgainstCloud = source === "local-override" && cloudValue !== null && (cloudValue.mode !== mode || mode === "dev" && cloudValue.reliance !== semantics.reliance);
272
+ const cloudCheck = overrideCloudCheck(source, cloudKnown, staleAgainstCloud);
267
273
  return {
268
274
  mode,
269
275
  reliance: semantics.reliance,
@@ -281,7 +287,8 @@ function resolveDevMode({
281
287
  present: local.record !== null,
282
288
  record: local.record,
283
289
  parseError: local.parseError,
284
- staleAgainstCloud
290
+ staleAgainstCloud,
291
+ cloudCheck
285
292
  },
286
293
  cloudSetting: { known: cloudKnown, value: cloudValue }
287
294
  };
@@ -295,18 +302,20 @@ var init_dev_mode = __esm({
295
302
  CLOUD_CONSOLE_URL = "https://app.heyspex.com";
296
303
  LOCAL_CONSOLE_URL = "http://localhost:3000";
297
304
  DEV_MODE_FILENAME = "dev-mode.json";
298
- defaultReadFile = (p) => readFileSync7(p, "utf8");
305
+ defaultReadFile = (p) => readFileSync8(p, "utf8");
299
306
  }
300
307
  });
301
308
 
302
309
  // spex-console/bin/hs-check.ts
303
310
  import { spawn, spawnSync } from "node:child_process";
304
- import { existsSync as existsSync5, readFileSync as readFileSync9 } from "node:fs";
305
- import path2 from "node:path";
311
+ import { existsSync as existsSync6, readFileSync as readFileSync10 } from "node:fs";
312
+ import path3 from "node:path";
306
313
  import process3 from "node:process";
307
314
 
308
315
  // scripts/lib/shared-path-helpers.mjs
316
+ import { realpathSync } from "node:fs";
309
317
  import { isAbsolute, resolve, sep } from "node:path";
318
+ import { fileURLToPath } from "node:url";
310
319
  function resolveCodeRoot({
311
320
  explicit,
312
321
  packageRoot,
@@ -751,16 +760,16 @@ var HELP = [
751
760
  ].join("\n");
752
761
 
753
762
  // spex-console/src/lib/cli/control-shared.ts
754
- import path from "node:path";
763
+ import path2 from "node:path";
755
764
 
756
765
  // scripts/lib/hs-command.mjs
757
766
  import { existsSync } from "node:fs";
758
767
  import { delimiter, dirname, join } from "node:path";
759
- import { fileURLToPath } from "node:url";
768
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
760
769
  var HS_BIN_NAME = "hs";
761
770
  var HS_DISPATCHER_PATH = (() => {
762
771
  try {
763
- const here = dirname(fileURLToPath(import.meta.url));
772
+ const here = dirname(fileURLToPath2(import.meta.url));
764
773
  return join(dirname(dirname(here)), "bin", "hs.mjs");
765
774
  } catch {
766
775
  return null;
@@ -841,8 +850,8 @@ function hsCommand(args = "", options = {}) {
841
850
  // vendor/heyspex-mcp/console-fetch.mjs
842
851
  init_heyspex_home();
843
852
  init_credentials();
844
- import { readFileSync as readFileSync5 } from "node:fs";
845
- import { homedir as homedir5 } from "node:os";
853
+ import { readFileSync as readFileSync6 } from "node:fs";
854
+ import { homedir as homedir6 } from "node:os";
846
855
  import { join as join6 } from "node:path";
847
856
 
848
857
  // vendor/heyspex-mcp/session-owner.mjs
@@ -867,14 +876,14 @@ var VAULT_LESS_ENDPOINTS = Object.freeze([
867
876
  "GET /api/workspaces",
868
877
  "* /api/admin/dev-mode"
869
878
  ]);
870
- function pathnameOf(path3) {
871
- const raw = String(path3 ?? "").split("?")[0].split("#")[0];
879
+ function pathnameOf(path4) {
880
+ const raw = String(path4 ?? "").split("?")[0].split("#")[0];
872
881
  const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
873
882
  return noTrailing || "/";
874
883
  }
875
- function isVaultLessEndpoint(method, path3) {
884
+ function isVaultLessEndpoint(method, path4) {
876
885
  const m = String(method || "GET").toUpperCase();
877
- const pathname = pathnameOf(path3);
886
+ const pathname = pathnameOf(path4);
878
887
  return VAULT_LESS_ENDPOINTS.some((row) => {
879
888
  const [rowMethod, rowPath] = row.split(" ");
880
889
  return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
@@ -895,9 +904,30 @@ import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
895
904
 
896
905
  // vendor/heyspex-mcp/vault-resolve.mjs
897
906
  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";
907
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
908
+ import { homedir as homedir5, tmpdir } from "node:os";
909
+
910
+ // vendor/heyspex-mcp/usable-dir-env.mjs
911
+ import { statSync } from "node:fs";
912
+ import { isAbsolute as isAbsolute3 } from "node:path";
913
+
914
+ // vendor/heyspex-mcp/vault-resolve.mjs
915
+ import { basename as basename2, dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
916
+
917
+ // vendor/heyspex-mcp/vault-binding.mjs
918
+ import {
919
+ copyFileSync,
920
+ existsSync as existsSync3,
921
+ mkdirSync as mkdirSync3,
922
+ readFileSync as readFileSync4,
923
+ realpathSync as realpathSync2,
924
+ renameSync as renameSync3,
925
+ statSync as statSync2,
926
+ unlinkSync,
927
+ writeFileSync as writeFileSync3
928
+ } from "node:fs";
929
+ import { homedir as homedir4 } from "node:os";
930
+ import path from "node:path";
901
931
 
902
932
  // vendor/heyspex-mcp/console-fetch.mjs
903
933
  var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
@@ -929,7 +959,7 @@ function isCloudVaultId(value) {
929
959
  var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
930
960
  function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
931
961
  if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
932
- const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
962
+ const read = readFile ?? ((p) => readFileSync6(p, "utf8"));
933
963
  try {
934
964
  const raw = read(join6(repoRootAbs, "heyspex.yaml"));
935
965
  const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
@@ -951,17 +981,17 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
951
981
  init_credentials();
952
982
 
953
983
  // spex-console/src/lib/vault-registry/registry-file.ts
954
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
984
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
955
985
  import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
956
986
 
957
987
  // spex-console/src/lib/heyspex-home.ts
958
- import { homedir as homedir6 } from "node:os";
988
+ import { homedir as homedir7 } from "node:os";
959
989
  function heyspexHome2(env = process.env) {
960
990
  const override = env?.HEYSPEX_HOME;
961
991
  if (typeof override === "string" && override.trim().length > 0) {
962
992
  return override.trim();
963
993
  }
964
- return homedir6();
994
+ return homedir7();
965
995
  }
966
996
 
967
997
  // spex-console/src/lib/vault-registry/types.ts
@@ -1083,9 +1113,9 @@ function parseVaultRegistryFile(json) {
1083
1113
  }
1084
1114
  function readRegistryJson(opts) {
1085
1115
  const filePath = vaultRegistryFilePath(opts);
1086
- if (!existsSync4(filePath)) return { kind: "missing" };
1116
+ if (!existsSync5(filePath)) return { kind: "missing" };
1087
1117
  try {
1088
- const raw = readFileSync6(filePath, "utf8");
1118
+ const raw = readFileSync7(filePath, "utf8");
1089
1119
  const json = JSON.parse(raw);
1090
1120
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
1091
1121
  return { kind: "ok", raw, json };
@@ -1220,11 +1250,11 @@ function bearerHeaderFor(consoleUrl, opts = {}) {
1220
1250
  return {};
1221
1251
  }
1222
1252
  }
1223
- function scopeHeadersFor(scope, method, path3) {
1253
+ function scopeHeadersFor(scope, method, path4) {
1224
1254
  if (scope === "none") {
1225
- if (!isVaultLessEndpoint(method, path3)) {
1255
+ if (!isVaultLessEndpoint(method, path4)) {
1226
1256
  throw new Error(
1227
- `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path3} is vault-scoped and must name { accountId, vaultId }.`
1257
+ `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path4} is vault-scoped and must name { accountId, vaultId }.`
1228
1258
  );
1229
1259
  }
1230
1260
  return {};
@@ -1293,7 +1323,7 @@ async function controlRequest(deps, req) {
1293
1323
  return { ok: true, status: res.status, body: parsed };
1294
1324
  }
1295
1325
  function normalizeRoot(p) {
1296
- return path.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
1326
+ return path2.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
1297
1327
  }
1298
1328
  function resolveControlScope(input) {
1299
1329
  const target = normalizeRoot(input.vaultRoot);
@@ -1362,8 +1392,8 @@ function resolveControlScopeFromMachine(vaultRoot, opts = {}) {
1362
1392
  // scripts/lib/console-url-resolver.mjs
1363
1393
  init_heyspex_home();
1364
1394
  init_dev_mode();
1365
- import { readFileSync as readFileSync8 } from "node:fs";
1366
- import { homedir as homedir8 } from "node:os";
1395
+ import { readFileSync as readFileSync9 } from "node:fs";
1396
+ import { homedir as homedir9 } from "node:os";
1367
1397
  import { join as join9 } from "node:path";
1368
1398
  var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
1369
1399
  function trimTrailingSlash(url) {
@@ -1401,7 +1431,7 @@ function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = default
1401
1431
  }
1402
1432
  }
1403
1433
  function defaultReadFile2(p) {
1404
- return readFileSync8(p, "utf8");
1434
+ return readFileSync9(p, "utf8");
1405
1435
  }
1406
1436
  function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery = defaultReadFile2 } = {}) {
1407
1437
  if (!repoRoot) return null;
@@ -1527,14 +1557,14 @@ function runEntry(name, main2, options = {}) {
1527
1557
 
1528
1558
  // spex-console/bin/hs-check.ts
1529
1559
  function manifestForSpecId(vaultRoot, specId) {
1530
- return path2.join(vaultRoot, "specs", "checks", `${specId}.checks.json`);
1560
+ return path3.join(vaultRoot, "specs", "checks", `${specId}.checks.json`);
1531
1561
  }
1532
1562
  function resolveManifestPath(target, vaultRoot, invokedCwd) {
1533
- const literal = path2.resolve(invokedCwd, target);
1534
- if (existsSync5(literal)) return literal;
1563
+ const literal = path3.resolve(invokedCwd, target);
1564
+ if (existsSync6(literal)) return literal;
1535
1565
  if (target.endsWith(".json")) return null;
1536
1566
  const byId = manifestForSpecId(vaultRoot, target);
1537
- return existsSync5(byId) ? byId : null;
1567
+ return existsSync6(byId) ? byId : null;
1538
1568
  }
1539
1569
  function countTscErrors(output) {
1540
1570
  const matches = output.match(/error TS\d+/g);
@@ -1548,12 +1578,12 @@ function resolveWindowsPosixShell() {
1548
1578
  "C:\\Program Files\\Git\\bin\\bash.exe"
1549
1579
  ].filter((c) => typeof c === "string" && c.length > 0);
1550
1580
  for (const candidate of candidates) {
1551
- if (existsSync5(candidate)) return cachedPosixShell = candidate;
1581
+ if (existsSync6(candidate)) return cachedPosixShell = candidate;
1552
1582
  }
1553
1583
  const where = spawnSync("where", ["bash"], { encoding: "utf8", windowsHide: true });
1554
1584
  if (where.status === 0 && where.stdout) {
1555
1585
  const hit = where.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).find((line) => !/\\system32\\/i.test(line));
1556
- if (hit && existsSync5(hit)) return cachedPosixShell = hit;
1586
+ if (hit && existsSync6(hit)) return cachedPosixShell = hit;
1557
1587
  }
1558
1588
  return cachedPosixShell = null;
1559
1589
  }
@@ -1610,7 +1640,7 @@ function makeRunCheck(vaultRoot, codeRoot) {
1610
1640
  return async (check) => {
1611
1641
  const cwds = resolveCheckCwds(check, {
1612
1642
  repoRoot: codeRoot,
1613
- consoleRoot: path2.join(codeRoot, "spex-console"),
1643
+ consoleRoot: path3.join(codeRoot, "spex-console"),
1614
1644
  vaultRoot
1615
1645
  });
1616
1646
  let lastFailure = null;
@@ -1641,8 +1671,8 @@ async function main() {
1641
1671
  }
1642
1672
  if (process3.argv[3] === "judge") {
1643
1673
  const pkgRoot = resolveCodeRoot({ vaultRoot });
1644
- const judgeScript = path2.join(pkgRoot, "dev-factory", "judge.mjs");
1645
- if (!existsSync5(judgeScript)) {
1674
+ const judgeScript = path3.join(pkgRoot, "dev-factory", "judge.mjs");
1675
+ if (!existsSync6(judgeScript)) {
1646
1676
  process3.stderr.write(
1647
1677
  `hs check judge: script not found at ${judgeScript} (source checkout only \u2014 not available in a published install).
1648
1678
  `
@@ -1678,13 +1708,13 @@ async function main() {
1678
1708
  if (!manifestPath) {
1679
1709
  process3.stderr.write(
1680
1710
  `hs check: no manifest for "${args.target}".
1681
- Pass a manifest path, or create ${path2.join("specs", "checks", `${args.target}.checks.json`)} under the vault root.
1711
+ Pass a manifest path, or create ${path3.join("specs", "checks", `${args.target}.checks.json`)} under the vault root.
1682
1712
  `
1683
1713
  );
1684
1714
  process3.exitCode = EXIT.NOT_FOUND;
1685
1715
  return;
1686
1716
  }
1687
- const { manifest, error } = parseChecksManifest(readFileSync9(manifestPath, "utf-8"));
1717
+ const { manifest, error } = parseChecksManifest(readFileSync10(manifestPath, "utf-8"));
1688
1718
  if (error) {
1689
1719
  process3.stderr.write(`hs check: ${manifestPath}: ${error}
1690
1720
  `);
@@ -1716,7 +1746,7 @@ async function main() {
1716
1746
  process3.exitCode = reportExitCode(report);
1717
1747
  }
1718
1748
  async function reportVerdict(target, report, vaultRoot, unattended = false) {
1719
- if (existsSync5(target) || target.endsWith(".json")) {
1749
+ if (existsSync6(target) || target.endsWith(".json")) {
1720
1750
  process3.stderr.write(
1721
1751
  "hs check: --report needs a spec-id target (to identify the claim), not a manifest path.\n"
1722
1752
  );
@@ -1724,7 +1754,7 @@ async function reportVerdict(target, report, vaultRoot, unattended = false) {
1724
1754
  }
1725
1755
  const consoleUrl = resolveSpexConsoleUrl(vaultRoot, (p) => {
1726
1756
  try {
1727
- return readFileSync9(p, "utf-8");
1757
+ return readFileSync10(p, "utf-8");
1728
1758
  } catch {
1729
1759
  return null;
1730
1760
  }