heyspex 0.2.4 → 0.2.6

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 (48) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/bin/hs.mjs +281 -30
  3. package/dev-factory/lib/dispatch-guard.mjs +75 -0
  4. package/dev-factory/lib/runner-path.mjs +51 -0
  5. package/dev-factory/spexcrawl/spexcrawl-cli.mjs +734 -0
  6. package/dist/cli/hs-connect.mjs +20 -1
  7. package/dist/cli/hs-defer.mjs +3802 -3744
  8. package/dist/cli/hs-events.mjs +1296 -0
  9. package/dist/cli/hs-export-events.mjs +1252 -0
  10. package/dist/cli/hs-handoff.mjs +2576 -0
  11. package/dist/cli/hs-index-rebuild.mjs +209 -36
  12. package/dist/cli/hs-index-reconcile.mjs +212 -39
  13. package/dist/cli/hs-init.mjs +20 -1
  14. package/dist/cli/hs-install.mjs +20 -1
  15. package/dist/cli/hs-join.mjs +20 -1
  16. package/dist/cli/hs-next-launch.mjs +2698 -0
  17. package/dist/cli/hs-pin.mjs +1336 -0
  18. package/dist/cli/hs-prereq.mjs +1380 -0
  19. package/dist/cli/hs-revalidate.mjs +3257 -0
  20. package/dist/cli/hs-spawn.mjs +12377 -0
  21. package/dist/cli/hs-test.mjs +1046 -0
  22. package/dist/cli/hs-trash.mjs +362 -0
  23. package/dist/cli/hs-trust.mjs +197 -0
  24. package/dist/cli/hs-vault-pull.mjs +214 -41
  25. package/dist/cli/hs-vault-push.mjs +212 -39
  26. package/dist/cli-templates/harness-commands/claude/hs-log.md +1 -1
  27. package/dist/cli-templates/harness-commands/claude/hs-res.md +71 -63
  28. package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
  29. package/dist/cli-templates/harness-commands/cursor/hs-log.md +1 -1
  30. package/dist/cli-templates/harness-commands/cursor/hs-res.md +66 -58
  31. package/dist/cli-templates/harness-commands/cursor/hs-review.md +18 -18
  32. package/docs/copilot-plugin-install.md +2 -2
  33. package/package.json +9 -5
  34. package/plugins/heyspex/.claude-plugin/plugin.json +1 -1
  35. package/plugins/heyspex/.cursor-plugin/plugin.json +1 -1
  36. package/scripts/claude-hooks/wip-checkpoint.mjs +17 -2
  37. package/scripts/claude-statusline/statusline-command.mjs +287 -27
  38. package/scripts/hs-companion.mjs +8 -0
  39. package/scripts/install-claude-code-hooks.mjs +104 -3
  40. package/scripts/install-copilot-hooks.mjs +21 -1
  41. package/scripts/install-cursor-hooks.mjs +4 -3
  42. package/scripts/lib/capture-execution-guard.mjs +221 -0
  43. package/scripts/lib/customer-kit.mjs +48 -0
  44. package/scripts/lib/hs-companion-settings.mjs +25 -0
  45. package/scripts/record-grounding.mjs +38 -0
  46. package/vendor/heyspex-mcp/http-transport.mjs +1 -1
  47. package/vendor/heyspex-mcp/test-helpers.mjs +37 -23
  48. package/bin/postinstall.mjs +0 -88
