heyspex 0.1.0

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 (104) hide show
  1. package/README.md +157 -0
  2. package/bin/hs.mjs +1028 -0
  3. package/bin/postinstall.mjs +88 -0
  4. package/dev-factory/lib/fleet-readiness.mjs +274 -0
  5. package/dev-factory/lib/fleet-state.mjs +250 -0
  6. package/dev-factory/lib/sufficiency-score.mjs +441 -0
  7. package/dist/cli/hs-check.mjs +747 -0
  8. package/dist/cli/hs-claim.mjs +541 -0
  9. package/dist/cli/hs-connect.mjs +3436 -0
  10. package/dist/cli/hs-defer.mjs +8455 -0
  11. package/dist/cli/hs-env.mjs +36845 -0
  12. package/dist/cli/hs-init.mjs +1145 -0
  13. package/dist/cli/hs-install.mjs +1390 -0
  14. package/dist/cli/hs-join.mjs +38553 -0
  15. package/dist/cli/hs-vault-pull.mjs +7782 -0
  16. package/dist/cli/hs-vault.mjs +4166 -0
  17. package/dist/cli/hs-workspace.mjs +2898 -0
  18. package/dist/cli-templates/customer-vault/specs/entity-model.md +1735 -0
  19. package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +531 -0
  20. package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +48 -0
  21. package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +548 -0
  22. package/docs/mcp-client-config.md +200 -0
  23. package/package.json +124 -0
  24. package/scripts/check-mcp-freshness.mjs +379 -0
  25. package/scripts/dev-server-status.mjs +722 -0
  26. package/scripts/gateway-skills-lib.mjs +421 -0
  27. package/scripts/lib/console-url-resolver.mjs +152 -0
  28. package/scripts/lib/dev-mode.mjs +369 -0
  29. package/scripts/lib/hs-link.mjs +154 -0
  30. package/scripts/lib/hs-source-commit.mjs +207 -0
  31. package/scripts/lib/hs-source-log.mjs +366 -0
  32. package/scripts/lib/hs-source-preflight.mjs +402 -0
  33. package/scripts/lib/hs-source-push.mjs +307 -0
  34. package/scripts/lib/hs-source-secret-scan.mjs +263 -0
  35. package/scripts/lib/standalone-doctor.mjs +89 -0
  36. package/scripts/vault-id-lib.mjs +173 -0
  37. package/vendor/heyspex-mcp/alignment-gate.mjs +1347 -0
  38. package/vendor/heyspex-mcp/blocker-resolver.mjs +891 -0
  39. package/vendor/heyspex-mcp/breakdown-ladder.mjs +309 -0
  40. package/vendor/heyspex-mcp/breakdown-source.mjs +236 -0
  41. package/vendor/heyspex-mcp/breakdown-writer.mjs +524 -0
  42. package/vendor/heyspex-mcp/brief-observability.mjs +67 -0
  43. package/vendor/heyspex-mcp/canvas-inbox.mjs +130 -0
  44. package/vendor/heyspex-mcp/capture-active-rehydrate.mjs +76 -0
  45. package/vendor/heyspex-mcp/capture-buffer-patches.mjs +147 -0
  46. package/vendor/heyspex-mcp/capture-channel.mjs +577 -0
  47. package/vendor/heyspex-mcp/capture-console-client.mjs +290 -0
  48. package/vendor/heyspex-mcp/capture-focus.mjs +1366 -0
  49. package/vendor/heyspex-mcp/capture-kind-suggest.mjs +121 -0
  50. package/vendor/heyspex-mcp/capture-outbox.mjs +239 -0
  51. package/vendor/heyspex-mcp/capture-stage-gate.mjs +160 -0
  52. package/vendor/heyspex-mcp/cli.mjs +15754 -0
  53. package/vendor/heyspex-mcp/config-settings.mjs +419 -0
  54. package/vendor/heyspex-mcp/console-fetch.mjs +342 -0
  55. package/vendor/heyspex-mcp/defer-spec.mjs +527 -0
  56. package/vendor/heyspex-mcp/deferred-reclaim.mjs +284 -0
  57. package/vendor/heyspex-mcp/design-tools.mjs +449 -0
  58. package/vendor/heyspex-mcp/dev-factory-bridge.mjs +66 -0
  59. package/vendor/heyspex-mcp/diagnose-skeleton.mjs +114 -0
  60. package/vendor/heyspex-mcp/fleet-queue-tools.mjs +241 -0
  61. package/vendor/heyspex-mcp/hooks-engine.mjs +399 -0
  62. package/vendor/heyspex-mcp/login-handoff.mjs +438 -0
  63. package/vendor/heyspex-mcp/mcp-subprocess.mjs +136 -0
  64. package/vendor/heyspex-mcp/mvp-mode.mjs +101 -0
  65. package/vendor/heyspex-mcp/next-resolution.mjs +240 -0
  66. package/vendor/heyspex-mcp/next-review.mjs +548 -0
  67. package/vendor/heyspex-mcp/peer-presence.mjs +430 -0
  68. package/vendor/heyspex-mcp/plan-ground-conflicts.mjs +321 -0
  69. package/vendor/heyspex-mcp/plan-ground.mjs +2001 -0
  70. package/vendor/heyspex-mcp/plan-ingest.mjs +476 -0
  71. package/vendor/heyspex-mcp/plan-push.mjs +1826 -0
  72. package/vendor/heyspex-mcp/plan-review.mjs +1419 -0
  73. package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
  74. package/vendor/heyspex-mcp/procedure-tools.mjs +970 -0
  75. package/vendor/heyspex-mcp/promote-capture.mjs +1280 -0
  76. package/vendor/heyspex-mcp/promote-diagnose.mjs +187 -0
  77. package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +338 -0
  78. package/vendor/heyspex-mcp/promote-research.mjs +1553 -0
  79. package/vendor/heyspex-mcp/promotion-helpers.mjs +280 -0
  80. package/vendor/heyspex-mcp/read-path.mjs +138 -0
  81. package/vendor/heyspex-mcp/recipe-tools.mjs +258 -0
  82. package/vendor/heyspex-mcp/regen-tasks.mjs +326 -0
  83. package/vendor/heyspex-mcp/register-spec.mjs +417 -0
  84. package/vendor/heyspex-mcp/research-focus.mjs +476 -0
  85. package/vendor/heyspex-mcp/research-tools.mjs +109 -0
  86. package/vendor/heyspex-mcp/research-type-suggest.mjs +112 -0
  87. package/vendor/heyspex-mcp/resolution-focus.mjs +45 -0
  88. package/vendor/heyspex-mcp/review-focus.mjs +55 -0
  89. package/vendor/heyspex-mcp/review-rollup.mjs +47 -0
  90. package/vendor/heyspex-mcp/run-front.mjs +60 -0
  91. package/vendor/heyspex-mcp/scheduler.mjs +813 -0
  92. package/vendor/heyspex-mcp/seat-identity.mjs +281 -0
  93. package/vendor/heyspex-mcp/settings-tools.mjs +119 -0
  94. package/vendor/heyspex-mcp/spec-id-lib.mjs +790 -0
  95. package/vendor/heyspex-mcp/split-research.mjs +832 -0
  96. package/vendor/heyspex-mcp/submit-for-review.mjs +190 -0
  97. package/vendor/heyspex-mcp/summary-contract.mjs +162 -0
  98. package/vendor/heyspex-mcp/test-helpers.mjs +43 -0
  99. package/vendor/heyspex-mcp/validate-linkage.mjs +574 -0
  100. package/vendor/heyspex-mcp/vault-resolve.mjs +297 -0
  101. package/vendor/heyspex-mcp/vault-settings.mjs +87 -0
  102. package/vendor/heyspex-mcp/verify-reproduction.mjs +314 -0
  103. package/vendor/heyspex-mcp/why-now.mjs +64 -0
  104. package/vendor/heyspex-mcp/yaml-scalar.mjs +34 -0
