heyspex 0.2.2 → 0.2.4

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 (79) hide show
  1. package/.claude-plugin/marketplace.json +2 -1
  2. package/README.md +11 -0
  3. package/bin/hs-vault-binding.mjs +157 -0
  4. package/bin/hs.mjs +83 -79
  5. package/bin/spexcode.mjs +91 -0
  6. package/dev-factory/engine/worktree.mjs +36 -7
  7. package/dev-factory/lib/epic-detect.mjs +2 -2
  8. package/dev-factory/lib/fleet-readiness.mjs +3 -5
  9. package/dev-factory/lib/fleet-state.mjs +2 -2
  10. package/dev-factory/lib/sufficiency-score.mjs +3 -5
  11. package/dist/cli/hs-check.mjs +76 -46
  12. package/dist/cli/hs-claim.mjs +63 -33
  13. package/dist/cli/hs-connect.mjs +520 -214
  14. package/dist/cli/hs-defer.mjs +161 -19
  15. package/dist/cli/hs-dev.mjs +11 -5
  16. package/dist/cli/hs-env.mjs +187 -161
  17. package/dist/cli/hs-index-rebuild.mjs +123 -78
  18. package/dist/cli/hs-index-reconcile.mjs +203 -107
  19. package/dist/cli/hs-init.mjs +426 -179
  20. package/dist/cli/hs-install.mjs +759 -437
  21. package/dist/cli/hs-join.mjs +590 -383
  22. package/dist/cli/hs-vault-pull.mjs +359 -254
  23. package/dist/cli/hs-vault-push.mjs +151 -113
  24. package/dist/cli/hs-vault.mjs +271 -104
  25. package/dist/cli/hs-workspace.mjs +90 -57
  26. package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +178 -91
  27. package/docs/copilot-plugin-install.md +200 -177
  28. package/docs/driving-heyspex-from-copilot.md +174 -165
  29. package/docs/mcp-client-config.md +93 -25
  30. package/package.json +257 -250
  31. package/plugins/heyspex/.claude-plugin/plugin.json +13 -12
  32. package/plugins/heyspex/.cursor-plugin/plugin.json +16 -15
  33. package/plugins/heyspex/.mcp.json +3 -4
  34. package/plugins/heyspex/README.md +323 -312
  35. package/plugins/heyspex/assets/heyspex-glyph-128.png +0 -0
  36. package/plugins/heyspex/assets/heyspex-glyph-256.png +0 -0
  37. package/plugins/heyspex/assets/heyspex-glyph.svg +12 -0
  38. package/plugins/heyspex/bin/launch-mcp.mjs +21 -7
  39. package/plugins/heyspex/mcp.json +12 -13
  40. package/scripts/check-mcp-freshness.mjs +6 -4
  41. package/scripts/claude-hooks/_intent-classifier-router.mjs +518 -456
  42. package/scripts/claude-hooks/intent-classifier-trigger.mjs +305 -279
  43. package/scripts/claude-hooks/research-method-guard.mjs +2 -2
  44. package/scripts/claude-hooks/wip-checkpoint.mjs +33 -9
  45. package/scripts/claude-statusline/fonts/HeySpexIcons.ttf +0 -0
  46. package/scripts/claude-statusline/statusline-command.mjs +1073 -0
  47. package/scripts/copilot-statusline.mjs +130 -0
  48. package/scripts/dev-server-status.mjs +3 -2
  49. package/scripts/dev-server-stop.mjs +3 -2
  50. package/scripts/hs-capture-cli.mjs +2 -2
  51. package/scripts/hs-settings.mjs +2 -2
  52. package/scripts/hs-view.mjs +3 -2
  53. package/scripts/install-copilot-hooks.mjs +152 -36
  54. package/scripts/lib/copilot-cli.mjs +108 -0
  55. package/scripts/lib/customer-kit.mjs +223 -30
  56. package/scripts/lib/dev-mode.mjs +28 -2
  57. package/scripts/lib/shared-path-helpers.mjs +32 -0
  58. package/scripts/lib/standalone-doctor.mjs +131 -32
  59. package/scripts/wip-ledger-health.mjs +3 -2
  60. package/spexharness/src/gates/vault-mirror-catchup.mjs +113 -24
  61. package/spexharness/src/gates/vault-mirror-catchup.runner.mjs +52 -0
  62. package/spexharness/src/intent-directive.mjs +839 -626
  63. package/spexharness/src/rule-table.mjs +1 -1
  64. package/vendor/heyspex-mcp/cli-verbs.mjs +16 -2
  65. package/vendor/heyspex-mcp/cli.mjs +20 -4
  66. package/vendor/heyspex-mcp/console-fetch.mjs +15 -3
  67. package/vendor/heyspex-mcp/dev-tools.mjs +11 -4
  68. package/vendor/heyspex-mcp/events-tools.mjs +1 -1
  69. package/vendor/heyspex-mcp/factory-run-status.mjs +42 -0
  70. package/vendor/heyspex-mcp/http-transport.mjs +6 -1
  71. package/vendor/heyspex-mcp/mcp-config.mjs +4 -2
  72. package/vendor/heyspex-mcp/server-identity.mjs +13 -0
  73. package/vendor/heyspex-mcp/spec-edit-tools.mjs +26 -1
  74. package/vendor/heyspex-mcp/test-helpers.mjs +5 -2
  75. package/vendor/heyspex-mcp/tool-catalog.mjs +12 -0
  76. package/vendor/heyspex-mcp/tool-dispatch.mjs +63 -23
  77. package/vendor/heyspex-mcp/usable-dir-env.mjs +82 -0
  78. package/vendor/heyspex-mcp/vault-binding.mjs +839 -0
  79. package/vendor/heyspex-mcp/vault-resolve.mjs +353 -245
