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,290 @@
1
+ /**
2
+ * Console capture-session client — extracted from cli.mjs so the error
3
+ * classification and outbox drain logic are unit-testable without starting
4
+ * the full MCP server (which begins reading stdin + spawns the
5
+ * capture-channel watcher on import).
6
+ *
7
+ * Owning epic: [[HS-EPIC-CPMR-capture-session-patch-materialized-rows-reliability]].
8
+ *
9
+ * Design: a factory `makeCaptureConsoleClient(consoleFetch)` returns a bound
10
+ * client. cli.mjs creates one at module load with its real `consoleFetch`;
11
+ * tests create one with a stub. No module-level side effects on import.
12
+ */
13
+
14
+ import {
15
+ listOutbox,
16
+ outboxCount,
17
+ outboxEntryToPatch,
18
+ quarantineOutboxEntry,
19
+ removeOutboxEntry,
20
+ stashCaptureArgs,
21
+ } from "./capture-outbox.mjs";
22
+
23
+ /**
24
+ * Error taxonomy for capture-session console calls ([[HS-K27H-DIAG-0G86]]).
25
+ *
26
+ * - `CONSOLE_UNREACHABLE` (status === 0): genuine TRANSPORT failure —
27
+ * ECONNREFUSED / DNS / timeout / abort (console-fetch stamps status 0 on
28
+ * all of these; an HTTP response the server returned always has status > 0).
29
+ * The ONLY class that touches the offline buffer: stash, the next drain
30
+ * replays.
31
+ * - `CAPTURE_CONFLICT` (4xx): the server ANSWERED and structurally refused
32
+ * (abandoned row, validation failure, malformed body). A client error that
33
+ * will fail identically on retry. NEVER stash; surface the server's
34
+ * error + message verbatim; quarantine on drain.
35
+ * - `PATCH_FAILED` (5xx or other): the server ANSWERED and errored applying
36
+ * the write. NEVER stash — pre-K27H this was "stashed like UNREACHABLE",
37
+ * which reported real server failures as connectivity blips with a false
38
+ * "your words are saved and will sync" promise. Surface the server's
39
+ * message verbatim. (Entries ALREADY in the outbox from a genuine
40
+ * transport failure still pause the drain on 5xx and retry later — the
41
+ * ban is on adding new entries for an answered request.)
42
+ */
43
+ export const CAPTURE_ERROR_UNREACHABLE = "CONSOLE_UNREACHABLE";
44
+ export const CAPTURE_ERROR_CONFLICT = "CAPTURE_CONFLICT";
45
+ export const CAPTURE_ERROR_FAILED = "PATCH_FAILED";
46
+
47
+ /** Classify a `consoleFetch` result by HTTP status range. */
48
+ export function classifyConsoleFetchError(result) {
49
+ if (result.status === 0) return CAPTURE_ERROR_UNREACHABLE;
50
+ if (result.status >= 400 && result.status < 500)
51
+ return CAPTURE_ERROR_CONFLICT;
52
+ return CAPTURE_ERROR_FAILED;
53
+ }
54
+
55
+ /** Extract the console's error message from a failed `consoleFetch` result. */
56
+ export function extractConsoleMessage(result) {
57
+ return result?.body?.message || result?.body?.error || null;
58
+ }
59
+
60
+ /**
61
+ * Classify a failed capture write and build the honest description of what
62
+ * happened ([[HS-K27H-DIAG-0G86]] AC1-AC5). Pure — no fs, no HTTP.
63
+ *
64
+ * Accepts either failure shape used on the capture surface:
65
+ * - patchCaptureSession failures: `{ ok:false, error, status, consoleMessage, result }`
66
+ * - list/create helper failures: `{ ok:false, error, result }` (status on `result`)
67
+ *
68
+ * @returns {{
69
+ * kind: 'transport'|'client'|'server',
70
+ * shouldStash: boolean,
71
+ * error: string,
72
+ * http_status: number,
73
+ * server_error_code: string|null,
74
+ * server_message: string|null,
75
+ * message: string|null,
76
+ * }}
77
+ */
78
+ export function describeCaptureWriteFailure(failure) {
79
+ const result = failure?.result ?? failure ?? {};
80
+ const status =
81
+ typeof failure?.status === "number"
82
+ ? failure.status
83
+ : typeof result?.status === "number"
84
+ ? result.status
85
+ : 0;
86
+ const serverCode = typeof result?.body?.error === "string" ? result.body.error : null;
87
+ const serverMessage = typeof result?.body?.message === "string" ? result.body.message : null;
88
+
89
+ // status 0 ⇔ genuine transport failure (ECONNREFUSED / DNS / timeout /
90
+ // abort — console-fetch stamps status 0 on every non-response). Anything
91
+ // else is an HTTP response the server actually returned.
92
+ if (status === 0) {
93
+ return {
94
+ kind: "transport",
95
+ shouldStash: true,
96
+ error: failure?.error || CAPTURE_ERROR_UNREACHABLE,
97
+ http_status: 0,
98
+ server_error_code: null,
99
+ server_message: null,
100
+ message: null, // the responder composes the transport message (depends on what stashed)
101
+ };
102
+ }
103
+
104
+ const label = `HTTP ${status}${serverCode ? ` ${serverCode}` : ""}`;
105
+ const verbatim = serverMessage ? `: ${serverMessage}` : "";
106
+ if (status >= 400 && status < 500) {
107
+ return {
108
+ kind: "client",
109
+ shouldStash: false,
110
+ error: CAPTURE_ERROR_CONFLICT,
111
+ http_status: status,
112
+ server_error_code: serverCode,
113
+ server_message: serverMessage,
114
+ message:
115
+ `The server rejected this write (${label})${verbatim}. ` +
116
+ `The console answered — it is not down. This write was NOT saved and will NOT retry automatically; fix the request and send it again.`,
117
+ };
118
+ }
119
+ return {
120
+ kind: "server",
121
+ shouldStash: false,
122
+ error: CAPTURE_ERROR_FAILED,
123
+ http_status: status,
124
+ server_error_code: serverCode,
125
+ server_message: serverMessage,
126
+ message:
127
+ `The server failed to apply this write (${label})${verbatim}. ` +
128
+ `The console answered — it is not down; the write errored server-side. This write was NOT saved and will NOT retry automatically; check the server logs and send it again.`,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Build the tool-facing payload for a failed capture write, performing the
134
+ * offline-buffer stash ONLY for genuine transport failures (AC1). This is the
135
+ * single buffering decision point for the capture write path — extracted here
136
+ * (rather than living in cli.mjs's handler) so the stash-vs-surface rule is
137
+ * unit-testable against a real tmp outbox.
138
+ *
139
+ * - transport → stash `args`' content fields; the "will sync automatically"
140
+ * promise appears ONLY when something was actually written to the outbox
141
+ * (AC4) — the drain path replays every OUTBOX_KINDS field in canonical
142
+ * structured shape.
143
+ * - 4xx → `status: "refused"` + the server's error/message verbatim (AC2/AC5).
144
+ * - 5xx → `status: "failed"` + the server's message verbatim (AC3/AC5).
145
+ */
146
+ export function respondToCaptureWriteFailure(vaultRoot, args, failure) {
147
+ const d = describeCaptureWriteFailure(failure);
148
+ if (d.kind === "transport") {
149
+ const stashed = stashCaptureArgs(vaultRoot, args || {});
150
+ const pending = outboxCount(vaultRoot);
151
+ return {
152
+ ok: false,
153
+ status: "deferred",
154
+ error: d.error,
155
+ source: "offline-buffer",
156
+ message:
157
+ stashed.length > 0
158
+ ? "HeySpex can't reach the server right now, so this capture isn't filed yet — but your exact words are saved to the offline buffer and will sync automatically on the next capture call once the connection is back. Nothing is lost."
159
+ : "HeySpex can't reach the server right now. Anything you've already captured is safe — please try again in a moment.",
160
+ stashed: stashed.map((s) => ({ kind: s.kind })),
161
+ pending_sync: pending,
162
+ };
163
+ }
164
+ return {
165
+ ok: false,
166
+ status: d.kind === "client" ? "refused" : "failed",
167
+ error: d.error,
168
+ http_status: d.http_status,
169
+ server_error: d.server_error_code,
170
+ // Back-compat key (tool description documents `console_error` as the
171
+ // verbatim server message).
172
+ console_error: d.server_message ?? d.server_error_code,
173
+ buffered: false,
174
+ message: d.message,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * @typedef {object} ConsoleFetchResult
180
+ * @property {boolean} ok
181
+ * @property {number} status
182
+ * @property {*} body
183
+ * @property {string} [url]
184
+ * @property {string} [error]
185
+ *
186
+ * @callback ConsoleFetchFn
187
+ * @param {string} path
188
+ * @param {object} [options]
189
+ * @returns {Promise<ConsoleFetchResult>}
190
+ */
191
+
192
+ /**
193
+ * Build a capture-session console client bound to a `consoleFetch` function.
194
+ *
195
+ * @param {ConsoleFetchFn} consoleFetch
196
+ */
197
+ export function makeCaptureConsoleClient(consoleFetch) {
198
+ /**
199
+ * PATCH a capture session's content_buffer or attach an agent.
200
+ *
201
+ * Returns:
202
+ * - on success: `{ ok: true, session, materialization, result }`
203
+ * - on failure: `{ ok: false, error: <code>, status, consoleMessage, result }`
204
+ * where `error` is one of CAPTURE_ERROR_*.
205
+ */
206
+ async function patchCaptureSession(sessionId, patchBody) {
207
+ const result = await consoleFetch(
208
+ `/api/capture/sessions/${encodeURIComponent(sessionId)}`,
209
+ { method: "PATCH", body: patchBody },
210
+ );
211
+ if (!result.ok) {
212
+ return {
213
+ ok: false,
214
+ error: classifyConsoleFetchError(result),
215
+ status: result.status,
216
+ consoleMessage: extractConsoleMessage(result),
217
+ result,
218
+ };
219
+ }
220
+ return {
221
+ ok: true,
222
+ session: result.body?.session,
223
+ materialization: result.body?.materialization,
224
+ result,
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Drain offline-buffered captures into the now-reachable session, oldest first.
230
+ *
231
+ * Per [[HS-EPIC-CPMR]]:
232
+ * - `CAPTURE_CONFLICT` (4xx): quarantine + KEEP DRAINING. Pre-fix this
233
+ * would head-of-line-block the queue forever (the offline buffer was the
234
+ * dead-letter graveyard for misclassified materialized-row 409s).
235
+ * - `CONSOLE_UNREACHABLE` / `PATCH_FAILED` (5xx): break — leave the rest
236
+ * for the next reachable call.
237
+ *
238
+ * @param {object} [currentBuffer] - the session's current content_buffer,
239
+ * threaded through `outboxEntryToPatch` so append-shaped sections
240
+ * (userQuotes/chatTurns) merge with what is already on the session
241
+ * instead of clobbering it; updated locally as entries drain so
242
+ * multiple stashed quotes accumulate ([[HS-K27H-DIAG-0G86]] AC4 —
243
+ * replay must be genuinely lossless for the sync promise to be honest).
244
+ * @returns {Promise<{drained: number, quarantined: number, remaining: number}>}
245
+ */
246
+ async function drainOutbox(vaultRoot, sessionId, identity, currentBuffer = {}) {
247
+ const pending = listOutbox(vaultRoot);
248
+ if (pending.length === 0) {
249
+ return { drained: 0, quarantined: 0, remaining: 0 };
250
+ }
251
+ let drained = 0;
252
+ let quarantined = 0;
253
+ let localBuffer =
254
+ currentBuffer && typeof currentBuffer === "object" ? currentBuffer : {};
255
+ for (const entry of pending) {
256
+ const patch = outboxEntryToPatch(entry, new Date().toISOString(), localBuffer);
257
+ if (!patch) {
258
+ removeOutboxEntry(entry.path); // unmappable — discard so it can't wedge the queue
259
+ continue;
260
+ }
261
+ const res = await patchCaptureSession(sessionId, {
262
+ patches: [patch],
263
+ attach: { agent: identity.agent, sessionId: identity.sessionId },
264
+ });
265
+ if (res.ok) {
266
+ removeOutboxEntry(entry.path);
267
+ drained += 1;
268
+ // Mirror the server's last-write-wins merge locally so the NEXT
269
+ // entry's append-shaped patch builds on this one.
270
+ localBuffer = { ...localBuffer, [patch.path]: patch.value };
271
+ continue;
272
+ }
273
+ if (res.error === CAPTURE_ERROR_CONFLICT) {
274
+ quarantineOutboxEntry(vaultRoot, entry.path, {
275
+ reason: "console_refused",
276
+ status: res.status,
277
+ consoleMessage: res.consoleMessage,
278
+ });
279
+ quarantined += 1;
280
+ continue;
281
+ }
282
+ // Transient (unreachable / 5xx) — leave remainder for next call.
283
+ // Do not drain any more entries after encountering a transient error
284
+ break;
285
+ }
286
+ return { drained, quarantined, remaining: outboxCount(vaultRoot) };
287
+ }
288
+
289
+ return { patchCaptureSession, drainOutbox };
290
+ }