Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "torque-loop",
"interface": {
"displayName": "Torque Loop"
},
"plugins": [
{
"name": "torque-loop",
"source": {
"source": "local",
"path": "./"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Developer Tools"
}
]
}
41 changes: 41 additions & 0 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "torque-loop",
"version": "0.2.0",
"description": "Torque Loop evolves ambiguous work into shipped, tested, serialized artifacts through evidence-gated loops.",
"author": {
"name": "Danny Gillespie",
"url": "https://github.com/TheLucidTech"
},
"homepage": "https://github.com/TheLucidTech/torque-loop",
"repository": "https://github.com/TheLucidTech/torque-loop",
"license": "MIT",
"keywords": [
"workflow",
"quality",
"validation",
"skills",
"state",
"qa",
"decision"
],
"skills": "./skills/",
"interface": {
"displayName": "Torque Loop",
"shortDescription": "Evidence-gated workflow skills and ratchet state helpers",
"longDescription": "Use Torque Loop to lock vague goals, rank blockers, build small artifacts, attack assumptions, patch failures, serialize state, and run bounded evidence-gated evolution loops.",
"developerName": "Danny Gillespie",
"category": "Developer Tools",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"Use Torque Loop to lock and ship this task.",
"Run an evidence-gated evolve pass on this file.",
"Attack this plan and patch only what fails."
],
"websiteURL": "https://github.com/TheLucidTech/torque-loop",
"brandColor": "#111827"
}
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Codex install metadata: `.codex-plugin/plugin.json` plus a repo-local
`.agents/plugins/marketplace.json`, so the plugin can be registered and installed by
Codex CLI and surfaced in the Codex app.

### Changed

- Package metadata, `ratchet doctor`, and plugin-shape tests now cover both Claude Code
and Codex manifests.
- npm packaging includes the Codex marketplace file explicitly as
`.agents/plugins/marketplace.json`, without packaging the whole `.agents` tree.
- Repo snapshots now surface `.agents` and `.codex-plugin` alongside the existing
plugin-critical dot directories.

## [0.2.0] - 2026-07-03 — Proof Gate

Hardening, not expansion. This release makes the loop's philosophy mechanically true:
Expand Down
80 changes: 58 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

**Mutate. Test. Keep the delta.**

A Claude Code plugin for evolving one artifact through evidence-gated improvement loops.
A Claude Code and Codex plugin for evolving one artifact through evidence-gated improvement
loops.

> Not affiliated with, endorsed by, or sponsored by Anthropic.
> Not affiliated with, endorsed by, or sponsored by Anthropic or OpenAI.

Torque Loop is **not a prompt library.** A prompt library gives Claude better words. Torque
Loop gives Claude a job:
Expand All @@ -36,20 +37,19 @@ restarting. *Ambiguity in. Artifact out. Failure tested. State advanced.*

## Install

Torque Loop has two halves: the **Claude Code plugin** (the slash commands + agents + hooks)
and the **`ratchet` CLI** (the state engine the commands call). Installing the plugin is the
main path; the CLI ships inside it and is put on `PATH` automatically while the plugin is
enabled. The plugin's command namespace stays `/ratchet:*` — the brand is Torque Loop, the
commands keep the wrench energy.
Torque Loop has two halves: the **agent plugin** (skills/commands, agents, and Claude-only
hooks) and the **`ratchet` CLI** (the state engine the skills call). Claude Code exposes the
skills as `/ratchet:*` slash commands. Codex installs the same skills under the
`torque-loop` plugin and can use them from Codex CLI or the Codex app.

### Requirements