@@ -1,312 +1,323 @@
1
- # SpexCode plugin (for Claude Code and GitHub Copilot CLI) — Claude Code + Copilot CLI + Cursor (prototype)
2
-
3
- Prototype packaging of HeySpex's agent integration as a self-hosted plugin for **both** hosts,
4
- implementing migration-path steps 1–2 of [[HS-QTBK-5EK5-RES-claude-code-plugin-packaging-for]].
5
- **Additive:** the project-level wiring stays live and remains the source of truth
6
- (`.mcp.json` + `.claude/settings.json` for Claude Code; `.cursor/mcp.json` +
7
- `.cursor/hooks.json` for Cursor). Nothing here activates until the plugin is explicitly installed.
8
-
9
- One plugin directory, two manifests — the hosts read different files and never see each other's.
10
-
11
- ## Layout
12
-
13
- | Path | Host | What it is |
14
- | --- | --- | --- |
15
- | `.claude-plugin/plugin.json` | Claude Code | Plugin manifest (`name: heyspex`). |
16
- | `.cursor-plugin/plugin.json` | Cursor | Plugin manifest. Names `mcp.json` and `hooks/cursor-hooks.json` EXPLICITLY, because a Cursor manifest path replaces auto-discovery — without it Cursor would find `hooks/hooks.json` and try to read Claude-shaped config. |
17
- | `.mcp.json` | Claude Code | MCP server declaration — launches `bin/launch-mcp.mjs` via `${CLAUDE_PLUGIN_ROOT}`. |
18
- | `mcp.json` | Cursor | MCP server declaration. Runs `${CURSOR_PLUGIN_ROOT}/bin/launch-mcp.mjs` (Cursor expands that variable to the plugin install path), the same launcher `.mcp.json` uses, so it boots from a plain `npm install heyspex`; `HEYSPEX_VAULT_ROOT` is `${workspaceFolder}`. |
19
- | `bin/launch-mcp.mjs` | Claude Code | Resolver shim: locates `vendor/heyspex-mcp/cli.mjs` in the host vault repo (`HEYSPEX_MCP_ENTRY` override → `$CLAUDE_PROJECT_DIR` → `$CURSOR_PROJECT_DIR` → `$HEYSPEX_VAULT_ROOT` → cwd) and hands off in-process. Needed because the server's deps come from the repo's `node_modules`, so it can't yet run from the plugin's versioned cache copy. |
20
- | `hooks/hooks.json` | Claude Code, Copilot CLI | **Generated** from the customer-scoped rows of `scripts/lib/customer-kit.mjs` (`node scripts/lib/customer-kit.mjs --write-plugin-hooks`; a test fails on drift). Every command is a self-locating inline bootstrap (plugin-root env `CLAUDE_PLUGIN_ROOT`/`COPILOT_PLUGIN_ROOT`/`PLUGIN_ROOT` → `bin/run-hook.mjs`, else a walk up from the cwd to `scripts/claude-hooks/_run.mjs`, else exit 0) that reaches `bin/run-hook.mjs`, which finds the platform checkout (`HEYSPEX_PLATFORM_ROOT` → `HEYSPEX_MCP_ENTRY` → its own checkout → `node_modules/heyspex` → `$CLAUDE_PROJECT_DIR`) — never `$CLAUDE_PROJECT_DIR/scripts`, which only exists in heyspex-app. MCP-tool matchers carry both `mcp__heyspex__*` and `mcp__plugin_heyspex_heyspex__*` names. |
21
- | `.github/hooks/heyspex.json` (repo root) | Copilot cloud agent | **Generated** by `npm run hooks:write-github`; bash-only, default branch only. See "Copilot cloud agent" below. |
22
- | `bin/run-hook.mjs` | Claude Code | Hook launcher: resolves the platform shim `scripts/claude-hooks/_run.mjs` and hands off in-process (fail-open, like the shim). |
23
- | `hooks/cursor-hooks.json` | Cursor | **Generated** from `scripts/cursor-hooks/manifest.mjs` by `npm run hooks:install-cursor`; drift is a `hs doctor` failure. Commands resolve through `$CURSOR_PROJECT_DIR`. |
24
-
25
- Each host has its own marketplace catalog at the repo root, both pointing at this directory:
26
- `.claude-plugin/marketplace.json` and `.cursor-plugin/marketplace.json` — the documented
27
- self-hosted single-repo pattern; no hosted registry involved.
28
-
29
- ## Installing
30
-
31
- ### From the npm package (customers; no heyspex-app checkout)
32
-
33
- A team that only installs the published `heyspex` package does not have, and does not need, the
34
- heyspex-app repository. The package carries the marketplace (`node_modules/heyspex/.claude-plugin/marketplace.json`,
35
- plugin source `./plugins/heyspex`), the hooks and the MCP server. From the root of your own repo:
36
-
37
- ```bash
38
- npm install heyspex # or: npm install ./heyspex-<version>.tgz
39
- npx hs login
40
- npx heyspex setup copilot # GitHub Copilot CLI: plugin route ...
41
- npx heyspex setup copilot --hooks # ... OR cloud agent: .github/hooks/heyspex.json, never both
42
- npx heyspex setup copilot --print # paste this to your agent: next command + the full guide
43
- npx hs install --target . --plugin-apply # Claude Code
44
- ```
45
-
46
- `setup copilot --check` shows which route is active; `--uninstall` removes the hooks file and prints `copilot plugin uninstall heyspex`.
47
-
48
- Fallback, by hand: `claude plugin marketplace add ./node_modules/heyspex` /
49
- `copilot plugin marketplace add ./node_modules/heyspex`, then `... plugin install heyspex@heyspex-marketplace`.
50
- Restart the session (`/restart` in Copilot), then check `copilot plugin list` and `npx hs doctor`.
51
- Full walkthrough: `node_modules/heyspex/docs/copilot-plugin-install.md`. The sections below describe the same plugin from the
52
- platform checkout and are for HeySpex developers.
53
-
54
- ### Claude Code
55
-
56
- One command, from the platform checkout (wraps the manual flow below):
57
-
58
- ```bash
59
- node bin/hs.mjs install --target <consumer-repo> --plugin-apply
60
- ```
61
-
62
- `--plugin` prints the commands instead of running them. The equivalent manual flow, inside a
63
- Claude Code session in the consumer repo — or via the `claude plugin` CLI:
64
-
65
- ```
66
- /plugin marketplace add <path-or-git-url-of-this-repo>
67
- /plugin install heyspex@heyspex-marketplace
68
- ```
69
-
70
- ### GitHub Copilot CLI
71
-
72
- Copilot CLI installs Claude-format plugins (`.claude-plugin/plugin.json`) from a marketplace
73
- (`.claude-plugin/marketplace.json` is on its search path). Source of truth for the commands and the
74
- file layout: <https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference>
75
- (fetched 2026-10-16). Hook behaviour: <https://docs.github.com/en/copilot/reference/hooks-configuration>.
76
-
77
- ```bash
78
- copilot plugin marketplace add <owner>/<repo> # or a git URL or a local path to this repo
79
- copilot plugin install heyspex@heyspex-marketplace # SPECIFICATION is plugin@marketplace
80
- copilot plugin list # confirm it is enabled
81
- ```
82
-
83
- For local development: `copilot plugin marketplace add /path/to/heyspex-app`; a path-sourced plugin in a local
84
- marketplace loads live (`/restart` picks up edits).
85
-
86
- What gets loaded:
87
-
88
- | Component | Loaded from | Notes |
89
- | --- | --- | --- |
90
- | Hooks | `hooks/hooks.json` | Same file Claude Code reads. PascalCase events (`PreToolUse`, `PostToolUse`, `Stop`...) use Claude matcher semantics and snake_case payloads. Every command is a self-locating inline bootstrap: it needs no `$CLAUDE_PROJECT_DIR` and exits 0 when it cannot find the runtime (Copilot turns any non-zero exit into a deny). |
91
- | MCP | `.mcp.json` | Legacy-plugin MCP file. Its `${CLAUDE_PLUGIN_ROOT}` expansion under Copilot is **not documented** (Copilot documents `${PLUGIN_ROOT}` for Agent Plugins `mcp.json` only) and could not be verified without a Copilot CLI on the build machine. If the server does not start, add it explicitly: `copilot mcp add heyspex -- node <install-path>/plugins/heyspex/bin/launch-mcp.mjs`. |
92
- | Skills / prompts | MCP prompts (`hs-*`) | Delivered by the MCP server. **The Copilot cloud coding agent has no prompts**; there use the `hs` CLI loop in `docs/driving-heyspex-from-copilot.md`. |
93
- | Commands | not loaded | Claude Code slash commands are not a Copilot component. |
94
-
95
- ### One source of hooks: plugin OR repo hooks, never both
96
-
97
- Copilot CLI (and Claude Code) load hooks from the plugin AND from the repository's own registrations
98
- (`.claude/settings.json`, `.claude/settings.local.json`, `.github/hooks/*.json`). A repo that has both runs every HeySpex
99
- hook twice. Pick one per repo:
100
-
101
- - **Plugin** (this directory): `copilot plugin install heyspex@heyspex-marketplace` / `/plugin install ...`. Do not run
102
- `hs install` hook registration in the same repo, or strip it afterwards.
103
- - **Repo hooks** (`hs install`, which writes `.claude/settings.json` + the committed launcher): do not also install the plugin.
104
-
105
- `npm run doctor` has a `hook-double-fire` check: it warns when HeySpex rows in the repo's `.claude/settings*.json` or
106
- `.github/hooks/*.json` coexist with an installed heyspex plugin, and reports `unknown` (never a failure) when no plugin
107
- registry can be read. What it looks at: `~/.claude/plugins/installed_plugins.json` (a `heyspex@...` key),
108
- `enabledPlugins` in `~/.claude/settings.json` and the project's Claude settings, and, best effort, a heyspex directory
109
- under `~/.copilot/installed-plugins` or `~/.copilot/plugins` (the Copilot plugin list file is not documented; `copilot
110
- plugin list` is the authority). As a runtime backstop, `_run.mjs` also de-duplicates a repeated Copilot invocation
111
- (`HEYSPEX_COPILOT_DEDUPE_OFF=1` disables that).
112
-
113
- ### Copilot cloud agent: `.github/hooks/heyspex.json`
114
-
115
- The Copilot **cloud agent** loads no plugins and no user-level hook files; its only hook source is `.github/hooks/*.json`
116
- **on the default branch**. `.github/hooks/heyspex.json` is therefore generated from the same customer rows as
117
- `hooks/hooks.json`, restricted to the hooks whose Copilot disposition is `mapped` (plus `intent-classifier-trigger`, which
118
- only arms the capture obligation state and whose dropped output does not matter):
119
-
120
- ```bash
121
- npm run hooks:write-github # node scripts/lib/customer-kit.mjs --write-github-hooks (a test fails on drift)
122
- npm run hooks:check-github # exit 1 when stale
123
- ```
124
-
125
- - Linux bash only: every entry is `type: "command"` with a `bash` command that resolves
126
- `node_modules/heyspex/scripts/claude-hooks/_run.mjs`, then `./scripts/claude-hooks/_run.mjs`, else `exit 0` (fail open).
127
- Events are the camelCase names (`sessionStart`, `preToolUse`, `postToolUse`, `userPromptSubmitted`, `agentStop`,
128
- `sessionEnd`, `preCompact`); matchers use Copilot tool names (`create|edit|bash|powershell|task`, MCP tools as a
129
- `.*<tool>` suffix).
130
- - The cloud sandbox must contain `node_modules/heyspex` (run `npm install` in the `copilot-setup-steps` workflow), or the
131
- hooks silently no-op.
132
- - The firewall must allow the console URL (`emit-claim-status` and claim heartbeats call it), or those hooks time out (timeouts fail open).
133
- - The file only takes effect once it is on the repository's **default branch**.
134
- - If the repo also carries `.claude/settings.json` HeySpex rows, Copilot CLI reads both files: that is the double-fire case
135
- above. Use the manifest for the cloud agent only, or rely on the shim de-duplication.
136
- - MCP is configured in the repository's Copilot settings (tools only, no OAuth), not by a committed file.
137
-
138
- ### Gates that are weaker or absent on Copilot (published list)
139
-
140
- Parity is not identity. Where Copilot has a trigger the gate is a hard deny or re-drive; the rows below are the exceptions.
141
- The list is generated from `scripts/lib/hook-dispositions.mjs` (the single three-host table, which also covers Claude Code
142
- and Cursor) and a test fails when this block is stale. The hard floor under all of it is the MCP server itself:
143
- `heyspex_claim_spec`, `heyspex_submit_for_review` and `heyspex_save_research` refuse without a preflight marker on every
144
- surface that connects the MCP server, with no hook involved.
145
-
146
- <!-- hook-dispositions:copilot:begin (generated by scripts/lib/hook-dispositions.test.mjs; UPDATE_HOOK_DOCS=1 refreshes) -->
147
- | Gate | Disposition | Compensating control / what is lost |
148
- | --- | --- | --- |
149
- | `research-method-guard` | unportable | MCP preflight refusal on heyspex_save_research (hard floor, hook-independent) + .github/copilot-instructions.md research rule; Copilot has no Skill or Workflow tool to hook. |
150
- | `deep-research-cost-gate` | unportable | MCP preflight refusal on heyspex_save_research + .github/copilot-instructions.md cost-confirmation rule; Copilot has no Skill or Workflow tool to hook. |
151
- | `plan-review-gate` | unportable | MCP preflight refusals (heyspex_claim_spec / heyspex_submit_for_review / heyspex_save_research) + .github/copilot-instructions.md plan rule (state the plan in chat, claim the spec before editing) + HS-20SW directive-obligation-gate claim-before-build deny |
152
- | `plan-exit-ingest-gate` | unportable | MCP preflight refusals (heyspex_claim_spec / heyspex_submit_for_review / heyspex_save_research) + .github/copilot-instructions.md plan rule (state the plan in chat, claim the spec before editing) + HS-20SW directive-obligation-gate claim-before-build deny |
153
- | `preflight-reminder` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. Real enforcement is the MCP preflight refusal. |
154
- | `intent-classifier-trigger` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. The hook still runs and arms obligation state; the HS-20SW directive-obligation-gate turns that state into a hard deny. |
155
- | `inject-communication-style` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. |
156
- | `inject-vault-context` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. Per-prompt vault retrieval has no Copilot channel at all. |
157
- | `claim-before-code-gate` | advisory-degraded | Armed only by plan-exit-ingest-gate, which has no Copilot trigger (no ExitPlanMode tool), so it stays dormant; covered by the HS-20SW claim-before-build rule. |
158
- | `diagnose-capture-reminder` | advisory-degraded | Copilot preToolUse has no context channel for an inject, so this advisory is silent (lost). |
159
- <!-- hook-dispositions:copilot:end -->
160
-
161
- Unverified on Copilot (no official page read, or not run live): the VS Code 128-tool-per-request cap; the spelling of MCP
162
- tool names inside hook payloads (matched by suffix); `${CLAUDE_PLUGIN_ROOT}` expansion for the plugin `.mcp.json` under
163
- the Copilot CLI (documented for VS Code only); and how the MCP server learns the Copilot session id (its preflight marker
164
- falls back to a 4 hour TTL when the runtime exposes none). Also unverified: that a Claude-format `hookSpecificOutput` deny is
165
- read by the CLI (only the top-level `permissionDecision` form and exit 2 are documented), and where a `matcher` is read
166
- in the camelCase `.github/hooks` entry shape.
167
-
168
- ### Turning HeySpex on and off (any harness)
169
-
170
- One switch, `hs mode` (alias `heyspex mode`; from a checkout without `hs` on PATH: `node bin/hs.mjs mode ...`):
171
-
172
- ```bash
173
- hs mode status # on | off, and why
174
- hs mode off # writes <project>/.heyspex/mode.json; every hook exits 0 at once (no restart)
175
- hs mode on
176
- hs mode toggle
177
- ```
178
-
179
- - `HEYSPEX_MODE=off` silences hooks for one shell/session without touching a file (wins over the file).
180
- - `HEYSPEX_COPILOT_HOOKS_OFF=1` (or `agent_behaviors.harness_hooks.copilot.enabled: false` in the vault `.heyspex/config.yaml`)
181
- silences every hook **only when the caller is GitHub Copilot**; Claude Code and Cursor keep theirs.
182
- - The switch is read from the PROJECT being worked on (`CLAUDE_PROJECT_DIR`, then the hook payload `cwd`, then upward
183
- from the working directory), so `hs mode off` in a customer repo works even though the hooks run from `node_modules/heyspex`.
184
- - Four security gates ignore the switch by design (`env-vault-guard`, `research-method-guard`, `research-protocol-gate`,
185
- `git-identity-guard`): an agent must not be able to disarm them by writing one JSON file. Their own env-var
186
- break-glass (set by a human) still applies. The Copilot opt-out does not cover them either unless the owner extends it.
187
-
188
- ### Cursor
189
-
190
- For local development, symlink or copy this directory into Cursor's local plugin root and
191
- reload the window (**Developer: Reload Window**):
192
-
193
- ```
194
- ~/.cursor/plugins/local/heyspex
195
- ```
196
-
197
- Otherwise add the repo as a marketplace source from Cursor's plugin surface; the root
198
- `.cursor-plugin/marketplace.json` catalogs it.
199
-
200
- **In the platform repo itself you do not need the plugin at all** — `.cursor/hooks.json`,
201
- `.cursor/mcp.json`, `.cursor/rules/`, `.cursor/skills/` and `.cursor/commands/` are already
202
- installed and are the live path. The plugin exists for CONSUMER repos.
203
-
204
- ## Running Claude Code against Ollama or DeepSeek
205
-
206
- Claude Code can be pointed at a local Ollama instance or DeepSeek's cloud API through
207
- `ANTHROPIC_BASE_URL`: both expose an Anthropic Messages API-compatible endpoint, so Claude
208
- Code talks to them directly with no proxy. The recipes below are verified against each
209
- vendor's own docs ([[HS-REF-pointing-claude-code-at-non-anthropic]]).
210
-
211
- ### Ollama (local)
212
-
213
- Requires Ollama 0.14+, which added a native Anthropic-compatible endpoint. The auth token is
214
- required but ignored.
215
-
216
- ```bash
217
- # one-shot
218
- ollama launch claude --model qwen3-coder
219
-
220
- # manual
221
- export ANTHROPIC_AUTH_TOKEN=ollama
222
- export ANTHROPIC_API_KEY=""
223
- export ANTHROPIC_BASE_URL=http://localhost:11434
224
- claude --model qwen3-coder
225
- ```
226
-
227
- ### DeepSeek (cloud)
228
-
229
- ```bash
230
- export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
231
- export ANTHROPIC_API_KEY=your-deepseek-api-key
232
- claude
233
- ```
234
-
235
- The `/anthropic` path is required — the bare host `https://api.deepseek.com` (without
236
- `/anthropic`) is not the Anthropic-compatible endpoint. DeepSeek's own docs specify
237
- `ANTHROPIC_API_KEY`, not `ANTHROPIC_AUTH_TOKEN`.
238
-
239
- ### Model name mapping (DeepSeek)
240
-
241
- There is no `ANTHROPIC_MODEL` var to set manually — model choice is automatic name-mapping:
242
- Claude Opus names map to `deepseek-v4-pro` (billed at V4 Pro price), while every other Claude
243
- name (Haiku/Sonnet) and any unmapped name maps to `deepseek-flash`.
244
-
245
- ### Known DeepSeek limitations
246
-
247
- - `top_p` fixed at 1.0 outside thinking mode (min 0.95 inside it).
248
- - Unsupported Anthropic content types: documents, search results, redacted thinking, code
249
- execution results, MCP tool integrations, container uploads.
250
- - `budget_tokens` in thinking-mode config is ignored.
251
-
252
- ### Enforcement is independent of the backend
253
-
254
- HeySpex hooks fire regardless of backend: Claude Code hooks fire at session, turn, and
255
- tool-call points of Claude Code's own loop, so every HeySpex hook in `.claude/settings.json`
256
- and `plugins/heyspex/hooks/` runs unchanged regardless of backend — see the
257
- [Claude Code hooks reference](https://code.claude.com/docs/en/hooks).
258
-
259
- ### Launcher
260
-
261
- `hs next <id> --launch --backend ollama|deepseek` sets the right env vars and execs `claude`.
262
- This is the supported path, shipping with epic
263
- [[HS-30JD-94Q5-EPIC-claude-code-launches-against-ollama-or]]; until then, use the manual
264
- recipe above.
265
-
266
- ## Validating (migration-path step 3)
267
-
268
- Run the install above in a **fresh clone / separate consumer repo** (avoids the double-hook
269
- caveat below), after `npm install` in the platform checkout.
270
-
271
- Then restart the session and confirm:
272
-
273
- 1. The MCP server connects and tools appear as `mcp__plugin_heyspex_heyspex__heyspex_*`.
274
- 2. Hooks fire (SessionStart seat injection, PreToolUse gates) — and only once.
275
- 3. The re-prefix blast radius: anything load-bearing that hardcodes `mcp__heyspex__*`
276
- (CLAUDE.md, `.claude/settings.json` permission allow/deny lists, procedures, hook scripts)
277
- still behaves. This is the migration cost the research flags as largest.
278
-
279
- ## Known caveats (by design, this stage)
280
-
281
- - **Double-fire:** enabling this plugin in a repo that still registers the same hooks project-level
282
- runs every hook twice. Single-source rule: plugin OR repo hooks, never both (see above; `npm run doctor` warns).
283
- Validate in a fresh clone, or strip the project hooks first (`node scripts/install-cursor-hooks.mjs --uninstall` for Cursor).
284
- - **Not standalone:** the plugin requires a HeySpex vault checkout with `npm install` run —
285
- the MCP server and hook scripts resolve from the host project, not the plugin cache.
286
- Bundling the server into the plugin is a full-cutover-stage task.
287
- - **`hs-*` skills stay MCP prompts** (research step 4, unresolved): they travel with the MCP
288
- server, not as plugin `SKILL.md` files. Commands, rules and agents are deliberately not
289
- duplicated here to avoid drift and double registration — which matters more for Cursor, whose
290
- plugin format CAN carry `rules/`, `commands/` and `skills/`. Bundling them is a cutover decision,
291
- not a packaging one.
292
- - **Permissions/statusLine are not mirrored** — those remain project-level concerns.
293
- - **Manual updates are two steps and scope-sensitive:** `claude plugin marketplace update
294
- heyspex-marketplace` refreshes the catalog only; the installed copy moves on
295
- `claude plugin update heyspex@heyspex-marketplace --scope project` (the CLI defaults to
296
- `--scope user` and errors "not installed at scope user" on a project-scoped install).
297
- Background auto-update does both, and the new version applies at restart or `/reload-plugins`.
298
- Version/auto-update findings: `specs/memory/decisions/claude-code-plugin-auto-update-third.md`.
299
- - **Two hooks have no Cursor trigger** (`plan-review-gate`, `plan-exit-ingest-gate`): Cursor
300
- exposes no `ExitPlanMode` tool call to hooks, so the plan-review gate is enforced by
301
- `.cursor/rules/plan-review-required.mdc` instead. Tracked in
302
- `scripts/cursor-hooks/manifest.mjs` → `UNPORTABLE`; hooks that run weaker are in `DEGRADED` there.
303
- Four hooks have no Copilot trigger (see the published list above).
304
- - **Backend choice is separate from enforcement:** running the Claude Code host against a local
305
- or cloud non-Anthropic model disables no hook — see
306
- [Running Claude Code against Ollama or DeepSeek](#running-claude-code-against-ollama-or-deepseek)
307
- for the recipes.
308
-
309
- ## Cutover criteria (research steps 4–5)
310
-
311
- Only after the validation above passes end-to-end and the skills-vs-prompts question is settled
312
- does removing the project-level wiring (or extending this to customer vaults) become a decision.
1
+ # HeySpex plugin — Claude Code + Copilot CLI + Cursor (prototype)
2
+
3
+ Prototype packaging of HeySpex's agent integration as a self-hosted plugin for **both** hosts,
4
+ implementing migration-path steps 1–2 of [[HS-QTBK-5EK5-RES-claude-code-plugin-packaging-for]].
5
+ **Additive:** the project-level wiring stays live and remains the source of truth
6
+ (`.mcp.json` + `.claude/settings.json` for Claude Code; `.cursor/mcp.json` +
7
+ `.cursor/hooks.json` for Cursor). Nothing here activates until the plugin is explicitly installed.
8
+
9
+ One plugin directory, two manifests — the hosts read different files and never see each other's.
10
+
11
+ ## Layout
12
+
13
+ | Path | Host | What it is |
14
+ | --- | --- | --- |
15
+ | `.claude-plugin/plugin.json` | Claude Code | Plugin manifest (`name: heyspex`). |
16
+ | `.cursor-plugin/plugin.json` | Cursor | Plugin manifest. Names `mcp.json` and `hooks/cursor-hooks.json` EXPLICITLY, because a Cursor manifest path replaces auto-discovery — without it Cursor would find `hooks/hooks.json` and try to read Claude-shaped config. |
17
+ | `.mcp.json` | Claude Code | MCP server declaration — launches `bin/launch-mcp.mjs` via `${CLAUDE_PLUGIN_ROOT}`. |
18
+ | `mcp.json` | Cursor | MCP server declaration. Runs `${CURSOR_PLUGIN_ROOT}/bin/launch-mcp.mjs` (Cursor expands that variable to the plugin install path), the same launcher `.mcp.json` uses, so it boots from a plain `npm install heyspex`; `HEYSPEX_VAULT_ROOT` is `${workspaceFolder}`. |
19
+ | `bin/launch-mcp.mjs` | Claude Code | Resolver shim: locates `vendor/heyspex-mcp/cli.mjs` in the host vault repo (`HEYSPEX_MCP_ENTRY` override → `$CLAUDE_PROJECT_DIR` → `$CURSOR_PROJECT_DIR` → `$HEYSPEX_VAULT_ROOT` → cwd) and hands off in-process. Needed because the server's deps come from the repo's `node_modules`, so it can't yet run from the plugin's versioned cache copy. |
20
+ | `hooks/hooks.json` | Claude Code, Copilot CLI | **Generated** from the customer-scoped rows of `scripts/lib/customer-kit.mjs` (`node scripts/lib/customer-kit.mjs --write-plugin-hooks`; a test fails on drift). Every command is a self-locating inline bootstrap (plugin-root env `CLAUDE_PLUGIN_ROOT`/`COPILOT_PLUGIN_ROOT`/`PLUGIN_ROOT` → `bin/run-hook.mjs`, else a walk up from the cwd to `scripts/claude-hooks/_run.mjs`, else exit 0) that reaches `bin/run-hook.mjs`, which finds the platform checkout (`HEYSPEX_PLATFORM_ROOT` → `HEYSPEX_MCP_ENTRY` → its own checkout → `node_modules/heyspex` → `$CLAUDE_PROJECT_DIR`) — never `$CLAUDE_PROJECT_DIR/scripts`, which only exists in heyspex-app. MCP-tool matchers carry both `mcp__heyspex__*` and `mcp__plugin_heyspex_heyspex__*` names. |
21
+ | `.github/hooks/heyspex.json` (repo root) | Copilot cloud agent | **Generated** by `npm run hooks:write-github`; bash-only, default branch only. See "Copilot cloud agent" below. |
22
+ | `bin/run-hook.mjs` | Claude Code | Hook launcher: resolves the platform shim `scripts/claude-hooks/_run.mjs` and hands off in-process (fail-open, like the shim). |
23
+ | `hooks/cursor-hooks.json` | Cursor | **Generated** from `scripts/cursor-hooks/manifest.mjs` by `npm run hooks:install-cursor`; drift is a `hs doctor` failure. Commands resolve through `$CURSOR_PROJECT_DIR`. |
24
+
25
+ Each host has its own marketplace catalog at the repo root, both pointing at this directory:
26
+ `.claude-plugin/marketplace.json` and `.cursor-plugin/marketplace.json` — the documented
27
+ self-hosted single-repo pattern; no hosted registry involved.
28
+
29
+ ## Installing
30
+
31
+ ### From the npm package (customers; no heyspex-app checkout)
32
+
33
+ A team that only installs the published `heyspex` package does not have, and does not need, the
34
+ heyspex-app repository. The package carries the marketplace (`node_modules/heyspex/.claude-plugin/marketplace.json`,
35
+ plugin source `./plugins/heyspex`), the hooks and the MCP server. From the root of your own repo:
36
+
37
+ ```bash
38
+ npm install heyspex # or: npm install ./heyspex-<version>.tgz
39
+ npx hs login
40
+ npx heyspex setup copilot # GitHub Copilot CLI: plugin route ...
41
+ npx heyspex setup copilot --hooks # ... OR cloud agent: .github/hooks/heyspex.json, never both
42
+ npx heyspex setup copilot --print # paste this to your agent: next command + the full guide
43
+ npx hs install --target . --plugin-apply # Claude Code
44
+ ```
45
+
46
+ `setup copilot --check` shows which route is active; `--uninstall` removes the hooks file and prints `copilot plugin uninstall heyspex`.
47
+
48
+ Fallback, by hand: `claude plugin marketplace add ./node_modules/heyspex` /
49
+ `copilot plugin marketplace add ./node_modules/heyspex`, then `... plugin install heyspex@heyspex-marketplace`.
50
+ Restart the session (`/restart` in Copilot), then check `copilot plugin list` and `npx hs doctor`.
51
+ Full walkthrough: `node_modules/heyspex/docs/copilot-plugin-install.md`. The sections below describe the same plugin from the
52
+ platform checkout and are for HeySpex developers.
53
+
54
+ ### Claude Code
55
+
56
+ One command, from the platform checkout (wraps the manual flow below):
57
+
58
+ ```bash
59
+ node bin/hs.mjs install --target <consumer-repo> --plugin-apply
60
+ ```
61
+
62
+ `--plugin` prints the commands instead of running them. The equivalent manual flow, inside a
63
+ Claude Code session in the consumer repo — or via the `claude plugin` CLI:
64
+
65
+ ```
66
+ /plugin marketplace add <path-or-git-url-of-this-repo>
67
+ /plugin install heyspex@heyspex-marketplace
68
+ ```
69
+
70
+ ### GitHub Copilot CLI
71
+
72
+ Copilot CLI installs Claude-format plugins (`.claude-plugin/plugin.json`) from a marketplace
73
+ (`.claude-plugin/marketplace.json` is on its search path). Source of truth for the commands and the
74
+ file layout: <https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference>
75
+ (fetched 2026-10-16). Hook behaviour: <https://docs.github.com/en/copilot/reference/hooks-configuration>.
76
+
77
+ ```bash
78
+ copilot plugin marketplace add <owner>/<repo> # or a git URL or a local path to this repo
79
+ copilot plugin install heyspex@heyspex-marketplace # SPECIFICATION is plugin@marketplace
80
+ copilot plugin list # confirm it is enabled
81
+ ```
82
+
83
+ For local development: `copilot plugin marketplace add /path/to/heyspex-app`; a path-sourced plugin in a local
84
+ marketplace loads live (`/restart` picks up edits).
85
+
86
+ What gets loaded:
87
+
88
+ | Component | Loaded from | Notes |
89
+ | --- | --- | --- |
90
+ | Hooks | `hooks/hooks.json` | Same file Claude Code reads. PascalCase events (`PreToolUse`, `PostToolUse`, `Stop`...) use Claude matcher semantics and snake_case payloads. Every command is a self-locating inline bootstrap: it needs no `$CLAUDE_PROJECT_DIR` and exits 0 when it cannot find the runtime (Copilot turns any non-zero exit into a deny). |
91
+ | MCP | `.mcp.json` | Legacy-plugin MCP file. Its `${CLAUDE_PLUGIN_ROOT}` expansion under Copilot is **not documented** (Copilot documents `${PLUGIN_ROOT}` for Agent Plugins `mcp.json` only) and could not be verified without a Copilot CLI on the build machine. If the server does not start, add it explicitly: `copilot mcp add heyspex -- node <install-path>/plugins/heyspex/bin/launch-mcp.mjs`. |
92
+ | Skills / prompts | MCP prompts (`hs-*`) | Delivered by the MCP server. **The Copilot cloud coding agent has no prompts**; there use the `hs` CLI loop in `docs/driving-heyspex-from-copilot.md`. |
93
+ | Commands | not loaded | Claude Code slash commands are not a Copilot component. |
94
+
95
+ ### One source of hooks: plugin OR repo hooks, never both
96
+
97
+ Copilot CLI (and Claude Code) load hooks from the plugin AND from the repository's own registrations
98
+ (`.claude/settings.json`, `.claude/settings.local.json`, `.github/hooks/*.json`). A repo that has both runs every HeySpex
99
+ hook twice. Pick one per repo:
100
+
101
+ - **Plugin** (this directory): `copilot plugin install heyspex@heyspex-marketplace` / `/plugin install ...`. Do not run
102
+ `hs install` hook registration in the same repo, or strip it afterwards.
103
+ - **Repo hooks** (`hs install`, which writes `.claude/settings.json` + the committed launcher): do not also install the plugin.
104
+
105
+ **The committed Claude launcher yields to the plugin.** `hs install` commits `.claude/settings.json` rows for every
106
+ teammate, but the plugin is a per-machine choice, so the committed file cannot pick one source per machine — the
107
+ launcher it routes through (`.claude/heyspex/run-hook.mjs`, marker `// yields-to-plugin: v1`) decides at run time
108
+ instead: when a heyspex plugin is enabled on this machine (a `heyspex@...` key in
109
+ `~/.claude/plugins/installed_plugins.json`, `enabledPlugins` naming heyspex with a non-false value, or a heyspex
110
+ directory under `~/.copilot/installed-plugins` / `~/.copilot/plugins`) it exits 0 silently before resolving the
111
+ package, so every hook fires once — from the plugin. `enabledPlugins["heyspex@..."] === false` (in `~/.claude/settings.json`
112
+ or the repo's `.claude/settings[.local].json`) means the plugin is off for that harness and the repo rows run;
113
+ `HEYSPEX_REPO_HOOKS=1` never yields. The plugin's own `bin/run-hook.mjs` never calls the committed launcher, so there is
114
+ no recursion. `.github/hooks/*.json` rows do not go through the launcher: for those, "never both" still applies as written.
115
+
116
+ `npm run doctor` has a `hook-double-fire` check (reports `ok` when every repo row routes through a yielding launcher): it warns when HeySpex rows in the repo's `.claude/settings*.json` or
117
+ `.github/hooks/*.json` coexist with an installed heyspex plugin, and reports `unknown` (never a failure) when no plugin
118
+ registry can be read. What it looks at: `~/.claude/plugins/installed_plugins.json` (a `heyspex@...` key),
119
+ `enabledPlugins` in `~/.claude/settings.json` and the project's Claude settings, and, best effort, a heyspex directory
120
+ under `~/.copilot/installed-plugins` or `~/.copilot/plugins` (the Copilot plugin list file is not documented; `copilot
121
+ plugin list` is the authority). As a runtime backstop, `_run.mjs` also de-duplicates a repeated Copilot invocation
122
+ (`HEYSPEX_COPILOT_DEDUPE_OFF=1` disables that).
123
+
124
+ ### Copilot cloud agent: `.github/hooks/heyspex.json`
125
+
126
+ The Copilot **cloud agent** loads no plugins and no user-level hook files; its only hook source is `.github/hooks/*.json`
127
+ **on the default branch**. `.github/hooks/heyspex.json` is therefore generated from the same customer rows as
128
+ `hooks/hooks.json`, restricted to the hooks whose Copilot disposition is `mapped` (plus `intent-classifier-trigger`, which
129
+ only arms the capture obligation state and whose dropped output does not matter):
130
+
131
+ ```bash
132
+ npm run hooks:write-github # node scripts/lib/customer-kit.mjs --write-github-hooks (a test fails on drift)
133
+ npm run hooks:check-github # exit 1 when stale
134
+ ```
135
+
136
+ - Linux bash only: every entry is `type: "command"` with a `bash` command that resolves
137
+ `node_modules/heyspex/scripts/claude-hooks/_run.mjs`, then `./scripts/claude-hooks/_run.mjs`, else `exit 0` (fail open).
138
+ Events are the camelCase names (`sessionStart`, `preToolUse`, `postToolUse`, `userPromptSubmitted`, `agentStop`,
139
+ `sessionEnd`, `preCompact`); matchers use Copilot tool names (`create|edit|bash|powershell|task`, MCP tools as a
140
+ `.*<tool>` suffix).
141
+ - The cloud sandbox must contain `node_modules/heyspex` (run `npm install` in the `copilot-setup-steps` workflow), or the
142
+ hooks silently no-op.
143
+ - The firewall must allow the console URL (`emit-claim-status` and claim heartbeats call it), or those hooks time out (timeouts fail open).
144
+ - The file only takes effect once it is on the repository's **default branch**.
145
+ - If the repo also carries `.claude/settings.json` HeySpex rows, Copilot CLI reads both files: that is the double-fire case
146
+ above. Use the manifest for the cloud agent only, or rely on the shim de-duplication.
147
+ - MCP is configured in the repository's Copilot settings (tools only, no OAuth), not by a committed file.
148
+
149
+ ### Gates that are weaker or absent on Copilot (published list)
150
+
151
+ Parity is not identity. Where Copilot has a trigger the gate is a hard deny or re-drive; the rows below are the exceptions.
152
+ The list is generated from `scripts/lib/hook-dispositions.mjs` (the single three-host table, which also covers Claude Code
153
+ and Cursor) and a test fails when this block is stale. The hard floor under all of it is the MCP server itself:
154
+ `heyspex_claim_spec`, `heyspex_submit_for_review` and `heyspex_save_research` refuse without a preflight marker on every
155
+ surface that connects the MCP server, with no hook involved.
156
+
157
+ <!-- hook-dispositions:copilot:begin (generated by scripts/lib/hook-dispositions.test.mjs; UPDATE_HOOK_DOCS=1 refreshes) -->
158
+ | Gate | Disposition | Compensating control / what is lost |
159
+ | --- | --- | --- |
160
+ | `research-method-guard` | unportable | MCP preflight refusal on heyspex_save_research (hard floor, hook-independent) + .github/copilot-instructions.md research rule; Copilot has no Skill or Workflow tool to hook. |
161
+ | `deep-research-cost-gate` | unportable | MCP preflight refusal on heyspex_save_research + .github/copilot-instructions.md cost-confirmation rule; Copilot has no Skill or Workflow tool to hook. |
162
+ | `plan-review-gate` | unportable | MCP preflight refusals (heyspex_claim_spec / heyspex_submit_for_review / heyspex_save_research) + .github/copilot-instructions.md plan rule (state the plan in chat, claim the spec before editing) + HS-20SW directive-obligation-gate claim-before-build deny |
163
+ | `plan-exit-ingest-gate` | unportable | MCP preflight refusals (heyspex_claim_spec / heyspex_submit_for_review / heyspex_save_research) + .github/copilot-instructions.md plan rule (state the plan in chat, claim the spec before editing) + HS-20SW directive-obligation-gate claim-before-build deny |
164
+ | `preflight-reminder` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. Real enforcement is the MCP preflight refusal. |
165
+ | `intent-classifier-trigger` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. The hook still runs and arms obligation state; the HS-20SW directive-obligation-gate turns that state into a hard deny. |
166
+ | `inject-communication-style` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. |
167
+ | `inject-vault-context` | advisory-degraded | Copilot drops command-hook output on userPromptSubmitted (no prompt-time context channel); the text moves to sessionStart / .github/copilot-instructions.md. Per-prompt vault retrieval has no Copilot channel at all. |
168
+ | `claim-before-code-gate` | advisory-degraded | Armed only by plan-exit-ingest-gate, which has no Copilot trigger (no ExitPlanMode tool), so it stays dormant; covered by the HS-20SW claim-before-build rule. |
169
+ | `diagnose-capture-reminder` | advisory-degraded | Copilot preToolUse has no context channel for an inject, so this advisory is silent (lost). |
170
+ <!-- hook-dispositions:copilot:end -->
171
+
172
+ Unverified on Copilot (no official page read, or not run live): the VS Code 128-tool-per-request cap; the spelling of MCP
173
+ tool names inside hook payloads (matched by suffix); `${CLAUDE_PLUGIN_ROOT}` expansion for the plugin `.mcp.json` under
174
+ the Copilot CLI (documented for VS Code only); and how the MCP server learns the Copilot session id (its preflight marker
175
+ falls back to a 4 hour TTL when the runtime exposes none). Also unverified: that a Claude-format `hookSpecificOutput` deny is
176
+ read by the CLI (only the top-level `permissionDecision` form and exit 2 are documented), and where a `matcher` is read
177
+ in the camelCase `.github/hooks` entry shape.
178
+
179
+ ### Turning HeySpex on and off (any harness)
180
+
181
+ One switch, `hs mode` (alias `heyspex mode`; from a checkout without `hs` on PATH: `node bin/hs.mjs mode ...`):
182
+
183
+ ```bash
184
+ hs mode status # on | off, and why
185
+ hs mode off # writes <project>/.heyspex/mode.json; every hook exits 0 at once (no restart)
186
+ hs mode on
187
+ hs mode toggle
188
+ ```
189
+
190
+ - `HEYSPEX_MODE=off` silences hooks for one shell/session without touching a file (wins over the file).
191
+ - `HEYSPEX_COPILOT_HOOKS_OFF=1` (or `agent_behaviors.harness_hooks.copilot.enabled: false` in the vault `.heyspex/config.yaml`)
192
+ silences every hook **only when the caller is GitHub Copilot**; Claude Code and Cursor keep theirs.
193
+ - The switch is read from the PROJECT being worked on (`CLAUDE_PROJECT_DIR`, then the hook payload `cwd`, then upward
194
+ from the working directory), so `hs mode off` in a customer repo works even though the hooks run from `node_modules/heyspex`.
195
+ - Four security gates ignore the switch by design (`env-vault-guard`, `research-method-guard`, `research-protocol-gate`,
196
+ `git-identity-guard`): an agent must not be able to disarm them by writing one JSON file. Their own env-var
197
+ break-glass (set by a human) still applies. The Copilot opt-out does not cover them either unless the owner extends it.
198
+
199
+ ### Cursor
200
+
201
+ For local development, symlink or copy this directory into Cursor's local plugin root and
202
+ reload the window (**Developer: Reload Window**):
203
+
204
+ ```
205
+ ~/.cursor/plugins/local/heyspex
206
+ ```
207
+
208
+ Otherwise add the repo as a marketplace source from Cursor's plugin surface; the root
209
+ `.cursor-plugin/marketplace.json` catalogs it.
210
+
211
+ **In the platform repo itself you do not need the plugin at all** — `.cursor/hooks.json`,
212
+ `.cursor/mcp.json`, `.cursor/rules/`, `.cursor/skills/` and `.cursor/commands/` are already
213
+ installed and are the live path. The plugin exists for CONSUMER repos.
214
+
215
+ ## Running Claude Code against Ollama or DeepSeek
216
+
217
+ Claude Code can be pointed at a local Ollama instance or DeepSeek's cloud API through
218
+ `ANTHROPIC_BASE_URL`: both expose an Anthropic Messages API-compatible endpoint, so Claude
219
+ Code talks to them directly with no proxy. The recipes below are verified against each
220
+ vendor's own docs ([[HS-REF-pointing-claude-code-at-non-anthropic]]).
221
+
222
+ ### Ollama (local)
223
+
224
+ Requires Ollama 0.14+, which added a native Anthropic-compatible endpoint. The auth token is
225
+ required but ignored.
226
+
227
+ ```bash
228
+ # one-shot
229
+ ollama launch claude --model qwen3-coder
230
+
231
+ # manual
232
+ export ANTHROPIC_AUTH_TOKEN=ollama
233
+ export ANTHROPIC_API_KEY=""
234
+ export ANTHROPIC_BASE_URL=http://localhost:11434
235
+ claude --model qwen3-coder
236
+ ```
237
+
238
+ ### DeepSeek (cloud)
239
+
240
+ ```bash
241
+ export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
242
+ export ANTHROPIC_API_KEY=your-deepseek-api-key
243
+ claude
244
+ ```
245
+
246
+ The `/anthropic` path is required — the bare host `https://api.deepseek.com` (without
247
+ `/anthropic`) is not the Anthropic-compatible endpoint. DeepSeek's own docs specify
248
+ `ANTHROPIC_API_KEY`, not `ANTHROPIC_AUTH_TOKEN`.
249
+
250
+ ### Model name mapping (DeepSeek)
251
+
252
+ There is no `ANTHROPIC_MODEL` var to set manually — model choice is automatic name-mapping:
253
+ Claude Opus names map to `deepseek-v4-pro` (billed at V4 Pro price), while every other Claude
254
+ name (Haiku/Sonnet) and any unmapped name maps to `deepseek-flash`.
255
+
256
+ ### Known DeepSeek limitations
257
+
258
+ - `top_p` fixed at 1.0 outside thinking mode (min 0.95 inside it).
259
+ - Unsupported Anthropic content types: documents, search results, redacted thinking, code
260
+ execution results, MCP tool integrations, container uploads.
261
+ - `budget_tokens` in thinking-mode config is ignored.
262
+
263
+ ### Enforcement is independent of the backend
264
+
265
+ HeySpex hooks fire regardless of backend: Claude Code hooks fire at session, turn, and
266
+ tool-call points of Claude Code's own loop, so every HeySpex hook in `.claude/settings.json`
267
+ and `plugins/heyspex/hooks/` runs unchanged regardless of backend — see the
268
+ [Claude Code hooks reference](https://code.claude.com/docs/en/hooks).
269
+
270
+ ### Launcher
271
+
272
+ `hs next <id> --launch --backend ollama|deepseek` sets the right env vars and execs `claude`.
273
+ This is the supported path, shipping with epic
274
+ [[HS-30JD-94Q5-EPIC-claude-code-launches-against-ollama-or]]; until then, use the manual
275
+ recipe above.
276
+
277
+ ## Validating (migration-path step 3)
278
+
279
+ Run the install above in a **fresh clone / separate consumer repo** (avoids the double-hook
280
+ caveat below), after `npm install` in the platform checkout.
281
+
282
+ Then restart the session and confirm:
283
+
284
+ 1. The MCP server connects and tools appear as `mcp__plugin_heyspex_heyspex__heyspex_*`.
285
+ 2. Hooks fire (SessionStart seat injection, PreToolUse gates) — and only once.
286
+ 3. The re-prefix blast radius: anything load-bearing that hardcodes `mcp__heyspex__*`
287
+ (CLAUDE.md, `.claude/settings.json` permission allow/deny lists, procedures, hook scripts)
288
+ still behaves. This is the migration cost the research flags as largest.
289
+
290
+ ## Known caveats (by design, this stage)
291
+
292
+ - **Double-fire:** enabling this plugin in a repo that still registers the same hooks project-level
293
+ runs every hook twice. Single-source rule: plugin OR repo hooks, never both (see above; `npm run doctor` warns).
294
+ Validate in a fresh clone, or strip the project hooks first (`node scripts/install-cursor-hooks.mjs --uninstall` for Cursor).
295
+ - **Not standalone:** the plugin requires a HeySpex vault checkout with `npm install` run —
296
+ the MCP server and hook scripts resolve from the host project, not the plugin cache.
297
+ Bundling the server into the plugin is a full-cutover-stage task.
298
+ - **`hs-*` skills stay MCP prompts** (research step 4, unresolved): they travel with the MCP
299
+ server, not as plugin `SKILL.md` files. Commands, rules and agents are deliberately not
300
+ duplicated here to avoid drift and double registration — which matters more for Cursor, whose
301
+ plugin format CAN carry `rules/`, `commands/` and `skills/`. Bundling them is a cutover decision,
302
+ not a packaging one.
303
+ - **Permissions/statusLine are not mirrored** — those remain project-level concerns.
304
+ - **Manual updates are two steps and scope-sensitive:** `claude plugin marketplace update
305
+ heyspex-marketplace` refreshes the catalog only; the installed copy moves on
306
+ `claude plugin update heyspex@heyspex-marketplace --scope project` (the CLI defaults to
307
+ `--scope user` and errors "not installed at scope user" on a project-scoped install).
308
+ Background auto-update does both, and the new version applies at restart or `/reload-plugins`.
309
+ Version/auto-update findings: `specs/memory/decisions/claude-code-plugin-auto-update-third.md`.
310
+ - **Two hooks have no Cursor trigger** (`plan-review-gate`, `plan-exit-ingest-gate`): Cursor
311
+ exposes no `ExitPlanMode` tool call to hooks, so the plan-review gate is enforced by
312
+ `.cursor/rules/plan-review-required.mdc` instead. Tracked in
313
+ `scripts/cursor-hooks/manifest.mjs` → `UNPORTABLE`; hooks that run weaker are in `DEGRADED` there.
314
+ Four hooks have no Copilot trigger (see the published list above).
315
+ - **Backend choice is separate from enforcement:** running the Claude Code host against a local
316
+ or cloud non-Anthropic model disables no hook — see
317
+ [Running Claude Code against Ollama or DeepSeek](#running-claude-code-against-ollama-or-deepseek)
318
+ for the recipes.
319
+
320
+ ## Cutover criteria (research steps 4–5)
321
+
322
+ Only after the validation above passes end-to-end and the skills-vs-prompts question is settled
323
+ does removing the project-level wiring (or extending this to customer vaults) become a decision.