diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..05a7759 --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Web session-start install hook — ensures dev tooling (Biome, tsc) is present so +# tests and linters run in Claude Code on the web. Synchronous + idempotent. +# Only runs in the remote (web) environment; a no-op locally. +set -euo pipefail + +# Local sessions already have their environment — nothing to do. +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "${CLAUDE_PROJECT_DIR:-.}" + +# forgekit has ZERO runtime deps; the only install is dev tooling (biome, typescript, +# @types/node). If Biome is already resolvable, the container is warm — skip the install. +if [ -x node_modules/.bin/biome ]; then + exit 0 +fi + +# Prefer `npm install` over `npm ci` so a warm-cached container is reused. +npm install --no-audit --no-fund diff --git a/.claude/settings.json b/.claude/settings.json index d3f476c..c0dfef0 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,21 +1,40 @@ { "//": "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.", - "statusLine": { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/statusline.sh" }, + "statusLine": { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/statusline.sh" + }, "hooks": { "SessionStart": [ { "matcher": "startup|resume|clear", "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/recall-load.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh session-start" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/recall-load.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh session-start" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/session-start.sh" + } ] } ], "UserPromptSubmit": [ { "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh prompt" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh preflight" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh prompt" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh preflight" + } ] } ], @@ -23,15 +42,27 @@ { "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/protect-paths.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cost-budget.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/doom-loop.sh" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/protect-paths.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cost-budget.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/doom-loop.sh" + } ] }, { "matcher": "Edit|Write|MultiEdit", "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh pre-edit" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh pre-edit" + } ] } ], @@ -39,24 +70,45 @@ { "matcher": "Edit|Write|MultiEdit", "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/format-on-edit.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/format-on-edit.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture" + } ] }, { "matcher": "Bash|Read|Grep", "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/secret-redact.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/secret-redact.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh capture" + } ] } ], "Stop": [ { "hooks": [ - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/completion-gate.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/lean-guard.sh" }, - { "type": "command", "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh stop" } + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/completion-gate.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/lean-guard.sh" + }, + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR}\"/global/guards/cortex.sh stop" + } ] } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 46d4575..bd3f69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,53 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- **Per-command help + word forms.** `forge --help` / `-h` now works for + every command (intercepted centrally in the dispatcher, no longer silently dropped), + alongside the word forms `forge help [command]` and `forge version`. Help renders from + the same `COMMANDS` table the docs check reconciles: entries may now be a plain summary + string OR a rich `{summary, usage, flags, examples, env}` object (both coexist; the + high-traffic commands are migrated), read through new `commandSummary`/`commandHelp` + helpers. An unknown command now suggests the nearest match ("did you mean …?") via a + character-bigram `suggest()` over the existing `setOverlap` similarity. +- **`forge doctor --fix` — one-command auto-repair.** Each safely fixable finding now + carries a repair that reuses an existing idempotent function: missing + hooks/permissions in `~/.claude/settings.json` → `mergeSettings`, a missing ledger + union-merge rule → `ensureLedgerGitattributes`, a missing/stale `AGENTS.md` → `sync`, + and non-executable guards → `chmod +x`. `forge doctor --fix` runs the repairs, prints a + `repairs:` block, then re-checks; a second run is a clean no-op. Unsafe findings + (provider keys, MCP count, pricing, gateway) stay report-only. The `node` check is now + reconciled with `package.json` engines (`>=20`): fail below 18, warn on 18–19, ok on 20+. +- **Zero-config settings wiring + first-run hint.** `forge init --settings-only` + runs only the idempotent, `_forge`-marker-guarded settings merge (hooks + + permissions into `~/.claude/settings.json`) with no repo emit, and `install.sh` + now calls it instead of printing a block to paste by hand (honoring `--dry-run`). + When a real command runs before settings are forge-managed, one tip line to stderr + points at `forge init` / `forge doctor --fix`; it is stateless, self-silences once + init runs, and `FORGE_NO_HINT=1` mutes it. +- **`forge tools` — primary-tool config + auto-gitignore for secondary-tool artifacts.** + A repo that only uses one agent no longer has to track every other tool's emitted + files. `forge tools ` records the primary tool in `.forge/config.json` and writes + a marked, reversible block into `.gitignore` (`# forge:gitignore:begin … :end`, managed + by `src/gitignore.js`) that ignores exactly the NON-primary emit targets — computed from + `sync()`'s own report rows, so the block always matches what Forge emits. User lines, + the shared `AGENTS.md`, and the primary tool's own files are never touched. `forge tools` + shows the detected/primary tool (auto-detected from which agent folder exists, mirroring + provider detection) and what's gitignored; `forge tools --reset` clears the config and + strips only the block. Opt-in — plain `forge sync` never writes `.gitignore`. +- **`forge dash` first-run clarity.** `dashData` now reports a `meta.empty` / + `meta.forgeDir` signal (true when `.forge/` holds no ledger claims and no metrics + events), and the dashboard shows a plain-language empty-state banner ("No data in + .forge/ yet — run `forge sync`, then work a session") plus a `?` legend that + explains what every panel means in one sentence. Display-only — the append-only + ratify/retract writes are unchanged. +- **Web session-start install hook.** `.claude/hooks/session-start.sh` (wired into the + repo's `SessionStart` hooks alongside the existing recall/cortex context hooks) makes + Claude Code on the web ready to lint and test: synchronous, idempotent, gated on + `CLAUDE_CODE_REMOTE=true` (a no-op locally), and it skips the install entirely when the + dev toolchain is already resolvable. + ## [0.20.0] - 2026-07-17 ### Added diff --git a/README.md b/README.md index 4b7c66e..f7eb5f7 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,9 @@ Install — pick one row (the recommended paths need no token and no clone): Then, in your project: ```bash -forge init # emit every AI tool's native config from one shared source -forge doctor # pass/fail health check: tools, guards, MCP, config drift +forge init # emit every AI tool's native config from one shared source +forge doctor # pass/fail health check: tools, guards, MCP, config drift +forge doctor --fix # auto-repair the safely fixable findings, then re-check # pre-action check before you (or your agent) edit anything: forge substrate "Change verifyToken in src/auth.js to require length > 20; update tests" @@ -173,50 +174,56 @@ Output is plain text when piped; on a TTY it adds brand-palette color and confid meters. `NO_COLOR` turns color off, `FORCE_COLOR=1` forces it on (e.g. in CI, `0` forces off), and `TERM`/`COLORTERM` follow the usual terminal conventions. -| Group | Command | Does | -| -------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| **Config layer** | `forge init` | emit every tool's native config from one source | -| | `forge sync` | recompile canonical source → each tool's native files (idempotent) | -| | `forge doctor` | pass/fail health check: tools, guards, MCP, drift, update | -| | `forge update` | self-update — `--check` reports if a newer version exists, bare applies it, `--to ` pins/downgrades | -| | `forge docs` | docs↔code drift — `check` reconciles commands/env/MCP/CHANGELOG; `sync` sweeps the diff for stale doc mentions | -| | `forge config` | provider setup — show / switch / add providers, set the default model | -| | `forge harden` | wire the pre-commit gate (gitleaks + commit gate) + sandbox settings | -| | `forge catalog` | Start-Here index of every tool / crew / guard | -| | `forge brand` | print the brand token map | -| **Memory & team** | `forge ledger` | proof-carrying memory — stats / verify / show / blame / query / ratify / retract / merge / sync / import | -| | `forge recall` | cross-session personal memory — list / add / consolidate | -| | `forge remember` | durable, repo-committable fact | -| | `forge brain` | portable project-memory index | -| | `forge cortex` | self-correcting lessons — `status` / `why` | -| | `forge deja` | anti-repetition — ranks prior solved/verified sessions for a task you're about to start (`FORGE_DEJA=0` disables) | -| | `forge reuse` | proof-carrying code cache — query / mint / stats | -| | `forge handoff` | bounded session snapshot (`.forge/state.md`) — rewritten each handoff, re-injected every session start | -| | `forge decide` | append-only decision log (`.forge/decisions.md`, D-#### ADR-lite) — future sessions read it instead of re-deciding | -| | `forge know` | route any fact to its storage home (decision / ledger / recall / …) — total routing, an unsure fact still lands | -| **Substrate (pre-action)** | `forge substrate` | the full pre-action gate in one pass | -| | `forge preflight` | assumption / info-gap check | -| | `forge route` | cheapest capable model tier (`route gateway` emits LiteLLM config) | -| | `forge impact` | predict blast radius for a symbol or file | -| | `forge scope` | cluster + surface coupled files | -| | `forge imagine` | consequence sim + minimal dry-run suite (`--run` executes it sandboxed) | -| | `forge context` | budgeted context assembly + completeness gate | -| | `forge atlas` | build / query / has (hallucinated-symbol check) the code graph | -| | `forge stack` | detect this repo's real stack (languages, frameworks, test commands) from its manifests | -| | `forge anchor` | goal-drift check (advisory) — `set`/`show`/`clear` persists the goal across sessions | -| | `forge diagnose` | doom-loop: same failure 3× → diagnosis + escalation | -| | `forge lean` | scope-minimality footprint (advisory) | -| | `forge cost` | real per-day spend · measured stage factors (`--stages`) | -| **Verification & safety** | `forge verify` | independent gate — tests + hallucinated-symbol flag + provenance; `--deep` multi-lens consensus (`--llm` reviewer panel) | -| | `forge precommit` | commit-level gate rung — staged code w/o docs + secret scan (`FORGE_COMMIT_GATE=block\|warn\|0`) | -| | `forge radar` | dependency-currency rings (adopt/trial/assess/hold) from registry evidence — cached, offline-honest | -| | `forge scan` | skill-gate: vet a SKILL.md / .mcp.json for injection / RCE / exfil | -| | `forge spec` | spec-as-contract drift — init / lock / check | -| **UI / design** | `forge taste` | pick one visual direction → DESIGN.md | -| | `forge uicheck` | contrast · fingerprint · design · visual (WCAG · slop+conformance · Playwright) | -| **Observability** | `forge dash` | localhost-only live dashboard: ledger, metrics trends, radar rings, memory browser, session timeline, blast radius (default port 4242) | -| | `forge report` | static, self-contained HTML snapshot of `.forge/` (`.forge/report.html`) — opens offline, no server | - +The first time you run a real command before `~/.claude/settings.json` is forge-managed, +one tip line points at `forge init` (or `forge doctor --fix`) to wire hooks + permissions; +it self-silences once init runs and `FORGE_NO_HINT=1` mutes it entirely. `install.sh` does +this wiring for you via `forge init --settings-only` — an idempotent, marker-guarded merge +that never clobbers your existing settings. + +| Group | Command | Does | +| -------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Config layer** | `forge init` | emit every tool's native config from one source | +| | `forge sync` | recompile canonical source → each tool's native files (idempotent) | +| | `forge tools` | primary-tool config — gitignore secondary-tool artifacts (`.cursor/`, `.gemini/`, …) for tools this repo doesn't use; `forge tools ` sets it, `--reset` clears | +| | `forge doctor` | pass/fail health check: tools, guards, MCP, drift, update | +| | `forge update` | self-update — `--check` reports if a newer version exists, bare applies it, `--to ` pins/downgrades | +| | `forge docs` | docs↔code drift — `check` reconciles commands/env/MCP/CHANGELOG; `sync` sweeps the diff for stale doc mentions | +| | `forge config` | provider setup — show / switch / add providers, set the default model | +| | `forge harden` | wire the pre-commit gate (gitleaks + commit gate) + sandbox settings | +| | `forge catalog` | Start-Here index of every tool / crew / guard | +| | `forge brand` | print the brand token map | +| **Memory & team** | `forge ledger` | proof-carrying memory — stats / verify / show / blame / query / ratify / retract / merge / sync / import | +| | `forge recall` | cross-session personal memory — list / add / consolidate | +| | `forge remember` | durable, repo-committable fact | +| | `forge brain` | portable project-memory index | +| | `forge cortex` | self-correcting lessons — `status` / `why` | +| | `forge deja` | anti-repetition — ranks prior solved/verified sessions for a task you're about to start (`FORGE_DEJA=0` disables) | +| | `forge reuse` | proof-carrying code cache — query / mint / stats | +| | `forge handoff` | bounded session snapshot (`.forge/state.md`) — rewritten each handoff, re-injected every session start | +| | `forge decide` | append-only decision log (`.forge/decisions.md`, D-#### ADR-lite) — future sessions read it instead of re-deciding | +| | `forge know` | route any fact to its storage home (decision / ledger / recall / …) — total routing, an unsure fact still lands | +| **Substrate (pre-action)** | `forge substrate` | the full pre-action gate in one pass | +| | `forge preflight` | assumption / info-gap check | +| | `forge route` | cheapest capable model tier (`route gateway` emits LiteLLM config) | +| | `forge impact` | predict blast radius for a symbol or file | +| | `forge scope` | cluster + surface coupled files | +| | `forge imagine` | consequence sim + minimal dry-run suite (`--run` executes it sandboxed) | +| | `forge context` | budgeted context assembly + completeness gate | +| | `forge atlas` | build / query / has (hallucinated-symbol check) the code graph | +| | `forge stack` | detect this repo's real stack (languages, frameworks, test commands) from its manifests | +| | `forge anchor` | goal-drift check (advisory) — `set`/`show`/`clear` persists the goal across sessions | +| | `forge diagnose` | doom-loop: same failure 3× → diagnosis + escalation | +| | `forge lean` | scope-minimality footprint (advisory) | +| | `forge cost` | real per-day spend · measured stage factors (`--stages`) | +| **Verification & safety** | `forge verify` | independent gate — tests + hallucinated-symbol flag + provenance; `--deep` multi-lens consensus (`--llm` reviewer panel) | +| | `forge precommit` | commit-level gate rung — staged code w/o docs + secret scan (`FORGE_COMMIT_GATE=block\|warn\|0`) | +| | `forge radar` | dependency-currency rings (adopt/trial/assess/hold) from registry evidence — cached, offline-honest | +| | `forge scan` | skill-gate: vet a SKILL.md / .mcp.json for injection / RCE / exfil | +| | `forge spec` | spec-as-contract drift — init / lock / check | +| **UI / design** | `forge taste` | pick one visual direction → DESIGN.md | +| | `forge uicheck` | contrast · fingerprint · design · visual (WCAG · slop+conformance · Playwright) | +| **Observability** | `forge dash` | localhost-only live dashboard: ledger, metrics trends, radar rings, memory browser, session timeline, blast radius (default port 4242) | +| | `forge report` | static, self-contained HTML snapshot of `.forge/` (`.forge/report.html`) — opens offline, no server | **→ Every command with a worked example and real output: [`docs/GUIDE.md`](docs/GUIDE.md).** diff --git a/docs/GUIDE.md b/docs/GUIDE.md index 9ea8ce9..68b210b 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -27,7 +27,7 @@ Every command is real and wired. Grouped by what it does: | Group | Commands | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Config / cross-tool sync** | `forge init` · `forge sync` · `forge doctor` · `forge update` · `forge docs` · `forge config` · `forge harden` · `forge catalog` · `forge brand` | +| **Config / cross-tool sync** | `forge init` · `forge sync` · `forge tools` · `forge doctor` · `forge update` · `forge docs` · `forge config` · `forge harden` · `forge catalog` · `forge brand` | | **Memory & ledger (PCM)** | `forge ledger` · `forge recall` · `forge remember` · `forge brain` · `forge cortex` · `forge reuse` · `forge handoff` · `forge decide` · `forge know` | | **Code graph & retrieval** | `forge atlas` · `forge stack` · `forge context` | | **Substrate / pre-action** | `forge substrate` · `forge preflight` · `forge route` · `forge impact` · `forge scope` · `forge imagine` · `forge anchor` · `forge diagnose` · `forge lean` · `forge cost` | @@ -558,6 +558,33 @@ tag — the printed note tells you how to get back to latest (`git checkout @` command instead. Accepts `0.17.0` or `v0.17.0`. +### `forge tools` — one repo, one agent tool (gitignore the rest) + +`forge sync` emits config for **every** supported agent tool from one source — great for +portability, noisy for a repo where only one tool is ever used: `.cursor/`, `.gemini/`, +`.codex/`, `.zed/`, `.aider.conf.yml`, and friends all show up as tracked clutter. `forge +tools` fixes that without changing what `sync` emits. + +- `forge tools` — show the detected/primary tool (from `.forge/config.json`, else + auto-detected from which agent folder exists — `CLAUDE.md`, `.cursor/`, `.gemini/`, + `.codex/`, `.zed/`, `.vscode/`) and which targets are currently gitignored. +- `forge tools ` — record `` (`claude` · `cursor` · `gemini` · `codex` · + `zed` · `vscode` · `aider` · `continue` · `windsurf`) as this repo's primary tool in + `.forge/config.json`, then write a **marked, reversible** block into `.gitignore` + (`# forge:gitignore:begin … # forge:gitignore:end`) that ignores every OTHER tool's + emitted artifacts. Your own `.gitignore` lines are never touched, and the shared + `AGENTS.md` plus the primary tool's own files always stay tracked. +- `forge tools --reset` — clear the config and strip the managed block (only the block). + +This is **opt-in** — plain `forge sync` never writes `.gitignore`. The block lists the +exact target paths `sync` reports, so it always matches what Forge actually emits. + +```console +$ forge tools claude + primary tool claude + gitignored .aider.conf.yml, .codex/config.toml, .cursor/mcp.json, .gemini/settings.json, .zed/settings.json (block written) +``` + ### `forge recall add | list | consolidate` — cross-session memory Durable facts, one per file, injected at the start of the next session by the @@ -959,23 +986,23 @@ Plain `forge cost` remains the per-day spend view via `ccusage`. ### The rest -| Command | Answers | -| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `forge init` | Emit every tool's native config from one source. | -| `forge sync` | Recompile `source/` → each tool's files (idempotent). | -| `forge doctor` | Health check: layers, install, drift, cortex. | -| `forge docs check` | Docs↔code drift: commands, env vars, MCP tools, CHANGELOG reconciled against the code (CI-gated on the forge repo itself). | -| `forge docs sync` | Diff-driven stale-docs sweep: UPDATED / STALE (file:line hits) / VERIFIED-UNAFFECTED per artifact (see the full section above). | -| `forge catalog` | Start-Here index of every tool / crew / guard. | -| `forge brain` / `forge remember` | Portable project memory inlined into `AGENTS.md`. | -| `forge cost` | Real per-day spend (via `ccusage`) + the cost ceiling; `--stages` for the measured report. | -| `forge scan ` | Vet a skill/MCP (SKILL.md/.mcp.json) for injection/RCE/exfil before install. | -| `forge harden` | Wire the pre-commit gate (gitleaks-if-present + `forge precommit`) + sandbox settings; never clobbers a user-authored hook. | -| `forge precommit` | Commit-level gate rung: staged code with no doc/state artifact → finding (same classifier as the Stop gate) + built-in secret scan over staged added lines. `FORGE_COMMIT_GATE=block` refuses the commit, `warn` (default) prints and allows, `0` disables; a detected secret blocks in every mode. | -| `forge spec [init\|lock\|check]` | Spec-as-contract drift check. | -| `forge brand` | Print the active brand token map. | -| `forge lean ""` | Scope-minimality footprint for a task — advisory (the Lean Path as a command). | -| `forge taste [