heyspex 0.2.5 → 0.2.7

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 (110) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/bin/hs.mjs +349 -38
  3. package/bin/spexcode.mjs +31 -0
  4. package/dev-factory/lib/dispatch-guard.mjs +75 -0
  5. package/dev-factory/lib/local-firecrawl.mjs +294 -0
  6. package/dev-factory/lib/runner-path.mjs +51 -0
  7. package/dev-factory/lib/seat-context.mjs +20 -4
  8. package/dev-factory/model-catalog.json +222 -222
  9. package/dev-factory/spexcrawl/firecrawl-cli.mjs +755 -0
  10. package/dev-factory/spexcrawl/spexcrawl-cli.mjs +734 -0
  11. package/dist/cli/hs-connect.mjs +40 -3
  12. package/dist/cli/hs-defer.mjs +3817 -3741
  13. package/dist/cli/hs-events.mjs +1296 -0
  14. package/dist/cli/hs-export-events.mjs +1252 -0
  15. package/dist/cli/hs-handoff.mjs +2576 -0
  16. package/dist/cli/hs-index-rebuild.mjs +228 -37
  17. package/dist/cli/hs-index-reconcile.mjs +231 -40
  18. package/dist/cli/hs-init.mjs +22 -3
  19. package/dist/cli/hs-install.mjs +22 -3
  20. package/dist/cli/hs-join.mjs +40 -3
  21. package/dist/cli/hs-next-launch.mjs +2777 -0
  22. package/dist/cli/hs-pin.mjs +1336 -0
  23. package/dist/cli/hs-prereq.mjs +1380 -0
  24. package/dist/cli/hs-revalidate.mjs +3257 -0
  25. package/dist/cli/hs-spawn.mjs +12403 -0
  26. package/dist/cli/hs-test.mjs +1046 -0
  27. package/dist/cli/hs-trash.mjs +362 -0
  28. package/dist/cli/hs-trust.mjs +197 -0
  29. package/dist/cli/hs-vault-pull.mjs +233 -42
  30. package/dist/cli/hs-vault-push.mjs +231 -40
  31. package/dist/cli/hs-vault.mjs +21 -7
  32. package/dist/cli/hs-workspace.mjs +19 -1
  33. package/dist/cli-templates/customer-vault/specs/entity-model.md +1728 -1728
  34. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +464 -464
  35. package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -122
  36. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +46 -46
  37. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +530 -530
  38. package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -83
  39. package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
  40. package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
  41. package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
  42. package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -30
  43. package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
  44. package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
  45. package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
  46. package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
  47. package/dist/cli-templates/harness-commands/claude/hs-res.md +83 -63
  48. package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
  49. package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -28
  50. package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
  51. package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
  52. package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
  53. package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
  54. package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
  55. package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -24
  56. package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
  57. package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
  58. package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
  59. package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
  60. package/dist/cli-templates/harness-commands/cursor/hs-res.md +78 -58
  61. package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
  62. package/dist/cli-templates/harness-commands/cursor/hs-view.md +23 -23
  63. package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
  64. package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
  65. package/docs/copilot-plugin-install.md +9 -2
  66. package/docs/mcp-client-config.md +6 -0
  67. package/package.json +266 -257
  68. package/plugins/heyspex/.claude-plugin/plugin.json +1 -1
  69. package/plugins/heyspex/.cursor-plugin/plugin.json +1 -1
  70. package/plugins/heyspex/README.md +6 -0
  71. package/plugins/heyspex/bin/launch-mcp.mjs +17 -0
  72. package/plugins/heyspex/hooks/hooks.json +418 -418
  73. package/scripts/claude-hooks/_intent-classifier-semantic.mjs +2 -2
  74. package/scripts/claude-hooks/wip-checkpoint.mjs +17 -2
  75. package/scripts/claude-statusline/statusline-command.mjs +287 -27
  76. package/scripts/hs-companion.mjs +41 -3
  77. package/scripts/install-claude-code-hooks.mjs +104 -3
  78. package/scripts/install-copilot-hooks.mjs +21 -1
  79. package/scripts/install-cursor-hooks.mjs +4 -3
  80. package/scripts/lib/capture-execution-guard.mjs +221 -0
  81. package/scripts/lib/companion-install.mjs +768 -0
  82. package/scripts/lib/customer-kit.mjs +53 -3
  83. package/scripts/lib/hs-companion-settings.mjs +26 -0
  84. package/scripts/lib/hs-source-push.mjs +386 -386
  85. package/scripts/record-grounding.mjs +38 -0
  86. package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -29
  87. package/spexharness/src/adapters/copilot-models.mjs +179 -0
  88. package/spexharness/src/adapters/harness-detect.mjs +148 -0
  89. package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -132
  90. package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +153 -125
  91. package/spexharness/src/seat-context.mjs +10 -5
  92. package/spexharness/src/session-bootstrap.mjs +6 -3
  93. package/vendor/heyspex-mcp/cli.mjs +6 -1
  94. package/vendor/heyspex-mcp/config-seed.yaml +15 -15
  95. package/vendor/heyspex-mcp/dev-factory-overlay.mjs +142 -0
  96. package/vendor/heyspex-mcp/http-transport.mjs +1 -1
  97. package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
  98. package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
  99. package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -327
  100. package/vendor/heyspex-mcp/procedure-tools.mjs +1209 -1181
  101. package/vendor/heyspex-mcp/research-tools.mjs +22 -2
  102. package/vendor/heyspex-mcp/seat-assign-cli.mjs +242 -0
  103. package/vendor/heyspex-mcp/seat-assignment-rules.mjs +96 -0
  104. package/vendor/heyspex-mcp/seat-assignments-client.mjs +265 -0
  105. package/vendor/heyspex-mcp/seat-defaults-cli.mjs +45 -0
  106. package/vendor/heyspex-mcp/seat-defaults.mjs +232 -0
  107. package/vendor/heyspex-mcp/seat-identity.mjs +171 -0
  108. package/vendor/heyspex-mcp/test-helpers.mjs +37 -23
  109. package/vendor/heyspex-mcp/vault-io.mjs +720 -720
  110. package/bin/postinstall.mjs +0 -88
