heyspex 0.2.2 → 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 +81 -77
  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 -177
  27. package/docs/driving-heyspex-from-copilot.md +174 -165
  28. package/docs/mcp-client-config.md +93 -25
  29. package/package.json +255 -250
  30. package/plugins/heyspex/.mcp.json +3 -4
  31. package/plugins/heyspex/README.md +323 -312
  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 +64 -34
  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
@@ -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
  }
@@ -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, path2) {
127
+ function readTextOrNull(read, path3) {
128
128
  try {
129
- const raw = read(path2);
129
+ const raw = read(path3);
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,12 +302,12 @@ 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-claim.ts
303
- import { readFileSync as readFileSync9 } from "node:fs";
310
+ import { readFileSync as readFileSync10 } from "node:fs";
304
311
  import process3 from "node:process";
305
312
 
306
313
  // spex-console/src/lib/work-stages.ts
@@ -344,7 +351,7 @@ function resolveAgentIdentity(argAgent, argSession) {
344
351
  }
345
352
 
346
353
  // spex-console/src/lib/cli/control-shared.ts
347
- import path from "node:path";
354
+ import path2 from "node:path";
348
355
 
349
356
  // scripts/lib/hs-command.mjs
350
357
  import { existsSync } from "node:fs";
@@ -434,8 +441,8 @@ function hsCommand(args = "", options = {}) {
434
441
  // vendor/heyspex-mcp/console-fetch.mjs
435
442
  init_heyspex_home();
436
443
  init_credentials();
437
- import { readFileSync as readFileSync5 } from "node:fs";
438
- import { homedir as homedir5 } from "node:os";
444
+ import { readFileSync as readFileSync6 } from "node:fs";
445
+ import { homedir as homedir6 } from "node:os";
439
446
  import { join as join6 } from "node:path";
440
447
 
441
448
  // vendor/heyspex-mcp/session-owner.mjs
@@ -460,14 +467,14 @@ var VAULT_LESS_ENDPOINTS = Object.freeze([
460
467
  "GET /api/workspaces",
461
468
  "* /api/admin/dev-mode"
462
469
  ]);
463
- function pathnameOf(path2) {
464
- const raw = String(path2 ?? "").split("?")[0].split("#")[0];
470
+ function pathnameOf(path3) {
471
+ const raw = String(path3 ?? "").split("?")[0].split("#")[0];
465
472
  const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
466
473
  return noTrailing || "/";
467
474
  }
468
- function isVaultLessEndpoint(method, path2) {
475
+ function isVaultLessEndpoint(method, path3) {
469
476
  const m = String(method || "GET").toUpperCase();
470
- const pathname = pathnameOf(path2);
477
+ const pathname = pathnameOf(path3);
471
478
  return VAULT_LESS_ENDPOINTS.some((row) => {
472
479
  const [rowMethod, rowPath] = row.split(" ");
473
480
  return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
@@ -487,13 +494,36 @@ import {
487
494
  import { basename, join as join4, isAbsolute as isAbsolute2 } from "node:path";
488
495
 
489
496
  // scripts/lib/shared-path-helpers.mjs
497
+ import { realpathSync } from "node:fs";
490
498
  import { isAbsolute, resolve as resolve2, sep } from "node:path";
499
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
491
500
 
492
501
  // vendor/heyspex-mcp/vault-resolve.mjs
493
502
  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";
503
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
504
+ import { homedir as homedir5, tmpdir } from "node:os";
505
+
506
+ // vendor/heyspex-mcp/usable-dir-env.mjs
507
+ import { statSync } from "node:fs";
508
+ import { isAbsolute as isAbsolute3 } from "node:path";
509
+
510
+ // vendor/heyspex-mcp/vault-resolve.mjs
511
+ import { basename as basename2, dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
512
+
513
+ // vendor/heyspex-mcp/vault-binding.mjs
514
+ import {
515
+ copyFileSync,
516
+ existsSync as existsSync3,
517
+ mkdirSync as mkdirSync3,
518
+ readFileSync as readFileSync4,
519
+ realpathSync as realpathSync2,
520
+ renameSync as renameSync3,
521
+ statSync as statSync2,
522
+ unlinkSync,
523
+ writeFileSync as writeFileSync3
524
+ } from "node:fs";
525
+ import { homedir as homedir4 } from "node:os";
526
+ import path from "node:path";
497
527
 
498
528
  // vendor/heyspex-mcp/console-fetch.mjs
499
529
  var VAULT_NOT_REGISTERED = "VAULT_NOT_REGISTERED";
@@ -525,7 +555,7 @@ function isCloudVaultId(value) {
525
555
  var MARKER_VAULT_ID_RE = /^vault_id:[ \t]*["']?([^"'\r\n#]+)/m;
526
556
  function readMarkerVaultId(repoRootAbs, { readFile } = {}) {
527
557
  if (typeof repoRootAbs !== "string" || !repoRootAbs.trim()) return null;
528
- const read = readFile ?? ((p) => readFileSync5(p, "utf8"));
558
+ const read = readFile ?? ((p) => readFileSync6(p, "utf8"));
529
559
  try {
530
560
  const raw = read(join6(repoRootAbs, "heyspex.yaml"));
531
561
  const value = String(raw ?? "").match(MARKER_VAULT_ID_RE)?.[1]?.trim() ?? "";
@@ -547,17 +577,17 @@ var SCOPE_REFUSAL_CODES = Object.freeze(["VAULT_SCOPE_MISMATCH", "ACCOUNT_MISMAT
547
577
  init_credentials();
548
578
 
549
579
  // spex-console/src/lib/vault-registry/registry-file.ts
550
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
580
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
551
581
  import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
552
582
 
553
583
  // spex-console/src/lib/heyspex-home.ts
554
- import { homedir as homedir6 } from "node:os";
584
+ import { homedir as homedir7 } from "node:os";
555
585
  function heyspexHome2(env = process.env) {
556
586
  const override = env?.HEYSPEX_HOME;
557
587
  if (typeof override === "string" && override.trim().length > 0) {
558
588
  return override.trim();
559
589
  }
560
- return homedir6();
590
+ return homedir7();
561
591
  }
562
592
 
563
593
  // spex-console/src/lib/vault-registry/types.ts
@@ -679,9 +709,9 @@ function parseVaultRegistryFile(json) {
679
709
  }
680
710
  function readRegistryJson(opts) {
681
711
  const filePath = vaultRegistryFilePath(opts);
682
- if (!existsSync4(filePath)) return { kind: "missing" };
712
+ if (!existsSync5(filePath)) return { kind: "missing" };
683
713
  try {
684
- const raw = readFileSync6(filePath, "utf8");
714
+ const raw = readFileSync7(filePath, "utf8");
685
715
  const json = JSON.parse(raw);
686
716
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
687
717
  return { kind: "ok", raw, json };
@@ -816,11 +846,11 @@ function bearerHeaderFor(consoleUrl, opts = {}) {
816
846
  return {};
817
847
  }
818
848
  }
819
- function scopeHeadersFor(scope, method, path2) {
849
+ function scopeHeadersFor(scope, method, path3) {
820
850
  if (scope === "none") {
821
- if (!isVaultLessEndpoint(method, path2)) {
851
+ if (!isVaultLessEndpoint(method, path3)) {
822
852
  throw new Error(
823
- `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path2} is vault-scoped and must name { accountId, vaultId }.`
853
+ `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 }.`
824
854
  );
825
855
  }
826
856
  return {};
@@ -914,7 +944,7 @@ function parseTtl(token) {
914
944
  return { value: n };
915
945
  }
916
946
  function normalizeRoot(p) {
917
- return path.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
947
+ return path2.resolve(p).replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
918
948
  }
919
949
  function resolveControlScope(input) {
920
950
  const target = normalizeRoot(input.vaultRoot);
@@ -1184,8 +1214,8 @@ ${lines}`,
1184
1214
  return writeFailed(res2.status, res2.body);
1185
1215
  }
1186
1216
  const releaser = resolveAgentIdentity(args.agent, args.session).agent;
1187
- const path2 = `/api/control/active-claims/${encodeURIComponent(args.specId)}?agent=${encodeURIComponent(releaser)}`;
1188
- const res = await controlRequest(deps, { method: "DELETE", path: path2 });
1217
+ const path3 = `/api/control/active-claims/${encodeURIComponent(args.specId)}?agent=${encodeURIComponent(releaser)}`;
1218
+ const res = await controlRequest(deps, { method: "DELETE", path: path3 });
1189
1219
  if (!res.ok) return res.error;
1190
1220
  if (res.status === 200) {
1191
1221
  return {
@@ -1245,8 +1275,8 @@ var HELP = [
1245
1275
  // scripts/lib/console-url-resolver.mjs
1246
1276
  init_heyspex_home();
1247
1277
  init_dev_mode();
1248
- import { readFileSync as readFileSync8 } from "node:fs";
1249
- import { homedir as homedir8 } from "node:os";
1278
+ import { readFileSync as readFileSync9 } from "node:fs";
1279
+ import { homedir as homedir9 } from "node:os";
1250
1280
  import { join as join9 } from "node:path";
1251
1281
  var DEFAULT_CONSOLE_URL = LOCAL_CONSOLE_URL;
1252
1282
  function trimTrailingSlash(url) {
@@ -1284,7 +1314,7 @@ function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = default
1284
1314
  }
1285
1315
  }
1286
1316
  function defaultReadFile2(p) {
1287
- return readFileSync8(p, "utf8");
1317
+ return readFileSync9(p, "utf8");
1288
1318
  }
1289
1319
  function readDiscoveryFileConsoleUrl(repoRoot, { readDiscovery: readDiscovery2 = defaultReadFile2 } = {}) {
1290
1320
  if (!repoRoot) return null;
@@ -1411,7 +1441,7 @@ function runEntry(name, main2, options = {}) {
1411
1441
  // spex-console/bin/hs-claim.ts
1412
1442
  function readDiscovery(p) {
1413
1443
  try {
1414
- return readFileSync9(p, "utf-8");
1444
+ return readFileSync10(p, "utf-8");
1415
1445
  } catch {
1416
1446
  return null;
1417
1447
  }