Skip to content
Open
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
111 changes: 93 additions & 18 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"//": "forgekit dogfoods its own plugin during local dev. These hooks mirror hooks/hooks.json but resolve through ${CLAUDE_PROJECT_DIR} (this repo) instead of ${CLAUDE_PLUGIN_ROOT}, so the guards run without a marketplace install. Every guard is advisory/fail-open; the completion gate honors FORGE_STOPGATE=0.",
"//": "forgekit dogfoods its own plugin during local dev. These hooks mirror hooks/hooks.json but resolve through ${CLAUDE_PROJECT_DIR} (this repo) instead of ${CLAUDE_PLUGIN_ROOT}, so the guards run without a marketplace install. Exec form via global/guards/run.mjs: node finds bash (Git Bash on Windows, where bash is usually NOT on PATH) and passes stdin/stdout/exit code through untouched. Every guard is advisory/fail-open; the completion gate honors FORGE_STOPGATE=0.",
"statusLine": {
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/statusline.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/statusline.sh"
]
},
"hooks": {
"SessionStart": [
Expand All @@ -11,15 +15,28 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/recall-load.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/recall-load.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh session-start"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"session-start"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/session-start.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh"
]
}
]
}
Expand All @@ -29,11 +46,21 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh prompt"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"prompt"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh preflight"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"preflight"
]
}
]
}
Expand All @@ -44,15 +71,27 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/protect-paths.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/protect-paths.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cost-budget.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cost-budget.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/doom-loop.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/doom-loop.sh"
]
}
]
},
Expand All @@ -61,7 +100,12 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh pre-edit"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"pre-edit"
]
}
]
}
Expand All @@ -72,11 +116,20 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/format-on-edit.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/format-on-edit.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"capture"
]
}
]
},
Expand All @@ -85,11 +138,20 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/secret-redact.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/secret-redact.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"capture"
]
}
]
}
Expand All @@ -99,15 +161,28 @@
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/completion-gate.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/completion-gate.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/lean-guard.sh"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/lean-guard.sh"
]
},
{
"type": "command",
"command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh stop"
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/global/guards/run.mjs",
"${CLAUDE_PROJECT_DIR}/global/guards/cortex.sh",
"stop"
]
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"interface": {
"displayName": "Forge",
"shortDescription": "Cognitive substrate and one config for every AI coding tool.",
"longDescription": "Forge adds a pre-action cognitive substrate for coding agents: assumption gating, transparent model routing, impact prediction, scope decomposition, memory/learning context, and verification checklists. It also emits shared rules and MCP config for Codex, Claude Code, Cursor, Gemini, Aider, Copilot, Windsurf/Devin, Zed, Continue, and Roo.",
"longDescription": "Forge adds a pre-action cognitive substrate for coding agents: assumption gating, transparent model routing, impact prediction, scope decomposition, memory/learning context, and verification checklists. It also emits shared rules and MCP config for Codex, Claude Code, Cursor, Gemini, Aider, Copilot, Windsurf/Devin, Zed, Continue, OpenClaw, and Roo.",
"developerName": "CodeWithJuber",
"category": "Productivity",
"capabilities": ["MCP", "Skills", "Code Review", "Verification"],
Expand Down
43 changes: 37 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ for the full list.
Brand stored as **one token** (the `brand` key in `brand.json`); rebrand = 1 edit.
- **Distributable id = `forgekit`** (npm package + marketplace id) — fixed even if
the brand token changes, so a rename never breaks install.
- **Scope = full multi-tool day 1** — nine tools plus MCP, from one canonical source.
- **Scope = full multi-tool day 1** — ten tools plus MCP, from one canonical source.
- **Install = all three channels** (plugin + hardened installer + npm CLI), all
three pointing at the _same_ tree ("one tree, three front doors").
- **Own `lean` + `atlas`** — as _thin layers over proven primitives_, not
Expand Down Expand Up @@ -443,7 +443,7 @@ guards through `${CLAUDE_PROJECT_DIR}`.

## Verified cross-tool emit matrix

_(All rows confirmed against vendor docs.)_ Forge emits config for **nine tools**, plus
_(All rows confirmed against vendor docs.)_ Forge emits config for **ten tools**, plus
an **MCP server** for Roo Code and VS Code.

| Tool | Native target | How Forge emits |
Expand All @@ -457,10 +457,39 @@ an **MCP server** for Roo Code and VS Code.
| **Windsurf/Devin** | `AGENTS.md` auto-discovered; caps 6k/12k chars | Root `AGENTS.md` under caps; detect `.windsurf` vs `.devin` at init |
| **Zed** | first match of a precedence list incl. `AGENTS.md` | Emit `AGENTS.md` + doctor flags any earlier-precedence legacy file shadowing it |
| **Continue** | `.continue/rules/*.md` + `.continue/mcpServers/*.yaml` | Emit a rules file plus the Forge MCP server config |
| **OpenClaw** | execution-folder `AGENTS.md` as project context; MCP registry is global | Rely on root `AGENTS.md`; write an OpenClaw-shaped `.openclaw/mcp.json` the operator applies with one `openclaw mcp add` |

Roo Code and VS Code receive the Forge MCP server via `forge init`
(`.roo/mcp.json`, `.vscode/mcp.json`) rather than a rules file.

### OpenClaw: what is automatic and what is not

OpenClaw appends the execution folder's `AGENTS.md` after its configured agent-workspace
files as project context, so the canonical rules reach it with **no** extra instruction
file — the same deal as Codex, Cursor and Copilot. Only `AGENTS.md` travels this way:
OpenClaw deliberately does not load `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md` or
`BOOTSTRAP.md` from the execution folder, so anything Forge wants OpenClaw to read has to
be inside the canonical body.

For the **config compiler path**, MCP is deliberately not automatic. OpenClaw's server
registry is `mcp.servers` in the user's global `~/.openclaw/openclaw.json`; Forge never
writes to another tool's global config. Instead `forge sync` emits a repo-local,
OpenClaw-shaped fragment at `.openclaw/mcp.json` and reports the exact command that
registers it:

```bash
openclaw mcp add forge-cortex --command forge --arg cortex-mcp
openclaw mcp doctor forge-cortex --probe # prove it starts and lists tools
```

There is also a separate **bundle installation path**. The published package already ships
`.codex-plugin/plugin.json`, `global/tools`, and `.mcp.json`; OpenClaw auto-detects that
layout as a Codex bundle. Installing a trusted local directory or packed archive through
`openclaw plugins install` loads Forge's skills and bundle-scoped `forge-cortex` MCP server,
so the manual global registration above is unnecessary for that installation. This does not
turn Forge's Claude `hooks/hooks.json` automation into OpenClaw guards: only OpenClaw-style
hook packs execute. Forge therefore provides no ambient pre-action guard on OpenClaw.

## Repo layout — one tree, three front doors

```
Expand Down Expand Up @@ -552,20 +581,22 @@ from the tree it describes.
```mermaid
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#201a15','primaryTextColor':'#f2ede7','primaryBorderColor':'#372c22','lineColor':'#f26430','secondaryColor':'#272019','tertiaryColor':'#171310','edgeLabelBackground':'#201a15','clusterBkg':'#171310','clusterBorder':'#4a3b2e','fontFamily':'ui-sans-serif, system-ui, sans-serif','fontSize':'14px'},'flowchart':{'curve':'basis','padding':10,'nodeSpacing':36,'rankSpacing':44}}}%%
flowchart LR
test["test<br/>100 files"]
src["src<br/>94 files"]
test["test<br/>106 files"]
src["src<br/>97 files"]
landing["landing<br/>61 files"]
research["research<br/>35 files"]
global["global<br/>3 files"]
bench["bench<br/>2 files"]
global["global<br/>2 files"]
scripts["scripts<br/>2 files"]
docs["docs<br/>1 file"]
examples["examples<br/>1 file"]
test -- 195 --> src
test -- 206 --> src
bench -- 7 --> src
examples -- 4 --> src
test -- 2 --> scripts
scripts --> src
src --> global
test --> bench
test --> global
```
<!-- forge:render:repo-map:end -->
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,57 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **OpenClaw is a first-class emit target — the compiler's tenth tool.** Instructions need
no new file: OpenClaw appends the execution folder's `AGENTS.md` after its configured
agent-workspace files as project context, so the canonical source reaches it the same way
it reaches Codex, Cursor and Copilot. MCP is registered explicitly rather than silently:
OpenClaw keeps its server registry in the user's global `~/.openclaw/openclaw.json`, which
Forge will not write to, so `forge sync` emits an OpenClaw-shaped fragment to
`.openclaw/mcp.json` and reports the exact enabling command
(`openclaw mcp add forge-cortex --command forge --arg cortex-mcp`). `.openclaw/mcp.json`
is an ordinary managed MCP target: idempotent, per-target ownership (a same-name server
you wrote yourself is preserved until `--adopt`), and reversible via
`forge integrations remove`. `openclaw` is now selectable and auto-detected by
`forge tools`. The packaged `.codex-plugin/plugin.json`, `global/tools`, and `.mcp.json`
also form an OpenClaw-compatible Codex bundle: installing a trusted checkout or packed
archive loads Forge's skills and bundle-scoped MCP server without the config-only path's
manual global registration. Forge installs **nothing** into OpenClaw's hook system — there
are no ambient guards there, only `AGENTS.md`/skill text and the MCP tools.

### Changed

- **MCP targets address their server bucket by dotted key path.** `emit/mcp.js` resolved a
single top-level key (`mcpServers`, `servers`, `context_servers`); OpenClaw nests its
registry under `mcp.servers`. The resolver now walks a path, creating missing objects only
on write, and refuses to restructure a file where any step already holds a non-object —
that shape is the user's and is reported, never rewritten.

### Fixed

- **Claude Code hooks no longer fail on Windows with `spawn bash ENOENT`.** Every Forge hook
(the plugin's `hooks/hooks.json`, the `settings.template.json` that `forge init` merges, the
statusline) was exec form with `command: "bash"`. Exec-form hooks are spawned directly — no
shell, a plain `PATH` lookup — and a default Git for Windows install puts `git.exe` on `PATH`
(`Git\cmd`) but not `bash.exe` (`Git\bin`, `Git\usr\bin`), so SessionStart and every other
guard died before it ran. Hooks now spawn the zero-dependency launcher
`global/guards/run.mjs` (`node run.mjs <guard>.sh …`), which resolves bash — `FORGE_BASH`,
`CLAUDE_CODE_GIT_BASH_PATH`, the Git install that owns `git` on `PATH`, the standard install
dirs, then `PATH` (never WSL's System32 `bash.exe`) — and passes stdin, stdout and the exit
code through verbatim, so exit-2 blocks are unchanged. POSIX behaviour is identical (`bash`
from `PATH`). `forge init` heals a Forge-owned install left in the old `bash` spelling in
place — ownership manifest included, so uninstall still reverses it — while a hand-written
hook at a Forge path is left alone; `forge doctor` shows the resolved bash, flags stale hooks
(`--fix` re-merges) and requires the launcher as an install asset. Regression tests cover the
Windows default-install `PATH` shape, paths with spaces on both OSes, the no-bash failure
mode (exit 1 + hint, never a fabricated block) and the packed archive.
- **`protect-paths` no longer dies (exit 1, fail-open) on machines without `jq`.** Its grep
fallback ran under `set -euo pipefail`, so a payload missing `command` (every Write/Edit) or
`file_path` (every Bash call) aborted the guard before it could decide — invisible in CI, where
`jq` is preinstalled, but the norm on Windows. The fallback now yields an empty field exactly
like the `jq` branch, so `.env` writes and destructive `rm` are blocked without `jq`.

## [0.32.1] - 2026-08-22

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ small and dependency-free is the whole point, so please read this before opening
- **Node.js ESM only.** All code is ES modules (`"type": "module"`). No CommonJS.
- **Supported Node versions:** 20 and 22 (the `>=20` engines floor; Node 18 is EOL).
- **Cross-tool first.** New behavior should work across the tools forgekit targets
(Claude Code, Codex, Cursor, Gemini, Aider, …), emitted from one source — not
(Claude Code, Codex, Cursor, Gemini, Aider, OpenClaw, …), emitted from one source — not
Claude-only. Say so in the PR if a piece is unavoidably tool-specific.

## Getting started
Expand Down
Loading