heyspex 0.2.0 → 0.2.1
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 +163 -11
- 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 +162 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +20 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +309 -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/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,247 +1,279 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Claude Code hook → the HeySpex Intent Classifier's SINGLE UserPromptSubmit entry point.
|
|
3
|
-
//
|
|
4
|
-
// HS-5ISS ENGINE CUTOVER: this hook now classifies through the engine module
|
|
5
|
-
// (spexharness/src/intent-classifier.mjs's classifyPrompt) instead of the old
|
|
6
|
-
// per-prompt Layer 2 "self_check" prose block (_intent-classifier-router.mjs's
|
|
7
|
-
// buildSelfCheckBlock — ~4,200 tokens / ~6.5 KB, pasted into EVERY prompt that missed
|
|
8
|
-
// every lexical pattern, i.e. almost every ordinary work order). The engine module is
|
|
9
|
-
// LEXICAL ONLY: a prompt that matches none of the five classifier rules
|
|
10
|
-
// (capture-on-idea, research-before-decision, settings-intent, defer-intent,
|
|
11
|
-
// spec-reference-lookup) now injects NOTHING — under 500 bytes, usually zero — instead
|
|
12
|
-
// of the old self-check block (AC3, [[HS-5ISS]]).
|
|
13
|
-
//
|
|
14
|
-
// A rule the engine matches still has to clear the SAME gating the old lexical sweep
|
|
15
|
-
// applied (env kill switch, agent_behaviors.<key>.enabled, the injected-content guard)
|
|
16
|
-
// before it fires — the engine module itself knows only patterns, not config or env,
|
|
17
|
-
// so isBehaviorEligible() re-applies that gating here. Once eligible, the directive is
|
|
18
|
-
// built through the SAME per-behavior transform (capture's confirm-mode substitution +
|
|
19
|
-
// hints, research's source hint, settings/defer/spec-reference's raw inject) and
|
|
20
|
-
// wrapped in the same CONFIRMED MATCH marker as the old lexical path
|
|
21
|
-
// (buildConfirmedDirective) — so a gate that fires emits a byte-identical directive to
|
|
22
|
-
// before. The five gates' own matching patterns and inject text are untouched.
|
|
23
|
-
//
|
|
24
|
-
// CUTOVER FLAG: agent_behaviors.intent_classifier.engine (.heyspex/config.yaml, read
|
|
25
|
-
// from the VAULT root via the loader's default config root — this hook never joins a
|
|
26
|
-
// config path itself; [[HS-4C76-9W86-DIAG-heyspex-config-yaml-has-no-single-home]]).
|
|
27
|
-
// Defaults ON (isConfigKeyEnabled semantics — absent/anything but explicit `false` is
|
|
28
|
-
// enabled). Set to `false` to fall back to the pre-HS-5ISS path in full — the unified
|
|
29
|
-
// lexical-sweep-plus-Layer-2-self-check hook, unchanged (_intent-classifier-router.mjs's
|
|
30
|
-
// runClassifierHook()).
|
|
31
|
-
//
|
|
32
|
-
// UNGATED by CLAUDE_AGENT — interactive human typing is the point. Per-behavior mutes
|
|
33
|
-
// still work: HEYSPEX_CAPTURE_TRIGGER_OFF / HEYSPEX_RESEARCH_TRIGGER_OFF /
|
|
34
|
-
// HEYSPEX_SETTINGS_INTENT_OFF / HEYSPEX_DEFER_INTENT_OFF / HEYSPEX_SPEC_REFERENCE_LOOKUP_OFF
|
|
35
|
-
// = 1. HEYSPEX_INTENT_ENGINE_OFF=1 is the cutover's own env kill switch — same
|
|
36
|
-
// contract as every other behavior here (a config_key AND an env var, never just
|
|
37
|
-
// one) — and forces the old fallback path without touching the git-tracked config
|
|
38
|
-
// file, which is what tests use to exercise the fallback safely. Decisions audited
|
|
39
|
-
// to .heyspex/intent-classifier-history.jsonl.
|
|
40
|
-
//
|
|
41
|
-
// FAIL-SAFE: exits 0 on every path. A missing/corrupt config, a missing rule table, or
|
|
42
|
-
// any thrown error degrades to "no injection" (engine path) or defers to the untouched
|
|
43
|
-
// fallback (runClassifierHook never throws either).
|
|
44
|
-
|
|
45
|
-
import { classifyPrompt } from '../../spexharness/src/intent-classifier.mjs';
|
|
46
|
-
import { isConfigKeyEnabled, startsWithInjectedContent } from './_guardrail-loader.mjs';
|
|
47
|
-
import {
|
|
48
|
-
appendClassifierEvent,
|
|
49
|
-
classifyIntent,
|
|
50
|
-
readIntentClassifierConfig,
|
|
51
|
-
} from './_intent-classifier-semantic.mjs';
|
|
52
|
-
import {
|
|
53
|
-
runClassifierHook,
|
|
54
|
-
readStdinJson,
|
|
55
|
-
behaviorKeyForRuleId,
|
|
56
|
-
isBehaviorEligible,
|
|
57
|
-
buildConfirmedDirective,
|
|
58
|
-
buildSelfCheckBlock,
|
|
59
|
-
CLASSIFIER_BEHAVIORS,
|
|
60
|
-
MIN_SELF_CHECK_CHARS,
|
|
61
|
-
} from './_intent-classifier-router.mjs';
|
|
62
|
-
|
|
63
|
-
const ENGINE_FLAG = 'agent_behaviors.intent_classifier.engine';
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code hook → the HeySpex Intent Classifier's SINGLE UserPromptSubmit entry point.
|
|
3
|
+
//
|
|
4
|
+
// HS-5ISS ENGINE CUTOVER: this hook now classifies through the engine module
|
|
5
|
+
// (spexharness/src/intent-classifier.mjs's classifyPrompt) instead of the old
|
|
6
|
+
// per-prompt Layer 2 "self_check" prose block (_intent-classifier-router.mjs's
|
|
7
|
+
// buildSelfCheckBlock — ~4,200 tokens / ~6.5 KB, pasted into EVERY prompt that missed
|
|
8
|
+
// every lexical pattern, i.e. almost every ordinary work order). The engine module is
|
|
9
|
+
// LEXICAL ONLY: a prompt that matches none of the five classifier rules
|
|
10
|
+
// (capture-on-idea, research-before-decision, settings-intent, defer-intent,
|
|
11
|
+
// spec-reference-lookup) now injects NOTHING — under 500 bytes, usually zero — instead
|
|
12
|
+
// of the old self-check block (AC3, [[HS-5ISS]]).
|
|
13
|
+
//
|
|
14
|
+
// A rule the engine matches still has to clear the SAME gating the old lexical sweep
|
|
15
|
+
// applied (env kill switch, agent_behaviors.<key>.enabled, the injected-content guard)
|
|
16
|
+
// before it fires — the engine module itself knows only patterns, not config or env,
|
|
17
|
+
// so isBehaviorEligible() re-applies that gating here. Once eligible, the directive is
|
|
18
|
+
// built through the SAME per-behavior transform (capture's confirm-mode substitution +
|
|
19
|
+
// hints, research's source hint, settings/defer/spec-reference's raw inject) and
|
|
20
|
+
// wrapped in the same CONFIRMED MATCH marker as the old lexical path
|
|
21
|
+
// (buildConfirmedDirective) — so a gate that fires emits a byte-identical directive to
|
|
22
|
+
// before. The five gates' own matching patterns and inject text are untouched.
|
|
23
|
+
//
|
|
24
|
+
// CUTOVER FLAG: agent_behaviors.intent_classifier.engine (.heyspex/config.yaml, read
|
|
25
|
+
// from the VAULT root via the loader's default config root — this hook never joins a
|
|
26
|
+
// config path itself; [[HS-4C76-9W86-DIAG-heyspex-config-yaml-has-no-single-home]]).
|
|
27
|
+
// Defaults ON (isConfigKeyEnabled semantics — absent/anything but explicit `false` is
|
|
28
|
+
// enabled). Set to `false` to fall back to the pre-HS-5ISS path in full — the unified
|
|
29
|
+
// lexical-sweep-plus-Layer-2-self-check hook, unchanged (_intent-classifier-router.mjs's
|
|
30
|
+
// runClassifierHook()).
|
|
31
|
+
//
|
|
32
|
+
// UNGATED by CLAUDE_AGENT — interactive human typing is the point. Per-behavior mutes
|
|
33
|
+
// still work: HEYSPEX_CAPTURE_TRIGGER_OFF / HEYSPEX_RESEARCH_TRIGGER_OFF /
|
|
34
|
+
// HEYSPEX_SETTINGS_INTENT_OFF / HEYSPEX_DEFER_INTENT_OFF / HEYSPEX_SPEC_REFERENCE_LOOKUP_OFF
|
|
35
|
+
// = 1. HEYSPEX_INTENT_ENGINE_OFF=1 is the cutover's own env kill switch — same
|
|
36
|
+
// contract as every other behavior here (a config_key AND an env var, never just
|
|
37
|
+
// one) — and forces the old fallback path without touching the git-tracked config
|
|
38
|
+
// file, which is what tests use to exercise the fallback safely. Decisions audited
|
|
39
|
+
// to .heyspex/intent-classifier-history.jsonl.
|
|
40
|
+
//
|
|
41
|
+
// FAIL-SAFE: exits 0 on every path. A missing/corrupt config, a missing rule table, or
|
|
42
|
+
// any thrown error degrades to "no injection" (engine path) or defers to the untouched
|
|
43
|
+
// fallback (runClassifierHook never throws either).
|
|
44
|
+
|
|
45
|
+
import { classifyPrompt } from '../../spexharness/src/intent-classifier.mjs';
|
|
46
|
+
import { isConfigKeyEnabled, startsWithInjectedContent } from './_guardrail-loader.mjs';
|
|
47
|
+
import {
|
|
48
|
+
appendClassifierEvent,
|
|
49
|
+
classifyIntent,
|
|
50
|
+
readIntentClassifierConfig,
|
|
51
|
+
} from './_intent-classifier-semantic.mjs';
|
|
52
|
+
import {
|
|
53
|
+
runClassifierHook,
|
|
54
|
+
readStdinJson,
|
|
55
|
+
behaviorKeyForRuleId,
|
|
56
|
+
isBehaviorEligible,
|
|
57
|
+
buildConfirmedDirective,
|
|
58
|
+
buildSelfCheckBlock,
|
|
59
|
+
CLASSIFIER_BEHAVIORS,
|
|
60
|
+
MIN_SELF_CHECK_CHARS,
|
|
61
|
+
} from './_intent-classifier-router.mjs';
|
|
62
|
+
|
|
63
|
+
const ENGINE_FLAG = 'agent_behaviors.intent_classifier.engine';
|
|
64
|
+
|
|
65
|
+
// HS-KNJ8: deterministic verbatim capture. After the classifier has run, an idea-bearing prompt
|
|
66
|
+
// (a capture_on_idea / research_before_decision match, or the semantic-miss rung firing) is appended
|
|
67
|
+
// VERBATIM to the capture on deck for THIS session, and the model is told it was done. Also refreshes
|
|
68
|
+
// the PreToolUse capture-execution-gate's state file. Returns the note to append to the emitted context.
|
|
69
|
+
// Body moved to scripts/lib/capture-verbatim-append.mjs (runVerbatimStageForHook) so the legacy router shares it.
|
|
70
|
+
// Dynamic + variable specifier on purpose: platform-only module (customer-kit scope 'platform'), NOT in the
|
|
71
|
+
// published npm closure; a customer install simply has no verbatim stage (caught -> null).
|
|
72
|
+
//
|
|
73
|
+
// HS-20SW: BOTH UserPromptSubmit entry points (this engine path AND the legacy router) now pass the same
|
|
74
|
+
// options, so engine:true and engine:false behave identically ([[HS-LES-a-safety-net-wired-into-only-one-of-two]]):
|
|
75
|
+
// `directiveIfNoCapture` on the semantic-miss rung (the hard directive when nothing is on deck, exactly what the
|
|
76
|
+
// legacy self_check rung does) and `armObligation` on every capture-bearing path, which records the outstanding
|
|
77
|
+
// capture obligation the directive-obligation-gate enforces.
|
|
78
|
+
async function verbatimStage(payload, prompt, append, options = {}) {
|
|
79
|
+
try {
|
|
80
|
+
const mod = '../lib/capture-verbatim-append.mjs';
|
|
81
|
+
const { runVerbatimStageForHook } = await import(mod);
|
|
82
|
+
return await runVerbatimStageForHook(payload, prompt, append, options);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const NOTE_SEP = '\n\n';
|
|
89
|
+
const IDEA_BEARING_BEHAVIORS =new Set(['capture_on_idea', 'research_before_decision']);
|
|
90
|
+
|
|
91
|
+
// THE SEMANTIC NET ON THE ENGINE PATH ([[HS-3J57-7EDT-DIAG-intent-classifier-s-semantic-fallback]],
|
|
92
|
+
// [[HS-8A3Y-LELB-DIAG-a-prose-feature-request-bypassed]]). The HS-5ISS cutover made a
|
|
93
|
+
// lexical miss a silent no-op — the ~6.5 KB Layer 2 self-check block was dropped and
|
|
94
|
+
// nothing took its place, so a paraphrased idea, or an owner's work-order-shaped
|
|
95
|
+
// feature ask ("please also add this to heyspex-app … should we create our own …"),
|
|
96
|
+
// reached the agent with no capture directive at all and was built straight from the
|
|
97
|
+
// prompt. This is the replacement: on a miss, when the semantic layer is on
|
|
98
|
+
// (agent_behaviors.intent_classifier.semantic_enabled, env HEYSPEX_INTENT_SEMANTIC_OFF=1
|
|
99
|
+
// to mute), the hook's behavior depends on the resolved mode
|
|
100
|
+
// (agent_behaviors.intent_classifier.mode, default self_check):
|
|
101
|
+
// - mode:self_check (DEFAULT) — the SAME deterministic SELF-CHECK block the legacy
|
|
102
|
+
// _intent-classifier-router.mjs path builds (buildSelfCheckBlock, reused verbatim
|
|
103
|
+
// — not a second copy of its prose) over whichever behaviors are still eligible on
|
|
104
|
+
// this prompt (env kill switch / config_key / injected-content guard, via
|
|
105
|
+
// isBehaviorEligible). This is a REAL directive, not an advisory: the agent judges
|
|
106
|
+
// the gates and, on a hit, fetches and follows the exact directive via
|
|
107
|
+
// `--directive <key>`, byte-identical to Layer 1. A prompt with zero eligible
|
|
108
|
+
// behaviors emits nothing (nothing to self-check).
|
|
109
|
+
// - mode:model (OPT-IN) — the local classifier is consulted first and a confident hit
|
|
110
|
+
// fires the full CONFIRMED MATCH directive, exactly as the legacy router's opt-in
|
|
111
|
+
// path did. When the model is silent, low-confidence, or unreachable, the hook
|
|
112
|
+
// falls back to the COMPACT_GATE_CHECK advisory below (under 400 bytes) rather than
|
|
113
|
+
// going fully silent again.
|
|
114
|
+
// A sub-12-char prompt ("yes", "proceed") and a harness envelope (task-notification /
|
|
115
|
+
// system-reminder as the prompt) never get either treatment.
|
|
116
|
+
export const COMPACT_GATE_CHECK =
|
|
117
|
+
'HEYSPEX GATE CHECK (system note): no lexical rule matched. If this is a raw idea or a ' +
|
|
118
|
+
'feature ask (please add/build X, should we build our own X), run `node ' +
|
|
119
|
+
'scripts/claude-hooks/_intent-classifier-router.mjs --directive capture_on_idea` and ' +
|
|
120
|
+
'follow it. Other keys: research_before_decision, settings_intent, defer_intent, ' +
|
|
121
|
+
'spec_reference_lookup. Otherwise proceed silently.';
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The engine-miss fallback. Returns the additionalContext to emit, or null when the
|
|
125
|
+
* miss must stay silent (semantic layer off, prompt too short, harness envelope).
|
|
126
|
+
* Never throws.
|
|
127
|
+
*/
|
|
128
|
+
export async function engineMissFallback(prompt) {
|
|
129
|
+
try {
|
|
130
|
+
if (typeof prompt !== 'string' || prompt.trim().length < MIN_SELF_CHECK_CHARS) return null;
|
|
131
|
+
if (startsWithInjectedContent(prompt)) return null;
|
|
132
|
+
const cfg = readIntentClassifierConfig();
|
|
133
|
+
if (!cfg.semanticEnabled) return null;
|
|
134
|
+
|
|
135
|
+
if (cfg.semanticMode === 'model') {
|
|
136
|
+
const result = await classifyIntent(prompt);
|
|
137
|
+
if (
|
|
138
|
+
result?.behavior &&
|
|
139
|
+
result.confidence >= cfg.minConfidence &&
|
|
140
|
+
isBehaviorEligible(result.behavior, prompt)
|
|
141
|
+
) {
|
|
142
|
+
const directive = buildConfirmedDirective(result.behavior, undefined, prompt);
|
|
143
|
+
if (directive) {
|
|
144
|
+
appendClassifierEvent({
|
|
145
|
+
surface: 'userpromptsubmit',
|
|
146
|
+
rule: result.behavior,
|
|
147
|
+
layer: 'semantic',
|
|
148
|
+
decision: 'fire',
|
|
149
|
+
engine: true,
|
|
150
|
+
confidence: result.confidence,
|
|
151
|
+
latency_ms: result.latencyMs,
|
|
152
|
+
prompt_chars: prompt.length,
|
|
153
|
+
});
|
|
154
|
+
return directive;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// model silent/low-confidence/unreachable/ineligible → fall through to the
|
|
158
|
+
// COMPACT_GATE_CHECK advisory below (unchanged from before this fix).
|
|
159
|
+
} else if (cfg.semanticMode === 'self_check') {
|
|
160
|
+
// THE FIX: reuse the legacy router's own self-check block builder
|
|
161
|
+
// (_intent-classifier-router.mjs's buildSelfCheckBlock) rather than the weak
|
|
162
|
+
// COMPACT_GATE_CHECK advisory — this is what closes the Layer 2 hole if
|
|
163
|
+
// `engine` is ever flipped back to true while mode stays self_check (the
|
|
164
|
+
// default and, on this machine, the only reachable mode: `model` needs an
|
|
165
|
+
// Ollama endpoint that is off the network here). Byte-identical to what the
|
|
166
|
+
// legacy path would emit for the SAME considered set, so a gate an agent
|
|
167
|
+
// judges to fire resolves to the exact same directive either way.
|
|
168
|
+
const considered = CLASSIFIER_BEHAVIORS.filter((behavior) =>
|
|
169
|
+
isBehaviorEligible(behavior.key, prompt),
|
|
170
|
+
).map((behavior) => ({ behavior }));
|
|
171
|
+
if (considered.length) {
|
|
172
|
+
const additionalContext = buildSelfCheckBlock(considered, undefined, prompt);
|
|
173
|
+
if (additionalContext) {
|
|
174
|
+
appendClassifierEvent({
|
|
175
|
+
surface: 'userpromptsubmit',
|
|
176
|
+
rule: 'intent-classifier',
|
|
177
|
+
layer: 'semantic',
|
|
178
|
+
decision: 'self_check_injected',
|
|
179
|
+
engine: true,
|
|
180
|
+
offered: considered.map((entry) => entry.behavior.key),
|
|
181
|
+
prompt_chars: prompt.length,
|
|
182
|
+
});
|
|
183
|
+
return additionalContext;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// No behavior is currently eligible (every gate env/config-disabled) — mirror
|
|
187
|
+
// the legacy router's routePrompt(), which returns null before ever building a
|
|
188
|
+
// self-check block when its `eligible` set is empty. Nothing to self-check.
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
appendClassifierEvent({
|
|
193
|
+
surface: 'userpromptsubmit',
|
|
194
|
+
rule: null,
|
|
195
|
+
layer: 'semantic',
|
|
196
|
+
decision: 'compact_gate_check',
|
|
197
|
+
engine: true,
|
|
198
|
+
mode: cfg.semanticMode,
|
|
199
|
+
prompt_chars: prompt.length,
|
|
200
|
+
});
|
|
201
|
+
return COMPACT_GATE_CHECK;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function emit(additionalContext) {
|
|
208
|
+
if (!additionalContext) return;
|
|
209
|
+
process.stdout.write(
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext },
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The engine-cutover hook body: read the payload, classify via the engine module, and
|
|
218
|
+
* emit a directive ONLY when a gate both matches lexically AND is eligible (env/config/
|
|
219
|
+
* guard). Every other path — a full lexical miss, an unknown rule id, an ineligible
|
|
220
|
+
* behavior, a malformed payload — emits nothing at all, which is the point (AC3).
|
|
221
|
+
* Never throws (mirrors runClassifierHook's own contract).
|
|
222
|
+
*/
|
|
223
|
+
export async function runEngineClassifierHook() {
|
|
224
|
+
try {
|
|
225
|
+
const raw = await readStdinJson();
|
|
226
|
+
if (!raw) return;
|
|
227
|
+
let payload;
|
|
228
|
+
try {
|
|
229
|
+
payload = JSON.parse(raw);
|
|
230
|
+
} catch {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (payload?.hook_event_name !== 'UserPromptSubmit') return;
|
|
234
|
+
const prompt = payload?.prompt;
|
|
235
|
+
if (typeof prompt !== 'string' || !prompt.trim()) return;
|
|
236
|
+
|
|
237
|
+
const hit = classifyPrompt(prompt);
|
|
238
|
+
if (!hit) {
|
|
239
|
+
// Lexical miss: the compact semantic net (see engineMissFallback) — never the
|
|
240
|
+
// old multi-KB self-check block, and nothing at all when the layer is off.
|
|
241
|
+
const missContext = await engineMissFallback(prompt);
|
|
242
|
+
// The semantic-miss rung fired (substantive, non-envelope prompt, layer on) = idea-bearing.
|
|
243
|
+
const missNote = await verbatimStage(payload, prompt, Boolean(missContext), { directiveIfNoCapture: Boolean(missContext), armObligation: Boolean(missContext) });
|
|
244
|
+
emit([missContext, missNote].filter(Boolean).join(NOTE_SEP));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const behaviorKey = behaviorKeyForRuleId(hit.rule);
|
|
249
|
+
if (!behaviorKey) return; // an engine rule id this router doesn't know as a behavior
|
|
250
|
+
|
|
251
|
+
if (!isBehaviorEligible(behaviorKey, prompt)) return; // env-off / config-disabled / injected-content guard
|
|
252
|
+
|
|
253
|
+
const directive = buildConfirmedDirective(behaviorKey, undefined, prompt);
|
|
254
|
+
if (!directive) return;
|
|
255
|
+
|
|
256
|
+
appendClassifierEvent({
|
|
257
|
+
surface: 'userpromptsubmit',
|
|
258
|
+
rule: hit.rule,
|
|
259
|
+
layer: 'lexical',
|
|
260
|
+
decision: 'fire',
|
|
261
|
+
engine: true,
|
|
262
|
+
prompt_chars: prompt.length,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const hitNote = await verbatimStage(payload, prompt, IDEA_BEARING_BEHAVIORS.has(behaviorKey), {
|
|
266
|
+
armObligation: behaviorKey === 'capture_on_idea',
|
|
267
|
+
});
|
|
268
|
+
emit([directive, hitNote].filter(Boolean).join(NOTE_SEP));
|
|
269
|
+
} catch {
|
|
270
|
+
// Never break Claude Code on a hook failure.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const engineForcedOff = process.env.HEYSPEX_INTENT_ENGINE_OFF === '1';
|
|
275
|
+
if (!engineForcedOff && isConfigKeyEnabled(ENGINE_FLAG)) {
|
|
276
|
+
runEngineClassifierHook();
|
|
277
|
+
} else {
|
|
278
|
+
runClassifierHook();
|
|
279
|
+
}
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
// Bearer for /api/agent/session-end — same reader every other consumer uses, never
|
|
68
68
|
// re-implemented and never printed.
|
|
69
69
|
import { readCredentialsMcpToken } from '../lib/dev-mode.mjs';
|
|
70
|
+
import { hookVaultHeaders } from '../lib/hook-vault-header.mjs';
|
|
70
71
|
import {
|
|
71
72
|
appendSessionRecord,
|
|
72
73
|
createSessionStore,
|
|
@@ -152,6 +153,7 @@ function postSessionEnd(consoleUrl, body, token) {
|
|
|
152
153
|
'content-type': 'application/json',
|
|
153
154
|
'content-length': Buffer.byteLength(payload),
|
|
154
155
|
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
156
|
+
...hookVaultHeaders(),
|
|
155
157
|
},
|
|
156
158
|
timeout: REQUEST_TIMEOUT_MS,
|
|
157
159
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
|
|
3
|
+
// This file only wires Claude Code's hook stdin/stdout contract to the
|
|
4
|
+
// spexharness engine port of this gate. To change what this gate DECIDES,
|
|
5
|
+
// edit spexharness/src/gates/vault-mirror-catchup.mjs. To change the SHAPE of this shell,
|
|
6
|
+
// edit the generator, then rerun: node scripts/generate-hook-shells.mjs
|
|
7
|
+
|
|
8
|
+
import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
|
|
9
|
+
import gate from '../../spexharness/src/gates/vault-mirror-catchup.mjs';
|
|
10
|
+
|
|
11
|
+
await runGateAsHook(gate);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// GENERATED by scripts/generate-hook-shells.mjs (HS-IQBF) — do not hand-edit.
|
|
3
|
+
// This file only wires Claude Code's hook stdin/stdout contract to the
|
|
4
|
+
// spexharness engine port of this gate. To change what this gate DECIDES,
|
|
5
|
+
// edit spexharness/src/gates/vault-write-tier-guard.mjs. To change the SHAPE of this shell,
|
|
6
|
+
// edit the generator, then rerun: node scripts/generate-hook-shells.mjs
|
|
7
|
+
|
|
8
|
+
import { runGateAsHook } from '../../spexharness/src/adapters/claude-code.mjs';
|
|
9
|
+
import gate from '../../spexharness/src/gates/vault-write-tier-guard.mjs';
|
|
10
|
+
|
|
11
|
+
await runGateAsHook(gate);
|
|
@@ -49,6 +49,7 @@ import fs from 'node:fs';
|
|
|
49
49
|
import path from 'node:path';
|
|
50
50
|
import { fileURLToPath } from 'node:url';
|
|
51
51
|
import { getActiveConfig } from '../../vendor/heyspex-mcp/vault-resolve.mjs';
|
|
52
|
+
import { hookVaultHeaders } from '../lib/hook-vault-header.mjs';
|
|
52
53
|
import { resolveConsoleUrl as resolveConsoleUrlShared } from '../lib/console-url-resolver.mjs';
|
|
53
54
|
import { readCredentialsMcpToken, resolveDevMode } from '../lib/dev-mode.mjs';
|
|
54
55
|
|
|
@@ -214,7 +215,10 @@ async function fetchCloudLedgerEntry(root) {
|
|
|
214
215
|
timer.unref?.();
|
|
215
216
|
try {
|
|
216
217
|
const res = await fetch(`${consoleUrl}/api/wip`, {
|
|
217
|
-
|
|
218
|
+
// Repo-bound vault id (P1): without it the console refuses a bearer
|
|
219
|
+
// caller (409 vault-scope-required) and this preview silently fell back
|
|
220
|
+
// to a stale disk ledger (diag HS-5M0G).
|
|
221
|
+
headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...hookVaultHeaders() },
|
|
218
222
|
signal: controller.signal,
|
|
219
223
|
});
|
|
220
224
|
if (!res.ok) return null;
|