@adityaaria/spark 6.0.3

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 (91) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.codex-plugin/plugin.json +48 -0
  4. package/.cursor-plugin/plugin.json +23 -0
  5. package/.kimi-plugin/plugin.json +38 -0
  6. package/.opencode/INSTALL.md +115 -0
  7. package/.opencode/plugins/spark.js +139 -0
  8. package/.pi/extensions/spark.ts +121 -0
  9. package/.version-bump.json +21 -0
  10. package/CLAUDE.md +115 -0
  11. package/CODE_OF_CONDUCT.md +128 -0
  12. package/GEMINI.md +2 -0
  13. package/LICENSE +21 -0
  14. package/README.md +282 -0
  15. package/RELEASE-NOTES.md +1299 -0
  16. package/assets/app-icon.png +0 -0
  17. package/assets/spark-small.svg +1 -0
  18. package/bin/spark.js +7 -0
  19. package/docs/README.kimi.md +94 -0
  20. package/docs/README.opencode.md +170 -0
  21. package/docs/porting-to-a-new-harness.md +830 -0
  22. package/gemini-extension.json +6 -0
  23. package/hooks/hooks-codex.json +16 -0
  24. package/hooks/hooks-cursor.json +10 -0
  25. package/hooks/hooks.json +16 -0
  26. package/hooks/run-hook.cmd +46 -0
  27. package/hooks/session-start +49 -0
  28. package/hooks/session-start-codex +26 -0
  29. package/package.json +52 -0
  30. package/skills/brainstorming/SKILL.md +159 -0
  31. package/skills/brainstorming/scripts/frame-template.html +213 -0
  32. package/skills/brainstorming/scripts/helper.js +167 -0
  33. package/skills/brainstorming/scripts/server.cjs +722 -0
  34. package/skills/brainstorming/scripts/start-server.sh +209 -0
  35. package/skills/brainstorming/scripts/stop-server.sh +120 -0
  36. package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  37. package/skills/brainstorming/visual-companion.md +298 -0
  38. package/skills/dispatching-parallel-agents/SKILL.md +185 -0
  39. package/skills/executing-plans/SKILL.md +70 -0
  40. package/skills/finishing-a-development-branch/SKILL.md +241 -0
  41. package/skills/receiving-code-review/SKILL.md +213 -0
  42. package/skills/requesting-code-review/SKILL.md +103 -0
  43. package/skills/requesting-code-review/code-reviewer.md +172 -0
  44. package/skills/subagent-driven-development/SKILL.md +418 -0
  45. package/skills/subagent-driven-development/implementer-prompt.md +139 -0
  46. package/skills/subagent-driven-development/scripts/review-package +44 -0
  47. package/skills/subagent-driven-development/scripts/sdd-workspace +22 -0
  48. package/skills/subagent-driven-development/scripts/task-brief +40 -0
  49. package/skills/subagent-driven-development/task-reviewer-prompt.md +188 -0
  50. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  51. package/skills/systematic-debugging/SKILL.md +296 -0
  52. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  53. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  54. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  55. package/skills/systematic-debugging/find-polluter.sh +63 -0
  56. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  57. package/skills/systematic-debugging/test-academic.md +14 -0
  58. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  59. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  60. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  61. package/skills/test-driven-development/SKILL.md +371 -0
  62. package/skills/test-driven-development/testing-anti-patterns.md +299 -0
  63. package/skills/using-git-worktrees/SKILL.md +202 -0
  64. package/skills/using-spark/SKILL.md +121 -0
  65. package/skills/using-spark/references/antigravity-tools.md +96 -0
  66. package/skills/using-spark/references/claude-code-tools.md +50 -0
  67. package/skills/using-spark/references/codex-tools.md +72 -0
  68. package/skills/using-spark/references/copilot-tools.md +49 -0
  69. package/skills/using-spark/references/gemini-tools.md +63 -0
  70. package/skills/using-spark/references/pi-tools.md +28 -0
  71. package/skills/verification-before-completion/SKILL.md +139 -0
  72. package/skills/writing-plans/SKILL.md +174 -0
  73. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  74. package/skills/writing-skills/SKILL.md +689 -0
  75. package/skills/writing-skills/anthropic-best-practices.md +1150 -0
  76. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  77. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  78. package/skills/writing-skills/persuasion-principles.md +187 -0
  79. package/skills/writing-skills/render-graphs.js +168 -0
  80. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  81. package/src/cli/index.js +26 -0
  82. package/src/cli/install.js +47 -0
  83. package/src/cli/output.js +11 -0
  84. package/src/cli/parse-args.js +46 -0
  85. package/src/cli/prompt.js +10 -0
  86. package/src/installer/adapters/common.js +59 -0
  87. package/src/installer/adapters/extension-style.js +67 -0
  88. package/src/installer/adapters/shell-hook.js +57 -0
  89. package/src/installer/detect.js +168 -0
  90. package/src/installer/errors.js +7 -0
  91. package/src/installer/registry.js +35 -0
