heyspex 0.2.0 → 0.2.2
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.
- package/.claude-plugin/marketplace.json +14 -0
- package/bin/hs.mjs +181 -15
- package/dist/cli/hs-check.mjs +88 -46
- package/dist/cli/hs-claim.mjs +84 -39
- package/dist/cli/hs-connect.mjs +583 -129
- package/dist/cli/hs-defer.mjs +251 -30
- package/dist/cli/hs-dev.mjs +20 -8
- package/dist/cli/hs-env.mjs +62 -34
- package/dist/cli/hs-index-rebuild.mjs +288 -83
- package/dist/cli/hs-index-reconcile.mjs +397 -111
- package/dist/cli/hs-init.mjs +2143 -91
- package/dist/cli/hs-install.mjs +628 -106
- package/dist/cli/hs-join.mjs +686 -236
- package/dist/cli/hs-vault-pull.mjs +3189 -179
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +396 -178
- package/dist/cli/hs-workspace.mjs +289 -151
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
- package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
- package/docs/copilot-plugin-install.md +177 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +21 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +312 -0
- package/plugins/heyspex/bin/run-hook.mjs +20 -2
- package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
- package/plugins/heyspex/hooks/hooks.json +94 -37
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
- package/scripts/claude-hooks/_run.mjs +316 -23
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
- package/scripts/claude-hooks/session-end.mjs +2 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
- package/scripts/cursor-hooks/manifest.mjs +46 -0
- package/scripts/heyspex-mode.mjs +20 -0
- package/scripts/hs-mode.mjs +1 -1
- package/scripts/hs-view.mjs +180 -17
- package/scripts/install-claude-code-hooks.mjs +3 -1
- package/scripts/install-copilot-hooks.mjs +288 -0
- package/scripts/lib/console-url-resolver.mjs +3 -2
- package/scripts/lib/customer-kit.mjs +327 -10
- package/scripts/lib/dev-mode.mjs +7 -6
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-log.mjs +2 -1
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -356
- package/scripts/lib/window-placement.mjs +109 -0
- package/spexharness/src/adapters/claude-code.mjs +11 -3
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/intent-classifier.mjs +4 -2
- package/spexharness/src/intent-directive.mjs +626 -541
- package/spexharness/src/rule-table.mjs +31 -1
- package/vendor/heyspex-mcp/account-switch.mjs +6 -5
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
- package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
- package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
- package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
- package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
- package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
- package/vendor/heyspex-mcp/cli.mjs +7 -1
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
- package/vendor/heyspex-mcp/console-transport.mjs +7 -1
- package/vendor/heyspex-mcp/credentials.mjs +9 -8
- package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
- package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
- package/vendor/heyspex-mcp/http-transport.mjs +2 -0
- package/vendor/heyspex-mcp/list-tools.mjs +1 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/plan-push.mjs +3 -2
- package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
- package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
- package/vendor/heyspex-mcp/promote-research.mjs +6 -0
- package/vendor/heyspex-mcp/read-path.mjs +162 -6
- package/vendor/heyspex-mcp/request-context.mjs +25 -1
- package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/session-owner.mjs +3 -2
- package/vendor/heyspex-mcp/session-tools.mjs +10 -0
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
- package/vendor/heyspex-mcp/split-research.mjs +3 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
- package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
- package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
- package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
- package/vendor/heyspex-mcp/vault-io.mjs +720 -724
- package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
- package/vendor/heyspex-mcp/whoami.mjs +2 -1
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -1,325 +1,327 @@
|
|
|
1
|
-
// heyspex_list_procedures, get_procedure, preflight handlers (disk and tier-aware
|
|
2
|
-
// variants; the procedure store itself is procedure-tools.mjs). Extracted from cli.mjs under
|
|
3
|
-
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
4
|
-
|
|
5
|
-
import { executeGetProcedure, executeGetProcedureViaTier, executeListProcedures, executeListProceduresViaTier, executeRunPreflight, executeRunPreflightViaTier } from './procedure-tools.mjs';
|
|
6
|
-
import { detectAgentIdentity, effectiveConsoleUrl } from './mcp-config.mjs';
|
|
7
|
-
import { preflightMarkerNotice, writePreflightMarker } from './preflight-gate.mjs';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* [[HS-K0ZP]]: reading research-capture-protocol directly is the same proof that
|
|
11
|
-
* running heyspex_preflight({ intent: "research" }) gives, so a successful
|
|
12
|
-
* heyspex_get_procedure on it writes the `research` marker too. Only a real body
|
|
13
|
-
* counts -- a NOT_FOUND response taught the agent nothing. Never throws: the
|
|
14
|
-
* marker write is fail-open by contract, and a read must never break on it.
|
|
15
|
-
*/
|
|
16
|
-
export const RESEARCH_PROTOCOL_NAME = 'research-capture-protocol';
|
|
17
|
-
export function recordResearchProtocolRead(vaultRoot, args, result) {
|
|
18
|
-
try {
|
|
19
|
-
if (String(args?.name ?? '').trim() !== RESEARCH_PROTOCOL_NAME) return false;
|
|
20
|
-
if (!result || result.error || !result.content) return false;
|
|
21
|
-
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
22
|
-
return writePreflightMarker(vaultRoot, 'research', {
|
|
23
|
-
sessionId: identity.sessionId,
|
|
24
|
-
agent: identity.agent,
|
|
25
|
-
procedures: [RESEARCH_PROTOCOL_NAME],
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
recipes =
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* `
|
|
230
|
-
*
|
|
231
|
-
* `
|
|
232
|
-
*
|
|
233
|
-
* `
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1
|
+
// heyspex_list_procedures, get_procedure, preflight handlers (disk and tier-aware
|
|
2
|
+
// variants; the procedure store itself is procedure-tools.mjs). Extracted from cli.mjs under
|
|
3
|
+
// [[HS-BLNF-6W61-DIAG-cli-mjs-has-grown-into-a-13-665-line]] (no behavior change).
|
|
4
|
+
|
|
5
|
+
import { executeGetProcedure, executeGetProcedureViaTier, executeListProcedures, executeListProceduresViaTier, executeRunPreflight, executeRunPreflightViaTier } from './procedure-tools.mjs';
|
|
6
|
+
import { detectAgentIdentity, effectiveConsoleUrl } from './mcp-config.mjs';
|
|
7
|
+
import { preflightMarkerNotice, writePreflightMarker } from './preflight-gate.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* [[HS-K0ZP]]: reading research-capture-protocol directly is the same proof that
|
|
11
|
+
* running heyspex_preflight({ intent: "research" }) gives, so a successful
|
|
12
|
+
* heyspex_get_procedure on it writes the `research` marker too. Only a real body
|
|
13
|
+
* counts -- a NOT_FOUND response taught the agent nothing. Never throws: the
|
|
14
|
+
* marker write is fail-open by contract, and a read must never break on it.
|
|
15
|
+
*/
|
|
16
|
+
export const RESEARCH_PROTOCOL_NAME = 'research-capture-protocol';
|
|
17
|
+
export function recordResearchProtocolRead(vaultRoot, args, result) {
|
|
18
|
+
try {
|
|
19
|
+
if (String(args?.name ?? '').trim() !== RESEARCH_PROTOCOL_NAME) return false;
|
|
20
|
+
if (!result || result.error || !result.content) return false;
|
|
21
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
22
|
+
return writePreflightMarker(vaultRoot, 'research', {
|
|
23
|
+
sessionId: identity.sessionId,
|
|
24
|
+
agent: identity.agent,
|
|
25
|
+
procedures: [RESEARCH_PROTOCOL_NAME],
|
|
26
|
+
degradedSource: result.degraded ? 'disk' : undefined,
|
|
27
|
+
});
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
import { matchingRecipes } from './recipe-tools.mjs';
|
|
33
|
+
import { wrapDesignResult } from './tool-helpers.mjs';
|
|
34
|
+
import { MEMORY_BASELINE_TAGS, MEMORY_DESCRIPTION_TRUNCATE, MEMORY_INJECTION_LIMIT, detectMemoryAmendment, scoreRelevantMemories } from './on-deck-brief.mjs';
|
|
35
|
+
import { consoleReadErrorFrom, readViaSelector, resolveReadTier } from './read-path.mjs';
|
|
36
|
+
import { consoleFetch } from './console-transport.mjs';
|
|
37
|
+
import { extractSummary } from './memory-tools.mjs';
|
|
38
|
+
|
|
39
|
+
export function handleListProcedures(vaultRoot, args) {
|
|
40
|
+
return wrapDesignResult(executeListProcedures(vaultRoot, args));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function handleGetProcedure(vaultRoot, args) {
|
|
44
|
+
const result = executeGetProcedure(vaultRoot, args);
|
|
45
|
+
recordResearchProtocolRead(vaultRoot, args, result);
|
|
46
|
+
return wrapDesignResult(result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function handleRunPreflight(vaultRoot, args) {
|
|
50
|
+
const result = executeRunPreflight(vaultRoot, args);
|
|
51
|
+
|
|
52
|
+
// Proof-of-use ([[preflight-gate]]): record that the questions were actually
|
|
53
|
+
// asked, so the tools those questions govern can refuse when they were not.
|
|
54
|
+
// Only a real answer counts — an UNKNOWN_INTENT / CATALOG_NOT_FOUND response
|
|
55
|
+
// taught the agent nothing and must not clear a gate.
|
|
56
|
+
if (result && !result.error) {
|
|
57
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
58
|
+
const wrote = writePreflightMarker(vaultRoot, String(args.intent ?? '').trim(), {
|
|
59
|
+
sessionId: identity.sessionId,
|
|
60
|
+
agent: identity.agent,
|
|
61
|
+
threadType: args.thread_type ?? null,
|
|
62
|
+
procedures: Array.isArray(result.procedures)
|
|
63
|
+
? result.procedures.map((p) => p?.procedure).filter(Boolean)
|
|
64
|
+
: [],
|
|
65
|
+
});
|
|
66
|
+
// A marker we could not write used to be swallowed silently, so preflight
|
|
67
|
+
// reported success while the gated tools refused forever and told the caller
|
|
68
|
+
// to "call preflight, then retry" — a loop no retry escaped
|
|
69
|
+
// ([[HS-N71O-P3XQ-DIAG-six-new-qc-gates-fail-cold-verification]] item 3).
|
|
70
|
+
// Preflight still succeeds (fail-open); it just stops claiming it recorded
|
|
71
|
+
// something it did not.
|
|
72
|
+
const notice = preflightMarkerNotice(wrote);
|
|
73
|
+
if (notice) Object.assign(result, notice);
|
|
74
|
+
}
|
|
75
|
+
// Match workflow-recipes against the intent so recipes are discoverable the
|
|
76
|
+
// same way procedures are ([[HS-RQ9K-TASK]] / [[HS-5G0P-EPIC]]). Never throws.
|
|
77
|
+
let recipes = [];
|
|
78
|
+
try {
|
|
79
|
+
recipes = matchingRecipes(vaultRoot, args.intent);
|
|
80
|
+
} catch {
|
|
81
|
+
recipes = [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Recipe-only intent (e.g. "bug_fix"): a valid recipe signature that is NOT a
|
|
85
|
+
// procedure intent returns UNKNOWN_INTENT from the procedure map. Rather than
|
|
86
|
+
// hard-error, surface the matching recipes so the caller still gets a useful
|
|
87
|
+
// answer — recipe intents live in a different namespace than procedure intents.
|
|
88
|
+
if (result && result.error === 'UNKNOWN_INTENT' && recipes.length > 0) {
|
|
89
|
+
return wrapDesignResult({
|
|
90
|
+
intent: String(args.intent ?? '').trim(),
|
|
91
|
+
procedures: [],
|
|
92
|
+
recipes,
|
|
93
|
+
note:
|
|
94
|
+
'No procedure checklist for this intent, but it matches one or more workflow-recipes. Start one with heyspex_start_recipe, or call heyspex_preflight with a procedure intent (e.g. file_task, execute_spec).',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Attach recalled_memories so high-stakes decision points carry the same
|
|
99
|
+
// memory load /hs-next does. Skipped on error responses ([[HS-TASK-IMMF]]).
|
|
100
|
+
if (result && !result.error) {
|
|
101
|
+
try {
|
|
102
|
+
const recalled = scoreRelevantMemories(vaultRoot, {
|
|
103
|
+
// Use the intent name as a primary signal so an intent like
|
|
104
|
+
// recommend_location surfaces template/saas/architecture memories
|
|
105
|
+
// strongly. The baseline pool still applies on top.
|
|
106
|
+
primaryTags: typeof args.intent === 'string' ? [args.intent] : [],
|
|
107
|
+
});
|
|
108
|
+
if (recalled.items.length > 0) {
|
|
109
|
+
result.recalled_memories = recalled;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Recall failure must never break preflight — surface the procedures
|
|
113
|
+
// without memories rather than 500ing the response.
|
|
114
|
+
}
|
|
115
|
+
if (recipes.length > 0) {
|
|
116
|
+
result.recipes = recipes;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return wrapDesignResult(result);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Cloud-reliant sibling of `handleListProcedures` ([[HS-DJWP-MCPC-TASK]]
|
|
124
|
+
* "Procedures" family, backing `heyspex_list_procedures`). `handleListProcedures`
|
|
125
|
+
* stays untouched (its `diskRead` branch).
|
|
126
|
+
*/
|
|
127
|
+
export async function handleListProceduresViaTier(vaultRoot, args) {
|
|
128
|
+
const tier = await resolveReadTier();
|
|
129
|
+
const result = await executeListProceduresViaTier({
|
|
130
|
+
vaultRoot,
|
|
131
|
+
args,
|
|
132
|
+
tier,
|
|
133
|
+
consoleFetch,
|
|
134
|
+
consoleUrl: effectiveConsoleUrl(),
|
|
135
|
+
});
|
|
136
|
+
return wrapDesignResult(result);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Cloud-reliant sibling of `handleGetProcedure` ([[HS-DJWP-MCPC-TASK]]
|
|
141
|
+
* "Procedures" family, backing `heyspex_get_procedure`). `handleGetProcedure`
|
|
142
|
+
* stays untouched (its `diskRead` branch).
|
|
143
|
+
*/
|
|
144
|
+
export async function handleGetProcedureViaTier(vaultRoot, args) {
|
|
145
|
+
const tier = await resolveReadTier();
|
|
146
|
+
const result = await executeGetProcedureViaTier({
|
|
147
|
+
vaultRoot,
|
|
148
|
+
args,
|
|
149
|
+
tier,
|
|
150
|
+
consoleFetch,
|
|
151
|
+
consoleUrl: effectiveConsoleUrl(),
|
|
152
|
+
});
|
|
153
|
+
recordResearchProtocolRead(vaultRoot, args, result);
|
|
154
|
+
return wrapDesignResult(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cloud-reliant sibling of `handleRunPreflight` ([[HS-DJWP-MCPC-TASK]]
|
|
159
|
+
* "Procedures" + "Memories" families, backing `heyspex_preflight`).
|
|
160
|
+
* `handleRunPreflight` is left completely untouched — it is imported and
|
|
161
|
+
* called directly (synchronously) by `preflight-gate.test.mjs`, so it must
|
|
162
|
+
* keep returning a plain object, never a Promise. This is a SEPARATE function,
|
|
163
|
+
* used only by the live tool dispatch (`case 'heyspex_preflight'` below),
|
|
164
|
+
* which duplicates the marker-write / recipe-match / memory-recall tail
|
|
165
|
+
* (`writePreflightMarker`, `preflightMarkerNotice`, `matchingRecipes` are
|
|
166
|
+
* reused unchanged — only the tier-aware catalog + memory fetch differ).
|
|
167
|
+
*/
|
|
168
|
+
export async function handleRunPreflightViaTier(vaultRoot, args) {
|
|
169
|
+
const tier = await resolveReadTier();
|
|
170
|
+
const consoleUrl = effectiveConsoleUrl();
|
|
171
|
+
const result = await executeRunPreflightViaTier({ vaultRoot, args, tier, consoleFetch, consoleUrl });
|
|
172
|
+
|
|
173
|
+
if (result && !result.error) {
|
|
174
|
+
const identity = detectAgentIdentity(args.agent, args.session_id);
|
|
175
|
+
const wrote = writePreflightMarker(vaultRoot, String(args.intent ?? '').trim(), {
|
|
176
|
+
sessionId: identity.sessionId,
|
|
177
|
+
agent: identity.agent,
|
|
178
|
+
threadType: args.thread_type ?? null,
|
|
179
|
+
procedures: Array.isArray(result.procedures)
|
|
180
|
+
? result.procedures.map((p) => p?.procedure).filter(Boolean)
|
|
181
|
+
: [],
|
|
182
|
+
degradedSource: result.degraded ? 'disk' : undefined,
|
|
183
|
+
});
|
|
184
|
+
const notice = preflightMarkerNotice(wrote);
|
|
185
|
+
if (notice) Object.assign(result, notice);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let recipes = [];
|
|
189
|
+
try {
|
|
190
|
+
recipes = matchingRecipes(vaultRoot, args.intent);
|
|
191
|
+
} catch {
|
|
192
|
+
recipes = [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (result && result.error === 'UNKNOWN_INTENT' && recipes.length > 0) {
|
|
196
|
+
return wrapDesignResult({
|
|
197
|
+
intent: String(args.intent ?? '').trim(),
|
|
198
|
+
procedures: [],
|
|
199
|
+
recipes,
|
|
200
|
+
note:
|
|
201
|
+
'No procedure checklist for this intent, but it matches one or more workflow-recipes. Start one with heyspex_start_recipe, or call heyspex_preflight with a procedure intent (e.g. file_task, execute_spec).',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (result && !result.error) {
|
|
206
|
+
try {
|
|
207
|
+
const recalled = await scoreRelevantMemoriesViaTier(
|
|
208
|
+
vaultRoot,
|
|
209
|
+
{ primaryTags: typeof args.intent === 'string' ? [args.intent] : [] },
|
|
210
|
+
{ tier, consoleUrl },
|
|
211
|
+
);
|
|
212
|
+
if (recalled.items.length > 0) {
|
|
213
|
+
result.recalled_memories = recalled;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Recall failure must never break preflight.
|
|
217
|
+
}
|
|
218
|
+
if (recipes.length > 0) {
|
|
219
|
+
result.recipes = recipes;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return wrapDesignResult(result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Cloud-reliant sibling of `scoreRelevantMemories` ([[HS-DJWP-MCPC-TASK]]
|
|
227
|
+
* "Memories" family, backing the preflight memory recall). `scoreRelevantMemories`
|
|
228
|
+
* is left completely untouched (its callers — `handleRunPreflight`,
|
|
229
|
+
* `enrichOnDeckWithRecalledMemories` — keep the exact disk-only behavior they
|
|
230
|
+
* have today); this is a NEW, separate cloud path used only by
|
|
231
|
+
* `handleRunPreflightViaTier` below. Scoring logic (tag-overlap, amendment
|
|
232
|
+
* detection, truncation, sort) is duplicated rather than shared with
|
|
233
|
+
* `scoreRelevantMemories` deliberately — the two operate on differently-shaped
|
|
234
|
+
* inputs (disk: `{frontmatter, body}` per file; cloud: the console's flat
|
|
235
|
+
* `MemoryDoc` rows) and duplicating ~25 lines is a smaller risk than
|
|
236
|
+
* refactoring the already-tested disk function to share a helper.
|
|
237
|
+
*
|
|
238
|
+
* Unlike `handleRecallMemoriesViaTier`, no fidelity gap here — `MemoryDoc`
|
|
239
|
+
* already carries everything this scorer reads (`tags`, `body`, `name`, `id`).
|
|
240
|
+
*
|
|
241
|
+
* Split into a tag-only ranking pass ({@link rankMemoryCandidatesByTags}) and a
|
|
242
|
+
* per-item body-dependent builder ({@link buildScoredMemoryItem}) so the
|
|
243
|
+
* caller ({@link scoreRelevantMemoriesViaTier}) can rank against a
|
|
244
|
+
* summary-only listing (score is pure tag-overlap, never touches body) and
|
|
245
|
+
* only fetch body text for the top `limit` survivors —
|
|
246
|
+
* [[HS-LKV0-FUU4-DIAG-cloud-mode-recall-times-out-the]]: the console's
|
|
247
|
+
* `GET /api/memories` used to always ship a body per doc, which is what made
|
|
248
|
+
* a 896-doc vault blow the 30s fetch timeout on every one of these read paths.
|
|
249
|
+
*/
|
|
250
|
+
export function rankMemoryCandidatesByTags(docs, options = {}) {
|
|
251
|
+
const primaryTags = Array.isArray(options.primaryTags) ? options.primaryTags : [];
|
|
252
|
+
const baselineTags = Array.isArray(options.baselineTags) ? options.baselineTags : MEMORY_BASELINE_TAGS;
|
|
253
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : MEMORY_INJECTION_LIMIT;
|
|
254
|
+
|
|
255
|
+
const querySet = new Set([...primaryTags, ...baselineTags].map((t) => String(t).toLowerCase()));
|
|
256
|
+
if (querySet.size === 0) return [];
|
|
257
|
+
|
|
258
|
+
const scored = [];
|
|
259
|
+
for (const doc of docs) {
|
|
260
|
+
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
|
261
|
+
if (tags.length === 0) continue;
|
|
262
|
+
const tagSet = new Set(tags.map((t) => String(t).toLowerCase()));
|
|
263
|
+
let score = 0;
|
|
264
|
+
for (const q of querySet) if (tagSet.has(q)) score += 1;
|
|
265
|
+
if (score === 0) continue;
|
|
266
|
+
scored.push({ doc, score });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
scored.sort((a, b) => {
|
|
270
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
271
|
+
const aName = a.doc.name || a.doc.id || '';
|
|
272
|
+
const bName = b.doc.name || b.doc.id || '';
|
|
273
|
+
return String(aName).localeCompare(String(bName));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return scored.slice(0, limit);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Builds one scored-memory item from a ranked candidate + its (separately fetched) body. */
|
|
280
|
+
export function buildScoredMemoryItem(doc, score, body) {
|
|
281
|
+
const slug = doc.id;
|
|
282
|
+
const name = doc.name || doc.id || slug;
|
|
283
|
+
let description = extractSummary(body || '');
|
|
284
|
+
if (description.length > MEMORY_DESCRIPTION_TRUNCATE) {
|
|
285
|
+
description = description.slice(0, MEMORY_DESCRIPTION_TRUNCATE - 1) + '…';
|
|
286
|
+
}
|
|
287
|
+
const amended = detectMemoryAmendment(body || '', description);
|
|
288
|
+
if (amended) {
|
|
289
|
+
description = `${description} ⚠ ${amended} — this summary may not reflect the current ruling; READ THE FULL BODY before applying.`;
|
|
290
|
+
}
|
|
291
|
+
return { source: 'vault', name, link: `[[${slug}]]`, description, amended: amended || undefined, score };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function scoreRelevantMemoriesViaTier(vaultRoot, options, { tier, consoleUrl }) {
|
|
295
|
+
return readViaSelector({
|
|
296
|
+
tier,
|
|
297
|
+
diskRead: () => scoreRelevantMemories(vaultRoot, options),
|
|
298
|
+
neonRead: async () => {
|
|
299
|
+
// Summary-only listing — scoring is pure tag-overlap and never needs body.
|
|
300
|
+
const res = await consoleFetch('/api/memories');
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
throw consoleReadErrorFrom(res, `GET /api/memories returned ${res.status}: ${res.error || JSON.stringify(res.body ?? {})}`, null);
|
|
303
|
+
}
|
|
304
|
+
const docs = Array.isArray(res.body?.memories) ? res.body.memories : [];
|
|
305
|
+
const ranked = rankMemoryCandidatesByTags(docs, options);
|
|
306
|
+
if (ranked.length === 0) return { header: null, items: [] };
|
|
307
|
+
|
|
308
|
+
// Fetch bodies ONLY for the survivors that ranking already narrowed to
|
|
309
|
+
// (`limit`, default 5) — never one fetch per candidate across the whole set.
|
|
310
|
+
const idsQs = new URLSearchParams({ ids: ranked.map(({ doc }) => doc.id).join(','), fields: 'body' });
|
|
311
|
+
const bodyPath = `/api/memories?${idsQs}`;
|
|
312
|
+
const bodyRes = await consoleFetch(bodyPath);
|
|
313
|
+
if (!bodyRes.ok) {
|
|
314
|
+
throw consoleReadErrorFrom(bodyRes, `GET ${bodyPath} returned ${bodyRes.status}: ${bodyRes.error || JSON.stringify(bodyRes.body ?? {})}`, null);
|
|
315
|
+
}
|
|
316
|
+
const bodyDocs = Array.isArray(bodyRes.body?.memories) ? bodyRes.body.memories : [];
|
|
317
|
+
const bodyById = new Map(bodyDocs.map((d) => [d.id, typeof d.body === 'string' ? d.body : '']));
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
header: 'Retrieved from prior sessions. Apply at decision points; do not echo verbatim.',
|
|
321
|
+
items: ranked.map(({ doc, score }) => buildScoredMemoryItem(doc, score, bodyById.get(doc.id) || '')),
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
routeLabel: 'GET /api/memories',
|
|
325
|
+
consoleUrl,
|
|
326
|
+
});
|
|
327
|
+
}
|