heyspex 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +2 -1
  2. package/README.md +11 -0
  3. package/bin/hs-vault-binding.mjs +157 -0
  4. package/bin/hs.mjs +83 -79
  5. package/bin/spexcode.mjs +91 -0
  6. package/dev-factory/engine/worktree.mjs +36 -7
  7. package/dev-factory/lib/epic-detect.mjs +2 -2
  8. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  9. package/dev-factory/lib/fleet-state.mjs +2 -2
  10. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  11. package/dist/cli/hs-check.mjs +76 -46
  12. package/dist/cli/hs-claim.mjs +63 -33
  13. package/dist/cli/hs-connect.mjs +520 -214
  14. package/dist/cli/hs-defer.mjs +161 -19
  15. package/dist/cli/hs-dev.mjs +11 -5
  16. package/dist/cli/hs-env.mjs +187 -161
  17. package/dist/cli/hs-index-rebuild.mjs +123 -78
  18. package/dist/cli/hs-index-reconcile.mjs +203 -107
  19. package/dist/cli/hs-init.mjs +426 -179
  20. package/dist/cli/hs-install.mjs +759 -437
  21. package/dist/cli/hs-join.mjs +590 -383
  22. package/dist/cli/hs-vault-pull.mjs +359 -254
  23. package/dist/cli/hs-vault-push.mjs +151 -113
  24. package/dist/cli/hs-vault.mjs +271 -104
  25. package/dist/cli/hs-workspace.mjs +90 -57
  26. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  27. package/docs/copilot-plugin-install.md +200 -177
  28. package/docs/driving-heyspex-from-copilot.md +174 -165
  29. package/docs/mcp-client-config.md +93 -25
  30. package/package.json +257 -250
  31. package/plugins/heyspex/.claude-plugin/plugin.json +13 -12
  32. package/plugins/heyspex/.cursor-plugin/plugin.json +16 -15
  33. package/plugins/heyspex/.mcp.json +3 -4
  34. package/plugins/heyspex/README.md +323 -312
  35. package/plugins/heyspex/assets/heyspex-glyph-128.png +0 -0
  36. package/plugins/heyspex/assets/heyspex-glyph-256.png +0 -0
  37. package/plugins/heyspex/assets/heyspex-glyph.svg +12 -0
  38. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  39. package/plugins/heyspex/mcp.json +12 -13
  40. package/scripts/check-mcp-freshness.mjs +6 -4
  41. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  42. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  43. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  44. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  45. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  46. package/scripts/claude-statusline/statusline-command.mjs +1073 -0
  47. package/scripts/copilot-statusline.mjs +130 -0
  48. package/scripts/dev-server-status.mjs +3 -2
  49. package/scripts/dev-server-stop.mjs +3 -2
  50. package/scripts/hs-capture-cli.mjs +2 -2
  51. package/scripts/hs-settings.mjs +2 -2
  52. package/scripts/hs-view.mjs +3 -2
  53. package/scripts/install-copilot-hooks.mjs +152 -36
  54. package/scripts/lib/copilot-cli.mjs +108 -0
  55. package/scripts/lib/customer-kit.mjs +223 -30
  56. package/scripts/lib/dev-mode.mjs +28 -2
  57. package/scripts/lib/shared-path-helpers.mjs +32 -0
  58. package/scripts/lib/standalone-doctor.mjs +131 -32
  59. package/scripts/wip-ledger-health.mjs +3 -2
  60. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  61. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  62. package/spexharness/src/intent-directive.mjs +839 -626
  63. package/spexharness/src/rule-table.mjs +1 -1
  64. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  65. package/vendor/heyspex-mcp/cli.mjs +20 -4
  66. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  67. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  68. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  69. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  70. package/vendor/heyspex-mcp/http-transport.mjs +6 -1
  71. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  72. package/vendor/heyspex-mcp/server-identity.mjs +13 -0
  73. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  74. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  75. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  76. package/vendor/heyspex-mcp/tool-dispatch.mjs +63 -23
  77. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  78. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  79. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -161,9 +161,9 @@ function parseCredentialsStore(raw) {
161
161
  accounts
162
162
  };
163
163
  }