@@ -0,0 +1,830 @@
1
+ # Porting SPARK to a New Harness
2
+
3
+ This guide explains how to add support for a new harness — an IDE, CLI, or
4
+ agent runner that isn't Claude Code — so that SPARK skills auto-trigger
5
+ there the same way they do natively.
6
+
7
+ For end users, the preferred front door is now `npx @adityaaria/spark install`; the
8
+ CLI detects the harness or asks which one to target, then delegates to the
9
+ appropriate adapter.
10
+
11
+ It is written in two layers. **Part 1–3** explain how the system works and how
12
+ to tell whether a harness can be supported at all; read these before you touch
13
+ anything. **Part 4–8** are a prescriptive procedure for an agent (supervised by
14
+ a human partner) to execute the port end to end, through distribution. An
15
+ appendix indexes the current reference integrations so you can copy the closest
16
+ one.
17
+
18
+ The integration mechanism differs across harnesses, and it will keep changing.
19
+ This guide deliberately teaches the **invariants** — the things that must be
20
+ true no matter the mechanism — and points you at a live reference implementation
21
+ to copy. When this guide and the code disagree, the code wins; fix the guide.
22
+
23
+ ## Before you start
24
+
25
+ Adding a harness is the highest-stakes contribution type in this repo. Before
26
+ writing anything:
27
+
28
+ - Read `CLAUDE.md` and `.github/PULL_REQUEST_TEMPLATE.md` in full — the
29
+ contributor rules and the new-harness PR requirements are not optional.
30
+ - Search open **and closed** PRs for a prior attempt at this harness. If one
31
+ exists, understand why it stalled before starting your own.
32
+
33
+ ---
34
+
35
+ ## Part 1 — How SPARK works across harnesses
36
+
37
+ SPARK is the same content everywhere. What changes per harness is the thin
38
+ layer that delivers that content to the model and translates its instructions
39
+ into the harness's native tools. Three components:
40
+
41
+ 1. **Skills (harness-agnostic).** Everything in `skills/` is the source of
42
+ truth, shared verbatim by every harness. Skills are written to describe
43
+ *actions* — "invoke a skill", "read a file", "dispatch a subagent", "create a
44
+ todo" — and never name a specific tool. This is what lets one skill body run
45
+ on Claude Code, Codex, Gemini, pi, and the rest without edits.
46
+
47
+ 2. **Tool mapping (per-harness).** Each harness needs the action vocabulary
48
+ translated into its real tool names. That translation lives in
49
+ `skills/using-spark/references/<harness>-tools.md` and/or inline in the
50
+ harness's bootstrap injector (see Part 5). It says, e.g., "*dispatch a
51
+ subagent* → call `task` with `subagent_type`."
52
+
53
+ 3. **Bootstrap (per-harness).** At the start of every session, the full
54
+ `skills/using-spark/SKILL.md` is injected into the model's context,
55
+ wrapped in `<EXTREMELY_IMPORTANT>` tags, with the tool mapping appended. That
56
+ injected skill is what teaches the model that skills exist and that it must
57
+ check for a relevant skill before acting. **The bootstrap is the entire
58
+ integration.** Without it, the skill files are inert — present on disk, never
59
+ invoked.
60
+
61
+ ### Two rules that make this work
62
+
63
+ **1. Skills name actions, not tools.** Do **not** edit skill bodies to fit your
64
+ harness. Porting adds a tool-mapping reference and a bootstrap injector; it
65
+ never reaches into `skills/*/SKILL.md` to swap tool names. (The project's
66
+ contributor guidelines treat skill content as carefully-tuned behavior-shaping
67
+ code; rewording it for "compliance" is rejected on sight.)
68
+
69
+ **2. Everything ships through the harness's own install mechanism. Never edit the
70
+ user's files.** The bootstrap, the skills, and the tool mapping all get delivered
71
+ *as part of what the harness installs* — a plugin, an extension, a marketplace
72
+ entry, an extension-bundled context file. A port **must not** reach into a user's
73
+ global or personal config (`~/.gemini/config/AGENTS.md`, `settings.json`,
74
+ `trustedFolders.json`, a hand-edited `~/.bashrc`, etc.) to inject anything. The
75
+ harness owns what it loads; your install artifact is the only thing you get to
76
+ write. If the install mechanism genuinely can't carry the bootstrap, that is a
77
+ limitation to surface (Part 6) — never a license to hand-edit the user's config.
78
+ (Shape C is *not* an exception: Gemini's context file is fine because it ships
79
+ *inside the installed extension* and is declared by the manifest's
80
+ `contextFileName` — the harness loads the extension's own file, not a file you
81
+ edited in the user's home.)
82
+
83
+ ---
84
+
85
+ ## Part 2 — Can this harness be supported?
86
+
87
+ A harness can support SPARK only if it can do all of the following. Check
88
+ these before writing code — if the first one fails, stop.
89
+
90
+ ### Hard requirement: automatic session-start injection
91
+
92
+ The harness must let you inject text into the model's context **at the start of
93
+ every session, with no per-session opt-in by your human partner.** This is the
94
+ one non-negotiable capability. It can take any form:
95
+
96
+ - a **hook/event system** that runs a shell command at session start and reads
97
+ its stdout (Claude Code, Codex, Cursor, Copilot CLI), or
98
+ - an **in-process plugin/extension** with a session-start or message lifecycle
99
+ callback that can mutate the message array (OpenCode, pi), or
100
+ - an **instructions-file** convention where the harness loads a context file that
101
+ *your installed extension ships and declares* (e.g. Gemini's `contextFileName`
102
+ pointing at the extension's own `GEMINI.md`) — not a file you edit in the user's
103
+ home.
104
+
105
+ If the only way to get SPARK in front of the model is for your human
106
+ partner to opt in each session (paste a prompt, run a command, enable a mode),
107
+ the harness
108
+ **cannot** be properly supported. The acceptance test in Part 3 will fail, and
109
+ the PR will be closed. This is the single most common reason a "port" isn't a
110
+ real port.
111
+
112
+ ### The rest of the capability checklist
113
+
114
+ | Capability | Why it's needed | If absent |
115
+ |---|---|---|
116
+ | **Skill discovery + invocation** | The model must be able to load a skill's full content on demand | If there's no native skill tool, the sanctioned fallback is to `read` the relevant `SKILL.md` directly — see Part 5. A harness with neither a skill tool nor file-read cannot work. |
117
+ | **File read / write / edit** | Nearly every skill manipulates files | Essential. No workaround. |
118
+ | **Run shell commands** | TDD, verification, git workflows | Essential. |
119
+ | **Subagent / task dispatch** | `dispatching-parallel-agents`, `subagent-driven-development` | Degradable: if unavailable, those specific skills tell the model to do the work inline or report the missing capability — *never* to invent a `Task` call. Some harnesses gate this behind a config flag (e.g. Codex needs multi-agent enabled). |
120
+ | **Todo / task tracking** | Progress tracking in several skills | Degradable: fall back to a plan file or `TODO.md`. |
121
+ | **Web fetch / search** | A few skills | Degradable. |
122
+ | **Shell or polyglot script execution (Windows)** | Only for the shell-hook shape, only if you want Windows support | See Part 7. In-process-plugin harnesses sidestep this entirely. |
123
+
124
+ "Degradable" means: the skill already has fallback wording for the missing
125
+ tool. Your job in the tool mapping is to point at the real tool when it exists
126
+ and reuse that fallback wording when it doesn't.
127
+
128
+ ### You may not need a new directory at all
129
+
130
+ Some "new harnesses" are really existing integrations under a different
131
+ installer. Factory's Droid, for example, consumes the Claude Code plugin via its
132
+ own `plugin install` command and needs no new files here. Before building,
133
+ check whether the harness can simply load an existing manifest. A port that adds
134
+ nothing to this repo but a paragraph in the README is a perfectly good outcome.
135
+
136
+ ---
137
+
138
+ ## Part 3 — Definition of done
139
+
140
+ A port is finished when **all** of these are true:
141
+
142
+ 1. The `using-spark` bootstrap loads at session start, every session, with
143
+ no per-session opt-in.
144
+ 2. A tool mapping exists for the harness (in
145
+ `references/<harness>-tools.md`, inline in the bootstrap, or both — per Part 5).
146
+ 3. Skills can actually be invoked — natively, or via the documented
147
+ read-`SKILL.md` fallback — and the model follows them.
148
+ 4. **The acceptance test passes.** In a clean session, the user message:
149
+
150
+ > Let's make a react todo list
151
+
152
+ auto-triggers the `brainstorming` skill *before any code is written*. Capture
153
+ the full transcript — the PR requires it.
154
+ 5. Tests cover the integration (Part 5) and pass.
155
+ 6. A real user can install it through the harness's own mechanism (not by
156
+ hand-copying files), and the version is tracked in `.version-bump.json` where
157
+ applicable (Part 6). Note that some installers rewrite or strip the manifest on
158
+ install (one drops it to just `{"name": …}`), so "the *installed* files report
159
+ the repo version" is not always achievable — track the version at the source
160
+ manifest and don't treat a rewritten installed manifest as a failure.
161
+
162
+ A quick smoke check before the full acceptance test: start a session and ask the
163
+ model to describe its spark. If the bootstrap injected, it knows it has
164
+ them. (OpenCode's install doc uses `opencode run --print-logs "hello" 2>&1 |
165
+ grep -i spark` for the same goal via a different mechanism — log-grep
166
+ rather than asking the model; the `2>&1` matters because logs go to stderr. Find
167
+ your harness's equivalent.)
168
+
169
+ ---
170
+
171
+ ## Part 4 — Choose your integration shape
172
+
173
+ There are three structural shapes, distinguished by *how you get the bootstrap
174
+ in front of the model*. Pick the one that matches what your harness exposes,
175
+ then copy that reference implementation. The shape determines almost everything
176
+ in Part 5 — the steps below branch on it.
177
+
178
+ ### How to tell which shape you have
179
+
180
+ Before routing, learn the harness's *actual* mechanism — and don't assume it's
181
+ well documented or that it behaves like whatever harness it forked from.
182
+
183
+ **Find the surface:**
184
+
185
+ - **Search the web for the harness's docs** (extension / plugin / hook / skill /
186
+ MCP / "context file" / "rules file"). Vendor tools change fast; search rather
187
+ than trust training knowledge.
188
+ - **Find and read an existing third-party extension/plugin for the harness.** A
189
+ real working example beats docs — it shows the manifest shape, the install
190
+ command, and which components the harness actually loads.
191
+ - Check what the harness loads at startup: a settings file? an extensions
192
+ directory? a per-project or global instructions file (`AGENTS.md`, `<NAME>.md`)?
193
+
194
+ **If it's underdocumented, reverse-engineer it empirically** (a real porter has
195
+ had to do every one of these):
196
+
197
+ - `strings` the binary / grep the install tree for hook event names, config
198
+ paths, and the instructions file it reads.
199
+ - **Ask the running model to enumerate its own tool names** — e.g. "list the
200
+ exact machine names of every tool you can call." This is the authoritative way
201
+ to get tool names without inventing them (see Step 4).
202
+ - Prove every assumption with a **unique-marker test**: inject a nonsense token
203
+ through the mechanism you think works, start a fresh session, and confirm the
204
+ token actually reached the model.
205
+
206
+ **A fork does not inherit its parent's behavior.** A harness derived from another
207
+ (e.g. a Gemini-derived CLI) may expose the parent's manifest fields and
208
+ `@`-include syntax and *still not honor them the same way*. Verify with a marker;
209
+ never assume the parent's recipe transfers.
210
+
211
+ Then route to a shape:
212
+
213
+ - Shell command at session start whose stdout is read → **Shape A**.
214
+ - Plugin/extension module with lifecycle callbacks you run code in → **Shape B**.
215
+ - Only ever an always-on instructions file, no hook and no code plugin →
216
+ **Shape C**.
217
+
218
+ **Shapes compose — they are not mutually exclusive.** The *skill-discovery*
219
+ mechanism and the *bootstrap* mechanism need not be the same shape — but **both
220
+ must still ride the install mechanism** (rule 2). Decide the two questions
221
+ separately: *where do skills get discovered?* and *how does the bootstrap reach
222
+ the model every session?* A harness might install skills via a plugin yet need
223
+ the bootstrap delivered another install-shipped way (an extension-declared
224
+ context file, or — see below — by the harness surfacing the installed
225
+ `using-spark` skill's own description at session start). If more than one
226
+ install-mechanism surface injects automatically, prefer the most reliable. What
227
+ you may **not** do is bridge a gap by editing the user's global config.
228
+
229
+ ### Shape A — Shell-hook
230
+
231
+ The harness has a hook system that runs a shell command at session start and
232
+ reads JSON from its stdout. The configured command runs `run-hook.cmd`, a
233
+ polyglot wrapper that just locates bash and dispatches the named script; the
234
+ script (`hooks/session-start`, or a harness-specific variant like
235
+ `hooks/session-start-codex`) is what reads `using-spark/SKILL.md` and
236
+ prints a JSON object whose **field name and nesting differ per harness**.
237
+
238
+ - Reference: `hooks/session-start` (and `hooks/session-start-codex`),
239
+ `hooks/run-hook.cmd`, and the per-harness hook config `hooks/hooks.json`
240
+ (Claude Code), `hooks/hooks-codex.json` (Codex), `hooks/hooks-cursor.json`
241
+ (Cursor).
242
+ - Manifests: `.codex-plugin/plugin.json`, `.cursor-plugin/plugin.json` point the
243
+ harness at `./skills/` and the right `hooks-*.json`. (Claude Code's
244
+ `.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/`
245
+ and `hooks/hooks.json` by convention.)
246
+
247
+ > **A hook *system* is not a session-start *event*.** A harness can have a
248
+ > `hooks.json` mechanism — and even contain the literal string `SessionStart` in
249
+ > its binary — while having no hook event that fires at session start and can
250
+ > inject context. (One real harness only exposed pre/post-tool and stop events;
251
+ > the `SessionStart` strings were telemetry.) Confirm the *specific event* you
252
+ > need exists and can write to the model's context before committing to Shape A.
253
+ > If it can't, the bootstrap belongs in an instructions file (Shape C) instead.
254
+
255
+ ### Shape B — In-process plugin / extension
256
+
257
+ The harness loads a JS/TS module that exposes lifecycle callbacks. You register
258
+ the skills directory through the harness's API and inject the bootstrap by
259
+ mutating the message array in code.
260
+
261
+ - Reference: `.opencode/plugins/spark.js` (JavaScript) and
262
+ `.pi/extensions/spark.ts` (TypeScript). pi is the closest reference for
263
+ any harness that has **no native skill tool**.
264
+
265
+ ### Shape C — Instructions-file
266
+
267
+ The harness has neither a shell hook nor a code plugin — its session-start
268
+ surface is a context file that *your installed extension ships and the manifest
269
+ declares* (e.g. Gemini's `contextFileName` → the extension's own `GEMINI.md`).
270
+ You can't run code or mutate messages; the extension's context file points at the
271
+ bootstrap. There is no injector to assemble a string or strip frontmatter — the
272
+ harness loads the referenced content as-is. **This works only because the file is
273
+ part of the installed extension** — never substitute "edit the user's global
274
+ `GEMINI.md`/`AGENTS.md`" for shipping your own (rule 2).
275
+
276
+ - Reference: `gemini-extension.json` (manifest, with `contextFileName`),
277
+ `GEMINI.md` (two `@`-includes — the bootstrap skill and the tool-mapping
278
+ reference), `skills/using-spark/references/gemini-tools.md`.
279
+ - Note: `@`-include is a Gemini feature. If your harness loads an instructions
280
+ file but has no include syntax, you must inline the bootstrap content into the
281
+ file instead.
282
+ - **Don't trust that an `@`-include is actually expanded — prove it.** A
283
+ Gemini-*derived* harness can accept `@./path` syntax yet treat it as a *hint
284
+ the model may choose to read* (it emits a file-read tool call) rather than a
285
+ guaranteed inline expansion. That's the difference between the bootstrap being
286
+ reliably present every session and the model maybe-reading it. Run a
287
+ unique-marker test: if the marker isn't in context *without* a tool call,
288
+ **inline the content** rather than `@`-include it.
289
+
290
+ ### Routing table
291
+
292
+ | If the harness… | Use shape | Copy from |
293
+ |---|---|---|
294
+ | runs a shell command at session start and reads its stdout | A (shell-hook) | Codex (`hooks/session-start-codex` + `hooks/hooks-codex.json` + `.codex-plugin/`) |
295
+ | is a JS/TS plugin host with session/message lifecycle callbacks | B (in-process) | OpenCode (`.opencode/`) — or pi (`.pi/`) if it has no native skill tool |
296
+ | ships an extension-declared context file it always loads | C (instructions-file) | Gemini (`gemini-extension.json` + `GEMINI.md` + `references/gemini-tools.md`) |
297
+ | has a plugin install command and a manifest `contextFileName` (or equivalent) the installer keeps | C via the plugin installer | Antigravity (`.antigravity-plugin/` — `agy plugin install` ships a generated context file; verify the installer preserves it — Part 6) |
298
+
299
+ Most real harnesses fit one row cleanly; the last is the hybrid case (rule 2 still
300
+ holds — the bootstrap rides the install mechanism, never a user-config edit).
301
+
302
+ ---
303
+
304
+ ## Part 5 — The porting procedure
305
+
306
+ ### Step 1 — Study the closest reference implementation
307
+
308
+ Open the files named in Part 4 for your shape and read them end to end. The
309
+ patterns below are summaries; the code is the spec.
310
+
311
+ ### Step 2 — Create the manifest / entry point
312
+
313
+ Create whatever the harness uses to recognize the plugin. Match the existing
314
+ ones in spirit:
315
+
316
+ - **Shape A:** a `*-plugin/plugin.json` (see `.codex-plugin/plugin.json`) with
317
+ `name`, `version`, `description`, author/license/keywords, `"skills":
318
+ "./skills/"`, and `"hooks": "./hooks/hooks-<harness>.json"`. Plus the
319
+ `hooks-<harness>.json` itself, registering a session-start hook whose command
320
+ invokes `run-hook.cmd`.
321
+ - **Shape B:** the module the harness loads (e.g. `.<harness>/plugins/*.js`) plus
322
+ whatever package metadata it needs to be discovered. The committed package
323
+ metadata is the **repo-root `package.json`**: `main` points at the OpenCode
324
+ plugin, the `pi` field (`pi.extensions`, `pi.skills`) plus the `pi-package`
325
+ keyword declare the pi extension. Per-harness local manifests and lockfiles are
326
+ kept out of git — `.opencode/.gitignore` excludes `node_modules`,
327
+ `package.json`, and lockfiles. Do the same for your harness's *local* install
328
+ artifacts so they don't pollute the repo — but never gitignore the repo-root
329
+ `package.json`, which is the tracked source of truth.
330
+ - **Build/dependency check.** Decide how the harness loads your module:
331
+ does it run the source directly (pi's `.ts` is referenced as-is from
332
+ `package.json`; OpenCode ships plain `.js`), or does it need a transpile/build
333
+ step? SPARK is zero-runtime-dependency. pi's `import type
334
+ { ExtensionAPI }` works specifically because the harness runs the `.ts`
335
+ directly, supplies that type at load, and the repo never type-checks the file
336
+ in CI — the import isn't even declared as a dependency. If *your* harness
337
+ actually type-checks or bundles the plugin, that breaks: an undeclared type
338
+ import fails, and the PR rules only carve out *runtime* deps for new
339
+ harnesses, not dev/type packages. If you hit this, confirm the approach with
340
+ the maintainer rather than quietly adding a dependency. Keep any build output
341
+ out of git and document the command.
342
+ - **Shape C (instructions-file):** a small manifest (see `gemini-extension.json`:
343
+ `name`, `description`, `version`, `contextFileName`) plus the context file
344
+ itself (`GEMINI.md` is just two `@`-includes: the bootstrap skill and the
345
+ tool-mapping reference). The Gemini manifest has no `skills` field — Gemini
346
+ auto-discovers the `skills/` directory bundled in the installed extension. If
347
+ your harness has a native skill tool but no manifest field to register the
348
+ directory, you must find its discovery convention (read its extension docs),
349
+ then verify empirically: after wiring, ask the model to list its available
350
+ skills — if the bundled skills don't appear, discovery isn't working yet.
351
+
352
+ ### Step 3 — Wire the bootstrap injection
353
+
354
+ This is the heart of the port. The shared goal: at session start, get the
355
+ `using-spark` skill content (wrapped in `<EXTREMELY_IMPORTANT>` tags) plus
356
+ the harness's tool mapping in front of the model, with a note that the skill is
357
+ already active so the model doesn't try to load it again. *How* you do that —
358
+ and what you assemble vs. what the harness loads raw — depends entirely on your
359
+ shape. Do **not** apply one shape's recipe to another.
360
+
361
+ **Shape A — a script reads `SKILL.md` and prints the harness's JSON.** The
362
+ dispatched script (`hooks/session-start`) `cat`s the whole `SKILL.md` (frontmatter
363
+ included — that's fine; it's emitted verbatim), wraps it with the "You have
364
+ spark… for all other skills use the Skill tool" preamble, escapes it, and
365
+ prints the harness's JSON shape. The tool mapping for Shape A does **not** go
366
+ inline here — it lives in `references/<harness>-tools.md` (Step 4). Get the JSON
367
+ output shape exactly right. `hooks/session-start`
368
+ detects the harness from environment variables and prints *one of three* shapes:
369
+
370
+ - Cursor (`CURSOR_PLUGIN_ROOT` set): `{ "additional_context": "…" }`
371
+ - Claude Code (`CLAUDE_PLUGIN_ROOT` set, `COPILOT_CLI` unset):
372
+ `{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "…" } }`
373
+ - Copilot CLI / SDK standard (else): `{ "additionalContext": "…" }`
374
+
375
+ This is a trap. Emitting the wrong field, or an extra one, means the bootstrap
376
+ either never injects or injects twice (Claude Code reads both
377
+ `additional_context` and `hookSpecificOutput` without de-duplicating, so emitting
378
+ both double-injects). Find the
379
+ exact field, nesting, and event-matcher values your harness expects. Then
380
+ decide: add a fourth branch to `hooks/session-start`, or — if the harness needs
381
+ a different bootstrap message or env contract — add a dedicated
382
+ `hooks/session-start-<harness>` script, the way Codex did. If you add a branch
383
+ and your harness *also* sets an env var an earlier branch keys on (some harnesses
384
+ set `CLAUDE_PLUGIN_ROOT` too), order your branch before the one that would
385
+ otherwise shadow it. Match the harness's
386
+ own event-matcher strings (Claude Code uses `startup|clear|compact`, Codex
387
+ `startup|resume|clear`, Cursor `sessionStart`); wrong matchers mean the hook
388
+ silently never fires.
389
+
390
+ The **hook-config schema itself varies per harness** — don't assume the
391
+ Claude/Codex shape is universal. Compare `hooks/hooks.json`,
392
+ `hooks/hooks-codex.json`, and `hooks/hooks-cursor.json`: Cursor's uses
393
+ `"version": 1`, a lowercase `sessionStart` key, a relative
394
+ `./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields the
395
+ others use. Match your `hooks-<harness>.json` to whichever existing file is
396
+ closest, not to a single canonical template.
397
+
398
+ The hook **command string references a harness-provided plugin-root variable**,
399
+ and its name differs per harness: `hooks.json` uses `${CLAUDE_PLUGIN_ROOT}`,
400
+ `hooks-codex.json` uses `${PLUGIN_ROOT}`, Cursor uses a relative path. Use
401
+ whatever your harness exports. (The `session-start` script re-derives the root
402
+ itself via `dirname`, so the script body doesn't depend on this — but the
403
+ command in the manifest does.)
404
+
405
+ **Discovering the harness's contract.** The three facts above — env var, JSON
406
+ field/nesting, matcher strings — are the harness's contract, not SPARK',
407
+ so you have to source them. Read the harness's hook docs, or find out
408
+ empirically: register a throwaway session-start hook that dumps its environment
409
+ and emits a marker, then observe which env var identifies the harness and
410
+ whether/how the harness ingests your stdout. Pin these down before writing the
411
+ real branch.
412
+
413
+ **Shape B — assemble the string in code, then inject as a user message.** Here
414
+ you build the bootstrap yourself: read `SKILL.md`, strip its YAML frontmatter,
415
+ and assemble `<EXTREMELY_IMPORTANT>` + a short preamble that the skill is already
416
+ loaded and must not be re-invoked + the stripped body + the inline tool mapping +
417
+ `</EXTREMELY_IMPORTANT>`. One subtlety the references disagree on: OpenCode's
418
+ preamble says "do NOT use the skill tool…" (assumes a `skill` tool exists), while
419
+ pi's just says "do not try to load using-spark again." If your harness has
420
+ no skill tool, use pi's wording, not OpenCode's.
421
+
422
+ Inject the result as a **user-role message, not a system message** — system
423
+ messages bloat tokens when repeated every turn (#750) and multiple system
424
+ messages break some models (#894). Three things you must replicate:
425
+
426
+ - **Dedup guard.** The lifecycle callback can fire repeatedly (OpenCode's
427
+ transform runs on *every* agent step; pi's `context` fires per turn). Before
428
+ injecting, check whether a bootstrap marker is already present and skip if so.
429
+ (The references pick different markers — pi a custom string, OpenCode the
430
+ `EXTREMELY_IMPORTANT` tag; matching the tag is more robust since it needs no
431
+ harness-specific constant.) Cache the bootstrap content at module level so
432
+ you're not re-reading and re-parsing `SKILL.md` on every call (#1202).
433
+ - **Compaction.** If the harness compacts/summarizes history, re-inject
434
+ afterward. pi sets an `injectBootstrap` flag on `session_start` and
435
+ `session_compact`, clears it on `agent_end`, and inserts the message *after*
436
+ any leading compaction-summary messages. OpenCode relies on its per-step
437
+ re-injection plus the dedup guard.
438
+ - **Message-object shape is per-harness — discover yours, don't copy a literal.**
439
+ The two references use *incompatible* shapes: pi builds
440
+ `{ role, content: [{ type, text }], timestamp }`; OpenCode manipulates
441
+ `message.info.role` and `message.parts[]`. Find your harness's message shape
442
+ from its API; copying a reference's object literal verbatim will fail silently.
443
+
444
+ **Shape C — point your extension's context file at the bootstrap; assemble
445
+ nothing.** There is no injector, so you do *not* strip frontmatter or build a
446
+ wrapped string. The context file your extension ships (declared by the manifest —
447
+ *not* the user's own global file) pulls in two things: the `using-spark`
448
+ skill and the harness's tool-mapping reference. `GEMINI.md`
449
+ does this with two `@`-includes (`@./skills/using-spark/SKILL.md` and
450
+ `@./skills/using-spark/references/<harness>-tools.md`); the harness loads
451
+ them raw, frontmatter and all, and `SKILL.md` already carries its own
452
+ `<EXTREMELY-IMPORTANT>` block internally. If your harness has no include syntax,
453
+ inline the content into the instructions file instead. Gemini ships **no**
454
+ "already loaded, don't re-invoke" preamble — for an `@`-include harness the
455
+ content is the active instruction set, not a skill the model would re-load. If
456
+ you find your harness does try to re-invoke, add that note as a literal line in
457
+ the instructions file (you have no code to add it any other way).
458
+
459
+ ### Step 4 — Write the tool mapping
460
+
461
+ Translate the action vocabulary into the harness's real tools. Cover every one
462
+ of these actions (omit only what genuinely doesn't apply):
463
+
464
+ - read a file
465
+ - create / edit / delete a file (one `apply_patch`-style tool, or separate
466
+ write/edit?)
467
+ - run a shell command
468
+ - search file contents / find files by name (grep, glob)
469
+ - fetch a URL / web search
470
+ - **dispatch a subagent**, including how to pass the agent type — and any config
471
+ flag needed to enable it
472
+ - **create / update todos** (treat older `TodoWrite` references as this action)
473
+ - **invoke a skill** — see Step 5
474
+
475
+ **Get the real tool names from the harness; never invent them.** If the docs
476
+ don't list them, the authoritative source is the harness itself: in a live
477
+ session, ask the model to "list the exact machine names of every tool you can
478
+ call, one per line" and use what it reports.
479
+
480
+ **How the harness finds the `skills/` directory is itself per-harness** — confirm
481
+ it, don't assume. Possibilities: a manifest `skills` path field (Codex's
482
+ `"skills": "./skills/"`); a *co-located* `skills/` the harness auto-scans (where a
483
+ path field is **ignored** — one real harness only scanned a `skills/` sitting next
484
+ to `plugin.json`); an API/registration call (OpenCode, pi); or you stage an
485
+ install dir that pairs the manifest with a **symlink to the repo's `skills/`** and
486
+ point the installer at the staging dir (verify the installer *dereferences* the
487
+ symlink and copies the real files — confirm with `agy plugin validate`/`install`
488
+ or the equivalent before relying on it). A `skills` path field is *not* portable.
489
+
490
+ Where the mapping lives depends on shape:
491
+
492
+ - **Shape A:** put it in `skills/using-spark/references/<harness>-tools.md`.
493
+ The agent reaches it from the bootstrap — `SKILL.md`'s "Platform Adaptation"
494
+ section links the per-harness references files. (Shape A harnesses have no
495
+ instructions file; the mapping is *not* inlined into the hook output.)
496
+ - **Shape B:** the mapping is typically inlined into the bootstrap string you
497
+ inject (see the `toolMapping` constant in `spark.js`). pi keeps it in
498
+ *both* places — `piToolMapping()` inline **and** `references/pi-tools.md`. If
499
+ you maintain it in two places, update both, or the port is half-done.
500
+ - **Shape C:** put it in `references/<harness>-tools.md` and pull it into the
501
+ always-loaded instructions file (e.g. `GEMINI.md` `@`-includes
502
+ `gemini-tools.md`).
503
+
504
+ You may also add a one-line pointer to your harness in `SKILL.md`'s "Platform
505
+ Adaptation" section so an agent reading the bootstrap knows where its mapping
506
+ lives. This is the one edit to a `SKILL.md` a port may make — and only because
507
+ that section is a pointer list, not behavior-shaping content. It does not violate
508
+ the "don't edit skill bodies" rule (Part 1); do not touch anything else in any
509
+ skill. (The list is a convenience pointer, not an exhaustive registry — not every
510
+ harness is listed.)
511
+
512
+ ### Step 5 — Handle a harness with no native skill tool
513
+
514
+ `using-spark/SKILL.md` tells the model to *never read skill files manually
515
+ with file tools — always use your platform's skill-loading mechanism.* The point
516
+ is "don't bypass the mechanism," not "never use file-read." What counts as "your
517
+ platform's mechanism" depends on the harness — and for a harness with no skill
518
+ tool, the documented mechanism *is* reading `SKILL.md`. So reading it there
519
+ honors the rule rather than breaking it. Distinguish three cases:
520
+
521
+ 1. **Native `Skill`-style tool** (Claude Code, Copilot CLI, Gemini's
522
+ `activate_skill`): point the mapping at that tool.
523
+ 2. **Native skill *discovery* but no `Skill` tool** (pi, Antigravity): the harness
524
+ can find and list skills, but the model can't call a tool to load one. Get the
525
+ skills installed where the harness scans (pi registers via `resources_discover`
526
+ → `skillPaths`; OpenCode via its `config` hook; `agy plugin install` copies
527
+ them in), and tell the model to load a skill by **reading its `SKILL.md` with
528
+ the file-read tool when the skill applies** — the sanctioned mechanism here,
529
+ the way `references/pi-tools.md` states it.
530
+
531
+ **For the bootstrap itself, prefer a declared context file (Part 6).** If the
532
+ harness has a `contextFileName`-style manifest field — as Antigravity does —
533
+ ship a generated context file through the installer: it's guaranteed-loaded and
534
+ carries both the `using-spark` content and the tool mapping. That is the
535
+ strong, preferred path.
536
+
537
+ **Fallback — the surfaced skill index.** If there's no context-file field but
538
+ the harness surfaces each installed skill's name + description at session start,
539
+ you need *neither* a built index nor a runtime-list instruction — the harness
540
+ is the index, and `using-spark`'s own surfaced description can be what
541
+ triggers the model to load it. This is softer than a declared context file;
542
+ two things it does **not** give you, versus a context file / hook / in-process
543
+ injector — account for both:
544
+ - **It bootstraps *triggering*, not the *tool mapping*.** An injector prepends
545
+ `<harness>-tools.md` alongside `using-spark` every session. Here nothing
546
+ injects the mapping — the model only sees skill *descriptions* and must *read*
547
+ your `references/<harness>-tools.md` when it needs tool names. It works
548
+ because skills name actions (the model reads the mapping when it acts), but
549
+ it's softer than injection. Make sure the mapping is reachable from what the
550
+ model loads — e.g. linked from `SKILL.md`'s Platform Adaptation section and
551
+ installed alongside the skills — not just sitting in the repo.
552
+ - **There's no structural guarantee the trigger fires.** No `<EXTREMELY_IMPORTANT>`
553
+ wrapper, no dedup, no re-injection after compaction — firing depends on the
554
+ model choosing to act on a description it sees in the index. This is exactly
555
+ why the acceptance test is mandatory here: it is the *only* guarantee, so run
556
+ it on the model(s) your users will actually use, not just the strongest one.
557
+ 3. **No skill system at all:** there is nothing to register, and the *only*
558
+ mechanism is the model reading `SKILL.md` on demand. But the model can't read
559
+ what it can't find: `using-spark/SKILL.md` does **not** enumerate the
560
+ available skills, so on its own the model won't know which skills exist or
561
+ their triggers. You must supply a discovery path. Two options, and they differ
562
+ in durability: (a) generate a skill index (each `skills/*/SKILL.md`'s `name` +
563
+ `description` frontmatter) and place it *inside* the `<EXTREMELY_IMPORTANT>`
564
+ wrapper alongside the tool mapping (Shape B recipe above) so it's covered by
565
+ the dedup guard — but a build-time index goes stale as skills are added; or
566
+ (b) instruct the model to list `skills/*/SKILL.md` at runtime and read their
567
+ frontmatter to find a match — slower but never stale. Prefer (b) unless you
568
+ have a reason not to. Without either, a no-skill-system port loads the
569
+ bootstrap but silently never triggers any other skill.
570
+
571
+ In cases 2 and 3, say plainly in your tool mapping that reading `SKILL.md` is the
572
+ blessed path, so the model doesn't think it's violating the "never read skill
573
+ files" rule. Don't go hunting for a `skillPaths`-style registration API in a
574
+ harness that has no skill system — case 3 has none.
575
+
576
+ ### Step 6 — Add tests
577
+
578
+ Match the existing per-harness test style:
579
+
580
+ - **Shape A:** assert the hook's stdout has the exact JSON shape your harness
581
+ consumes, and that it contains the bootstrap. See `tests/hooks/test-session-start.sh`,
582
+ which validates each harness's output shape.
583
+ - **Shape B:** a unit test that fakes the harness's plugin API and asserts the
584
+ lifecycle handlers register, the bootstrap injects once, the dedup guard
585
+ works, and (if relevant) compaction re-injection works. See
586
+ `tests/pi/test-pi-extension.mjs`. Add an isolated-install integration check in
587
+ the style of `tests/opencode/`.
588
+ - If the bootstrap is cached, test that the cache behaves when the file is
589
+ missing (see the OpenCode caching tests).
590
+
591
+ These automated tests cover the wiring; the live tmux run in Step 7 is what
592
+ proves the integration actually triggers skills.
593
+
594
+ ### Step 7 — Install locally, then drive a live instance to verify
595
+
596
+ You cannot confirm a port works by reading code. You have to run the harness with
597
+ your in-progress port loaded and watch a real session — which is also how you
598
+ produce the transcript the PR requires.
599
+
600
+ **Install locally.** Point a *local* instance of the harness at your working
601
+ tree, not a published build:
602
+
603
+ - **Shape A / C:** install the plugin/extension from this repo's local path (or
604
+ symlink its directory into wherever the harness looks). Find the harness's
605
+ "install from a local directory / git checkout" path in its docs.
606
+ - **Shape B:** register the local module — e.g. an `opencode.json` `plugin`
607
+ entry pointing at the local path, or pi resolving the `package.json` fields
608
+ from the repo.
609
+
610
+ Reinstall after each change and restart the harness, since the bootstrap loads at
611
+ startup.
612
+
613
+ **Drive it with tmux.** Most harnesses are interactive REPLs/TUIs that can't be
614
+ driven by piping stdin, so run the harness inside a detached tmux session and
615
+ control it with `send-keys` / `capture-pane`. A harness may advertise a
616
+ non-interactive "run one prompt" mode (e.g. `opencode run "..."`) — try it for the
617
+ quick smoke check, but **don't depend on it**: these modes are frequently flaky,
618
+ auth-gated, or trust-gated (one real harness's `--print` mode hung and timed out
619
+ with no output every time). Be ready to do *everything*, including the smoke
620
+ check, through tmux.
621
+
622
+ **Clear the gates first, or tmux stalls silently.** Many harnesses block on
623
+ first-run onboarding, a "do you trust this folder?" prompt, a sandbox mode, or a
624
+ permission gate — and a detached tmux session will just sit there with no error
625
+ while it waits. Before the run, pre-trust your scratch directory (in the harness's
626
+ settings/config) or be prepared to answer those prompts via `send-keys`, and
627
+ account for the harness's startup time in your first `sleep`.
628
+
629
+ ```bash
630
+ # 1. Launch the harness detached, in a throwaway project dir
631
+ mkdir -p /tmp/port-smoke
632
+ tmux new-session -d -s port-test -c /tmp/port-smoke '<harness-launch-command>'
633
+
634
+ # 2. Let it initialize — real TUIs take longer than you think (10s+ with a model
635
+ # handshake); tune this. THEN capture and clear any blocking modal before you
636
+ # type a prompt: first-run onboarding and "trust this folder?" are modal, so
637
+ # keystrokes sent during them select menu items instead of typing your prompt.
638
+ sleep 12
639
+ tmux capture-pane -t port-test -p # onboarding / trust prompt? answer it via send-keys first
640
+ # (e.g. tmux send-keys -t port-test Enter # to accept a trust prompt — inspect before assuming)
641
+
642
+ # 3. Smoke check: does the model know it has spark?
643
+ # Send the text and Enter as SEPARATE send-keys with a beat between them —
644
+ # sending them together races on some TUIs (Enter arrives before the text lands).
645
+ tmux send-keys -t port-test 'What are your spark?'; sleep 0.4; tmux send-keys -t port-test Enter
646
+ sleep 5
647
+ tmux capture-pane -t port-test -p # reply should show it knows its skills
648
+
649
+ # 4. Acceptance test: exact prompt (note the escaped apostrophe), fresh session
650
+ tmux send-keys -t port-test 'Let'\''s make a react todo list'; sleep 0.4; tmux send-keys -t port-test Enter
651
+ # poll until the turn finishes — re-capture every few seconds, don't capture once
652
+ sleep 8
653
+ tmux capture-pane -t port-test -p # PASS = brainstorming triggers BEFORE any code
654
+
655
+ # 5. Save the transcript for the PR, then clean up
656
+ tmux capture-pane -t port-test -p > /tmp/port-smoke/transcript.txt
657
+ tmux kill-session -t port-test
658
+ ```
659
+
660
+ tmux gotchas that bite here: wait after launch before the first capture; send the
661
+ prompt text and `Enter` as *separate* `send-keys` calls with a short `sleep`
662
+ between them (sending them together races on some TUIs), and `Enter` is a key name
663
+ not `\n`; the agent's turn takes time, so **poll `capture-pane` in a loop** rather
664
+ than capturing once; `capture-pane` shows only the visible pane, so for a long
665
+ conversation use the harness's own transcript/log file as the record of truth;
666
+ always `kill-session` when done.
667
+
668
+ If the smoke check shows the model *doesn't* know it has spark, the
669
+ bootstrap isn't loading — fix that before bothering with the acceptance test.
670
+
671
+ ---
672
+
673
+ ## Part 6 — Distribution and release
674
+
675
+ A working integration in this repo isn't usable until a real user can install
676
+ it. Distribution differs per harness ecosystem — find yours:
677
+
678
+ | Channel | Example | What you do |
679
+ |---|---|---|
680
+ | Native plugin marketplace | Claude Code | Register in `.claude-plugin/marketplace.json`; users `/plugin install`. The external `spark-marketplace` repo is the source of truth users install from — see the release steps in `CLAUDE.md`. |
681
+ | External marketplace fork, synced by script | Codex | `scripts/sync-to-codex-plugin.sh` rsyncs the tracked plugin files into a separate fork repo and opens a PR. Read its include/exclude list so you ship the right tree (it deliberately drops repo-internal dirs and other harnesses' dotdirs). |
682
+ | Git-URL extension install | Gemini, Kimi Code, OpenCode | Users install from a git URL (`gemini extensions install …`; Kimi Code `/plugins install …`; an `opencode.json` `plugin` array entry). Document the exact command. |
683
+ | Package-manifest fields | pi | Declared through fields in the repo-root `package.json`; users install via the harness's package command. |
684
+ | Local installer (plugin install) | Antigravity (`agy`) | A small `install.sh` that runs the harness's own `agy plugin install` against a staging dir holding the manifest, the skills, and a generated `contextFileName` context file (the bootstrap). Everything arrives through the install mechanism — *not* by editing the user's config (see below). |
685
+
686
+ Then:
687
+
688
+ - **A plugin installer may silently strip *undeclared* files — so make the
689
+ bootstrap a file the installer *recognizes*, never a user-config edit.** A
690
+ `plugin install` typically copies only the components it knows about
691
+ (skills/agents/commands/mcp/hooks/context) and discards anything else, so a
692
+ context file the manifest doesn't declare just vanishes from the install. The
693
+ fix is **not** to give up and write into the user's config (**rule 2**) — it's
694
+ to declare the bootstrap as a recognized component. In escalation order:
695
+ - **Ship a context file the manifest declares.** If the harness has a
696
+ `contextFileName`-style field (an extension-declared file it loads every
697
+ session), that is the strongest clean bootstrap: declare it, and the installer
698
+ preserves it *and* the harness loads it. Generate it at install time from the
699
+ live `using-spark/SKILL.md` + the tool mapping (wrapped in
700
+ `<EXTREMELY_IMPORTANT>`) so the installed bootstrap never drifts. This is what
701
+ `.antigravity-plugin/install.sh` does — `agy plugin install` reports
702
+ `✔ context : ANTIGRAVITY.md`, and a clean session reads `using-spark`'s
703
+ SKILL.md, loads `brainstorming`, and enters the brainstorming flow before any
704
+ code. **Verify with a marker** that the installer keeps the file and the
705
+ harness loads it: one porter wrongly concluded it couldn't, because they
706
+ shipped the file *without* declaring `contextFileName` and it was stripped as
707
+ unrecognized.
708
+ - **Otherwise lean on the installed `using-spark` skill itself.** If the
709
+ harness surfaces each installed skill's name + description at session start,
710
+ the `using-spark` description ("Use when starting any conversation…")
711
+ can prompt the model to load it — installing the skill *is* the bootstrap.
712
+ Softer (no guaranteed wrapper; it carries triggering but not the tool mapping
713
+ — see Step 5), so prefer the declared context file when available.
714
+ - If neither works, the harness cannot be cleanly supported yet — **say so**
715
+ and raise it, rather than hand-editing the user's config.
716
+
717
+ - **Write install docs.** A `docs/README.<harness>.md` and/or a
718
+ `.<harness>/INSTALL.md` (see `docs/README.opencode.md` and
719
+ `.opencode/INSTALL.md`), plus an install section in the top-level `README.md`.
720
+ The only supported install action is **running the harness's own install
721
+ command** (`agy plugin install`, `gemini extensions install`, `/plugin
722
+ install`, etc.). Hand-copying skill files and editing the user's global/personal
723
+ config are *both* off-limits (rule 2 / the PR rules). If the harness has no
724
+ install command at all — its only surface is a user-owned config file — then it
725
+ fails the "deliver via install mechanism" rule, and you should raise that rather
726
+ than ship an installer that edits the user's files.
727
+ - **Register the version.** If your harness introduces a *new* versioned
728
+ manifest, add its path and version field to `.version-bump.json` so
729
+ `scripts/bump-version.sh` keeps it in lockstep (read that file to see what's
730
+ currently tracked). A new manifest that isn't registered there will ship a
731
+ stale version. If your harness instead rides an already-tracked file — pi
732
+ declares itself in the repo-root `package.json`, which is already listed —
733
+ there's nothing new to add.
734
+ - **If no existing channel fits, you're standing up a new one.** None of the four
735
+ rows may match your harness. If it needs a Codex-style external fork sync,
736
+ `scripts/sync-to-codex-plugin.sh` is the template to clone (note its anchored
737
+ include/exclude list and its PR automation). And whenever you add a new
738
+ per-harness directory, add it to the *other* harnesses' sync excludes (e.g. the
739
+ EXCLUDES list in `sync-to-codex-plugin.sh`) so your dotdir doesn't leak into
740
+ their distributions.
741
+
742
+ ---
743
+
744
+ ## Part 7 — Cross-platform / Windows
745
+
746
+ Only relevant to the shell-hook shape. `hooks/run-hook.cmd` is a polyglot: a
747
+ single file that's valid as both a Windows batch script and a Unix shell script.
748
+ On Windows, `cmd.exe` runs the batch portion, which locates `bash` (Git for
749
+ Windows, then `bash` on PATH) and runs the named hook script; if no bash is
750
+ found it exits cleanly so the harness still works, just without injection. On
751
+ Unix, the leading `:` makes the batch block a no-op and the shell runs the
752
+ script directly.
753
+
754
+ Two rules this enforces, which you must respect:
755
+
756
+ - **Hook scripts are extensionless** (`session-start`, not `session-start.sh`).
757
+ Claude Code's Windows handling prepends `bash` to any command containing
758
+ `.sh`, which would double-invoke. Name your hook script without an extension.
759
+ - Don't write per-OS variants of the hook script. One extensionless bash script
760
+ plus the polyglot wrapper covers all three platforms.
761
+
762
+ `hooks/run-hook.cmd` itself is the authoritative implementation — read it. See
763
+ `docs/windows/polyglot-hooks.md` for the background and rationale behind the
764
+ dispatcher pattern.
765
+
766
+ ---
767
+
768
+ ## Part 8 — Submitting the PR
769
+
770
+ - Target the **`dev`** branch. One harness per PR.
771
+ - Fill in the PR template's **"New harness support"** section and paste the
772
+ complete acceptance-test transcript (the "Let's make a react todo list"
773
+ session showing `brainstorming` auto-triggering). A PR without this proof will
774
+ be closed.
775
+ - SPARK is a zero-dependency plugin. Don't add a third-party runtime
776
+ dependency. Adding a new harness is the one carve-out the contributor rules
777
+ allow, and even then keep it to what the integration strictly requires —
778
+ type-only imports that compile away are fine; runtime packages are not.
779
+ - Don't touch skill bodies (Part 1). If you found yourself editing a `SKILL.md`
780
+ to make the port work, the fix belongs in your tool mapping instead.
781
+
782
+ ---
783
+
784
+ ## Appendix A — Reference integrations (current)
785
+
786
+ Use this as the live index; when in doubt, read the files, not this table.
787
+
788
+ | Harness | Entry point | Bootstrap mechanism | Tool mapping | Tests | Distribution |
789
+ |---|---|---|---|---|---|
790
+ | Claude Code | `.claude-plugin/plugin.json` + `hooks/hooks.json` | shell hook → `hooks/session-start` (`hookSpecificOutput.additionalContext`) | native `Skill` tool; `references/claude-code-tools.md` | `tests/hooks/` | marketplace |
791
+ | Codex | `.codex-plugin/plugin.json` + `hooks/hooks-codex.json` | shell hook → `hooks/session-start-codex` | `references/codex-tools.md` | `tests/codex-plugin-sync/`, `tests/hooks/` | fork sync (`scripts/sync-to-codex-plugin.sh`) |
792
+ | Cursor | `.cursor-plugin/plugin.json` + `hooks/hooks-cursor.json` | shell hook → `hooks/session-start` (`additional_context`) | `references/claude-code-tools.md` | `tests/hooks/` | hand-authored |
793
+ | Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | `references/copilot-tools.md` | `tests/hooks/` | — |
794
+ | Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` |
795
+ | Kimi Code | `.kimi-plugin/plugin.json` | manifest `sessionStart.skill` loads `using-spark` | inline `skillInstructions` in manifest | `tests/kimi/` | marketplace or `/plugins install` GitHub URL |
796
+ | OpenCode | `.opencode/plugins/spark.js` (declared via root `package.json` `main`) | in-process: `config` hook registers skills dir; `experimental.chat.messages.transform` injects user message | inline in `spark.js` | `tests/opencode/` | `opencode.json` plugin git URL |
797
+ | pi | `.pi/extensions/spark.ts` | in-process: `resources_discover` registers skills; `context` event injects user message; lifecycle-flag + compaction-aware | `piToolMapping()` inline **and** `references/pi-tools.md` | `tests/pi/` | repo-root `package.json` fields |
798
+
799
+ ## Appendix B — Gotchas that have bitten porters
800
+
801
+ - **Opt-in isn't a port.** If your human partner has to do anything per session
802
+ to get SPARK, the acceptance test fails. Re-read Part 2.
803
+ - **Wrong JSON field → silent failure or double injection.** Shape A only.
804
+ Confirm the exact field/nesting; Claude Code reads two fields without dedup.
805
+ - **Hook-config schema varies per harness.** Shape A. Cursor's `hooks-cursor.json`
806
+ looks nothing like the Claude/Codex one (`version`, lowercase `sessionStart`,
807
+ relative command, no `matcher`/`type`/`async`). Match the closest existing file.
808
+ - **Plugin-root env var differs per harness.** Shape A. The hook command uses
809
+ `${CLAUDE_PLUGIN_ROOT}` (Claude), `${PLUGIN_ROOT}` (Codex), or a relative path
810
+ (Cursor). Use what your harness exports; the script re-derives the root itself.
811
+ - **System-message injection.** Shape B injects a *user* message on purpose
812
+ (#750, #894). Don't "fix" it to a system message.
813
+ - **Per-step vs per-turn callbacks.** OpenCode fires every step (per-call dedup
814
+ guard); pi fires per turn (lifecycle flag + `agent_end` reset). Copying one
815
+ harness's dedup strategy onto the other's callback frequency breaks injection.
816
+ - **Message-object shape is per-harness.** Shape B. pi and OpenCode use
817
+ incompatible shapes; discover yours, don't copy a reference's object literal.
818
+ - **Hunting for a skill-registration API that doesn't exist.** A harness with no
819
+ skill system (not just no `Skill` tool) has nothing to register — the model
820
+ reads `SKILL.md` on demand. Don't assume a `skillPaths` equivalent exists.
821
+ - **Mapping in two places.** For in-process plugins the mapping may live both
822
+ inline and in a `references/` file (pi). Update both.
823
+ - **The "never read skill files" line.** It means "don't bypass your platform's
824
+ skill-loading mechanism," not "never use file-read." On a no-skill-tool harness
825
+ that mechanism *is* reading `SKILL.md` — say so explicitly in the mapping
826
+ (Part 5).
827
+ - **`.sh` on Windows.** Keep hook scripts extensionless (Part 7).
828
+ - **Unregistered version.** A new manifest not added to `.version-bump.json`
829
+ ships stale (Part 6).
830
+ - **Editing skills to fit the harness.** Never. The fix goes in the tool mapping.