- [Claude Code](https://claude.com/claude-code)
- Claude Code or Codex CLI/app
- Node.js ≥ 18 (`node --version`)
- On Windows, the bundled hooks call the CLI via `node`, so no shell-specific setup is
needed.

### A. Install as a Claude Code plugin — global (available in every project)
### A. Install as a Claude Code plugin — global

The repo doubles as a single-plugin marketplace, so you can install it directly.

Expand Down Expand Up @@ -93,9 +93,44 @@ Or vendor it directly inside the project and reference the local path. Either wa
commands appear only when that project is open. (Prefer an absolute path, or a path relative
to the settings file, so it resolves on every machine.)

### C. Install the `ratchet` CLI on its own (optional)
### C. Install as a Codex plugin — CLI

The commands find the CLI automatically, but you can also use `ratchet` from any terminal.
The repo also contains a Codex marketplace manifest at
`.agents/plugins/marketplace.json` and a Codex plugin manifest at
`.codex-plugin/plugin.json`.

```bash
# 1. get the repo
git clone https://github.com/TheLucidTech/torque-loop.git

# 2. register this repo as a Codex marketplace
codex plugin marketplace add /absolute/path/to/torque-loop

# 3. install the plugin from that marketplace
codex plugin add torque-loop@torque-loop

# 4. verify Codex can see it
codex plugin list --marketplace torque-loop
```

For local development, re-run `codex plugin add torque-loop@torque-loop` after manifest
changes, then start a new Codex thread so the refreshed skills are loaded.

### D. Install as a Codex plugin — app

Register the marketplace once with the Codex CLI:

```bash
codex plugin marketplace add /absolute/path/to/torque-loop
```

Then open the Codex app, go to **Plugins**, find **Torque Loop** under Developer Tools,
and install it. The app and CLI share the configured marketplace source.

### E. Install the `ratchet` CLI on its own (optional)

Claude Code puts the bundled CLI on `PATH` while the plugin is enabled. Codex skills can
use a globally installed `ratchet`, or you can run the bundled CLI from the plugin root.

```bash
cd torque-loop
Expand All @@ -122,7 +157,7 @@ ratchet status

State survives plugin updates. It is written to, in order of preference:

1. `$CLAUDE_PLUGIN_DATA` — set by Claude Code for enabled plugins (recommended).
1. `$CLAUDE_PLUGIN_DATA` — set by Claude Code for enabled plugins.
2. `$RATCHET_DATA_DIR` — override it yourself.
3. `~/.ratchet` — fallback.

Expand All @@ -133,7 +168,9 @@ one shared data directory.

## Commands

Run `/ratchet:ignite` when you don't know which command to run — it drives the full loop.
In Claude Code, run `/ratchet:ignite` when you don't know which command to run — it drives
the full loop. In Codex, ask it to use the matching Torque Loop skill, for example
`torque-loop:ignite` or `torque-loop:evolve`.

### Core loop

Expand Down Expand Up @@ -191,10 +228,9 @@ absolute: **no proof → no keep; no keep → no progress claim.** It is never a
this better" — it evolves along one chosen pressure vector and records every verdict to
`.ratchet/evolve-log.jsonl` via the `ratchet-evolve` helper CLI.

> Note: because plugin skills are namespaced by the plugin, the command is invoked as
> `/ratchet:evolve` (renamed from the older `ratchet-evolve` skill in v0.2.0 — no alias is
> kept). Drop the `SKILL.md` into `~/.claude/skills/evolve/` to use it as a bare `/evolve`
> outside the plugin.
> Note: in Claude Code, the plugin command is invoked as `/ratchet:evolve` (renamed from
> the older `ratchet-evolve` skill in v0.2.0 — no alias is kept). In Codex, use the
> installed Torque Loop skill, typically surfaced as `torque-loop:evolve`.

#### One run, end to end

Expand Down Expand Up @@ -222,10 +258,10 @@ refused at write time — the loop cannot record progress it did not prove.
## How it works

```
skills/*/SKILL.md → Claude-facing operating discipline (the slash commands)
skills/*/SKILL.md → agent-facing operating discipline (Claude slash commands / Codex skills)
agents/*.md → ratchet-builder · ratchet-auditor · ratchet-scribe
hooks/hooks.json → session-start init · post-edit tracking · stop-compile reminder
bin/ratchet → the state CLI (added to PATH while the plugin is enabled)
hooks/hooks.json → Claude-only session-start init · post-edit tracking · stop reminder
bin/ratchet → the state CLI (PATH in Claude, global or plugin-root path in Codex)
bin/ratchet-evolve → the evolution-loop helper CLI (snapshot · score · verify · log)
src/*.js → state, scoring, ledger, artifact indexing, snapshots, rendering
src/evolve/*.js → snapshot · pressure · mutation scoring · verify runner · journal
Expand Down Expand Up @@ -283,8 +319,8 @@ public issue tracker.

MIT © 2026 Danny Gillespie

Not affiliated with, endorsed by, or sponsored by Anthropic. "Claude" and "Claude Code" are
trademarks of Anthropic.
Not affiliated with, endorsed by, or sponsored by Anthropic or OpenAI. "Claude" and
"Claude Code" are trademarks of Anthropic. "Codex" is a trademark of OpenAI.

---

Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@torque-loop/plugin",
"version": "0.2.0",
"description": "Torque Loop — a Claude Code plugin that evolves ambiguity into shipped, tested, serialized artifacts through evidence-gated loops. Not affiliated with Anthropic.",
"description": "Torque Loop — a Claude Code and Codex plugin that evolves ambiguity into shipped, tested, serialized artifacts through evidence-gated loops.",
"license": "MIT",
"author": "Danny Gillespie",
"type": "commonjs",
Expand All @@ -25,11 +25,14 @@
"agents",
"hooks",
"templates",
".claude-plugin"
".claude-plugin",
".codex-plugin",
".agents/plugins/marketplace.json"
],
"keywords": [
"torque-loop",
"claude-code",
"codex",
"plugin",
"workflow",
"quality",
Expand Down
4 changes: 3 additions & 1 deletion skills/evolve/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ without it, propose patches but do not modify files). If the target or goal is m
straight to **ASK**.

The deterministic helpers live in the `ratchet-evolve` CLI (call via Bash; fall back to
`node "$CLAUDE_PLUGIN_ROOT/bin/ratchet-evolve"` if not on PATH).
`node "<plugin root>/bin/ratchet-evolve"` if not on PATH). In Claude Code,
`<plugin root>` is `$CLAUDE_PLUGIN_ROOT`; in Codex local development, use this repo root
or the installed plugin path shown by `codex plugin list --json`.

## The loop

Expand Down
5 changes: 4 additions & 1 deletion skills/ignite/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ ratchet status
ratchet snapshot repo
```

If `ratchet` is not on PATH, call it as `node "$CLAUDE_PLUGIN_ROOT/bin/ratchet"`.
If `ratchet` is not on PATH, call the plugin-local CLI as
`node "<plugin root>/bin/ratchet"`. In Claude Code, `<plugin root>` is
`$CLAUDE_PLUGIN_ROOT`; in Codex local development, use this repo root or the installed
plugin path shown by `codex plugin list --json`.

## The pipeline (run all seven; do not skip)

Expand Down
49 changes: 37 additions & 12 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ function frontmatter(text) {
}

// "Is my plugin healthy?" — validate the plugin shape against what Claude Code
// expects, plus version alignment and a live state/snapshot check. Exits non-zero
// on any failure so CI and `npm test` can gate a release on it.
// and Codex expect, plus version alignment and a live state/snapshot check.
// Exits non-zero on any failure so CI and `npm test` can gate a release on it.
function cmdDoctor(cwd, asJson) {
const root = PLUGIN_ROOT;
const checks = [];
Expand All @@ -333,23 +333,48 @@ function cmdDoctor(cwd, asJson) {

const pkg = readJsonFile('package.json');
add('package.json parses', pkg.ok, pkg.ok ? '' : pkg.error);
const plugin = readJsonFile('.claude-plugin/plugin.json');
add('.claude-plugin/plugin.json parses', plugin.ok, plugin.ok ? '' : plugin.error);
const market = readJsonFile('.claude-plugin/marketplace.json');
add('.claude-plugin/marketplace.json parses', market.ok, market.ok ? '' : market.error);

if (pkg.ok && plugin.ok && market.ok) {
const claudePlugin = readJsonFile('.claude-plugin/plugin.json');
add('.claude-plugin/plugin.json parses', claudePlugin.ok, claudePlugin.ok ? '' : claudePlugin.error);
const claudeMarket = readJsonFile('.claude-plugin/marketplace.json');
add('.claude-plugin/marketplace.json parses', claudeMarket.ok, claudeMarket.ok ? '' : claudeMarket.error);
const codexPlugin = readJsonFile('.codex-plugin/plugin.json');
add('.codex-plugin/plugin.json parses', codexPlugin.ok, codexPlugin.ok ? '' : codexPlugin.error);
const codexMarket = readJsonFile('.agents/plugins/marketplace.json');
add('.agents/plugins/marketplace.json parses', codexMarket.ok, codexMarket.ok ? '' : codexMarket.error);

if (pkg.ok && claudePlugin.ok && claudeMarket.ok && codexPlugin.ok) {
const pv = pkg.data.version;
const others = [
['plugin.json', plugin.data.version],
['marketplace.metadata', market.data.metadata && market.data.metadata.version],
['marketplace.plugins[0]', market.data.plugins && market.data.plugins[0] && market.data.plugins[0].version],
['.claude-plugin/plugin.json', claudePlugin.data.version],
['.claude-plugin/marketplace.metadata', claudeMarket.data.metadata && claudeMarket.data.metadata.version],
['.claude-plugin/marketplace.plugins[0]', claudeMarket.data.plugins && claudeMarket.data.plugins[0] && claudeMarket.data.plugins[0].version],
['.codex-plugin/plugin.json', codexPlugin.data.version],
];
const mismatch = others.filter(([, v]) => v !== pv).map(([k, v]) => `${k}=${v}`);
add('versions aligned', mismatch.length === 0, mismatch.length ? `package=${pv} but ${mismatch.join(', ')}` : `all ${pv}`);
}

for (const d of ['skills', 'agents', 'hooks', 'bin', 'src']) {
if (codexPlugin.ok) {
const iface = codexPlugin.data.interface || {};
const required = ['displayName', 'shortDescription', 'longDescription', 'developerName', 'category'];
const missing = required.filter((field) => !iface[field]);
add('Codex manifest required interface fields exist', missing.length === 0, missing.join(', '));
add('Codex manifest points at skills', codexPlugin.data.skills === './skills/');
}

if (codexPlugin.ok && codexMarket.ok) {
const plugins = Array.isArray(codexMarket.data.plugins) ? codexMarket.data.plugins : [];
const entry = plugins.find((p) => p && p.name === codexPlugin.data.name);
add('Codex marketplace entry exists', Boolean(entry), codexPlugin.data.name);
if (entry) {
const sourceOk = entry.source && entry.source.source === 'local' && entry.source.path === './';
const policyOk = entry.policy && entry.policy.installation === 'AVAILABLE' && entry.policy.authentication === 'ON_INSTALL';
add('Codex marketplace source is local repo root', sourceOk);
add('Codex marketplace policy is installable', policyOk);
}
}

for (const d of ['.agents', '.claude-plugin', '.codex-plugin', 'skills', 'agents', 'hooks', 'bin', 'src']) {
const p = path.join(root, d);
add(`dir ${d}/ exists`, fs.existsSync(p) && fs.statSync(p).isDirectory());
}
Expand Down
5 changes: 2 additions & 3 deletions src/repoSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ const IGNORE_DIRS = new Set([
]);

// Dot-directories that ARE meaningful to a plugin/repo and must not be hidden.
// Without this, a blanket dot-dir skip loses .claude-plugin, .github, etc. — the
// exact directories a Claude Code plugin lives in.
const DOT_DIR_ALLOWLIST = new Set(['.claude', '.claude-plugin', '.github', '.ratchet']);
// Without this, a blanket dot-dir skip loses plugin manifests and CI metadata.
const DOT_DIR_ALLOWLIST = new Set(['.agents', '.claude', '.claude-plugin', '.codex-plugin', '.github', '.ratchet']);

function shouldSkipDir(name) {
if (IGNORE_DIRS.has(name)) return true;
Expand Down
4 changes: 3 additions & 1 deletion test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,13 @@ ok('corrupt ledger.json is backed up, not silently lost', () => {

ok('repo snapshot sees allowlisted dot dirs, skips .git / node_modules', () => {
const proj = path.join(tmp, 'snap-fixture');
for (const d of ['.claude-plugin', '.github', '.git', 'node_modules', 'src']) {
for (const d of ['.agents', '.claude-plugin', '.codex-plugin', '.github', '.git', 'node_modules', 'src']) {
fs.mkdirSync(path.join(proj, d), { recursive: true });
}
const snap = repo.snapshot(proj);
assert.ok(snap.dirs.includes('.agents'), '.agents is visible');
assert.ok(snap.dirs.includes('.claude-plugin'), '.claude-plugin is visible');
assert.ok(snap.dirs.includes('.codex-plugin'), '.codex-plugin is visible');
assert.ok(snap.dirs.includes('.github'), '.github is visible');
assert.ok(!snap.dirs.includes('.git'), '.git is skipped');
assert.ok(!snap.dirs.includes('node_modules'), 'node_modules is skipped');
Expand Down
Loading
Loading