@@ -1298,10 +1298,10 @@ var require_stringify = __commonJS({
1298
1298
  data = Object.assign({}, file.data, data);
1299
1299
  const open = opts.delimiters[0];
1300
1300
  const close = opts.delimiters[1];
1301
- const matter4 = engine.stringify(data, options2).trim();
1301
+ const matter5 = engine.stringify(data, options2).trim();
1302
1302
  let buf = "";
1303
- if (matter4 !== "{}") {
1304
- buf = newline(open) + newline(matter4) + newline(close);
1303
+ if (matter5 !== "{}") {
1304
+ buf = newline(open) + newline(matter5) + newline(close);
1305
1305
  }
1306
1306
  if (typeof file.excerpt === "string" && file.excerpt !== "") {
1307
1307
  if (str3.indexOf(file.excerpt.trim()) === -1) {
@@ -1407,19 +1407,19 @@ var require_gray_matter = __commonJS({
1407
1407
  var toFile = require_to_file();
1408
1408
  var parse2 = require_parse();
1409
1409
  var utils = require_utils();
1410
- function matter4(input, options2) {
1410
+ function matter5(input, options2) {
1411
1411
  if (input === "") {
1412
1412
  return { data: {}, content: input, excerpt: "", orig: input };
1413
1413
  }
1414
1414
  let file = toFile(input);
1415
- const cached = matter4.cache[file.content];
1415
+ const cached = matter5.cache[file.content];
1416
1416
  if (!options2) {
1417
1417
  if (cached) {
1418
1418
  file = Object.assign({}, cached);
1419
1419
  file.orig = cached.orig;
1420
1420
  return file;
1421
1421
  }
1422
- matter4.cache[file.content] = file;
1422
+ matter5.cache[file.content] = file;
1423
1423
  }
1424
1424
  return parseMatter(file, options2);
1425
1425
  }
@@ -1441,7 +1441,7 @@ var require_gray_matter = __commonJS({
1441
1441
  }
1442
1442
  str3 = str3.slice(openLen);
1443
1443
  const len = str3.length;
1444
- const language = matter4.language(str3, opts);
1444
+ const language = matter5.language(str3, opts);
1445
1445
  if (language.name) {
1446
1446
  file.language = language.name;
1447
1447
  str3 = str3.slice(language.raw.length);
@@ -1476,24 +1476,24 @@ var require_gray_matter = __commonJS({
1476
1476
  }
1477
1477
  return file;
1478
1478
  }
1479
- matter4.engines = engines2;
1480
- matter4.stringify = function(file, data, options2) {
1481
- if (typeof file === "string") file = matter4(file, options2);
1479
+ matter5.engines = engines2;
1480
+ matter5.stringify = function(file, data, options2) {
1481
+ if (typeof file === "string") file = matter5(file, options2);
1482
1482
  return stringify(file, data, options2);
1483
1483
  };
1484
- matter4.read = function(filepath, options2) {
1484
+ matter5.read = function(filepath, options2) {
1485
1485
  const str3 = fs11.readFileSync(filepath, "utf8");
1486
- const file = matter4(str3, options2);
1486
+ const file = matter5(str3, options2);
1487
1487
  file.path = filepath;
1488
1488
  return file;
1489
1489
  };
1490
- matter4.test = function(str3, options2) {
1490
+ matter5.test = function(str3, options2) {
1491
1491
  return utils.startsWith(str3, defaults(options2).delimiters[0]);
1492
1492
  };
1493
- matter4.language = function(str3, options2) {
1493
+ matter5.language = function(str3, options2) {
1494
1494
  const opts = defaults(options2);
1495
1495
  const open = opts.delimiters[0];
1496
- if (matter4.test(str3)) {
1496
+ if (matter5.test(str3)) {
1497
1497
  str3 = str3.slice(open.length);
1498
1498
  }
1499
1499
  const language = str3.slice(0, str3.search(/\r?\n/));
@@ -1502,11 +1502,11 @@ var require_gray_matter = __commonJS({
1502
1502
  name: language ? language.trim() : ""
1503
1503
  };
1504
1504
  };
1505
- matter4.cache = {};
1506
- matter4.clearCache = function() {
1507
- matter4.cache = {};
1505
+ matter5.cache = {};
1506
+ matter5.clearCache = function() {
1507
+ matter5.cache = {};
1508
1508
  };
1509
- module2.exports = matter4;
1509
+ module2.exports = matter5;
1510
1510
  }
1511
1511
  });
1512
1512
 
@@ -3714,11 +3714,12 @@ function getPostgresAdminDb() {
3714
3714
  function inertClient() {
3715
3715
  return false;
3716
3716
  }
3717
- var isNeonPool;
3717
+ var isNeonPool, registerVaultScopedDbAccessor;
3718
3718
  var init_postgres_client_cli_shim = __esm({
3719
3719
  "dist/cli/._postgres-client.cli-shim.mjs"() {
3720
3720
  "use strict";
3721
3721
  isNeonPool = inertClient;
3722
+ registerVaultScopedDbAccessor = inertClient;
3722
3723
  }
3723
3724
  });
3724
3725
 
@@ -3975,6 +3976,141 @@ var init_clients = __esm({
3975
3976
  }
3976
3977
  });
3977
3978
 
3979
+ // spex-console/src/lib/rate-limits.ts
3980
+ var RATE_LIMIT_BUDGETS;
3981
+ var init_rate_limits = __esm({
3982
+ "spex-console/src/lib/rate-limits.ts"() {
3983
+ "use strict";
3984
+ RATE_LIMIT_BUDGETS = {
3985
+ mcp_tool_calls: { daily: 500, hourly: 125 },
3986
+ spec_writes: { daily: 100, hourly: 25 },
3987
+ capture_sessions: { daily: 20, hourly: 5 },
3988
+ // Paul's Advocate free-text chat ([[HS-4YYB-XEBI-TASK-paul-s-advocate-answers-real-questions]]):
3989
+ // a public YC reviewer can otherwise run up LLM spend on an unauthenticated-feeling
3990
+ // widget. 30/hour matches the builder brief's "e.g. 30 messages/hour" starting point;
3991
+ // daily is 4x hourly (looser than the 25%-of-daily pattern above — a reviewer chatting
3992
+ // steadily through a demo session should not get cut off mid-conversation).
3993
+ advocate_chat: { daily: 120, hourly: 30 }
3994
+ };
3995
+ }
3996
+ });
3997
+
3998
+ // spex-console/src/lib/plan-tier.ts
3999
+ function scaledBudgets(multiplier) {
4000
+ const out = {};
4001
+ for (const [bucket, budget] of Object.entries(RATE_LIMIT_BUDGETS)) {
4002
+ out[bucket] = {
4003
+ daily: Math.round(budget.daily * multiplier),
4004
+ hourly: Math.round(budget.hourly * multiplier)
4005
+ };
4006
+ }
4007
+ return Object.freeze(out);
4008
+ }
4009
+ var PLAN_TIER_RATE_LIMIT_MULTIPLIER, PLAN_TIER_LIMITS;
4010
+ var init_plan_tier = __esm({
4011
+ "spex-console/src/lib/plan-tier.ts"() {
4012
+ "use strict";
4013
+ init_sql_orm_inert_cli_shim();
4014
+ init_postgres_client_cli_shim();
4015
+ init_postgres();
4016
+ init_rate_limits();
4017
+ PLAN_TIER_RATE_LIMIT_MULTIPLIER = {
4018
+ free: 1,
4019
+ pro: 4,
4020
+ enterprise: 20
4021
+ };
4022
+ PLAN_TIER_LIMITS = {
4023
+ free: {
4024
+ statement_timeout_ms: 5e3,
4025
+ read_work_mem: "4MB",
4026
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.free)
4027
+ },
4028
+ pro: {
4029
+ statement_timeout_ms: 15e3,
4030
+ read_work_mem: "16MB",
4031
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.pro)
4032
+ },
4033
+ enterprise: {
4034
+ statement_timeout_ms: 3e4,
4035
+ read_work_mem: "64MB",
4036
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.enterprise)
4037
+ }
4038
+ };
4039
+ }
4040
+ });
4041
+
4042
+ // spex-console/src/lib/db/clients/vault-router.ts
4043
+ import { existsSync as existsSync8, readFileSync as readFileSync13 } from "node:fs";
4044
+ import { homedir as homedir10 } from "node:os";
4045
+ import { join as join11 } from "node:path";
4046
+ var CREDENTIALS_PATH, ROUTER_KEY, globalRouter, router;
4047
+ var init_vault_router = __esm({
4048
+ "spex-console/src/lib/db/clients/vault-router.ts"() {
4049
+ "use strict";
4050
+ init_sql_orm_inert_cli_shim();
4051
+ init_sql_orm_inert_cli_shim();
4052
+ init_postgres();
4053
+ init_postgres_client_cli_shim();
4054
+ CREDENTIALS_PATH = join11(homedir10(), ".heyspex", "credentials.yaml");
4055
+ ROUTER_KEY = Symbol.for("heyspex.vaultRouter");
4056
+ globalRouter = globalThis;
4057
+ if (!globalRouter[ROUTER_KEY]) {
4058
+ globalRouter[ROUTER_KEY] = {
4059
+ dedicated: /* @__PURE__ */ new Map(),
4060
+ refCache: /* @__PURE__ */ new Map(),
4061
+ warnedSchemaMissing: false
4062
+ };
4063
+ }
4064
+ router = globalRouter[ROUTER_KEY];
4065
+ }
4066
+ });
4067
+
4068
+ // spex-console/src/lib/db/clients/vault-scope.ts
4069
+ import { AsyncLocalStorage } from "node:async_hooks";
4070
+ function warnScopeClosedOnce(ctx) {
4071
+ if (ctx.warnedClosed) return;
4072
+ ctx.warnedClosed = true;
4073
+ console.warn(
4074
+ `[vault-scope] db use after vault scope closed; falling back to the pool connection (unscoped) (vaultId: ${ctx.vaultId})`
4075
+ );
4076
+ }
4077
+ function handleScopeClosedAccess(ctx) {
4078
+ if (ctx.onStaleUse === "throw") {
4079
+ throw new VaultScopeClosedError(ctx.vaultId);
4080
+ }
4081
+ warnScopeClosedOnce(ctx);
4082
+ }
4083
+ var VaultScopeClosedError, vaultScopeStorage;
4084
+ var init_vault_scope = __esm({
4085
+ "spex-console/src/lib/db/clients/vault-scope.ts"() {
4086
+ "use strict";
4087
+ init_sql_orm_inert_cli_shim();
4088
+ init_plan_tier();
4089
+ init_postgres_client_cli_shim();
4090
+ init_vault_router();
4091
+ VaultScopeClosedError = class extends Error {
4092
+ vaultId;
4093
+ constructor(vaultId) {
4094
+ super(
4095
+ `Vault scope for "${vaultId}" is closed; this path opted into onStaleUseAfterClose: "throw", so a db handle used after the scope's transaction settled fails loudly instead of silently falling back to the unscoped pool connection.`
4096
+ );
4097
+ this.name = "VaultScopeClosedError";
4098
+ this.vaultId = vaultId;
4099
+ }
4100
+ };
4101
+ vaultScopeStorage = new AsyncLocalStorage();
4102
+ registerVaultScopedDbAccessor(() => {
4103
+ const ctx = vaultScopeStorage.getStore();
4104
+ if (!ctx) return null;
4105
+ if (ctx.closed) {
4106
+ handleScopeClosedAccess(ctx);
4107
+ return null;
4108
+ }
4109
+ return ctx.proxy;
4110
+ });
4111
+ }
4112
+ });
4113
+
3978
4114
  // spex-console/src/lib/scheduler/types.ts
3979
4115
  var init_types2 = __esm({
3980
4116
  "spex-console/src/lib/scheduler/types.ts"() {
@@ -4502,10 +4638,45 @@ var init_config = __esm({
4502
4638
  }
4503
4639
  });
4504
4640
 
4505
- // spex-console/src/lib/vault-frontmatter.ts
4641
+ // spex-console/src/lib/safe-yaml.ts
4506
4642
  import yaml2 from "js-yaml";
4643
+ function dropUndefinedDeep(value) {
4644
+ if (Array.isArray(value)) {
4645
+ return value.filter((item) => item !== void 0).map((item) => dropUndefinedDeep(item));
4646
+ }
4647
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
4648
+ const out = {};
4649
+ for (const [k, v] of Object.entries(value)) {
4650
+ if (v === void 0) continue;
4651
+ out[k] = dropUndefinedDeep(v);
4652
+ }
4653
+ return out;
4654
+ }
4655
+ return value;
4656
+ }
4657
+ function safeYamlDump(value, options2 = {}) {
4658
+ const { schema: _ignoredSchema, ...rest } = options2;
4659
+ const data = dropUndefinedDeep(value);
4660
+ if (grayMatterYamlStringify) return grayMatterYamlStringify(data, rest);
4661
+ return yaml2.dump(data, { ...rest, schema: SAFE_YAML_SCHEMA });
4662
+ }
4663
+ var import_gray_matter2, SAFE_YAML_SCHEMA, grayMatterYamlStringify, YAML_FAILSAFE_SCHEMA;
4664
+ var init_safe_yaml = __esm({
4665
+ "spex-console/src/lib/safe-yaml.ts"() {
4666
+ "use strict";
4667
+ import_gray_matter2 = __toESM(require_gray_matter(), 1);
4668
+ SAFE_YAML_SCHEMA = yaml2.DEFAULT_SAFE_SCHEMA ?? yaml2.DEFAULT_SCHEMA;
4669
+ grayMatterYamlStringify = (() => {
4670
+ const engine = import_gray_matter2.default.engines?.yaml;
4671
+ return typeof engine?.stringify === "function" ? engine.stringify : void 0;
4672
+ })();
4673
+ YAML_FAILSAFE_SCHEMA = yaml2.FAILSAFE_SCHEMA;
4674
+ }
4675
+ });
4676
+
4677
+ // spex-console/src/lib/vault-frontmatter.ts
4507
4678
  function normalizeFrontmatterForYaml(frontmatter) {
4508
- const processed = { ...frontmatter };
4679
+ const processed = dropUndefinedDeep(frontmatter);
4509
4680
  for (const [key, value] of Object.entries(processed)) {
4510
4681
  if (value instanceof Date) {
4511
4682
  processed[key] = value.toISOString();
@@ -4514,17 +4685,18 @@ function normalizeFrontmatterForYaml(frontmatter) {
4514
4685
  return processed;
4515
4686
  }
4516
4687
  function stringifySpecMarkdown(body, frontmatter) {
4517
- const fm = yaml2.dump(normalizeFrontmatterForYaml(frontmatter), YAML_STRINGIFY_OPTIONS).trim();
4688
+ const fm = safeYamlDump(normalizeFrontmatterForYaml(frontmatter), YAML_STRINGIFY_OPTIONS).trim();
4518
4689
  return `---
4519
4690
  ${fm}
4520
4691
  ---
4521
4692
  ${body}`;
4522
4693
  }
4523
- var import_gray_matter2, YAML_STRINGIFY_OPTIONS;
4694
+ var import_gray_matter3, YAML_STRINGIFY_OPTIONS;
4524
4695
  var init_vault_frontmatter = __esm({
4525
4696
  "spex-console/src/lib/vault-frontmatter.ts"() {
4526
4697
  "use strict";
4527
- import_gray_matter2 = __toESM(require_gray_matter(), 1);
4698
+ import_gray_matter3 = __toESM(require_gray_matter(), 1);
4699
+ init_safe_yaml();
4528
4700
  YAML_STRINGIFY_OPTIONS = {
4529
4701
  quotingType: '"',
4530
4702
  skipInvalid: true,
@@ -5654,12 +5826,12 @@ var init_linkage_validation = __esm({
5654
5826
  });
5655
5827
 
5656
5828
  // spex-console/src/lib/perf-timing.ts
5657
- import { AsyncLocalStorage } from "node:async_hooks";
5829
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5658
5830
  var storage;
5659
5831
  var init_perf_timing = __esm({
5660
5832
  "spex-console/src/lib/perf-timing.ts"() {
5661
5833
  "use strict";
5662
- storage = new AsyncLocalStorage();
5834
+ storage = new AsyncLocalStorage2();
5663
5835
  }
5664
5836
  });
5665
5837
 
@@ -5681,12 +5853,12 @@ var init_planning_flow_model = __esm({
5681
5853
  });
5682
5854
 
5683
5855
  // spex-console/src/lib/request-memo.ts
5684
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5856
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
5685
5857
  var storage2;
5686
5858
  var init_request_memo = __esm({
5687
5859
  "spex-console/src/lib/request-memo.ts"() {
5688
5860
  "use strict";
5689
- storage2 = new AsyncLocalStorage2();
5861
+ storage2 = new AsyncLocalStorage3();
5690
5862
  }
5691
5863
  });
5692
5864
 
@@ -6015,10 +6187,9 @@ var init_search_body_hit = __esm({
6015
6187
 
6016
6188
  // spex-console/src/lib/vault.ts
6017
6189
  import { spawn } from "node:child_process";
6018
- import { readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
6190
+ import { readdirSync as readdirSync2, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
6019
6191
  import { promises as fs7 } from "node:fs";
6020
6192
  import path14 from "node:path";
6021
- import yaml3 from "js-yaml";
6022
6193
  function normalizeDateField(value) {
6023
6194
  let d = null;
6024
6195
  if (value instanceof Date) {
@@ -6267,7 +6438,7 @@ async function parseLoadedSpec(absPath, root) {
6267
6438
  }
6268
6439
  let parsed;
6269
6440
  try {
6270
- parsed = (0, import_gray_matter3.default)(raw);
6441
+ parsed = (0, import_gray_matter4.default)(raw);
6271
6442
  } catch {
6272
6443
  const summary2 = toSummary(absPath, root, {}, raw);
6273
6444
  return { ...summary2, frontmatter: {}, body: raw, absPath };
@@ -6281,11 +6452,11 @@ function extractWikilinks2(value) {
6281
6452
  const arr = Array.isArray(value) ? value : [value];
6282
6453
  return arr.map((v) => normalizeWikilink(String(v))).filter(Boolean);
6283
6454
  }
6284
- var import_gray_matter3, DIAGNOSE_SEVERITY_TIER, VAULT_CACHE_TTL_MS, VAULT_CACHE_TTL_WATCHED_MS, VAULT_CACHE_KEY, vaultCacheStore, VAULT_CACHE_DELTA_LISTENERS_KEY, vaultCacheDeltaListeners, SEARCH_TEXT_KEY, searchTextStore, VAULT_SNAPSHOT_KEY, vaultSnapshotStore, VaultDiskWriteError;
6455
+ var import_gray_matter4, DIAGNOSE_SEVERITY_TIER, VAULT_CACHE_TTL_MS, VAULT_CACHE_TTL_WATCHED_MS, VAULT_CACHE_KEY, vaultCacheStore, VAULT_CACHE_DELTA_LISTENERS_KEY, vaultCacheDeltaListeners, SEARCH_TEXT_KEY, searchTextStore, VAULT_SNAPSHOT_KEY, vaultSnapshotStore, VaultDiskWriteError;
6285
6456
  var init_vault = __esm({
6286
6457
  "spex-console/src/lib/vault.ts"() {
6287
6458
  "use strict";
6288
- import_gray_matter3 = __toESM(require_gray_matter(), 1);
6459
+ import_gray_matter4 = __toESM(require_gray_matter(), 1);
6289
6460
  init_diagnose_skeleton();
6290
6461
  init_auth();
6291
6462
  init_capture_vault();
@@ -6296,6 +6467,7 @@ var init_vault = __esm({
6296
6467
  init_skips();
6297
6468
  init_clients();
6298
6469
  init_postgres_client_cli_shim();
6470
+ init_vault_scope();
6299
6471
  init_helpers();
6300
6472
  init_index_write_service();
6301
6473
  init_spec_read_service();
@@ -6312,6 +6484,7 @@ var init_vault = __esm({
6312
6484
  init_planning_flow_model();
6313
6485
  init_request_memo();
6314
6486
  init_review_mode();
6487
+ init_safe_yaml();
6315
6488
  init_active_milestone();
6316
6489
  init_area();
6317
6490
  init_claim_eligibility();
@@ -6413,7 +6586,7 @@ var init_vault = __esm({
6413
6586
 
6414
6587
  // spex-console/bin/hs-index-reconcile.ts
6415
6588
  import { execFileSync } from "node:child_process";
6416
- import { appendFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
6589
+ import { appendFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
6417
6590
  import { promises as fs10 } from "node:fs";
6418
6591
  import path15 from "node:path";
6419
6592
  import process4 from "node:process";
@@ -7370,7 +7543,7 @@ async function postEdgesChunk(input, specs2) {
7370
7543
  }
7371
7544
 
7372
7545
  // spex-console/src/lib/vault-sync/pull-io.ts
7373
- import { promises as fs9, readFileSync as readFileSync14 } from "node:fs";
7546
+ import { promises as fs9, readFileSync as readFileSync15 } from "node:fs";
7374
7547
  import { EOL } from "node:os";
7375
7548
  import nodePath2 from "node:path";
7376
7549
 
@@ -7425,7 +7598,7 @@ var PullTargetNotVaultError = class extends Error {
7425
7598
  function assertPullTargetIsVault(repoRoot) {
7426
7599
  let text2;
7427
7600
  try {
7428
- text2 = readFileSync14(nodePath2.join(repoRoot, "heyspex.yaml"), "utf8");
7601
+ text2 = readFileSync15(nodePath2.join(repoRoot, "heyspex.yaml"), "utf8");
7429
7602
  } catch {
7430
7603
  text2 = null;
7431
7604
  }
@@ -7829,7 +8002,7 @@ function say(args, text2) {
7829
8002
  }
7830
8003
  function readDiscovery(p) {
7831
8004
  try {
7832
- return readFileSync15(p, "utf-8");
8005
+ return readFileSync16(p, "utf-8");
7833
8006
  } catch {
7834
8007
  return null;
7835
8008
  }
@@ -7841,7 +8014,7 @@ function resolveConsole(args) {
7841
8014
  }
7842
8015
  function readMarkerVaultId(vaultRoot2) {
7843
8016
  try {
7844
- return parseVaultId(readFileSync15(path15.join(vaultRoot2, "heyspex.yaml"), "utf8"));
8017
+ return parseVaultId(readFileSync16(path15.join(vaultRoot2, "heyspex.yaml"), "utf8"));
7845
8018
  } catch {
7846
8019
  return null;
7847
8020
  }
@@ -3233,6 +3233,25 @@ var fsDeps = {
3233
3233
  }
3234
3234
  }
3235
3235
  };
3236
+ function findProjectRoot(start) {
3237
+ let dir = path4.resolve(start);
3238
+ for (; ; ) {
3239
+ if (fs.existsSync(path4.join(dir, ".git")) || fs.existsSync(path4.join(dir, "heyspex.yaml"))) return dir;
3240
+ const up = path4.dirname(dir);
3241
+ if (up === dir) return path4.resolve(start);
3242
+ dir = up;
3243
+ }
3244
+ }
3245
+ function isInstalledPackageRoot(platformRoot, exists = fs.existsSync) {
3246
+ if (toPosixPath(path4.resolve(platformRoot)).includes("/node_modules/")) return true;
3247
+ return !exists(path4.join(platformRoot, "scripts", "heyspex-doctor.mjs"));
3248
+ }
3249
+ function resolveSetupTarget({ platformRoot, env = process.env, cwd = process.cwd(), exists = fs.existsSync }) {
3250
+ const explicit = env.HS_SETUP_TARGET?.trim();
3251
+ if (explicit) return path4.resolve(explicit);
3252
+ if (isInstalledPackageRoot(platformRoot, exists)) return findProjectRoot(cwd);
3253
+ return path4.resolve(platformRoot);
3254
+ }
3236
3255
  function invokedDirectly(importMetaUrl, selfBasename, argv1 = process.argv[1]) {
3237
3256
  try {
3238
3257
  if (!argv1) return false;
@@ -3281,7 +3300,7 @@ if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
3281
3300
 
3282
3301
  // scripts/install-cursor-hooks.mjs
3283
3302
  var PLATFORM_ROOT = path5.resolve(path5.dirname(fileURLToPath4(import.meta.url)), "..");
3284
- var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path5.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
3303
+ var REPO_ROOT = resolveSetupTarget({ platformRoot: PLATFORM_ROOT });
3285
3304
  var SETTINGS_PATH = path5.join(REPO_ROOT, ".cursor", "hooks.json");
3286
3305
  function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
3287
3306
  const customer = !samePlatformPath(targetRoot, platformRoot);
@@ -2891,6 +2891,25 @@ var fsDeps = {
2891
2891
  }
2892
2892
  }
2893
2893
  };
2894
+ function findProjectRoot(start) {
2895
+ let dir = path4.resolve(start);
2896
+ for (; ; ) {
2897
+ if (fs.existsSync(path4.join(dir, ".git")) || fs.existsSync(path4.join(dir, "heyspex.yaml"))) return dir;
2898
+ const up = path4.dirname(dir);
2899
+ if (up === dir) return path4.resolve(start);
2900
+ dir = up;
2901
+ }
2902
+ }
2903
+ function isInstalledPackageRoot(platformRoot, exists = fs.existsSync) {
2904
+ if (toPosixPath(path4.resolve(platformRoot)).includes("/node_modules/")) return true;
2905
+ return !exists(path4.join(platformRoot, "scripts", "heyspex-doctor.mjs"));
2906
+ }
2907
+ function resolveSetupTarget({ platformRoot, env = process.env, cwd = process.cwd(), exists = fs.existsSync }) {
2908
+ const explicit = env.HS_SETUP_TARGET?.trim();
2909
+ if (explicit) return path4.resolve(explicit);
2910
+ if (isInstalledPackageRoot(platformRoot, exists)) return findProjectRoot(cwd);
2911
+ return path4.resolve(platformRoot);
2912
+ }
2894
2913
  function invokedDirectly(importMetaUrl, selfBasename, argv1 = process.argv[1]) {
2895
2914
  try {
2896
2915
  if (!argv1) return false;
@@ -2939,7 +2958,7 @@ if (invokedDirectly(import.meta.url, "customer-kit.mjs")) {
2939
2958
 
2940
2959
  // scripts/install-cursor-hooks.mjs
2941
2960
  var PLATFORM_ROOT = path5.resolve(path5.dirname(fileURLToPath4(import.meta.url)), "..");
2942
- var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path5.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
2961
+ var REPO_ROOT = resolveSetupTarget({ platformRoot: PLATFORM_ROOT });
2943
2962
  var SETTINGS_PATH = path5.join(REPO_ROOT, ".cursor", "hooks.json");
2944
2963
  function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
2945
2964
  const customer = !samePlatformPath(targetRoot, platformRoot);
@@ -26583,6 +26583,25 @@ var fsDeps = {
26583
26583
  }
26584
26584
  }
26585
26585
  };
26586
+ function findProjectRoot(start) {
26587
+ let dir = path2.resolve(start);
26588
+ for (; ; ) {
26589
+ if (fs.existsSync(path2.join(dir, ".git")) || fs.existsSync(path2.join(dir, "heyspex.yaml"))) return dir;
26590
+ const up = path2.dirname(dir);
26591
+ if (up === dir) return path2.resolve(start);
26592
+ dir = up;
26593
+ }
26594
+ }
26595
+ function isInstalledPackageRoot(platformRoot, exists2 = fs.existsSync) {
26596
+ if (toPosixPath(path2.resolve(platformRoot)).includes("/node_modules/")) return true;
26597
+ return !exists2(path2.join(platformRoot, "scripts", "heyspex-doctor.mjs"));
26598
+ }
26599
+ function resolveSetupTarget({ platformRoot, env = process.env, cwd = process.cwd(), exists: exists2 = fs.existsSync }) {
26600
+ const explicit = env.HS_SETUP_TARGET?.trim();
26601
+ if (explicit) return path2.resolve(explicit);
26602
+ if (isInstalledPackageRoot(platformRoot, exists2)) return findProjectRoot(cwd);
26603
+ return path2.resolve(platformRoot);
26604
+ }
26586
26605
  function invokedDirectly(importMetaUrl, selfBasename, argv1 = process.argv[1]) {
26587
26606
  try {
26588
26607
  if (!argv1) return false;
@@ -29537,7 +29556,7 @@ function timeoutFor(entry) {
29537
29556
 
29538
29557
  // scripts/install-cursor-hooks.mjs
29539
29558
  var PLATFORM_ROOT = path6.resolve(path6.dirname(fileURLToPath4(import.meta.url)), "..");
29540
- var REPO_ROOT = process.env.HS_SETUP_TARGET?.trim() ? path6.resolve(process.env.HS_SETUP_TARGET.trim()) : PLATFORM_ROOT;
29559
+ var REPO_ROOT = resolveSetupTarget({ platformRoot: PLATFORM_ROOT });
29541
29560
  var SETTINGS_PATH = path6.join(REPO_ROOT, ".cursor", "hooks.json");
29542
29561
  function cursorKitFor(targetRoot, platformRoot = PLATFORM_ROOT) {
29543
29562
  const customer = !samePlatformPath(targetRoot, platformRoot);