@@ -0,0 +1,541 @@
1
+ import { createRequire as __hsCreateRequire } from 'node:module';
2
+ const require = __hsCreateRequire(import.meta.url);
3
+
4
+ // spex-console/bin/hs-claim.ts
5
+ import { readFileSync as readFileSync4 } from "node:fs";
6
+ import process2 from "node:process";
7
+
8
+ // spex-console/src/lib/work-stages.ts
9
+ var WORK_STAGES = [
10
+ "Grounding",
11
+ "Breaking down",
12
+ "Drafting",
13
+ "Validating",
14
+ "Testing",
15
+ "Reviewing"
16
+ ];
17
+ function isWorkStage(value) {
18
+ return typeof value === "string" && WORK_STAGES.includes(value);
19
+ }
20
+
21
+ // spex-console/src/lib/cli/agent-identity.ts
22
+ function resolveAgentIdentity(argAgent, argSession) {
23
+ const trimmedAgent = typeof argAgent === "string" ? argAgent.trim() : "";
24
+ const trimmedSession = typeof argSession === "string" ? argSession.trim() : "";
25
+ if (trimmedAgent && trimmedSession) {
26
+ return { agent: trimmedAgent, sessionId: trimmedSession };
27
+ }
28
+ const claudeSession = (process.env.CLAUDE_CODE_SESSION_ID || process.env.CLAUDE_SESSION_ID || "").trim();
29
+ if (claudeSession) {
30
+ return {
31
+ agent: trimmedAgent || "Claude Code",
32
+ sessionId: trimmedSession || claudeSession
33
+ };
34
+ }
35
+ const cursorSession = (process.env.CURSOR_TRACE_ID || process.env.CURSOR_SESSION_ID || process.env.COMPOSER_SESSION_ID || "").trim();
36
+ if (cursorSession) {
37
+ return {
38
+ agent: trimmedAgent || "Cursor",
39
+ sessionId: trimmedSession || cursorSession
40
+ };
41
+ }
42
+ return {
43
+ agent: trimmedAgent || process.env.HEYSPEX_AGENT?.trim() || "External agent",
44
+ sessionId: trimmedSession || process.env.HEYSPEX_SESSION_ID?.trim() || process.env.HEYSPEX_AGENT_SESSION?.trim() || "local"
45
+ };
46
+ }
47
+
48
+ // spex-console/src/lib/cli/control-shared.ts
49
+ import { readFileSync } from "node:fs";
50
+ import { homedir } from "node:os";
51
+ import { join } from "node:path";
52
+ var EXIT = {
53
+ /** Success. */
54
+ OK: 0,
55
+ /** Usage error — unknown subcommand/flag, or a missing required argument. */
56
+ USAGE: 1,
57
+ /** Claim race lost — the spec is already claimed by another agent (409). */
58
+ CONFLICT: 9,
59
+ /** Target not found — release of an unheld claim, etc. (404). */
60
+ NOT_FOUND: 10,
61
+ /** Invalid argument — bad `--ttl`, unknown `--kind`, unparseable date. */
62
+ INVALID_ARG: 11,
63
+ /** SpexConsole unreachable (network error reaching the dev server). */
64
+ CONSOLE_UNREACHABLE: 12,
65
+ /** Server failure — a non-OK response that isn't 400/402/404/409. */
66
+ WRITE_FAILED: 13,
67
+ /** Neon control plane absent — the endpoint returned 402 (upgrade required). */
68
+ NEON_REQUIRED: 14,
69
+ /**
70
+ * A cross-account `--account <id>` override was refused — the caller is
71
+ * not a builder/admin (`resolveScopedAccountId`'s `forbidden_account_override`,
72
+ * [[HS-X3GB-EKLJ-TASK-hs-cli-account-flag-for-cross-account]]). Distinct
73
+ * from `USAGE`/`INVALID_ARG` — this is an authorization refusal, not a
74
+ * malformed command.
75
+ */
76
+ FORBIDDEN: 15
77
+ };
78
+ function readPairedToken() {
79
+ try {
80
+ const raw = readFileSync(join(homedir(), ".heyspex", "credentials.yaml"), "utf-8");
81
+ const match = raw.match(/\b(hsmcp_[A-Za-z0-9_-]+|hscmp_[A-Za-z0-9_-]+)\b/);
82
+ return match?.[1];
83
+ } catch {
84
+ return void 0;
85
+ }
86
+ }
87
+ function bearerHeaderFor(consoleUrl) {
88
+ try {
89
+ const host = new URL(consoleUrl).hostname;
90
+ if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1") {
91
+ return {};
92
+ }
93
+ const token = readPairedToken();
94
+ return token ? { authorization: `Bearer ${token}` } : {};
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
99
+ async function controlRequest(deps, req) {
100
+ const url = `${deps.consoleUrl}${req.path}`;
101
+ let res;
102
+ try {
103
+ res = await deps.fetchImpl(url, {
104
+ method: req.method,
105
+ headers: {
106
+ "content-type": "application/json",
107
+ // A remote console (cloud vault write mode, [[HS-PH7O-ILNC-DIAG-hs-check-report-cannot-authenticate]])
108
+ // authenticates every control call; loopback never asked, so the header
109
+ // is attached only when the target is non-loopback and a paired token
110
+ // exists. Same `hsmcp_` bearer the MCP server presents.
111
+ ...bearerHeaderFor(deps.consoleUrl)
112
+ },
113
+ ...req.body !== void 0 ? { body: JSON.stringify(req.body) } : {}
114
+ });
115
+ } catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ return {
118
+ ok: false,
119
+ error: {
120
+ code: EXIT.CONSOLE_UNREACHABLE,
121
+ message: `could not reach console at ${deps.consoleUrl}: ${message}
122
+ Start the dev server or set HEYSPEX_CONSOLE_URL.`,
123
+ payload: {
124
+ ok: false,
125
+ error: "console_unreachable",
126
+ url: deps.consoleUrl,
127
+ message
128
+ },
129
+ isError: true
130
+ }
131
+ };
132
+ }
133
+ let parsed = null;
134
+ try {
135
+ parsed = await res.json();
136
+ } catch {
137
+ }
138
+ if (res.status === 402) {
139
+ return {
140
+ ok: false,
141
+ error: {
142
+ code: EXIT.NEON_REQUIRED,
143
+ message: "this command requires the Neon control plane, which is not configured (console returned 402).",
144
+ payload: {
145
+ ok: false,
146
+ error: "neon_required",
147
+ status: 402,
148
+ console_response: parsed
149
+ },
150
+ isError: true
151
+ }
152
+ };
153
+ }
154
+ return { ok: true, status: res.status, body: parsed };
155
+ }
156
+ function envelopeMessage(body) {
157
+ if (body && typeof body === "object" && "message" in body) {
158
+ const m = body.message;
159
+ if (typeof m === "string") return m;
160
+ }
161
+ return "";
162
+ }
163
+ function writeFailed(status, body) {
164
+ const detail = envelopeMessage(body);
165
+ return {
166
+ code: EXIT.WRITE_FAILED,
167
+ message: `console returned ${status}${detail ? `: ${detail}` : ""}`,
168
+ payload: { ok: false, error: "write_failed", status, console_response: body },
169
+ isError: true
170
+ };
171
+ }
172
+ function parseTtl(token) {
173
+ const trimmed = token.trim().toLowerCase();
174
+ if (trimmed === "none" || trimmed === "null") return { value: null };
175
+ const n = Number(trimmed);
176
+ if (!Number.isFinite(n) || n < 0) {
177
+ return { error: `--ttl must be a non-negative number of ms, or "none"` };
178
+ }
179
+ return { value: n };
180
+ }
181
+
182
+ // spex-console/src/lib/cli/claim.ts
183
+ function parseClaimArgs(argv) {
184
+ const subRaw = argv[0];
185
+ if (subRaw === "--help" || subRaw === "-h" || subRaw === void 0) {
186
+ return { help: true };
187
+ }
188
+ if (subRaw !== "acquire" && subRaw !== "release" && subRaw !== "list" && subRaw !== "progress") {
189
+ return {
190
+ error: { code: EXIT.USAGE, message: `unknown subcommand: ${subRaw}` }
191
+ };
192
+ }
193
+ const sub = subRaw;
194
+ const takesSpecId = sub === "acquire" || sub === "release" || sub === "progress";
195
+ let specId;
196
+ let agent;
197
+ let session;
198
+ let agentProfile;
199
+ let ttlMs;
200
+ let stage;
201
+ let acProgress;
202
+ let json = false;
203
+ for (let i = 1; i < argv.length; i++) {
204
+ const arg = argv[i];
205
+ if (arg === "--help" || arg === "-h") return { help: true };
206
+ else if (arg === "--json") json = true;
207
+ else if (arg === "--agent" || arg.startsWith("--agent=")) {
208
+ const v = arg === "--agent" ? argv[++i] : arg.slice("--agent=".length);
209
+ if (v === void 0) {
210
+ return { error: { code: EXIT.INVALID_ARG, message: "--agent requires a value" } };
211
+ }
212
+ agent = v;
213
+ } else if (arg === "--session" || arg.startsWith("--session=")) {
214
+ const v = arg === "--session" ? argv[++i] : arg.slice("--session=".length);
215
+ if (v === void 0) {
216
+ return { error: { code: EXIT.INVALID_ARG, message: "--session requires a value" } };
217
+ }
218
+ session = v;
219
+ } else if (arg === "--agent-profile" || arg.startsWith("--agent-profile=")) {
220
+ const v = arg === "--agent-profile" ? argv[++i] : arg.slice("--agent-profile=".length);
221
+ if (v === void 0) {
222
+ return { error: { code: EXIT.INVALID_ARG, message: "--agent-profile requires a value" } };
223
+ }
224
+ agentProfile = v;
225
+ } else if (arg === "--ttl" || arg.startsWith("--ttl=")) {
226
+ const token = arg === "--ttl" ? argv[++i] : arg.slice("--ttl=".length);
227
+ if (token === void 0) {
228
+ return { error: { code: EXIT.INVALID_ARG, message: "--ttl requires a value" } };
229
+ }
230
+ const res = parseTtl(token);
231
+ if ("error" in res) {
232
+ return { error: { code: EXIT.INVALID_ARG, message: res.error } };
233
+ }
234
+ ttlMs = res.value;
235
+ } else if (arg === "--stage" || arg.startsWith("--stage=")) {
236
+ const v = arg === "--stage" ? argv[++i] : arg.slice("--stage=".length);
237
+ if (v === void 0) {
238
+ return { error: { code: EXIT.INVALID_ARG, message: "--stage requires a value" } };
239
+ }
240
+ stage = v;
241
+ } else if (arg === "--ac" || arg.startsWith("--ac=")) {
242
+ const v = arg === "--ac" ? argv[++i] : arg.slice("--ac=".length);
243
+ if (v === void 0) {
244
+ return { error: { code: EXIT.INVALID_ARG, message: "--ac requires a value" } };
245
+ }
246
+ const n = Number(v);
247
+ if (!Number.isInteger(n) || n < 0) {
248
+ return {
249
+ error: { code: EXIT.INVALID_ARG, message: "--ac must be a non-negative integer" }
250
+ };
251
+ }
252
+ acProgress = n;
253
+ } else if (arg.startsWith("-")) {
254
+ return { error: { code: EXIT.USAGE, message: `unknown flag: ${arg}` } };
255
+ } else if (takesSpecId && specId === void 0) {
256
+ specId = arg;
257
+ } else {
258
+ return { error: { code: EXIT.USAGE, message: `unexpected argument: ${arg}` } };
259
+ }
260
+ }
261
+ if (takesSpecId && !specId) {
262
+ return { error: { code: EXIT.USAGE, message: `claim ${sub} requires a spec id` } };
263
+ }
264
+ if (sub === "progress") {
265
+ if (!stage) {
266
+ return { error: { code: EXIT.USAGE, message: "claim progress requires --stage <verb>" } };
267
+ }
268
+ if (!isWorkStage(stage)) {
269
+ return {
270
+ error: {
271
+ code: EXIT.INVALID_ARG,
272
+ message: `--stage must be one of: ${WORK_STAGES.join(", ")}`
273
+ }
274
+ };
275
+ }
276
+ }
277
+ return { args: { sub, specId, agent, session, agentProfile, ttlMs, stage, acProgress, json } };
278
+ }
279
+ function describeClaim(c) {
280
+ if (!c) return "(unknown claim)";
281
+ return `${c.agentId ?? "?"}${c.sessionId ? ` \xB7 ${c.sessionId}` : ""}`;
282
+ }
283
+ async function runClaim(args, deps) {
284
+ if (args.sub === "list") {
285
+ const res2 = await controlRequest(deps, { method: "GET", path: "/api/control/active-claims" });
286
+ if (!res2.ok) return res2.error;
287
+ if (res2.status !== 200) return writeFailed(res2.status, res2.body);
288
+ const claims = res2.body?.claims ?? [];
289
+ const lines = claims.length ? claims.map((c) => ` ${c.specId ?? "?"} \u2014 ${describeClaim(c)}`).join("\n") : " (no active claims)";
290
+ return {
291
+ code: EXIT.OK,
292
+ message: `${claims.length} active claim(s):
293
+ ${lines}`,
294
+ payload: { ok: true, claims },
295
+ isError: false
296
+ };
297
+ }
298
+ if (args.sub === "progress") {
299
+ const res2 = await controlRequest(deps, {
300
+ method: "PATCH",
301
+ path: `/api/specs/${encodeURIComponent(args.specId)}/claim/status`,
302
+ body: {
303
+ status: "working",
304
+ currentStage: args.stage,
305
+ ...args.acProgress !== void 0 ? { acProgress: args.acProgress } : {}
306
+ }
307
+ });
308
+ if (!res2.ok) return res2.error;
309
+ if (res2.status === 200) {
310
+ const acSuffix = args.acProgress !== void 0 ? ` (criterion ${args.acProgress})` : "";
311
+ return {
312
+ code: EXIT.OK,
313
+ message: `${args.specId}: ${args.stage}${acSuffix}`,
314
+ payload: {
315
+ ok: true,
316
+ spec_id: args.specId,
317
+ current_stage: args.stage,
318
+ ac_progress: args.acProgress
319
+ },
320
+ isError: false
321
+ };
322
+ }
323
+ if (res2.status === 404) {
324
+ return {
325
+ code: EXIT.NOT_FOUND,
326
+ message: `no active claim for ${args.specId} \u2014 claim it first (hs claim acquire ${args.specId})`,
327
+ payload: { ok: false, error: "not_found" },
328
+ isError: true
329
+ };
330
+ }
331
+ if (res2.status === 400) {
332
+ const detail = envelopeMessage(res2.body);
333
+ return {
334
+ code: EXIT.INVALID_ARG,
335
+ message: `console rejected the request${detail ? `: ${detail}` : ""}`,
336
+ payload: { ok: false, error: "invalid_request", message: detail },
337
+ isError: true
338
+ };
339
+ }
340
+ return writeFailed(res2.status, res2.body);
341
+ }
342
+ if (args.sub === "acquire") {
343
+ const id = resolveAgentIdentity(args.agent, args.session);
344
+ const res2 = await controlRequest(deps, {
345
+ method: "POST",
346
+ path: "/api/control/active-claims/acquire",
347
+ body: {
348
+ spec_id: args.specId,
349
+ agent: id.agent,
350
+ session_id: id.sessionId,
351
+ ...args.agentProfile ? { agent_profile: args.agentProfile } : {},
352
+ ...args.ttlMs !== void 0 ? { ttl_ms: args.ttlMs } : {}
353
+ }
354
+ });
355
+ if (!res2.ok) return res2.error;
356
+ if (res2.status === 200) {
357
+ const claim = res2.body?.claim;
358
+ return {
359
+ code: EXIT.OK,
360
+ message: `Claimed ${args.specId} as ${id.agent}`,
361
+ payload: { ok: true, acquired: true, claim },
362
+ isError: false
363
+ };
364
+ }
365
+ if (res2.status === 409) {
366
+ const existing = res2.body?.existing;
367
+ return {
368
+ code: EXIT.CONFLICT,
369
+ message: `${args.specId} is already claimed by ${describeClaim(existing)}`,
370
+ payload: { ok: false, acquired: false, error: "already_claimed", existing },
371
+ isError: true
372
+ };
373
+ }
374
+ if (res2.status === 400) {
375
+ const detail = envelopeMessage(res2.body);
376
+ return {
377
+ code: EXIT.INVALID_ARG,
378
+ message: `console rejected the request${detail ? `: ${detail}` : ""}`,
379
+ payload: { ok: false, error: "invalid_request", message: detail },
380
+ isError: true
381
+ };
382
+ }
383
+ return writeFailed(res2.status, res2.body);
384
+ }
385
+ const releaser = resolveAgentIdentity(args.agent, args.session).agent;
386
+ const path2 = `/api/control/active-claims/${encodeURIComponent(args.specId)}?agent=${encodeURIComponent(releaser)}`;
387
+ const res = await controlRequest(deps, { method: "DELETE", path: path2 });
388
+ if (!res.ok) return res.error;
389
+ if (res.status === 200) {
390
+ return {
391
+ code: EXIT.OK,
392
+ message: `Released ${args.specId}`,
393
+ payload: { ok: true, released: true },
394
+ isError: false
395
+ };
396
+ }
397
+ if (res.status === 404) {
398
+ return {
399
+ code: EXIT.NOT_FOUND,
400
+ message: `no active claim for ${args.specId}`,
401
+ payload: { ok: false, released: false, error: "not_found" },
402
+ isError: true
403
+ };
404
+ }
405
+ if (res.status === 400) {
406
+ const detail = envelopeMessage(res.body);
407
+ return {
408
+ code: EXIT.INVALID_ARG,
409
+ message: `console rejected the request${detail ? `: ${detail}` : ""}`,
410
+ payload: { ok: false, error: "invalid_request", message: detail },
411
+ isError: true
412
+ };
413
+ }
414
+ return writeFailed(res.status, res.body);
415
+ }
416
+ var HELP = [
417
+ "Usage:",
418
+ " hs claim acquire <spec-id> [flags] Atomically claim a spec",
419
+ " hs claim release <spec-id> [flags] Release a claim",
420
+ " hs claim list [--json] List all active claims",
421
+ " hs claim progress <spec-id> --stage <verb> [--ac <n>]",
422
+ " Report the current work-stage of a claim",
423
+ "",
424
+ "Wraps the /api/control/active-claims endpoints (Neon control plane). Acquire",
425
+ "is atomic: two concurrent acquires for one spec yield exactly one success and",
426
+ "one `already_claimed` conflict (exit 9). `progress` PATCHes the claim status",
427
+ "route so the live verb shows on the What's Next surface.",
428
+ "",
429
+ "Flags:",
430
+ " --agent <name> Claiming agent (default: detected identity).",
431
+ " --session <id> Session id (default: detected identity).",
432
+ " --agent-profile <slug> Bind the claim to an agent identity (acquire only).",
433
+ " --ttl <ms|none> Claim fuse in ms; `none` = no fuse (acquire only).",
434
+ " --stage <verb> Work-stage verb for `progress` (Grounding, Breaking",
435
+ " down, Drafting, Validating, Testing, Reviewing).",
436
+ " --ac <n> 0-based acceptance-criterion index for `progress`.",
437
+ " --json Emit a machine-readable JSON result.",
438
+ " --help, -h Show this help.",
439
+ "",
440
+ "Exit codes: 0 ok \xB7 1 usage \xB7 9 already-claimed \xB7 10 not-found \xB7 11 bad-arg \xB7",
441
+ " 12 console-unreachable \xB7 13 server-error \xB7 14 neon-required"
442
+ ].join("\n");
443
+
444
+ // spex-console/src/lib/cli/defer.ts
445
+ import { readFileSync as readFileSync3 } from "node:fs";
446
+ import os from "node:os";
447
+ import path from "node:path";
448
+
449
+ // scripts/lib/console-url-resolver.mjs
450
+ import { readFileSync as readFileSync2 } from "node:fs";
451
+ import { homedir as homedir2 } from "node:os";
452
+ import { join as join2 } from "node:path";
453
+ var DEFAULT_CONSOLE_URL = "https://app.heyspex.com";
454
+
455
+ // spex-console/src/lib/cli/defer.ts
456
+ function resolveSpexConsoleUrl(vaultRoot, readDiscovery2, opts = {}) {
457
+ const env = opts.env ?? process.env;
458
+ const raw = (env.HEYSPEX_CONSOLE_URL ?? env.HEYSPEX_COCKPIT_URL)?.trim();
459
+ if (raw && raw.length > 0) return raw.replace(/\/+$/, "");
460
+ try {
461
+ const homeDir = opts.homeDir ?? os.homedir();
462
+ const readFile = opts.readFile ?? ((p) => readFileSync3(p, "utf-8"));
463
+ const credentials = readFile(path.join(homeDir, ".heyspex", "credentials.yaml"));
464
+ const match = credentials.match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
465
+ const value = match?.[1]?.trim();
466
+ if (value && value.length > 0) return value.replace(/\/+$/, "");
467
+ } catch {
468
+ }
469
+ for (const name of ["console-url", "cockpit-url"]) {
470
+ try {
471
+ const discovery = readDiscovery2(`${vaultRoot}/.heyspex/${name}`);
472
+ if (discovery && discovery.trim().length > 0) {
473
+ return discovery.trim().replace(/\/+$/, "");
474
+ }
475
+ } catch {
476
+ }
477
+ }
478
+ return DEFAULT_CONSOLE_URL;
479
+ }
480
+ var HELP2 = [
481
+ "Usage:",
482
+ " hs defer <spec-id> [--until <when>] [--json] [--no-daemon]",
483
+ " hs undefer <spec-id> [--json] [--no-daemon]",
484
+ "",
485
+ "Time-shift a spec via its `defer_until` frontmatter field. A deferred spec is",
486
+ "scheduler-invisible until the date passes, then auto-revives.",
487
+ "",
488
+ "Arguments:",
489
+ " <spec-id> The spec to defer/undefer (e.g. HS-TASK-EWL5).",
490
+ "",
491
+ "Flags:",
492
+ " --until <when> ISO date, or relative offset +Nd / +Nw / +Nm / +Ny.",
493
+ " Defaults to +1y (matches the console Defer button).",
494
+ " Ignored by `undefer` (which always clears the field).",
495
+ " --json Emit a machine-readable JSON result.",
496
+ " --no-daemon Write straight to the vault (no console needed; CI path).",
497
+ " --help, -h Show this help.",
498
+ "",
499
+ "Exit codes: 0 ok \xB7 1 usage \xB7 10 not-found \xB7 11 bad-arg \xB7 12 console-unreachable \xB7 13 write-failed"
500
+ ].join("\n");
501
+
502
+ // spex-console/bin/hs-claim.ts
503
+ function readDiscovery(p) {
504
+ try {
505
+ return readFileSync4(p, "utf-8");
506
+ } catch {
507
+ return null;
508
+ }
509
+ }
510
+ async function main() {
511
+ const vaultRoot = process2.env.HS_VAULT_ROOT;
512
+ if (!vaultRoot) {
513
+ process2.stderr.write(
514
+ "hs claim: HS_VAULT_ROOT is not set (the dispatcher must resolve and pass it).\n"
515
+ );
516
+ process2.exit(EXIT.USAGE);
517
+ }
518
+ const parsed = parseClaimArgs(process2.argv.slice(3));
519
+ if (parsed.help) {
520
+ process2.stdout.write(`${HELP}
521
+ `);
522
+ process2.exit(EXIT.OK);
523
+ }
524
+ if (parsed.error) {
525
+ process2.stderr.write(`hs claim: ${parsed.error.message}
526
+ `);
527
+ process2.exit(parsed.error.code);
528
+ }
529
+ const consoleUrl = resolveSpexConsoleUrl(vaultRoot, readDiscovery);
530
+ const result = await runClaim(parsed.args, { fetchImpl: fetch, consoleUrl });
531
+ const sink = result.isError ? process2.stderr : process2.stdout;
532
+ sink.write(`${parsed.args.json ? JSON.stringify(result.payload) : result.message}
533
+ `);
534
+ process2.exit(result.code);
535
+ }
536
+ main().catch((err) => {
537
+ const message = err instanceof Error ? err.message : String(err);
538
+ process2.stderr.write(`hs claim: unhandled: ${message}
539
+ `);
540
+ process2.exit(EXIT.WRITE_FAILED);
541
+ });