@@ -1073,10 +1073,10 @@ var require_stringify = __commonJS({
1073
1073
  data = Object.assign({}, file.data, data);
1074
1074
  const open = opts.delimiters[0];
1075
1075
  const close = opts.delimiters[1];
1076
- const matter4 = engine.stringify(data, options2).trim();
1076
+ const matter5 = engine.stringify(data, options2).trim();
1077
1077
  let buf = "";
1078
- if (matter4 !== "{}") {
1079
- buf = newline(open) + newline(matter4) + newline(close);
1078
+ if (matter5 !== "{}") {
1079
+ buf = newline(open) + newline(matter5) + newline(close);
1080
1080
  }
1081
1081
  if (typeof file.excerpt === "string" && file.excerpt !== "") {
1082
1082
  if (str2.indexOf(file.excerpt.trim()) === -1) {
@@ -1182,19 +1182,19 @@ var require_gray_matter = __commonJS({
1182
1182
  var toFile = require_to_file();
1183
1183
  var parse2 = require_parse();
1184
1184
  var utils = require_utils();
1185
- function matter4(input, options2) {
1185
+ function matter5(input, options2) {
1186
1186
  if (input === "") {
1187
1187
  return { data: {}, content: input, excerpt: "", orig: input };
1188
1188
  }
1189
1189
  let file = toFile(input);
1190
- const cached = matter4.cache[file.content];
1190
+ const cached = matter5.cache[file.content];
1191
1191
  if (!options2) {
1192
1192
  if (cached) {
1193
1193
  file = Object.assign({}, cached);
1194
1194
  file.orig = cached.orig;
1195
1195
  return file;
1196
1196
  }
1197
- matter4.cache[file.content] = file;
1197
+ matter5.cache[file.content] = file;
1198
1198
  }
1199
1199
  return parseMatter(file, options2);
1200
1200
  }
@@ -1216,7 +1216,7 @@ var require_gray_matter = __commonJS({
1216
1216
  }
1217
1217
  str2 = str2.slice(openLen);
1218
1218
  const len = str2.length;
1219
- const language = matter4.language(str2, opts);
1219
+ const language = matter5.language(str2, opts);
1220
1220
  if (language.name) {
1221
1221
  file.language = language.name;
1222
1222
  str2 = str2.slice(language.raw.length);
@@ -1251,24 +1251,24 @@ var require_gray_matter = __commonJS({
1251
1251
  }
1252
1252
  return file;
1253
1253
  }
1254
- matter4.engines = engines2;
1255
- matter4.stringify = function(file, data, options2) {
1256
- if (typeof file === "string") file = matter4(file, options2);
1254
+ matter5.engines = engines2;
1255
+ matter5.stringify = function(file, data, options2) {
1256
+ if (typeof file === "string") file = matter5(file, options2);
1257
1257
  return stringify(file, data, options2);
1258
1258
  };
1259
- matter4.read = function(filepath, options2) {
1259
+ matter5.read = function(filepath, options2) {
1260
1260
  const str2 = fs12.readFileSync(filepath, "utf8");
1261
- const file = matter4(str2, options2);
1261
+ const file = matter5(str2, options2);
1262
1262
  file.path = filepath;
1263
1263
  return file;
1264
1264
  };
1265
- matter4.test = function(str2, options2) {
1265
+ matter5.test = function(str2, options2) {
1266
1266
  return utils.startsWith(str2, defaults(options2).delimiters[0]);
1267
1267
  };
1268
- matter4.language = function(str2, options2) {
1268
+ matter5.language = function(str2, options2) {
1269
1269
  const opts = defaults(options2);
1270
1270
  const open = opts.delimiters[0];
1271
- if (matter4.test(str2)) {
1271
+ if (matter5.test(str2)) {
1272
1272
  str2 = str2.slice(open.length);
1273
1273
  }
1274
1274
  const language = str2.slice(0, str2.search(/\r?\n/));
@@ -1277,11 +1277,11 @@ var require_gray_matter = __commonJS({
1277
1277
  name: language ? language.trim() : ""
1278
1278
  };
1279
1279
  };
1280
- matter4.cache = {};
1281
- matter4.clearCache = function() {
1282
- matter4.cache = {};
1280
+ matter5.cache = {};
1281
+ matter5.clearCache = function() {
1282
+ matter5.cache = {};
1283
1283
  };
1284
- module2.exports = matter4;
1284
+ module2.exports = matter5;
1285
1285
  }
1286
1286
  });
1287
1287
 
@@ -1364,7 +1364,7 @@ var init_interfaces = __esm({
1364
1364
  });
1365
1365
 
1366
1366
  // spex-console/src/lib/db/schema/postgres.ts
1367
- var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
1367
+ var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, seatAssignments, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
1368
1368
  var init_postgres = __esm({
1369
1369
  "spex-console/src/lib/db/schema/postgres.ts"() {
1370
1370
  "use strict";
@@ -2314,6 +2314,24 @@ var init_postgres = __esm({
2314
2314
  },
2315
2315
  (table) => [primaryKey({ columns: [table.userId, table.vaultId, table.namespace] })]
2316
2316
  );
2317
+ seatAssignments = pgTable(
2318
+ "seat_assignments",
2319
+ {
2320
+ accountId: text("account_id").notNull(),
2321
+ vaultId: text("vault_id").notNull(),
2322
+ harness: text("harness").notNull(),
2323
+ seatId: text("seat_id").notNull(),
2324
+ model: text("model").notNull(),
2325
+ // Set when a launch reported the harness REFUSED this model and the run fell
2326
+ // back to auto (Copilot's `heyspex.copilot_model_fallback` event, HS-F4UM);
2327
+ // cleared whenever the user sets a new model.
2328
+ overrideRejectedAt: timestamp("override_rejected_at", { withTimezone: true }),
2329
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().default(sql`now()`)
2330
+ },
2331
+ (table) => [
2332
+ primaryKey({ columns: [table.accountId, table.vaultId, table.harness, table.seatId] })
2333
+ ]
2334
+ );
2317
2335
  wipLedger = pgTable(
2318
2336
  "wip_ledger",
2319
2337
  {
@@ -3068,11 +3086,12 @@ function getPostgresAdminDb() {
3068
3086
  function inertClient() {
3069
3087
  return false;
3070
3088
  }
3071
- var isNeonPool;
3089
+ var isNeonPool, registerVaultScopedDbAccessor;
3072
3090
  var init_postgres_client_cli_shim = __esm({
3073
3091
  "dist/cli/._postgres-client.cli-shim.mjs"() {
3074
3092
  "use strict";
3075
3093
  isNeonPool = inertClient;
3094
+ registerVaultScopedDbAccessor = inertClient;
3076
3095
  }
3077
3096
  });
3078
3097
 
@@ -3323,6 +3342,141 @@ var init_clients = __esm({
3323
3342
  }
3324
3343
  });
3325
3344
 
3345
+ // spex-console/src/lib/rate-limits.ts
3346
+ var RATE_LIMIT_BUDGETS;
3347
+ var init_rate_limits = __esm({
3348
+ "spex-console/src/lib/rate-limits.ts"() {
3349
+ "use strict";
3350
+ RATE_LIMIT_BUDGETS = {
3351
+ mcp_tool_calls: { daily: 500, hourly: 125 },
3352
+ spec_writes: { daily: 100, hourly: 25 },
3353
+ capture_sessions: { daily: 20, hourly: 5 },
3354
+ // Paul's Advocate free-text chat ([[HS-4YYB-XEBI-TASK-paul-s-advocate-answers-real-questions]]):
3355
+ // a public YC reviewer can otherwise run up LLM spend on an unauthenticated-feeling
3356
+ // widget. 30/hour matches the builder brief's "e.g. 30 messages/hour" starting point;
3357
+ // daily is 4x hourly (looser than the 25%-of-daily pattern above — a reviewer chatting
3358
+ // steadily through a demo session should not get cut off mid-conversation).
3359
+ advocate_chat: { daily: 120, hourly: 30 }
3360
+ };
3361
+ }
3362
+ });
3363
+
3364
+ // spex-console/src/lib/plan-tier.ts
3365
+ function scaledBudgets(multiplier) {
3366
+ const out = {};
3367
+ for (const [bucket, budget] of Object.entries(RATE_LIMIT_BUDGETS)) {
3368
+ out[bucket] = {
3369
+ daily: Math.round(budget.daily * multiplier),
3370
+ hourly: Math.round(budget.hourly * multiplier)
3371
+ };
3372
+ }
3373
+ return Object.freeze(out);
3374
+ }
3375
+ var PLAN_TIER_RATE_LIMIT_MULTIPLIER, PLAN_TIER_LIMITS;
3376
+ var init_plan_tier = __esm({
3377
+ "spex-console/src/lib/plan-tier.ts"() {
3378
+ "use strict";
3379
+ init_sql_orm_inert_cli_shim();
3380
+ init_postgres_client_cli_shim();
3381
+ init_postgres();
3382
+ init_rate_limits();
3383
+ PLAN_TIER_RATE_LIMIT_MULTIPLIER = {
3384
+ free: 1,
3385
+ pro: 4,
3386
+ enterprise: 20
3387
+ };
3388
+ PLAN_TIER_LIMITS = {
3389
+ free: {
3390
+ statement_timeout_ms: 5e3,
3391
+ read_work_mem: "4MB",
3392
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.free)
3393
+ },
3394
+ pro: {
3395
+ statement_timeout_ms: 15e3,
3396
+ read_work_mem: "16MB",
3397
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.pro)
3398
+ },
3399
+ enterprise: {
3400
+ statement_timeout_ms: 3e4,
3401
+ read_work_mem: "64MB",
3402
+ rate_limit_budgets: scaledBudgets(PLAN_TIER_RATE_LIMIT_MULTIPLIER.enterprise)
3403
+ }
3404
+ };
3405
+ }
3406
+ });
3407
+
3408
+ // spex-console/src/lib/db/clients/vault-router.ts
3409
+ import { existsSync as existsSync8, readFileSync as readFileSync13 } from "node:fs";
3410
+ import { homedir as homedir10 } from "node:os";
3411
+ import { join as join11 } from "node:path";
3412
+ var CREDENTIALS_PATH, ROUTER_KEY, globalRouter, router;
3413
+ var init_vault_router = __esm({
3414
+ "spex-console/src/lib/db/clients/vault-router.ts"() {
3415
+ "use strict";
3416
+ init_sql_orm_inert_cli_shim();
3417
+ init_sql_orm_inert_cli_shim();
3418
+ init_postgres();
3419
+ init_postgres_client_cli_shim();
3420
+ CREDENTIALS_PATH = join11(homedir10(), ".heyspex", "credentials.yaml");
3421
+ ROUTER_KEY = Symbol.for("heyspex.vaultRouter");
3422
+ globalRouter = globalThis;
3423
+ if (!globalRouter[ROUTER_KEY]) {
3424
+ globalRouter[ROUTER_KEY] = {
3425
+ dedicated: /* @__PURE__ */ new Map(),
3426
+ refCache: /* @__PURE__ */ new Map(),
3427
+ warnedSchemaMissing: false
3428
+ };
3429
+ }
3430
+ router = globalRouter[ROUTER_KEY];
3431
+ }
3432
+ });
3433
+
3434
+ // spex-console/src/lib/db/clients/vault-scope.ts
3435
+ import { AsyncLocalStorage } from "node:async_hooks";
3436
+ function warnScopeClosedOnce(ctx) {
3437
+ if (ctx.warnedClosed) return;
3438
+ ctx.warnedClosed = true;
3439
+ console.warn(
3440
+ `[vault-scope] db use after vault scope closed; falling back to the pool connection (unscoped) (vaultId: ${ctx.vaultId})`
3441
+ );
3442
+ }
3443
+ function handleScopeClosedAccess(ctx) {
3444
+ if (ctx.onStaleUse === "throw") {
3445
+ throw new VaultScopeClosedError(ctx.vaultId);
3446
+ }
3447
+ warnScopeClosedOnce(ctx);
3448
+ }
3449
+ var VaultScopeClosedError, vaultScopeStorage;
3450
+ var init_vault_scope = __esm({
3451
+ "spex-console/src/lib/db/clients/vault-scope.ts"() {
3452
+ "use strict";
3453
+ init_sql_orm_inert_cli_shim();
3454
+ init_plan_tier();
3455
+ init_postgres_client_cli_shim();
3456
+ init_vault_router();
3457
+ VaultScopeClosedError = class extends Error {
3458
+ vaultId;
3459
+ constructor(vaultId) {
3460
+ super(
3461
+ `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.`
3462
+ );
3463
+ this.name = "VaultScopeClosedError";
3464
+ this.vaultId = vaultId;
3465
+ }
3466
+ };
3467
+ vaultScopeStorage = new AsyncLocalStorage();
3468
+ registerVaultScopedDbAccessor(() => {
3469
+ const ctx = vaultScopeStorage.getStore();
3470
+ if (!ctx) return null;
3471
+ if (ctx.closed) {
3472
+ handleScopeClosedAccess(ctx);
3473
+ return null;
3474
+ }
3475
+ return ctx.proxy;
3476
+ });
3477
+ }
3478
+ });
3479
+
3326
3480
  // spex-console/src/lib/scheduler/types.ts
3327
3481
  var init_types2 = __esm({
3328
3482
  "spex-console/src/lib/scheduler/types.ts"() {
@@ -3857,10 +4011,45 @@ var init_config = __esm({
3857
4011
  }
3858
4012
  });
3859
4013
 
3860
- // spex-console/src/lib/vault-frontmatter.ts
4014
+ // spex-console/src/lib/safe-yaml.ts
3861
4015
  import yaml2 from "js-yaml";
4016
+ function dropUndefinedDeep(value) {
4017
+ if (Array.isArray(value)) {
4018
+ return value.filter((item) => item !== void 0).map((item) => dropUndefinedDeep(item));
4019
+ }
4020
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
4021
+ const out = {};
4022
+ for (const [k, v] of Object.entries(value)) {
4023
+ if (v === void 0) continue;
4024
+ out[k] = dropUndefinedDeep(v);
4025
+ }
4026
+ return out;
4027
+ }
4028
+ return value;
4029
+ }
4030
+ function safeYamlDump(value, options2 = {}) {
4031
+ const { schema: _ignoredSchema, ...rest } = options2;
4032
+ const data = dropUndefinedDeep(value);
4033
+ if (grayMatterYamlStringify) return grayMatterYamlStringify(data, rest);
4034
+ return yaml2.dump(data, { ...rest, schema: SAFE_YAML_SCHEMA });
4035
+ }
4036
+ var import_gray_matter, SAFE_YAML_SCHEMA, grayMatterYamlStringify, YAML_FAILSAFE_SCHEMA;
4037
+ var init_safe_yaml = __esm({
4038
+ "spex-console/src/lib/safe-yaml.ts"() {
4039
+ "use strict";
4040
+ import_gray_matter = __toESM(require_gray_matter(), 1);
4041
+ SAFE_YAML_SCHEMA = yaml2.DEFAULT_SAFE_SCHEMA ?? yaml2.DEFAULT_SCHEMA;
4042
+ grayMatterYamlStringify = (() => {
4043
+ const engine = import_gray_matter.default.engines?.yaml;
4044
+ return typeof engine?.stringify === "function" ? engine.stringify : void 0;
4045
+ })();
4046
+ YAML_FAILSAFE_SCHEMA = yaml2.FAILSAFE_SCHEMA;
4047
+ }
4048
+ });
4049
+
4050
+ // spex-console/src/lib/vault-frontmatter.ts
3862
4051
  function normalizeFrontmatterForYaml(frontmatter) {
3863
- const processed = { ...frontmatter };
4052
+ const processed = dropUndefinedDeep(frontmatter);
3864
4053
  for (const [key, value] of Object.entries(processed)) {
3865
4054
  if (value instanceof Date) {
3866
4055
  processed[key] = value.toISOString();
@@ -3869,20 +4058,21 @@ function normalizeFrontmatterForYaml(frontmatter) {
3869
4058
  return processed;
3870
4059
  }
3871
4060
  function stringifySpecMarkdown(body, frontmatter) {
3872
- const fm = yaml2.dump(normalizeFrontmatterForYaml(frontmatter), YAML_STRINGIFY_OPTIONS).trim();
4061
+ const fm = safeYamlDump(normalizeFrontmatterForYaml(frontmatter), YAML_STRINGIFY_OPTIONS).trim();
3873
4062
  return `---
3874
4063
  ${fm}
3875
4064
  ---
3876
4065
  ${body}`;
3877
4066
  }
3878
4067
  function parseSpecMarkdown(raw) {
3879
- return (0, import_gray_matter.default)(raw);
4068
+ return (0, import_gray_matter2.default)(raw);
3880
4069
  }
3881
- var import_gray_matter, YAML_STRINGIFY_OPTIONS;
4070
+ var import_gray_matter2, YAML_STRINGIFY_OPTIONS;
3882
4071
  var init_vault_frontmatter = __esm({
3883
4072
  "spex-console/src/lib/vault-frontmatter.ts"() {
3884
4073
  "use strict";
3885
- import_gray_matter = __toESM(require_gray_matter(), 1);
4074
+ import_gray_matter2 = __toESM(require_gray_matter(), 1);
4075
+ init_safe_yaml();
3886
4076
  YAML_STRINGIFY_OPTIONS = {
3887
4077
  quotingType: '"',
3888
4078
  skipInvalid: true,
@@ -4777,11 +4967,11 @@ var init_guardrail_naming = __esm({
4777
4967
  import { createHash as createHash3 } from "node:crypto";
4778
4968
  import { promises as fs6 } from "node:fs";
4779
4969
  import path9 from "node:path";
4780
- var import_gray_matter2;
4970
+ var import_gray_matter3;
4781
4971
  var init_parser = __esm({
4782
4972
  "spex-console/src/lib/indexer/parser.ts"() {
4783
4973
  "use strict";
4784
- import_gray_matter2 = __toESM(require_gray_matter(), 1);
4974
+ import_gray_matter3 = __toESM(require_gray_matter(), 1);
4785
4975
  init_vault_id();
4786
4976
  init_vault_shared();
4787
4977
  }
@@ -4920,12 +5110,12 @@ var init_linkage_validation = __esm({
4920
5110
  });
4921
5111
 
4922
5112
  // spex-console/src/lib/perf-timing.ts
4923
- import { AsyncLocalStorage } from "node:async_hooks";
5113
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
4924
5114
  var storage;
4925
5115
  var init_perf_timing = __esm({
4926
5116
  "spex-console/src/lib/perf-timing.ts"() {
4927
5117
  "use strict";
4928
- storage = new AsyncLocalStorage();
5118
+ storage = new AsyncLocalStorage2();
4929
5119
  }
4930
5120
  });
4931
5121
 
@@ -4947,12 +5137,12 @@ var init_planning_flow_model = __esm({
4947
5137
  });
4948
5138
 
4949
5139
  // spex-console/src/lib/request-memo.ts
4950
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5140
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
4951
5141
  var storage2;
4952
5142
  var init_request_memo = __esm({
4953
5143
  "spex-console/src/lib/request-memo.ts"() {
4954
5144
  "use strict";
4955
- storage2 = new AsyncLocalStorage2();
5145
+ storage2 = new AsyncLocalStorage3();
4956
5146
  }
4957
5147
  });
4958
5148
 
@@ -5200,18 +5390,17 @@ var init_search_body_hit = __esm({
5200
5390
 
5201
5391
  // spex-console/src/lib/vault.ts
5202
5392
  import { spawn } from "node:child_process";
5203
- import { readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
5393
+ import { readdirSync as readdirSync2, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
5204
5394
  import { promises as fs9 } from "node:fs";
5205
5395
  import path14 from "node:path";
5206
- import yaml3 from "js-yaml";
5207
5396
  function vaultRoot() {
5208
5397
  return getActiveSpecsDir();
5209
5398
  }
5210
- var import_gray_matter3, VAULT_CACHE_TTL_WATCHED_MS, VAULT_CACHE_KEY, vaultCacheStore, VAULT_CACHE_DELTA_LISTENERS_KEY, vaultCacheDeltaListeners, SEARCH_TEXT_KEY, searchTextStore, VAULT_SNAPSHOT_KEY, vaultSnapshotStore;
5399
+ var import_gray_matter4, VAULT_CACHE_TTL_WATCHED_MS, VAULT_CACHE_KEY, vaultCacheStore, VAULT_CACHE_DELTA_LISTENERS_KEY, vaultCacheDeltaListeners, SEARCH_TEXT_KEY, searchTextStore, VAULT_SNAPSHOT_KEY, vaultSnapshotStore;
5211
5400
  var init_vault = __esm({
5212
5401
  "spex-console/src/lib/vault.ts"() {
5213
5402
  "use strict";
5214
- import_gray_matter3 = __toESM(require_gray_matter(), 1);
5403
+ import_gray_matter4 = __toESM(require_gray_matter(), 1);
5215
5404
  init_diagnose_skeleton();
5216
5405
  init_auth();
5217
5406
  init_capture_vault();
@@ -5222,6 +5411,7 @@ var init_vault = __esm({
5222
5411
  init_skips();
5223
5412
  init_clients();
5224
5413
  init_postgres_client_cli_shim();
5414
+ init_vault_scope();
5225
5415
  init_helpers();
5226
5416
  init_index_write_service();
5227
5417
  init_spec_read_service();
@@ -5238,6 +5428,7 @@ var init_vault = __esm({
5238
5428
  init_planning_flow_model();
5239
5429
  init_request_memo();
5240
5430
  init_review_mode();
5431
+ init_safe_yaml();
5241
5432
  init_active_milestone();
5242
5433
  init_area();
5243
5434
  init_claim_eligibility();
@@ -5322,7 +5513,7 @@ var init_vault = __esm({
5322
5513
  });
5323
5514
 
5324
5515
  // spex-console/bin/hs-vault-push.ts
5325
- import { readFileSync as readFileSync15 } from "node:fs";
5516
+ import { readFileSync as readFileSync16 } from "node:fs";
5326
5517
  import path15 from "node:path";
5327
5518
  import process4 from "node:process";
5328
5519
 
@@ -5706,7 +5897,7 @@ async function writeDirtyMarker(vaultRoot2, id, now = () => /* @__PURE__ */ new
5706
5897
  }
5707
5898
 
5708
5899
  // spex-console/src/lib/vault-sync/pull-io.ts
5709
- import { promises as fs10, readFileSync as readFileSync14 } from "node:fs";
5900
+ import { promises as fs10, readFileSync as readFileSync15 } from "node:fs";
5710
5901
  import { EOL } from "node:os";
5711
5902
  import nodePath3 from "node:path";
5712
5903
 
@@ -6352,7 +6543,7 @@ function runEntry(name, main2, options2 = {}) {
6352
6543
  // spex-console/bin/hs-vault-push.ts
6353
6544
  function readMarkerVaultId(vaultRoot2) {
6354
6545
  try {
6355
- return parseVaultId(readFileSync15(path15.join(vaultRoot2, "heyspex.yaml"), "utf8"));
6546
+ return parseVaultId(readFileSync16(path15.join(vaultRoot2, "heyspex.yaml"), "utf8"));
6356
6547
  } catch {
6357
6548
  return null;
6358
6549
  }
@@ -1107,7 +1107,7 @@ var init_interfaces = __esm({
1107
1107
  });
1108
1108
 
1109
1109
  // spex-console/src/lib/db/schema/postgres.ts
1110
- var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
1110
+ var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, seatAssignments, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
1111
1111
  var init_postgres = __esm({
1112
1112
  "spex-console/src/lib/db/schema/postgres.ts"() {
1113
1113
  "use strict";
@@ -2057,6 +2057,24 @@ var init_postgres = __esm({
2057
2057
  },
2058
2058
  (table) => [primaryKey({ columns: [table.userId, table.vaultId, table.namespace] })]
2059
2059
  );
2060
+ seatAssignments = pgTable(
2061
+ "seat_assignments",
2062
+ {
2063
+ accountId: text("account_id").notNull(),
2064
+ vaultId: text("vault_id").notNull(),
2065
+ harness: text("harness").notNull(),
2066
+ seatId: text("seat_id").notNull(),
2067
+ model: text("model").notNull(),
2068
+ // Set when a launch reported the harness REFUSED this model and the run fell
2069
+ // back to auto (Copilot's `heyspex.copilot_model_fallback` event, HS-F4UM);
2070
+ // cleared whenever the user sets a new model.
2071
+ overrideRejectedAt: timestamp("override_rejected_at", { withTimezone: true }),
2072
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().default(sql`now()`)
2073
+ },
2074
+ (table) => [
2075
+ primaryKey({ columns: [table.accountId, table.vaultId, table.harness, table.seatId] })
2076
+ ]
2077
+ );
2060
2078
  wipLedger = pgTable(
2061
2079
  "wip_ledger",
2062
2080
  {
@@ -4519,9 +4537,7 @@ async function listVaultsNeon(accountId2, deps = {}) {
4519
4537
  githubRemote: vaults.githubRemote,
4520
4538
  schemaVersion: vaults.schemaVersion,
4521
4539
  createdAt: vaults.createdAt,
4522
- archived: vaults.archived,
4523
- // [[HS-RRCG-8QDO-DIAG-a-shared-vault-is-labelled-read-only-in]] -- the grant's access level must reach the client.
4524
- access: vaultGrants.access
4540
+ archived: vaults.archived
4525
4541
  }).from(vaultGrants).innerJoin(vaults, eq(vaults.id, vaultGrants.vaultId)).where(and(eq(vaultGrants.accountId, accountId2), ne(vaults.accountId, accountId2)));
4526
4542
  const toRecord = (r, granted) => ({
4527
4543
  id: r.id,
@@ -4540,9 +4556,7 @@ async function listVaultsNeon(accountId2, deps = {}) {
4540
4556
  // `GET /api/vaults` kept listing the vault with no `archived` field.
4541
4557
  archived: r.archived,
4542
4558
  ...r.githubRemote ? { github_remote: r.githubRemote } : {},
4543
- ...granted ? { granted: true } : {},
4544
- // [[HS-RRCG-8QDO-DIAG-a-shared-vault-is-labelled-read-only-in]]: granted rows only; unknown = read.
4545
- ...granted ? { access: r.access === "write" ? "write" : "read" } : {}
4559
+ ...granted ? { granted: true } : {}
4546
4560
  });
4547
4561
  return [
4548
4562
  ...ownedRows.map((r) => toRecord(r, false)),
@@ -376,7 +376,7 @@ var init_interfaces = __esm({
376
376
  });
377
377
 
378
378
  // spex-console/src/lib/db/schema/postgres.ts
379
- var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
379
+ var edgeTypeEnum, validationSeverityEnum, outboxOpEnum, tsvector, specs, tags, edges, wikilinks, specLinks, aliases, validationIssues, indexMeta, frontmatterFields, eventOutbox, captureSessions, handoffContext, claimEventKindEnum, activeClaimStatusEnum, pendingReasonEnum, focusEventKindEnum, specEventKindEnum, focusPin, captureFocusPin, captureFocusSelection, focusSelection, focusSkips, activeClaims, claimWaiters, fileClaims, claimEvents, focusEvents, focusAreas, focusAreasEvents, specEvents, rateLimitBuckets, userVaults, milestoneSnapshots, modelRegistryEntries, launchIntentStatusEnum, launchIntents, companionRelayPresence, companionSettingsStore, consoleSettingsStore, seatAssignments, wipLedger, wipLedgerArchive, gatherResults, agentSessions, usageEvents, streamEvents, jobStreams, resolutionStatusEnum, resolutionFocusPin, resolutionLedger, workspaces, vaults, accountSettings, vaultGrants, pendingVaultGrants, githubInstallations, fleetMachines, fleetTasks, fleetResults, userApiKeys, apiKeys, mcpSessions, pairingCodes, companionSessions, inviteRequests, notifications, webhookIngestions, specSettlements, sessionBranchDeclarations;
380
380
  var init_postgres = __esm({
381
381
  "spex-console/src/lib/db/schema/postgres.ts"() {
382
382
  "use strict";
@@ -1326,6 +1326,24 @@ var init_postgres = __esm({
1326
1326
  },
1327
1327
  (table) => [primaryKey({ columns: [table.userId, table.vaultId, table.namespace] })]
1328
1328
  );
1329
+ seatAssignments = pgTable(
1330
+ "seat_assignments",
1331
+ {
1332
+ accountId: text("account_id").notNull(),
1333
+ vaultId: text("vault_id").notNull(),
1334
+ harness: text("harness").notNull(),
1335
+ seatId: text("seat_id").notNull(),
1336
+ model: text("model").notNull(),
1337
+ // Set when a launch reported the harness REFUSED this model and the run fell
1338
+ // back to auto (Copilot's `heyspex.copilot_model_fallback` event, HS-F4UM);
1339
+ // cleared whenever the user sets a new model.
1340
+ overrideRejectedAt: timestamp("override_rejected_at", { withTimezone: true }),
1341
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().default(sql`now()`)
1342
+ },
1343
+ (table) => [
1344
+ primaryKey({ columns: [table.accountId, table.vaultId, table.harness, table.seatId] })
1345
+ ]
1346
+ );
1329
1347
  wipLedger = pgTable(
1330
1348
  "wip_ledger",
1331
1349
  {