164
- function readTextOrNull(read, path15) {
164
+ function readTextOrNull(read, path16) {
165
165
  try {
166
- const raw = read(path15);
166
+ const raw = read(path16);
167
167
  return typeof raw === "string" ? raw : null;
168
168
  } catch {
169
169
  return null;
@@ -215,14 +215,14 @@ var init_session_owner = __esm({
215
215
  });
216
216
 
217
217
  // vendor/heyspex-mcp/vault-less-endpoints.mjs
218
- function pathnameOf(path15) {
219
- const raw = String(path15 ?? "").split("?")[0].split("#")[0];
218
+ function pathnameOf(path16) {
219
+ const raw = String(path16 ?? "").split("?")[0].split("#")[0];
220
220
  const noTrailing = raw.length > 1 ? raw.replace(/\/+$/, "") : raw;
221
221
  return noTrailing || "/";
222
222
  }
223
- function isVaultLessEndpoint(method, path15) {
223
+ function isVaultLessEndpoint(method, path16) {
224
224
  const m = String(method || "GET").toUpperCase();
225
- const pathname = pathnameOf(path15);
225
+ const pathname = pathnameOf(path16);
226
226
  return VAULT_LESS_ENDPOINTS.some((row) => {
227
227
  const [rowMethod, rowPath] = row.split(" ");
228
228
  return rowPath === pathname && (rowMethod === "*" || rowMethod === m);
@@ -245,7 +245,9 @@ var init_vault_less_endpoints = __esm({
245
245
  });
246
246
 
247
247
  // scripts/lib/shared-path-helpers.mjs
248
+ import { realpathSync } from "node:fs";
248
249
  import { isAbsolute, resolve as resolve2, sep } from "node:path";
250
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
249
251
  var init_shared_path_helpers = __esm({
250
252
  "scripts/lib/shared-path-helpers.mjs"() {
251
253
  "use strict";
@@ -270,20 +272,52 @@ var init_write_outbox = __esm({
270
272
  }
271
273
  });
272
274
 
275
+ // vendor/heyspex-mcp/usable-dir-env.mjs
276
+ import { statSync } from "node:fs";
277
+ import { isAbsolute as isAbsolute3 } from "node:path";
278
+ var init_usable_dir_env = __esm({
279
+ "vendor/heyspex-mcp/usable-dir-env.mjs"() {
280
+ "use strict";
281
+ }
282
+ });
283
+
284
+ // vendor/heyspex-mcp/vault-binding.mjs
285
+ import {
286
+ copyFileSync,
287
+ existsSync as existsSync3,
288
+ mkdirSync as mkdirSync3,
289
+ readFileSync as readFileSync4,
290
+ realpathSync as realpathSync2,
291
+ renameSync as renameSync3,
292
+ statSync as statSync2,
293
+ unlinkSync,
294
+ writeFileSync as writeFileSync3
295
+ } from "node:fs";
296
+ import { homedir as homedir4 } from "node:os";
297
+ import path from "node:path";
298
+ var init_vault_binding = __esm({
299
+ "vendor/heyspex-mcp/vault-binding.mjs"() {
300
+ "use strict";
301
+ }
302
+ });
303
+
273
304
  // vendor/heyspex-mcp/vault-resolve.mjs
274
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
275
- import { homedir as homedir4, tmpdir } from "node:os";
276
- import { dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
305
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
306
+ import { homedir as homedir5, tmpdir } from "node:os";
307
+ import { basename as basename2, dirname as dirname3, join as join5, resolve as resolve3 } from "node:path";
277
308
  var init_vault_resolve = __esm({
278
309
  "vendor/heyspex-mcp/vault-resolve.mjs"() {
279
310
  "use strict";
280
311
  init_heyspex_home();
312
+ init_usable_dir_env();
313
+ init_vault_binding();
314
+ init_vault_binding();
281
315
  }
282
316
  });
283
317
 
284
318
  // vendor/heyspex-mcp/console-fetch.mjs
285
- import { readFileSync as readFileSync5 } from "node:fs";
286
- import { homedir as homedir5 } from "node:os";
319
+ import { readFileSync as readFileSync6 } from "node:fs";
320
+ import { homedir as homedir6 } from "node:os";
287
321
  import { join as join6 } from "node:path";
288
322
  function isCloudVaultId(value) {
289
323
  return typeof value === "string" && CLOUD_VAULT_ID_RE.test(value.trim());
@@ -305,13 +339,13 @@ var init_console_fetch = __esm({
305
339
  });
306
340
 
307
341
  // spex-console/src/lib/heyspex-home.ts
308
- import { homedir as homedir6 } from "node:os";
342
+ import { homedir as homedir7 } from "node:os";
309
343
  function heyspexHome2(env = process.env) {
310
344
  const override = env?.HEYSPEX_HOME;
311
345
  if (typeof override === "string" && override.trim().length > 0) {
312
346
  return override.trim();
313
347
  }
314
- return homedir6();
348
+ return homedir7();
315
349
  }
316
350
  var init_heyspex_home2 = __esm({
317
351
  "spex-console/src/lib/heyspex-home.ts"() {
@@ -339,7 +373,7 @@ var init_types = __esm({
339
373
  });
340
374
 
341
375
  // spex-console/src/lib/vault-registry/registry-file.ts
342
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
376
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "node:fs";
343
377
  import { dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
344
378
  function vaultRegistryDir(opts = {}) {
345
379
  return opts.registryDir ?? join7(heyspexHome2(), ".heyspex");
@@ -446,9 +480,9 @@ function parseVaultRegistryFile(json) {
446
480
  }
447
481
  function readRegistryJson(opts) {
448
482
  const filePath = vaultRegistryFilePath(opts);
449
- if (!existsSync4(filePath)) return { kind: "missing" };
483
+ if (!existsSync5(filePath)) return { kind: "missing" };
450
484
  try {
451
- const raw = readFileSync6(filePath, "utf8");
485
+ const raw = readFileSync7(filePath, "utf8");
452
486
  const json = JSON.parse(raw);
453
487
  if (registryFileSchemaOf(json) === null) return { kind: "unreadable" };
454
488
  return { kind: "ok", raw, json };
@@ -484,8 +518,8 @@ var init_registry_file = __esm({
484
518
  });
485
519
 
486
520
  // scripts/lib/dev-mode.mjs
487
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
488
- import { homedir as homedir7 } from "node:os";
521
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
522
+ import { homedir as homedir8 } from "node:os";
489
523
  import { join as join8 } from "node:path";
490
524
  var init_dev_mode = __esm({
491
525
  "scripts/lib/dev-mode.mjs"() {
@@ -496,8 +530,8 @@ var init_dev_mode = __esm({
496
530
  });
497
531
 
498
532
  // scripts/lib/console-url-resolver.mjs
499
- import { readFileSync as readFileSync8 } from "node:fs";
500
- import { homedir as homedir8 } from "node:os";
533
+ import { readFileSync as readFileSync9 } from "node:fs";
534
+ import { homedir as homedir9 } from "node:os";
501
535
  import { join as join9 } from "node:path";
502
536
  var init_console_url_resolver = __esm({
503
537
  "scripts/lib/console-url-resolver.mjs"() {
@@ -508,9 +542,9 @@ var init_console_url_resolver = __esm({
508
542
  });
509
543
 
510
544
  // spex-console/src/lib/vault-registry/repo-identity.ts
511
- import path2 from "node:path";
545
+ import path3 from "node:path";
512
546
  function normalizeRepoRoot(p) {
513
- let resolved = path2.resolve(p);
547
+ let resolved = path3.resolve(p);
514
548
  while (resolved.length > 1 && (resolved.endsWith("/") || resolved.endsWith("\\"))) {
515
549
  resolved = resolved.slice(0, -1);
516
550
  }
@@ -519,8 +553,8 @@ function normalizeRepoRoot(p) {
519
553
  function repoRootCandidates(p) {
520
554
  const norm = normalizeRepoRoot(p);
521
555
  const candidates = [norm];
522
- if (path2.basename(norm) === "specs") {
523
- const parent = path2.dirname(norm);
556
+ if (path3.basename(norm) === "specs") {
557
+ const parent = path3.dirname(norm);
524
558
  if (parent !== norm) candidates.push(parent);
525
559
  }
526
560
  return candidates;
@@ -545,22 +579,22 @@ var init_default_workspace = __esm({
545
579
  });
546
580
 
547
581
  // spex-console/src/lib/cli/vault-workspace-shared.ts
548
- import { readFileSync as readFileSync9 } from "node:fs";
582
+ import { readFileSync as readFileSync10 } from "node:fs";
549
583
  function resolveConfiguredAccountId(opts = {}) {
550
584
  return resolveSessionOwnerUncached({
551
585
  env: opts.env ?? process.env,
552
586
  homeDir: opts.homeDir ?? heyspexHome2(),
553
- readFile: opts.readFile ?? ((p) => readFileSync9(p, "utf-8"))
587
+ readFile: opts.readFile ?? ((p) => readFileSync10(p, "utf-8"))
554
588
  });
555
589
  }
556
590
  function resolveCloudAccountIdOrRefuse(opts = {}) {
557
591
  const accountId = resolveConfiguredAccountId(opts);
558
592
  return accountId ? { ok: true, accountId } : { ok: false, message: CLOUD_ACCOUNT_ID_REQUIRED_MESSAGE };
559
593
  }
560
- async function cloudHttpRequest(http, method, path15, body) {
561
- const scopeHeaders = http.scope ? http.scope.headersFor(method, path15) : {};
594
+ async function cloudHttpRequest(http, method, path16, body) {
595
+ const scopeHeaders = http.scope ? http.scope.headersFor(method, path16) : {};
562
596
  try {
563
- const res = await http.fetchImpl(`${http.consoleUrl}${path15}`, {
597
+ const res = await http.fetchImpl(`${http.consoleUrl}${path16}`, {
564
598
  method,
565
599
  headers: {
566
600
  "content-type": "application/json",
@@ -3170,9 +3204,9 @@ var init_satellite = __esm({
3170
3204
  });
3171
3205
 
3172
3206
  // spex-console/src/lib/vault-resolve/index.ts
3173
- import { existsSync as existsSync5, readFileSync as readFileSync11 } from "node:fs";
3207
+ import { existsSync as existsSync6, readFileSync as readFileSync12, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
3174
3208
  import { tmpdir as tmpdir2 } from "node:os";
3175
- import path4 from "node:path";
3209
+ import path5 from "node:path";
3176
3210
  var init_vault_resolve2 = __esm({
3177
3211
  "spex-console/src/lib/vault-resolve/index.ts"() {
3178
3212
  "use strict";
@@ -3209,12 +3243,12 @@ var init_resolve_status = __esm({
3209
3243
  });
3210
3244
 
3211
3245
  // spex-console/src/lib/vault-resolve/active.ts
3212
- import { existsSync as existsSync6 } from "node:fs";
3213
- import path5 from "node:path";
3246
+ import { existsSync as existsSync7 } from "node:fs";
3247
+ import path6 from "node:path";
3214
3248
  function fallbackSpecsDir() {
3215
3249
  const override = process.env.HEYSPEX_VAULT_DIR;
3216
- if (override) return path5.resolve(override);
3217
- return path5.resolve(process.cwd(), "..", "specs");
3250
+ if (override) return path6.resolve(override);
3251
+ return path6.resolve(process.cwd(), "..", "specs");
3218
3252
  }
3219
3253
  function getActiveSpecsDir() {
3220
3254
  return cacheStore.value ? cacheStore.value.specsDirAbs : fallbackSpecsDir();
@@ -3240,7 +3274,7 @@ var init_active = __esm({
3240
3274
 
3241
3275
  // spex-console/src/lib/control-plane/statusline-focus-cache.ts
3242
3276
  import fs4 from "node:fs";
3243
- import path6 from "node:path";
3277
+ import path7 from "node:path";
3244
3278
  var init_statusline_focus_cache = __esm({
3245
3279
  "spex-console/src/lib/control-plane/statusline-focus-cache.ts"() {
3246
3280
  "use strict";
@@ -3867,10 +3901,10 @@ function trashPathFor(relPath, now) {
3867
3901
  const normalized = relPath.split("\\").join("/");
3868
3902
  const slashIdx = normalized.lastIndexOf("/");
3869
3903
  const folder = slashIdx === -1 ? "" : normalized.slice(0, slashIdx);
3870
- const basename2 = slashIdx === -1 ? normalized : normalized.slice(slashIdx + 1);
3904
+ const basename3 = slashIdx === -1 ? normalized : normalized.slice(slashIdx + 1);
3871
3905
  const stamp = now.toISOString().replace(/[:.]/g, "-");
3872
3906
  const trashDir = folder ? `${folder}/.trash` : ".trash";
3873
- return `${trashDir}/${stamp}-${basename2}`;
3907
+ return `${trashDir}/${stamp}-${basename3}`;
3874
3908
  }
3875
3909
  function materializeSpecMarkdown(bundle) {
3876
3910
  return stringifySpecMarkdown(bundle.bodyText, bundle.frontmatter);
@@ -3992,10 +4026,10 @@ var init_export_status = __esm({
3992
4026
  function isGitHubFailure(value) {
3993
4027
  return typeof value === "object" && value !== null && "status" in value;
3994
4028
  }
3995
- async function githubJson(path15, target, fetchImpl, init) {
4029
+ async function githubJson(path16, target, fetchImpl, init) {
3996
4030
  let response;
3997
4031
  try {
3998
- response = await fetchImpl(`${GITHUB_API_BASE2}${path15}`, {
4032
+ response = await fetchImpl(`${GITHUB_API_BASE2}${path16}`, {
3999
4033
  ...init,
4000
4034
  headers: {
4001
4035
  ...githubHeaders(target.token),
@@ -4732,7 +4766,7 @@ var init_diagnose_spawned = __esm({
4732
4766
 
4733
4767
  // spex-console/src/lib/guardrail-naming.ts
4734
4768
  import fs5 from "node:fs";
4735
- import path7 from "node:path";
4769
+ import path8 from "node:path";
4736
4770
  var init_guardrail_naming = __esm({
4737
4771
  "spex-console/src/lib/guardrail-naming.ts"() {
4738
4772
  "use strict";
@@ -4742,7 +4776,7 @@ var init_guardrail_naming = __esm({
4742
4776
  // spex-console/src/lib/indexer/parser.ts
4743
4777
  import { createHash as createHash3 } from "node:crypto";
4744
4778
  import { promises as fs6 } from "node:fs";
4745
- import path8 from "node:path";
4779
+ import path9 from "node:path";
4746
4780
  var import_gray_matter2;
4747
4781
  var init_parser = __esm({
4748
4782
  "spex-console/src/lib/indexer/parser.ts"() {
@@ -4849,7 +4883,7 @@ var init_scoped_context = __esm({
4849
4883
  // spex-console/src/lib/indexer/indexer-service.ts
4850
4884
  import { spawnSync } from "node:child_process";
4851
4885
  import { promises as fs7 } from "node:fs";
4852
- import path9 from "node:path";
4886
+ import path10 from "node:path";
4853
4887
  var init_indexer_service = __esm({
4854
4888
  "spex-console/src/lib/indexer/indexer-service.ts"() {
4855
4889
  "use strict";
@@ -4868,7 +4902,7 @@ var init_indexer_service = __esm({
4868
4902
 
4869
4903
  // spex-console/src/lib/indexer/outbox-index-feeder.ts
4870
4904
  import { promises as fs8 } from "node:fs";
4871
- import path10 from "node:path";
4905
+ import path11 from "node:path";
4872
4906
  var init_outbox_index_feeder = __esm({
4873
4907
  "spex-console/src/lib/indexer/outbox-index-feeder.ts"() {
4874
4908
  "use strict";
@@ -5066,7 +5100,7 @@ var init_list_filter = __esm({
5066
5100
  });
5067
5101
 
5068
5102
  // spex-console/src/lib/specs/list-query.ts
5069
- import path11 from "node:path";
5103
+ import path12 from "node:path";
5070
5104
  var init_list_query = __esm({
5071
5105
  "spex-console/src/lib/specs/list-query.ts"() {
5072
5106
  "use strict";
@@ -5109,7 +5143,7 @@ var init_pipeline_neighborhood = __esm({
5109
5143
  });
5110
5144
 
5111
5145
  // spex-console/src/lib/specs/spec-by-id.ts
5112
- import path12 from "node:path";
5146
+ import path13 from "node:path";
5113
5147
  var init_spec_by_id = __esm({
5114
5148
  "spex-console/src/lib/specs/spec-by-id.ts"() {
5115
5149
  "use strict";
@@ -5166,9 +5200,9 @@ var init_search_body_hit = __esm({
5166
5200
 
5167
5201
  // spex-console/src/lib/vault.ts
5168
5202
  import { spawn } from "node:child_process";
5169
- import { readdirSync as readdirSync2, readFileSync as readFileSync12, statSync } from "node:fs";
5203
+ import { readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
5170
5204
  import { promises as fs9 } from "node:fs";
5171
- import path13 from "node:path";
5205
+ import path14 from "node:path";
5172
5206
  import yaml3 from "js-yaml";
5173
5207
  function vaultRoot() {
5174
5208
  return getActiveSpecsDir();
@@ -5288,8 +5322,8 @@ var init_vault = __esm({
5288
5322
  });
5289
5323
 
5290
5324
  // spex-console/bin/hs-vault-push.ts
5291
- import { readFileSync as readFileSync13 } from "node:fs";
5292
- import path14 from "node:path";
5325
+ import { readFileSync as readFileSync15 } from "node:fs";
5326
+ import path15 from "node:path";
5293
5327
  import process4 from "node:process";
5294
5328
 
5295
5329
  // spex-console/src/lib/cli/control-shared.ts
@@ -5299,14 +5333,14 @@ init_credentials();
5299
5333
  init_session_owner();
5300
5334
  init_vault_less_endpoints();
5301
5335
  init_registry_file();
5302
- import path from "node:path";
5336
+ import path2 from "node:path";
5303
5337
  var VAULT_ID_HEADER = "x-heyspex-vault-id";
5304
5338
  var ACCOUNT_ID_HEADER = "x-heyspex-account-id";
5305
- function scopeHeadersFor(scope, method, path15) {
5339
+ function scopeHeadersFor(scope, method, path16) {
5306
5340
  if (scope === "none") {
5307
- if (!isVaultLessEndpoint(method, path15)) {
5341
+ if (!isVaultLessEndpoint(method, path16)) {
5308
5342
  throw new Error(
5309
- `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path15} is vault-scoped and must name { accountId, vaultId }.`
5343
+ `controlRequest: scope "none" is only allowed for the vault-less endpoints (${VAULT_LESS_ENDPOINTS.join(", ")}); ${method.toUpperCase()} ${path16} is vault-scoped and must name { accountId, vaultId }.`
5310
5344
  );
5311
5345
  }
5312
5346
  return {};
@@ -5317,7 +5351,7 @@ function cloudRequestScope(scope) {
5317
5351
  const named = { accountId: scope.accountId, vaultId: scope.vaultId };
5318
5352
  return {
5319
5353
  ...named,
5320
- headersFor: (method, path15) => isVaultLessEndpoint(method, path15) ? {} : scopeHeadersFor(named, method, path15)
5354
+ headersFor: (method, path16) => isVaultLessEndpoint(method, path16) ? {} : scopeHeadersFor(named, method, path16)
5321
5355
  };
5322
5356
  }
5323
5357
 
@@ -5356,12 +5390,15 @@ var HELP = [
5356
5390
  "(spex-console/src/lib/cli/template-manifest.ts) \u2014 universal docs",
5357
5391
  "copied verbatim, template-shape docs written as author-prompt skeletons \u2014",
5358
5392
  "plus a generated CLAUDE.md, the kept-principles index, and the template_version",
5359
- "pin; with --git, one initial `git init` + commit. Rerunnable: a <dir> that already",
5360
- "holds heyspex.yaml and an empty/placeholder-only specs/ is accepted, existing",
5361
- "files are never overwritten (an existing CLAUDE.md gets a CLAUDE.md.template",
5362
- "beside it), and the result lists each manifest row as created or kept. Refuses a",
5363
- "non-empty <dir> without heyspex.yaml, or a specs/ holding managed specs.",
5364
- "See init.customer-vault.ts.",
5393
+ "pin; with --git, one initial `git init` + commit. On an EXISTING vault (<dir>",
5394
+ "holds heyspex.yaml with a vault_id) it is a REFRESH \u2014 the repair `hs doctor`'s",
5395
+ "vault-template row names: only missing shipped files are added, template_version",
5396
+ "is written/updated in heyspex.yaml (every other byte and the vault_id kept),",
5397
+ "nothing existing is ever overwritten (an existing CLAUDE.md gets a",
5398
+ "CLAUDE.md.template beside it), the result lists each manifest row as created or",
5399
+ "kept, and a second run writes nothing. <dir> may be any path (a satellite repo",
5400
+ "names its vault, e.g. `hs init --customer-vault ../my-specs`). Refuses only a",
5401
+ "non-empty <dir> that has no heyspex.yaml. See init.customer-vault.ts.",
5365
5402
  "",
5366
5403
  "Flags:",
5367
5404
  " --console-url <url> console_url value (default: the mode-aware console --",
@@ -5467,7 +5504,7 @@ init_console_fetch();
5467
5504
  init_registry_file();
5468
5505
  init_repo_identity();
5469
5506
  import { promises as fs } from "node:fs";
5470
- import path3 from "node:path";
5507
+ import path4 from "node:path";
5471
5508
  init_vault_workspace_shared();
5472
5509
  var SYNC_VAULT_EXIT_UNRESOLVED = 11;
5473
5510
  function refuse(message) {
@@ -5518,7 +5555,7 @@ async function resolveSyncVaultTargetFromEnv(deps) {
5518
5555
  let markerVaultId = null;
5519
5556
  try {
5520
5557
  markerVaultId = parseVaultId(
5521
- await fs.readFile(path3.join(deps.vaultRoot, "heyspex.yaml"), "utf8")
5558
+ await fs.readFile(path4.join(deps.vaultRoot, "heyspex.yaml"), "utf8")
5522
5559
  );
5523
5560
  } catch {
5524
5561
  markerVaultId = null;
@@ -5669,16 +5706,17 @@ async function writeDirtyMarker(vaultRoot2, id, now = () => /* @__PURE__ */ new
5669
5706
  }
5670
5707
 
5671
5708
  // spex-console/src/lib/vault-sync/pull-io.ts
5672
- import { promises as fs10 } from "node:fs";
5709
+ import { promises as fs10, readFileSync as readFileSync14 } from "node:fs";
5710
+ import { EOL } from "node:os";
5673
5711
  import nodePath3 from "node:path";
5674
5712
 
5675
5713
  // spex-console/src/lib/dev-mode.ts
5676
5714
  init_heyspex_home2();
5677
5715
  init_credentials();
5678
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
5716
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
5679
5717
  import { join as join10 } from "node:path";
5680
5718
  var CLOUD_CONSOLE_URL2 = "https://app.heyspex.com";
5681
- var defaultReadFile = (p) => readFileSync10(p, "utf8");
5719
+ var defaultReadFile = (p) => readFileSync11(p, "utf8");
5682
5720
  function readCredentialsMcpToken({
5683
5721
  homeDir = heyspexHome2(),
5684
5722
  readFile = defaultReadFile
@@ -5696,6 +5734,37 @@ function readCredentialsMcpToken({
5696
5734
  init_heyspex_home2();
5697
5735
  init_vault();
5698
5736
  init_vault_frontmatter();
5737
+
5738
+ // spex-console/src/lib/vault-sync/pull.ts
5739
+ function normalizeSpecRelPath(raw) {
5740
+ if (typeof raw !== "string") return null;
5741
+ let p = raw.trim();
5742
+ if (p.length === 0) return null;
5743
+ if (/^[a-zA-Z]:[\\/]/.test(p)) return null;
5744
+ p = p.replace(/\\/g, "/");
5745
+ if (p.startsWith("/")) return null;
5746
+ p = p.replace(/\/{2,}/g, "/");
5747
+ while (p.startsWith("./")) p = p.slice(2);
5748
+ if (p.startsWith("specs/")) p = p.slice("specs/".length);
5749
+ if (p.length === 0) return null;
5750
+ const segments = p.split("/");
5751
+ if (segments.some((s) => s === ".." || s === "." || s.length === 0)) return null;
5752
+ if (!/\.md$/i.test(p)) return null;
5753
+ return p;
5754
+ }
5755
+ function hasDotSegment(relPath) {
5756
+ const segments = relPath.split("/");
5757
+ return segments.slice(0, -1).some((s) => s.startsWith("."));
5758
+ }
5759
+ function basenameOfRelPath(relPath) {
5760
+ const last = relPath.split("/").pop() ?? relPath;
5761
+ return last.replace(/\.md$/i, "");
5762
+ }
5763
+ function instantMs(value) {
5764
+ return typeof value === "string" && value ? Date.parse(value) : Number.NaN;
5765
+ }
5766
+
5767
+ // spex-console/src/lib/vault-sync/pull-io.ts
5699
5768
  var LIST_FIELDS = "id,basename,relPath,type,status,updated,updatedAt";
5700
5769
  var PULL_LIST_PAGE_LIMIT = 500;
5701
5770
  var PULL_LIST_MAX_PAGES = 2e3;
@@ -5719,10 +5788,10 @@ function resolvePullSourceUrl(explicit) {
5719
5788
  const raw = explicit?.trim() || process.env.HEYSPEX_PULL_SOURCE_URL?.trim() || CLOUD_CONSOLE_URL2;
5720
5789
  return raw.replace(/\/+$/, "");
5721
5790
  }
5722
- async function getJson({ baseUrl, token, fetchImpl = fetch, scopeHeaders = {} }, path15) {
5791
+ async function getJson({ baseUrl, token, fetchImpl = fetch, scopeHeaders = {} }, path16) {
5723
5792
  const headers = { Accept: "application/json", ...scopeHeaders };
5724
5793
  if (token) headers.Authorization = `Bearer ${token}`;
5725
- const res = await fetchImpl(`${baseUrl}/${path15.replace(/^\/+/, "")}`, { headers });
5794
+ const res = await fetchImpl(`${baseUrl}/${path16.replace(/^\/+/, "")}`, { headers });
5726
5795
  const text2 = await res.text();
5727
5796
  let json = null;
5728
5797
  try {
@@ -5820,37 +5889,6 @@ async function listLocalSpecRelPaths(root = vaultRoot()) {
5820
5889
 
5821
5890
  // spex-console/src/lib/vault-sync/push.ts
5822
5891
  init_vault_frontmatter();
5823
-
5824
- // spex-console/src/lib/vault-sync/pull.ts
5825
- function normalizeSpecRelPath(raw) {
5826
- if (typeof raw !== "string") return null;
5827
- let p = raw.trim();
5828
- if (p.length === 0) return null;
5829
- if (/^[a-zA-Z]:[\\/]/.test(p)) return null;
5830
- p = p.replace(/\\/g, "/");
5831
- if (p.startsWith("/")) return null;
5832
- p = p.replace(/\/{2,}/g, "/");
5833
- while (p.startsWith("./")) p = p.slice(2);
5834
- if (p.startsWith("specs/")) p = p.slice("specs/".length);
5835
- if (p.length === 0) return null;
5836
- const segments = p.split("/");
5837
- if (segments.some((s) => s === ".." || s === "." || s.length === 0)) return null;
5838
- if (!/\.md$/i.test(p)) return null;
5839
- return p;
5840
- }
5841
- function hasDotSegment(relPath) {
5842
- const segments = relPath.split("/");
5843
- return segments.slice(0, -1).some((s) => s.startsWith("."));
5844
- }
5845
- function basenameOfRelPath(relPath) {
5846
- const last = relPath.split("/").pop() ?? relPath;
5847
- return last.replace(/\.md$/i, "");
5848
- }
5849
- function instantMs(value) {
5850
- return typeof value === "string" && value ? Date.parse(value) : Number.NaN;
5851
- }
5852
-
5853
- // spex-console/src/lib/vault-sync/push.ts
5854
5892
  var skip = (reason, detail) => ({
5855
5893
  reason,
5856
5894
  push: false,
@@ -5994,9 +6032,9 @@ async function runVaultPush(deps, options2 = {}) {
5994
6032
  result.skipped.push(item(pre.reason, pre.detail));
5995
6033
  continue;
5996
6034
  }
5997
- const basename2 = basenameOfRelPath(relPath);
6035
+ const basename3 = basenameOfRelPath(relPath);
5998
6036
  if (remoteIndex) {
5999
- const stamp = remoteIndex.get(basename2.toLowerCase());
6037
+ const stamp = remoteIndex.get(basename3.toLowerCase());
6000
6038
  if (stamp === void 0) {
6001
6039
  result.skipped.push(
6002
6040
  item("new-on-disk", "no remote row - hs never creates cloud specs from a disk file")
@@ -6015,7 +6053,7 @@ async function runVaultPush(deps, options2 = {}) {
6015
6053
  }
6016
6054
  let remote;
6017
6055
  try {
6018
- remote = await deps.fetchRemote(basename2);
6056
+ remote = await deps.fetchRemote(basename3);
6019
6057
  } catch (err) {
6020
6058
  result.failed.push({
6021
6059
  relPath,
@@ -6063,7 +6101,7 @@ async function runVaultPush(deps, options2 = {}) {
6063
6101
  puts += 1;
6064
6102
  let put;
6065
6103
  try {
6066
- put = await deps.putRemote(basename2, {
6104
+ put = await deps.putRemote(basename3, {
6067
6105
  frontmatter: payload.frontmatter,
6068
6106
  body: payload.body
6069
6107
  });
@@ -6172,7 +6210,7 @@ import nodePath4 from "node:path";
6172
6210
  init_vault_frontmatter();
6173
6211
  async function putRemoteSpec(http, id, payload) {
6174
6212
  const { baseUrl, token, fetchImpl = fetch } = http;
6175
- const path15 = `/api/specs/${encodeURIComponent(id)}`;
6213
+ const path16 = `/api/specs/${encodeURIComponent(id)}`;
6176
6214
  const scope = http.accountId && http.vaultId ? cloudRequestScope({ accountId: http.accountId, vaultId: http.vaultId }).headersFor(
6177
6215
  "PUT",
6178
6216
  "/api/specs/:id"
@@ -6183,7 +6221,7 @@ async function putRemoteSpec(http, id, payload) {
6183
6221
  ...scope
6184
6222
  };
6185
6223
  if (token) headers.Authorization = `Bearer ${token}`;
6186
- const res = await fetchImpl(`${baseUrl}${path15}`, {
6224
+ const res = await fetchImpl(`${baseUrl}${path16}`, {
6187
6225
  method: "PUT",
6188
6226
  headers,
6189
6227
  body: JSON.stringify({ frontmatter: payload.frontmatter, body: payload.body })
@@ -6199,7 +6237,7 @@ async function putRemoteSpec(http, id, payload) {
6199
6237
  return {
6200
6238
  ok: false,
6201
6239
  status: res.status,
6202
- error: `PUT ${path15} returned ${res.status}: ${text2.slice(0, 200)}`,
6240
+ error: `PUT ${path16} returned ${res.status}: ${text2.slice(0, 200)}`,
6203
6241
  ...res.status === 429 && Number.isFinite(retry) && retry > 0 ? { retryAfterSec: retry } : {}
6204
6242
  };
6205
6243
  }
@@ -6211,19 +6249,19 @@ async function putRemoteSpec(http, id, payload) {
6211
6249
  statusIgnored: json?.status_ignored === true
6212
6250
  };
6213
6251
  }
6214
- async function fetchPushRemote(http, basename2) {
6215
- const { status, json, text: text2 } = await getJson(http, `api/specs/${encodeURIComponent(basename2)}`);
6252
+ async function fetchPushRemote(http, basename3) {
6253
+ const { status, json, text: text2 } = await getJson(http, `api/specs/${encodeURIComponent(basename3)}`);
6216
6254
  if (status === 404) return null;
6217
6255
  if (status !== 200) {
6218
6256
  throw new VaultPullSourceError(
6219
- `GET /api/specs/${basename2} returned ${status}: ${text2.slice(0, 200)}`,
6257
+ `GET /api/specs/${basename3} returned ${status}: ${text2.slice(0, 200)}`,
6220
6258
  status
6221
6259
  );
6222
6260
  }
6223
6261
  const spec = json?.spec;
6224
6262
  const fm = spec?.frontmatter && typeof spec.frontmatter === "object" ? spec.frontmatter : null;
6225
6263
  if (!spec || typeof spec.body !== "string" || !fm) {
6226
- throw new VaultPullSourceError(`GET /api/specs/${basename2} returned no usable spec`, status);
6264
+ throw new VaultPullSourceError(`GET /api/specs/${basename3} returned no usable spec`, status);
6227
6265
  }
6228
6266
  return {
6229
6267
  id: String(spec.id ?? fm.id ?? ""),
@@ -6265,10 +6303,10 @@ function createVaultPushDeps({
6265
6303
  }
6266
6304
  },
6267
6305
  readLedger: () => ledger.snapshot(),
6268
- fetchRemote: (basename2) => fetchPushRemote(http, basename2),
6306
+ fetchRemote: (basename3) => fetchPushRemote(http, basename3),
6269
6307
  listRemote: withListIndex ? () => listRemoteSpecs(http) : void 0,
6270
6308
  serialize: stringifySpecMarkdown,
6271
- putRemote: (basename2, payload) => putRemoteSpec(http, basename2, payload),
6309
+ putRemote: (basename3, payload) => putRemoteSpec(http, basename3, payload),
6272
6310
  recordLedger: (relPath, entry) => ledger.record(relPath, entry),
6273
6311
  flushLedger: () => ledger.flush(),
6274
6312
  markDirty
@@ -6314,7 +6352,7 @@ function runEntry(name, main2, options2 = {}) {
6314
6352
  // spex-console/bin/hs-vault-push.ts
6315
6353
  function readMarkerVaultId(vaultRoot2) {
6316
6354
  try {
6317
- return parseVaultId(readFileSync13(path14.join(vaultRoot2, "heyspex.yaml"), "utf8"));
6355
+ return parseVaultId(readFileSync15(path15.join(vaultRoot2, "heyspex.yaml"), "utf8"));
6318
6356
  } catch {
6319
6357
  return null;
6320
6358
  }