ThinkRail
by andrewliu83
README.md
# thinkrail — one plugin, three CLI coding agents
ThinkRail's spec graph and workflow family, packaged so **Claude Code**, **Codex CLI** and the
**Grok CLI** all load it from one directory:
- **seven `spec_*` tools** over an MCP stdio server — `spec_grep`, `spec_get`, `spec_graph`,
`spec_create`, `spec_update`, `spec_delete`, `spec_validate`;
- **ten workflow skills** — `choosing-a-workflow` and the family it routes;
- **the standing rule** — specs are the ground truth, and every new piece of work enters the router.
It is a port: the spec-graph code and the workflow skill texts come from JetBrains ThinkRail
(`packages/spec-graph`, `packages/pi-thinkrail-workflow`, Apache-2.0 / MIT — see `NOTICE`), through the
DSH (`dshthinkrailspec`), PI-Desktop (`pideskthinkrails`) and omp (`ompthinkrail`) carriers. What changed
here is the carrier, not the workflow: see `architecture.md` and the difference table below.
## Install
The plugin is this directory. One command wires all three hosts (it is convergent — a second run leaves
the same state):
```bash
node scripts/install.mjs # add the plugin to claude, codex and grok; write the grok rule
node scripts/install.mjs --dry-run # print what it would run
node scripts/install.mjs --uninstall
```
What it does per host, and why:
| Host | Plugin | Tools | Rule |
|---|---|---|---|
| Claude Code | `claude plugin marketplace add <dir>` + `claude plugin install thinkrail@thinkrail` | `.mcp.json` (`${CLAUDE_PLUGIN_ROOT}`) | `hooks/hooks.json` → SessionStart `additionalContext` |
| Codex CLI | `codex plugin marketplace add <dir>` + `codex plugin add thinkrail@thinkrail` | `codex mcp add thinkrail -- node <dir>/mcp/server.mjs` | the same SessionStart hook (Codex trusts hooks by hash: run `/hooks` once) |
| Grok CLI | `grok plugin install <dir> --trust` + `grok plugin enable thinkrail` | `.mcp.json` (`${CLAUDE_PLUGIN_ROOT}`) | `~/.grok/rules/thinkrail-workflow.md` (Grok has no session-start context channel) |
Manual equivalents, if you prefer to run them yourself:
```bash
claude plugin marketplace add "$PWD" && claude plugin install thinkrail@thinkrail
codex plugin marketplace add "$PWD" && codex plugin add thinkrail@thinkrail
codex mcp add thinkrail -- node "$PWD/mcp/server.mjs"
grok plugin install "$PWD" --trust && grok plugin enable thinkrail
```
Restart the host (or reload plugins) so a new session picks everything up. In Claude Code, `claude plugin
details thinkrail` prints the component inventory; Codex needs `/hooks` once to trust the hook; Grok's
`grok plugin details thinkrail` and `grok mcp doctor thinkrail` show the same thing.
Edits to this directory reach the hosts differently: Claude Code loads the plugin **in place** and Grok
links it, so both pick up a skill or tool change at the next session; Codex **copies** the plugin into
`~/.codex/plugins/cache/`, so re-run `node scripts/install.mjs` after editing.
The MCP server takes the workspace root from `--root`, then `THINKRAIL_ROOT`, then `CLAUDE_PROJECT_DIR`,
then its own working directory (which every one of the three hosts sets to the session's project
directory). A root that is missing or not a directory is reported as an error instead of as an empty
project.
## What loads
- **Tools.** One MCP stdio server (`mcp/server.mjs`) exposes the seven tools with the family's names,
parameters and wording. The host namespaces them: `mcp__plugin_thinkrail_thinkrail__spec_get` on Claude
Code, `mcp__thinkrail__spec_get` on Codex, `thinkrail__spec_get` on Grok.
- **Skills.** `skills/<name>/SKILL.md` — Claude Code registers them as `thinkrail:<name>` (invoke
`/thinkrail:spec-graph`), Codex lists them as `thinkrail:<name>` (invoke `$spec-graph`), Grok lists them
by name. Descriptions are kept under 150 characters because Grok cuts a longer catalog entry at ~150;
everything else is the family's text, verbatim.
- **The rule.** `hooks/workflow-rule.mjs` holds the text; `hooks/session-start.mjs` hands it to
`SessionStart` as `hookSpecificOutput.additionalContext`, which is model-visible developer context on
Claude Code and Codex. Grok records a SessionStart hook's output without injecting it, so the installer
writes the same text to `$GROK_HOME/rules/thinkrail-workflow.md`, a rules directory Grok scans in every
project.
## Anatomy
```
.mcp.json the MCP server, on the path all three hosts read by default
hooks/ the standing rule (text) and its SessionStart hook
mcp/server.mjs JSON-RPC 2.0 over stdio; root resolution; error folding
mcp/tools.mjs the seven spec_* tools: names, parameters, wording, scaffold headings
core/ the host-free spec model (parse, graph, query, validate, store) + vendored yaml
skills/<name>/SKILL.md the ten workflow skills
.claude-plugin/ Claude Code plugin + marketplace manifests
.codex-plugin/ Codex plugin manifest
.agents/plugins/ Codex marketplace index
.grok-plugin/ Grok marketplace index
plugin.json Grok's optional manifest
scripts/install.mjs the per-host wiring above
tests/ node --test: core model, MCP transport, repo/host contract
```
## Deliberate carrier differences
Behavior is ported, the carrier is not. Against `pideskthinkrails` (and, through it, `dshthinkrailspec`):
| PI-Desktop carrier | Here | Why |
|---|---|---|
| Plugin process with `globalThis.pi` and a manifest permission model | An MCP stdio server started by the host | A CLI host has no plugin runtime; MCP is the one tool protocol all three speak |
| `pi.fs` gateway with declared scopes (`fs.read`/`fs.write`/`fs.delete`) | `node:fs` in the server process | The plugin *is* the process; a gateway would add a contract with nothing behind it |
| Right-hand panel, view tabs, `hint` polling, two commands that point at it | — (dropped) | No UI to host them; the commands only told a user where to look |
| 3-second short-term read cache for the panel | — (dropped) | Nothing human-paced reads here; a tool call always revalidates, so a generic `write`/`edit` cannot leave a stale graph |
| Two `systemPromptOptions.sections` rules through the agent extension | SessionStart hook (Claude, Codex) + `$GROK_HOME/rules/` (Grok) | No plugin prompt API exists in a CLI host |
| Description budget 240 characters | 150 characters | Grok truncates the catalog entry at ~150; the family's rule is to keep the whole trigger |
| `spec_delete` to the system trash via the host API | Trash on macOS (`rename` into `~/.Trash`), unlink elsewhere | Keeps the safer semantics where this is developed |
| Scaffold headings in the repo's language (Chinese) | English (`## Responsibility`, `## Boundary`, …) | Convention, not contract: no consumer parses body headings |
Everything else is byte-parity: tool names, parameter schemas, acceptance criteria, model-visible text,
frontmatter dialect (a file is a spec exactly when its frontmatter carries non-empty `id` and `type`),
lossless frontmatter edits, index revalidation, walk order, and the ten skill bodies.
## Verified against
Each host was exercised with the real CLI on this machine; `claude` = Claude Code 2.1.284, `codex` = Codex
CLI 0.153.4, `grok` = Grok 1.0.44.
| Claim | Evidence |
|---|---|
| Claude Code loads the plugin | `claude plugin details thinkrail` → Skills (10), Hooks (1), MCP servers (1) |
| …sees the skills and tools | session transcript: `skill_listing` with all ten `thinkrail:<name>` entries, `mcp__plugin_thinkrail_*` tool names, server `Connected` in `claude mcp list` |
| …gets the rule | transcript: `SessionStart:startup` `hook_success` whose stdout is the rule, then a `hook_additional_context` attachment carrying it |
| …launches the server with the session's root | probe server logged `cwd=/private/tmp/tr-smoke`, `CLAUDE_PLUGIN_ROOT=<plugin>`, `CLAUDE_PROJECT_DIR=<project>`, and answered `roots/list` with the project |
| Codex loads the plugin | `codex plugin list` → installed, enabled; `r7 = …/plugins/cache/thinkrail/thinkrail/1.0.0/skills` in the session's skill roots with all ten skills listed |
| Codex runs the rule hook | session rollout: the rule text as a `developer` message after `hook: SessionStart Completed` |
| Codex launches the server with the session's root | probe server logged `cwd=/private/tmp/tr-smoke` |
| Grok loads the plugin and its tools | `grok plugin details` → 1 skill dir, hooks, MCP servers; `grok mcp doctor thinkrail` → handshake OK, **7 tools discovered** |
| Grok sees the skills | `grok inspect --json` → the plugin provides 10 skills, and all ten are in the catalog |
| Grok gets the rule | session `prompt_context.json` → `agents_md_files` contains `~/.grok/rules/thinkrail-workflow.md` with the rule text |
| The tools work end to end | `node --test tests/*.test.mjs` (34 tests): MCP handshake, all seven tools over the transport, error wording, path escapes |
Codex and Grok could not complete a model turn on this machine (no reachable credentials), so their
per-host evidence stops at what the host records *before* the model call: plugin inventory, skill catalog,
hook injection, MCP handshake and tool discovery. The rule text and the tool results themselves are
verified live on Claude Code (which injected the rule and where the server answers), and over the MCP
transport directly.
## Known limits
- **Codex cannot use `${CLAUDE_PLUGIN_ROOT}` in MCP arguments.** Verified: the variable is absent from a
plugin MCP server's environment and a literal `${…}` argument never resolves. That is why Codex's server
is registered by absolute path in `~/.codex/config.toml`, where it takes precedence over the plugin's
own entry and inherits the session's working directory. Re-run `scripts/install.mjs` if the repository
moves.
- **Codex trusts hooks by hash.** Until you run Codex's `/hooks` review, the SessionStart rule does not
fire (the plugin's MCP tools still work).
- **Grok has no session-start context channel**, and its SessionStart hook output is recorded but not
injected — hence the rules file. Grok also gates a server whose command lives inside the folder you are
working in behind folder trust (`--trust`), which only matters when you run Grok inside this repository.
- **Claude Code's plugin install of a local directory is live**, not a frozen copy: the marketplace points
at this directory and `${CLAUDE_PLUGIN_ROOT}` resolves to it. Moving the directory means re-adding the
marketplace.
- **Rule 14 (verify-by-use) stays suspended.** No request has been observed flowing router → worker →
handoff in a real model turn; the tests pin contract-level facts, and the smoke checks above pin
loading, not routing.
- The three hosts' skill catalogs are always-on context. Ten skills cost ~40 tokens each on Claude Code
(`claude plugin details` prints the estimate); Codex shortens descriptions when its budget fills.
## Docs
- `goal-and-requirements.md` — what this port is for, in and out of scope, acceptance criteria.
- `architecture.md` — drivers, decisions, invariants, out-of-scope.
- `SPEC.md`, `core/SPEC.md`, `mcp/SPEC.md` — the plugin's own module specs.
- `NOTICE` — attribution and the license each part keeps.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues