#!/usr/bin/env bash # agent-primer.sh — SELF-CONTAINED CodeGraph rule+hook + Karpathy + Superpowers installer. # Carry/gist/curl this ONE file to any machine. Generated by make-portable.sh — do not hand-edit. # bash agent-primer.sh --global # wire all your agents, every project # bash agent-primer.sh --project [DIR] # wire one repo # curl -fsSL /agent-primer.sh | bash -s -- --global set -e DEST="${AGENT_PRIMER_KIT_DIR:-$HOME/.agent-primer}" mkdir -p "$DEST" cat > "$DEST/codegraph-session-check.sh" <<'CG_EOF_SCRIPT' #!/usr/bin/env bash # codegraph-session-check.sh — CodeGraph session-startup check for AI coding agents. # # Part of the portable "agent-primer" kit. Wired as a SessionStart hook # (Claude Code, Codex, Gemini, Antigravity, Kimi), a sessionStart hook (Cursor), # or invoked from a session.created plugin (opencode). Safe to run standalone. # # It NEVER mutates anything and ALWAYS exits 0 — it only inspects the index and # emits a directive telling the agent what to do (install / init / proceed). # The agent performs any install itself, per codegraph-policy.md, announcing commands. # # By DEFAULT it runs in "once per project" mode: it nudges every session UNTIL the # project is set up (CLI present + .codegraph/ at the project root), then goes SILENT # on later sessions (emits nothing; skips `codegraph status` for a fast no-op). Index # freshness after that is handled by CodeGraph's own file-watcher. Pass --always to # restore the legacy every-session behavior (print `codegraph status` + a reminder # even when the index is already present). # # Usage: # codegraph-session-check.sh [--format text|json|cursor] [--project DIR] [--always] # # Formats: # text plain stdout (default; Claude Code / Codex add SessionStart stdout to context) # json {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"…"}} # cursor {"additional_context":"…"} # # --always every-session mode: branch 3 prints status as before (default: silent once set up) set -u FORMAT="text" PROJECT_DIR="" ALWAYS=0 while [ "$#" -gt 0 ]; do # NOTE: `shift 2` fails and shifts nothing on bash 3.2 when the flag is the last # arg → infinite loop. As a SessionStart hook that would hang the agent's startup, # so use a guarded double-shift instead. case "$1" in --format) FORMAT="${2:-text}"; shift; [ "$#" -gt 0 ] && shift ;; --format=*) FORMAT="${1#*=}"; shift ;; --project) PROJECT_DIR="${2:-}"; shift; [ "$#" -gt 0 ] && shift ;; --project=*) PROJECT_DIR="${1#*=}"; shift ;; --always) ALWAYS=1; shift ;; *) shift ;; esac done # Resolve the project directory: explicit flag > known hook env vars > CWD. if [ -z "$PROJECT_DIR" ]; then PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${CODEX_PROJECT_DIR:-${PWD:-.}}}" fi INSTALL_SH="curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh" # --- helpers --------------------------------------------------------------- run_status() { # Bounded so a hung daemon can never block session start. if command -v timeout >/dev/null 2>&1; then timeout 8 codegraph status 2>&1 elif command -v gtimeout >/dev/null 2>&1; then gtimeout 8 codegraph status 2>&1 else codegraph status 2>&1 fi } json_escape() { # Emit "$1" as a JSON string literal (surrounding quotes included). if command -v jq >/dev/null 2>&1; then printf '%s' "$1" | jq -Rs . elif command -v python3 >/dev/null 2>&1; then printf '%s' "$1" | python3 -c 'import json,sys; sys.stdout.write(json.dumps(sys.stdin.read()))' else printf '%s' "$1" | awk ' BEGIN { ORS=""; print "\"" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t") } { if (NR > 1) printf "\\n"; printf "%s", $0 } END { print "\"" }' fi } emit() { # $1 = message. Render it in the requested format, then exit 0. case "$FORMAT" in json) printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' "$(json_escape "$1")" ;; cursor) printf '{"additional_context":%s}\n' "$(json_escape "$1")" ;; *) printf '%s\n' "$1" ;; esac } # --- checks ---------------------------------------------------------------- if ! command -v codegraph >/dev/null 2>&1; then emit "[CodeGraph] The \`codegraph\` CLI is NOT installed in this environment. Per the CodeGraph session-startup rule you MUST set it up before substantive work: 1. Install CLI: ${INSTALL_SH} (or: npm i -g @colbymchenry/codegraph) 2. Register the MCP server into this agent: codegraph install 3. Build the index: codegraph init -i 4. Then ask the user to RESTART this agent/IDE so the codegraph_* MCP tools load. Until the restart you can already use the \`codegraph\` CLI via Bash (status, query, context, callers, callees, impact, files). Announce each command before running it." exit 0 fi if [ ! -d "$PROJECT_DIR/.codegraph" ]; then emit "[CodeGraph] CLI present, but this project is NOT initialized (no .codegraph/ at ${PROJECT_DIR}). Build the index before relying on CodeGraph: codegraph init -i If the codegraph_* MCP tools are missing afterwards, run \`codegraph install\` then ask the user to restart this agent/IDE." exit 0 fi # Branch 3: CLI present + .codegraph/ exists = this project is already set up. # DEFAULT (once-mode): stay SILENT and skip `codegraph status` entirely — a fast no-op. # Freshness is the file-watcher's job; re-announcing every session is just noise. # Pass --always to restore the legacy every-session status + reminder output. if [ "$ALWAYS" = 0 ]; then exit 0 fi ESC="$(printf '\033')" STATUS_OUT="$(run_status | sed "s/${ESC}\[[0-9;]*m//g")" emit "[CodeGraph] Index present. \`codegraph status\`: ${STATUS_OUT} If the index looks behind (pending/changed/stale files above), run \`codegraph sync\` before relying on codegraph_* results. Prefer codegraph_* tools for structural questions (definitions, callers/callees, impact, traces) over a grep + read loop." exit 0 CG_EOF_SCRIPT cat > "$DEST/codegraph-policy.md" <<'CG_EOF_POLICY' # CodeGraph — Session-Startup Rule (every agent; set up once per project, then trust it) **This is a hard rule for every AI coding agent — Claude Code, Codex, Cursor, Gemini CLI, opencode, Antigravity, Kimi Code, Qoder, and any other.** The **first time** you work in a project, before doing substantive work, ensure CodeGraph is installed and this project is indexed. CodeGraph is a tree-sitter knowledge graph of every symbol, edge, and file; its `codegraph_*` MCP tools answer structural questions (definitions, callers/callees, impact, traces) that grep cannot, in sub-millisecond reads. Once a project is indexed, CodeGraph's file-watcher keeps the index fresh automatically — so this is **one-time setup per project, not an every-session ceremony**. In repos that wire the bundled `SessionStart` hook, `codegraph-session-check.sh` runs this check for you at session start. By default it runs in **once-per-project** mode: it injects a `[CodeGraph]` setup block **only while the project still needs setup** (no `codegraph` CLI, or no `.codegraph/` index yet); once the project is indexed it goes **silent**. So: - **A `[CodeGraph]` setup block IS present →** the project isn't set up yet; follow it (and the decision tree below) before starting the task. - **No `[CodeGraph]` block appears (in a hook-wired repo) →** the project is **already set up — just proceed**, and prefer `codegraph_*` tools. Do **not** re-run `codegraph init`/`status` "to be safe": the file-watcher keeps the index fresh, and re-checking every session is the noise this mode removes. - **No startup hook at all (e.g. Qoder), or you're unsure one is wired →** run the one-time decision tree below yourself; once `.codegraph/` exists, treat the project as set up and proceed thereafter. (Operators who want the legacy every-session `codegraph status` readout can install the hook with `--always`; it prints index status at the start of every session even after setup.) --- ## The one-time setup decision tree (first time you work in a project) 1. **Is the `codegraph` CLI installed?** — `command -v codegraph` (or `codegraph --version`). - **No →** install and set it up (see *Installing from scratch* below), then continue. 2. **Is THIS project indexed?** — does `.codegraph/` exist at the repo root, and does `codegraph status` succeed? - **No →** `codegraph init -i` (builds the initial graph). 3. **Index freshness** — after the initial `init -i`, CodeGraph's file-watcher keeps the index in sync with the working tree automatically; you do **not** need to run `codegraph status` / `sync` at the start of every session. Only if you have a concrete reason to suspect drift (e.g. a large external checkout or branch switch the watcher may have missed) run `codegraph status`, then `codegraph sync` if it reports pending / changed / stale files, before relying on `codegraph_*`. Once the index is present, start the user's task and prefer `codegraph_*` tools for structural work. --- ## Installing from scratch (when the CLI is missing) You are authorized to install and set CodeGraph up automatically. **Announce each command before running it** (it touches the user's machine), then run, in order: | Step | Command | Purpose | |---|---|---| | 1. Install CLI (macOS/Linux) | `curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh \| sh` | Self-contained binary; no Node required | | 1. Install CLI (any OS, alt) | `npm i -g @colbymchenry/codegraph` | npm alternative | | 1. Install CLI (Windows) | `irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 \| iex` | PowerShell | | 2. Register MCP into agents | `codegraph install` | Adds the `codegraph` MCP server to Claude Code / Cursor / Codex / opencode / Gemini / etc. config | | 3. Build the index | `codegraph init -i` | Creates `.codegraph/` and indexes the repo | | 4. Keep it fresh later | `codegraph sync` | Incremental update since last index | --- ## The restart reality — be honest about it Registering the MCP server (`codegraph install`) makes the `codegraph_*` **MCP tools** available **only after the agent/IDE restarts** — and **you cannot restart yourself or your host app**. So: - Do **everything that does not need a restart** right now: install the CLI, `codegraph install`, `codegraph init -i`, `codegraph sync`. - **Use the `codegraph` CLI via your shell/Bash immediately** for the current task — it works without the MCP tools (`codegraph query`, `codegraph context `, `codegraph callers`, `codegraph callees`, `codegraph impact`, `codegraph files`, `codegraph status`). - Then **explicitly ask the user to restart** the agent/IDE (Claude Code: restart or `/mcp` reconnect; Cursor: Reload Window; Codex/Gemini/opencode/Kimi: restart the CLI; Antigravity/Qoder: restart the IDE) so the `codegraph_*` MCP tools load, and resume the task afterward. - **Never claim you restarted yourself.** Never fabricate that the MCP tools are available when they are not — verify, or use the CLI. --- ## Don'ts - **Don't re-run the setup check once the project is already indexed.** A missing `[CodeGraph]` hook block in a wired repo means "set up — proceed," not "re-check"; re-running `init`/`status` every session is exactly the noise once-mode removes. Trust the file-watcher (or install with `--always`). - **Don't silently `curl | sh`** without announcing it — installing software touches the user's machine; say what you're running first. - **Don't run `codegraph uninit` / `uninstall`** unless the user explicitly asks. - **Don't re-query `codegraph_*` immediately after editing a file** in the same turn — the watcher debounces ~500 ms behind writes; `codegraph sync` or wait a beat first. - **Don't trust grep over a fresh index** for structural questions — prefer `codegraph_*`. --- ## Precedence Where this project's auto-generated CodeGraph block (between `` and `` in `CLAUDE.md` / `AGENTS.md` / `.cursor/rules/codegraph.mdc`) says to *ask the user before running `codegraph init -i`*, **this rule supersedes it**: at session start you may initialize/sync automatically (announcing commands). That managed block is regenerated by `codegraph install`, so this rule lives in a separate, unmanaged file on purpose. CG_EOF_POLICY cat > "$DEST/karpathy-policy.md" <<'CG_EOF_KARPATHY' # Karpathy Coding Guidelines — reduce common LLM coding mistakes (every session, every agent) Behavioral guidelines that counter the failure modes Andrej Karpathy catalogued for LLM coding agents: wrong assumptions made silently, hidden confusion, overcomplication and bloated abstractions, and drive-by edits to code that was orthogonal to the task. Distributed to every AI coding agent by `agent-primer` alongside the CodeGraph session-startup rule. Merge with project-specific instructions as needed. Source: derived from Andrej Karpathy's notes (https://x.com/karpathy/status/2015883857489522876). The single-file packaging originates with `forrestchang/andrej-karpathy-skills` (MIT); this version was adapted from the `multica-ai/andrej-karpathy-skills` republish and enhanced with an executable workflow. MIT-licensed. **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. ## Executable Workflow Use this workflow for non-trivial coding tasks: 1. **Assumption Check** - State the intended outcome, relevant scope, constraints, and success criteria. If intent or scope is ambiguous, ask before editing. 2. **Minimal Plan** - Give 2-5 concrete steps. Each step should include how it will be verified. 3. **Change Boundary** - Name the files or behavior you expect to touch, and name nearby things you will leave alone. 4. **Simplicity Gate** - Before coding, reject unrequested abstractions, frameworks, configuration, compatibility layers, or speculative features. 5. **Verification Contract** - Finish by reporting what you ran, what passed, what was not verified, and any remaining risk. For trivial typo fixes or obvious one-line edits, keep this lightweight: preserve the spirit of the workflow without adding ceremony. ## When to Ask vs Proceed Ask when the request has multiple plausible meanings, touches sensitive data, changes public APIs, risks data loss, or lacks success criteria. Proceed when the task is narrow, reversible, and the expected outcome is obvious from nearby code, tests, or documentation. ## 1. Think Before Coding **Don't assume. Don't hide confusion. Surface tradeoffs.** Before implementing: - State your assumptions explicitly. If uncertain, ask. - If multiple interpretations exist, present them - don't pick silently. - If a simpler approach exists, say so. Push back when warranted. - If something is unclear, stop. Name what's confusing. Ask. ## 2. Simplicity First **Minimum code that solves the problem. Nothing speculative.** - No features beyond what was asked. - No abstractions for single-use code. - No "flexibility" or "configurability" that wasn't requested. - No error handling for impossible scenarios. - If you write 200 lines and it could be 50, rewrite it. Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. ## 3. Surgical Changes **Touch only what you must. Clean up only your own mess.** When editing existing code: - Don't "improve" adjacent code, comments, or formatting. - Don't refactor things that aren't broken. - Match existing style, even if you'd do it differently. - If you notice unrelated dead code, mention it - don't delete it. When your changes create orphans: - Remove imports/variables/functions that YOUR changes made unused. - Don't remove pre-existing dead code unless asked. The test: Every changed line should trace directly to the user's request. ## 4. Goal-Driven Execution **Define success criteria. Loop until verified.** Transform tasks into verifiable goals: - "Add validation" → "Write tests for invalid inputs, then make them pass" - "Fix the bug" → "Write a test that reproduces it, then make it pass" - "Refactor X" → "Ensure tests pass before and after" For multi-step tasks, state a brief plan: ``` 1. [Step] → verify: [check] 2. [Step] → verify: [check] 3. [Step] → verify: [check] ``` Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. ## Completion Response End with evidence, not confidence. Summarize the changed behavior, list verification commands and outcomes, and call out anything not tested. --- **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. CG_EOF_KARPATHY cat > "$DEST/superpowers-policy.md" <<'CG_EOF_SUPERPOWERS' # Superpowers — install the skills plugin + follow its methodology (every session, every agent) [obra/superpowers](https://github.com/obra/superpowers) is "a complete software development methodology for your coding agents, built on composable skills" — a library of skills (testing, debugging, brainstorming, planning, code review, git workflows, subagent coordination, and meta-skills) that auto-activate during work. This kit both **bootstraps the plugin install** and carries its **core methodology**, so the principles apply even before the plugin is active. It complements the Karpathy coding guidelines (shared simplicity/evidence ethos); Superpowers adds the systematic, test-first skill library on top. ## Bootstrap — install the plugin at session start You are authorized to install Superpowers automatically. **Announce each command before running it** (it changes the agent's config), then install via your agent's marketplace: | Agent | Install | |---|---| | Claude Code | `/plugin install superpowers@claude-plugins-official` | | Codex | run `/plugins`, search "superpowers", install | | Cursor | `/add-plugin superpowers` (in Agent chat) | | Gemini CLI | `gemini extensions install https://github.com/obra/superpowers` | | opencode | follow `https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md` | For agents without a native plugin (Antigravity, Kimi, Qoder, or any not listed above), install the same skills the **agent-agnostic** way via [skills.sh](https://www.skills.sh/obra/superpowers) — one command that writes the skill files into your skills directory and works for any agent: ``` npx skills add obra/superpowers ``` Either way, the methodology below applies even before the skills load. Commands change, so the **authoritative sources** are the upstream README (https://github.com/obra/superpowers#installation) and https://www.skills.sh/obra/superpowers. A restart may be needed for the skills to load — **never claim you restarted yourself**; ask the user to restart if they aren't available yet. ## Core methodology (applies always, plugin or not) - **Test-Driven Development** — write the failing test first, then make it pass. - **Systematic over ad-hoc** — follow a process; don't guess-and-check. - **Complexity reduction** — simplicity is the primary goal; cut, don't add. - **Evidence over claims** — verify before declaring success; show what you ran. ## Don'ts - **Don't silently install** — announce the marketplace command first (it touches the agent's config). - **Don't fabricate install commands** — if an agent isn't listed, use the upstream README; don't invent one. - **Don't duplicate the Karpathy guidelines** — where they overlap (simplicity, evidence), follow either; Superpowers adds the test-first skill library on top. CG_EOF_SUPERPOWERS cat > "$DEST/mcp-policy.md" <<'CG_EOF_MCP' # MCP servers — recommended setup (opt-in bundle; complements CodeGraph) These Model Context Protocol servers give your agent capabilities CodeGraph (the structural code index) does not: up-to-date library docs, GitHub/repo data, and real browser automation. They **complement — never duplicate** — the CodeGraph index. Add only the ones the user needs. You are authorized to add these for the user. **Announce each command before running it** (it edits the agent's MCP config), and **verify the current command against the linked docs — never fabricate one**; MCP setup differs per agent and per server version. ## The servers - **Context7** — version-correct library/framework docs injected into the prompt (stops stale-API hallucinations). Official setup: `npx ctx7 setup` (add `--claude` / `--cursor` / `--opencode` to target an agent). Manual/remote: server URL `https://mcp.context7.com/mcp`, or the `@upstash/context7-mcp` package. Docs: https://github.com/upstash/context7 - **GitHub** (official) — issues, PRs, repo + code search, commit history. Remote (recommended): `https://api.githubcopilot.com/mcp/` (HTTP transport, OAuth) — e.g. Claude Code: `claude mcp add --transport http github https://api.githubcopilot.com/mcp/`. Local: the `ghcr.io/github/github-mcp-server` container. Docs: https://github.com/github/github-mcp-server - **Playwright** (Microsoft) — drive a real browser for UI/E2E testing & scraping via accessibility snapshots. e.g. Claude Code: `claude mcp add playwright -- npx @playwright/mcp@latest`. Docs: https://github.com/microsoft/playwright-mcp For other agents, use that agent's MCP config (Cursor `.cursor/mcp.json`, Codex `~/.codex/config.toml` `[mcp_servers]`, Gemini `.gemini/settings.json` `mcpServers`, opencode/Antigravity equivalents) — see each server's docs above. After adding a server, **restart the agent/IDE** so its tools load — **never claim you restarted yourself**. ## When to use which - **CodeGraph** — *this repo's* structure: definitions, callers/callees, impact, traces. - **Context7** — external library/framework API questions. - **GitHub** — issues/PRs/cross-repo search & history. - **Playwright** — anything that needs a live browser. ## Don'ts - Don't add servers that duplicate CodeGraph (it already covers structural code questions). - Don't paste tokens into configs in plaintext where the agent supports OAuth. - Don't enable a server you won't use — each adds tool-surface + latency. CG_EOF_MCP cat > "$DEST/tools-policy.md" <<'CG_EOF_TOOLS' # Code tools — ast-grep + repomix (opt-in bundle; companions to the CodeGraph CLI) Two CLI tools that make edits precise and context-gathering cheap. Install only what's missing (`command -v `), **announce the install command first**, and **verify it against the linked docs — don't fabricate**. ## The tools - **ast-grep** (`ast-grep` / `sg`) — AST-aware structural search & rewrite across many languages; ideal for safe codemods ("rewrite this call pattern everywhere") that regex can't do reliably. Install: `brew install ast-grep` · `npm install --global @ast-grep/cli` · `cargo install ast-grep --locked`. Docs: https://github.com/ast-grep/ast-grep - **repomix** — pack a repo (or a subtree) into one LLM-friendly file with token counts; use to hand an agent whole-repo context where no index exists. No install needed: `npx repomix@latest` (or `npm install -g repomix` / `brew install repomix`). Docs: https://github.com/yamadashy/repomix - **ripgrep (`rg`)** and **jq** are assumed present (fast text search / JSON munging). Install if missing: `brew install ripgrep jq`. ## When to use which - **CodeGraph** — structural questions on an *indexed* repo (callers/callees/impact/traces). Prefer it. - **ast-grep** — syntax-aware find/rewrite (codemods), or any repo that isn't indexed. - **ripgrep** — fast literal/regex text (comments, strings, TODOs) where structure doesn't matter. - **repomix** — bulk "give me the whole repo" context export. ## Don'ts - Don't grep/ast-grep for a structural question a fresh CodeGraph index answers better. - Don't `npm i -g` / `brew install` silently — announce the command first. CG_EOF_TOOLS cat > "$DEST/rules-policy.md" <<'CG_EOF_RULES' # Security + architecture + hygiene guardrails (opt-in bundle; additive to the Karpathy guidelines) The Karpathy guidelines cover code quality. These add three orthogonal concerns. Apply them on relevant work — they're principles, not commands to run. ## Security (OWASP-distilled) - Treat all external input as hostile: parameterize SQL, avoid shell-string interpolation, encode output for its sink (HTML / SQL / shell / URL). - Never log, print, or commit secrets; load them from env / a secret store; keep them out of git. - Check **authentication AND authorization** on every protected path — they're different things. - Guard deserialization, file paths (no traversal), and redirects against untrusted values. - Pin and verify dependencies; don't add a package without a reason. ## Architecture (12-Factor Agents) - Own your prompts and your context window; treat tool calls as structured outputs. - Prefer small, focused, composable units over one mega-agent; keep control flow explicit. - Compact errors into context and recover; put a human in the loop for risky/destructive actions. - Keep state explicit and serializable so runs are resumable. Reference: https://github.com/humanlayer/12-factor-agents ## Commit / PR hygiene - Small, focused commits; imperative subject line; explain *why* in the body. - One logical change per PR, with tests + a clear description. - No unrelated drive-by edits (this reinforces the Karpathy "surgical changes" rule). - Follow the repo's own commit conventions (e.g. its trailer / co-author policy). ## Don'ts - Don't bolt on security theater beyond the task's real threat model. - Don't reformat or refactor unrelated code in the name of "hygiene." CG_EOF_RULES cat > "$DEST/skills-policy.md" <<'CG_EOF_SKILLS' # Skill registries — beyond Superpowers (opt-in bundle) Superpowers (in the default bundle) is one skill library. These are additional sources of installable, agent-agnostic skills. **Announce installs; verify the command against the source — never fabricate one.** - **Anthropic official skills** — first-party skills (document handling, `mcp-builder`, `webapp-testing`, `skill-creator`, …) in the open `SKILL.md` format. Browse/install from https://github.com/anthropics/skills — via your agent's plugin marketplace, or by copying a `SKILL.md` into your skills directory. - **skills.sh registry** — the agent-agnostic installer/discovery layer: `npx skills add /` writes the skill files into your skills directory. Browse: https://skills.sh - **VoltAgent — awesome-agent-skills** — a large, *curated* (hand-picked, not AI-generated) collection, strong on security (Trail of Bits) and vendor SDKs: https://github.com/VoltAgent/awesome-agent-skills ## How to choose Prefer official (Anthropic) → curated (VoltAgent) → community. Install via skills.sh for any agent that isn't plugin-native. A restart may be needed for new skills to load — **never claim you restarted yourself**; ask the user. ## Don'ts - Don't bulk-install skills you won't use (context + maintenance cost). - Don't trust an uncurated / AI-generated skill without reading it first. CG_EOF_SKILLS cat > "$DEST/agent-extensions-policy.md" <<'CG_EOF_EXT' # First-party agent extensions — plugins/skills per agent (opt-in bundle) Each agent ecosystem ships its own plugins / skills / extensions. This points you at each one's **native** mechanism. **Commands and marketplaces change often and differ by agent version — VERIFY against the agent's current docs before running; never fabricate a command for an agent not listed.** Announce each install; you can't restart yourself, so ask the user to restart for new extensions to take effect. | Agent | First-party extension mechanism | |---|---| | Claude Code | Plugin marketplaces: `/plugin marketplace add /` then `/plugin install @`; plus Anthropic skills (github.com/anthropics/skills) | | Codex | `/plugins` — search & install in-app | | Cursor | Rules (`.cursor/rules/*.mdc`), `.cursor/mcp.json`, and Cursor's extension/MCP marketplace | | Gemini CLI | `gemini extensions install ` · `gemini extensions list` | | opencode | plugins under `~/.config/opencode/plugins/` (+ its plugin docs) | | Antigravity / Kimi / Qoder | follow each tool's current plugin/skill docs — some have no marketplace yet | ## Don'ts - Don't install marketplace plugins without announcing the command first. - Don't assume an extension mechanism exists for an agent that isn't listed — check its docs. - Don't duplicate what agent-primer already wired (the 3 core policies + any `--with` bundles). CG_EOF_EXT cat > "$DEST/install.sh" <<'CG_EOF_INSTALL' #!/usr/bin/env bash # install.sh — wire the CodeGraph session-startup rule (+ hook), the Karpathy coding # guidelines, and the Superpowers methodology + plugin-bootstrap into AI coding agents. # # Places codegraph-session-check.sh + the policy docs (codegraph-policy.md, # karpathy-policy.md, superpowers-policy.md), writes each agent's policy/instruction # file(s), and registers a session-start hook where the agent supports one (only the # CodeGraph rule has a hook; Karpathy + Superpowers are policy-only). Idempotent. Never clobbers an existing config: it merges via # python3 when available, otherwise prints the exact snippet to add by hand. # # Usage: # ./install.sh --project [DIR] wire into a project (default: current dir) # ./install.sh --global wire into your user-level (~/) configs — applies to ALL projects # ./install.sh ... --agents a,b only these agents (default: all) # ./install.sh ... --with a,b also install opt-in bundles (mcp, tools, rules, skills, agent-extensions; or 'all') # ./install.sh ... --dry-run show what would happen, write nothing # # Agents: claude, codex, cursor, gemini, opencode, antigravity, kimi, qoder # # Requires: bash, python3 (for safe JSON/TOML merges; without it, hooks for # pre-existing config files are printed as snippets instead of merged). set -u SELF_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_SRC="$SELF_DIR/codegraph-session-check.sh" POLICY_SRC="$SELF_DIR/codegraph-policy.md" KARPATHY_SRC="$SELF_DIR/karpathy-policy.md" SUPERPOWERS_SRC="$SELF_DIR/superpowers-policy.md" MCP_SRC="$SELF_DIR/mcp-policy.md" TOOLS_SRC="$SELF_DIR/tools-policy.md" RULES_SRC="$SELF_DIR/rules-policy.md" SKILLS_SRC="$SELF_DIR/skills-policy.md" EXT_SRC="$SELF_DIR/agent-extensions-policy.md" VERSION="0.1.0" SCOPE="" TARGET="" AGENTS="claude,codex,cursor,gemini,opencode,antigravity,kimi,qoder" KNOWN_AGENTS="claude codex cursor gemini opencode antigravity kimi qoder" WITH="" # opt-in extra bundles (comma list); the core 3 always install KNOWN_BUNDLES="mcp tools rules skills agent-extensions" DRYRUN=0 ALWAYS=0 # 1 => thread --always into every wired hook command (legacy every-session mode) FAILED=0 # set to 1 by any failed write/merge; controls the final exit code usage() { cat <<'EOF' agent-primer — wire the CodeGraph rule (+ hook), the Karpathy guidelines, and the Superpowers methodology/plugin-bootstrap into AI coding agents. Usage: install.sh --project [DIR] wire into a project (default: current dir) install.sh --global wire into your user-level (~/) configs — applies to ALL projects install.sh ... --agents a,b only these agents (comma-separated; default: all) install.sh ... --dry-run show what would happen, write nothing install.sh ... --always wire every-session hooks (default: once per project — quiet after setup) install.sh ... --with a,b also install opt-in bundles: mcp, tools, rules, skills, agent-extensions (or 'all') install.sh --version print version and exit install.sh -h | --help show this help Agents: claude, codex, cursor, gemini, opencode, antigravity, kimi, qoder EOF } while [ "$#" -gt 0 ]; do case "$1" in --project) SCOPE="project"; if [ "${2:-}" ] && [ "${2#-}" = "$2" ]; then TARGET="$2"; shift; fi; shift ;; --global) SCOPE="global"; shift ;; # NOTE: `shift 2` fails (and shifts nothing → infinite loop) on bash 3.2 when the # flag is the last arg; do a guarded double-shift instead. --agents) AGENTS="${2:-}"; shift; [ "$#" -gt 0 ] && shift ;; --agents=*) AGENTS="${1#*=}"; shift ;; --dry-run) DRYRUN=1; shift ;; --always) ALWAYS=1; shift ;; --with) WITH="${2:-}"; shift; [ "$#" -gt 0 ] && shift ;; --with=*) WITH="${1#*=}"; shift ;; --version) echo "agent-primer $VERSION"; exit 0 ;; -h|--help) usage; exit 0 ;; *) echo "error: unknown arg: $1" >&2; usage >&2; exit 2 ;; esac done [ -z "$SCOPE" ] && { echo "error: pass --project [DIR] or --global" >&2; usage >&2; exit 2; } # Normalize whitespace and validate --agents, so a typo/spaced list fails loudly # instead of silently installing nothing. AGENTS="$(printf '%s' "$AGENTS" | tr -d '[:space:]')" [ -z "$AGENTS" ] && { echo "error: --agents is empty" >&2; exit 2; } _bad=""; _oldifs="$IFS"; IFS=',' for _a in $AGENTS; do case " $KNOWN_AGENTS " in *" $_a "*) ;; *) _bad="$_bad $_a" ;; esac; done IFS="$_oldifs" [ -n "$_bad" ] && { echo "error: unknown agent(s):$_bad" >&2; echo "known agents: $KNOWN_AGENTS" >&2; exit 2; } # Opt-in bundles (--with): default install = the core 3 policies; --with adds extras; --with all = every bundle. WITH="$(printf '%s' "$WITH" | tr -d '[:space:]')" [ "$WITH" = "all" ] && WITH="$(printf '%s' "$KNOWN_BUNDLES" | tr ' ' ',')" policy_on() { case ",$WITH," in *",$1,"*) return 0 ;; *) return 1 ;; esac } if [ -n "$WITH" ]; then _badb=""; _oldifs="$IFS"; IFS=',' for _b in $WITH; do case " $KNOWN_BUNDLES " in *" $_b "*) ;; *) _badb="$_badb $_b" ;; esac; done IFS="$_oldifs" [ -n "$_badb" ] && { echo "error: unknown bundle(s):$_badb" >&2; echo "known bundles: $KNOWN_BUNDLES (or 'all')" >&2; exit 2; } fi [ -f "$SCRIPT_SRC" ] || { echo "error: $SCRIPT_SRC not found" >&2; exit 2; } [ -f "$POLICY_SRC" ] || { echo "error: $POLICY_SRC not found" >&2; exit 2; } [ -f "$KARPATHY_SRC" ] || { echo "error: $KARPATHY_SRC not found" >&2; exit 2; } [ -f "$SUPERPOWERS_SRC" ] || { echo "error: $SUPERPOWERS_SRC not found" >&2; exit 2; } policy_on mcp && { [ -f "$MCP_SRC" ] || { echo "error: $MCP_SRC not found" >&2; exit 2; }; } policy_on tools && { [ -f "$TOOLS_SRC" ] || { echo "error: $TOOLS_SRC not found" >&2; exit 2; }; } policy_on rules && { [ -f "$RULES_SRC" ] || { echo "error: $RULES_SRC not found" >&2; exit 2; }; } policy_on skills && { [ -f "$SKILLS_SRC" ] || { echo "error: $SKILLS_SRC not found" >&2; exit 2; }; } policy_on agent-extensions && { [ -f "$EXT_SRC" ] || { echo "error: $EXT_SRC not found" >&2; exit 2; }; } if [ "$SCOPE" = "project" ]; then TARGET="${TARGET:-$PWD}" TARGET="$(cd "$TARGET" 2>/dev/null && pwd || echo "$TARGET")" KIT_DEST="$TARGET/tools/agent-primer" # Committed configs use a project-relative script path so they work on any machine. SCRIPT_REL="tools/agent-primer/codegraph-session-check.sh" SCRIPT_CLAUDE="\$CLAUDE_PROJECT_DIR/$SCRIPT_REL" SCRIPT_OTHER="$SCRIPT_REL" ROOT="$TARGET" else KIT_DEST="$HOME/.agent-primer" SCRIPT_CLAUDE="$HOME/.agent-primer/codegraph-session-check.sh" SCRIPT_OTHER="$HOME/.agent-primer/codegraph-session-check.sh" ROOT="$HOME" fi # Scope-aware instruction-doc targets. Hooks always land in each agent's own config # dir (set in the branches below); only these instruction files differ by scope. In # --global mode they go to each agent's real global home, not the user's home root. if [ "$SCOPE" = "project" ]; then CLAUDE_RULE="$ROOT/.claude/rules/codegraph-session-startup.md"; CLAUDE_RULE_MODE="file" CODEX_INSTR="$ROOT/AGENTS.md" OPENCODE_INSTR="$ROOT/AGENTS.md" GEMINI_INSTR="$ROOT/GEMINI.md" ANTI_INSTR="$ROOT/AGENTS.md"; ANTI_RULE="$ROOT/.agents/rules/codegraph-session-startup.md" QODER_RULE="$ROOT/.qoder/rules/codegraph-session-startup.md"; QODER_INSTR="$ROOT/AGENTS.md" CURSOR_MDC="$ROOT/.cursor/rules/codegraph-session-startup.mdc" else CLAUDE_RULE="$HOME/.claude/CLAUDE.md"; CLAUDE_RULE_MODE="append" CODEX_INSTR="$HOME/.codex/AGENTS.md" OPENCODE_INSTR="$HOME/.config/opencode/AGENTS.md" GEMINI_INSTR="$HOME/.gemini/GEMINI.md" ANTI_INSTR="$HOME/.gemini/GEMINI.md"; ANTI_RULE="" QODER_RULE=""; QODER_INSTR="" CURSOR_MDC="" fi PY="$(command -v python3 || true)" HAVE_PY=0; [ -n "$PY" ] && HAVE_PY=1 note() { printf '[agent-primer] %s\n' "$*"; } # JSON-encode a string to a safe double-quoted literal (for embedding a path into # generated JS/JSON). Uses python3 when available; falls back to escaping \ and ". json_str() { # json_str VALUE -> "..." if [ "$HAVE_PY" = 1 ]; then CG_V="$1" "$PY" -c 'import os,json;print(json.dumps(os.environ["CG_V"]))' else printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')"; fi } # Escape a value for a TOML basic string (backslash, then double-quote). toml_esc() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; } putfile() { # putfile DEST < content (sets FAILED on error; never claims success it didn't achieve) local dest="$1"; local dir; dir="$(dirname "$dest")" if [ "$DRYRUN" = 1 ]; then note "would write $dest"; cat >/dev/null; return 0; fi if ! mkdir -p "$dir" 2>/dev/null; then FAILED=1; note "ERROR: cannot create $dir"; cat >/dev/null; return 1; fi if cat > "$dest"; then note "wrote $dest"; else FAILED=1; note "ERROR: failed to write $dest"; return 1; fi } # Insert/replace a marker-delimited block in a shared markdown file (idempotent). # Defaults to the CodeGraph policy + its marker; pass a policy file and marker id to # place a different policy block (e.g. the Karpathy guidelines) into the same file. append_marked() { # append_marked FILE [POLICY_FILE] [MARKER_ID] local file="$1"; local policy="${2:-$POLICY_SRC}"; local marker="${3:-codegraph-session-startup}" if [ "$DRYRUN" = 1 ]; then note "would update $marker block in $file"; return 0; fi mkdir -p "$(dirname "$file")" 2>/dev/null # ensure the agent's dir exists on a pristine HOME if [ "$HAVE_PY" = 1 ]; then if CG_FILE="$file" CG_POLICY="$policy" CG_MARKER="$marker" "$PY" - <<'PY' import os, re, sys, tempfile f=os.environ["CG_FILE"]; policy=open(os.environ["CG_POLICY"],encoding="utf-8").read().rstrip()+"\n" m=os.environ["CG_MARKER"]; s=f""; e=f"" block=f"{s}\n{policy}{e}\n" try: txt=open(f,encoding="utf-8").read() except FileNotFoundError: txt="" if s in txt and e in txt: txt=re.sub(re.escape(s)+r".*?"+re.escape(e)+r"\n?", block, txt, flags=re.S) else: if s in txt or e in txt: # tolerate a corrupted half-block: drop the lone marker, then re-add cleanly sys.stderr.write(f"[agent-primer] {f}: found a lone {m} marker; rewriting it cleanly\n") txt=txt.replace(s,"").replace(e,"") if txt and not txt.endswith("\n"): txt+="\n" txt += ("\n" if txt else "") + block d=os.path.dirname(f) or "." fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as out: out.write(txt) os.replace(tmp,f) # atomic: never leaves a half-written file except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.stderr.write(f"[agent-primer] {f}: write failed ({ex})\n"); sys.exit(1) PY then note "updated $marker block in $file" else FAILED=1; note "ERROR: failed to update $marker block in $file"; fi else if grep -q "$marker:start" "$file" 2>/dev/null; then note "$marker block already present in $file (no python3 to refresh) — skipping" elif { printf '\n\n' "$marker"; cat "$policy"; printf '\n\n' "$marker"; } >> "$file"; then note "appended $marker block to $file" else FAILED=1; note "ERROR: failed to append $marker block to $file"; fi fi } # Merge a session-start command hook into a JSON config (idempotent). Falls back # to printing the snippet when python3 is unavailable. json_hook() { # json_hook FILE KIND CMD (idempotent; refuses to clobber malformed JSON; atomic write) local file="$1" kind="$2" cmd="$3" if [ "$DRYRUN" = 1 ]; then note "would register $kind SessionStart hook in $file"; return 0; fi if [ "$HAVE_PY" = 1 ]; then mkdir -p "$(dirname "$file")" 2>/dev/null if CG_FILE="$file" CG_KIND="$kind" CG_CMD="$cmd" "$PY" - <<'PY' import os, json, sys, tempfile f=os.environ["CG_FILE"]; kind=os.environ["CG_KIND"]; cmd=os.environ["CG_CMD"] raw=open(f,encoding="utf-8").read() if os.path.exists(f) else "" data={} if raw.strip(): try: data=json.loads(raw) except Exception as ex: # Do NOT treat unparseable as empty — that would wipe the user's real config. sys.stderr.write(f"[agent-primer] {f}: existing file is not valid JSON ({ex}); refusing to modify it\n"); sys.exit(2) if not isinstance(data, dict): sys.stderr.write(f"[agent-primer] {f}: top-level JSON is not an object; refusing to modify it\n"); sys.exit(2) def has(arr): # Idempotency: compare the actual command field values, not serialized JSON # (json.dumps escapes embedded quotes, which broke substring matching). for e in arr: if isinstance(e, dict): if e.get("command")==cmd: return True for h in (e.get("hooks") or []): if isinstance(h, dict) and h.get("command")==cmd: return True return False if kind=="cursor": data.setdefault("version", 1) arr=data.setdefault("hooks", {}).setdefault("sessionStart", []) if not has(arr): arr.append({"command": cmd}) else: arr=data.setdefault("hooks", {}).setdefault("SessionStart", []) if not has(arr): if kind=="antigravity": arr.append({"command": cmd}) else: entry={"hooks":[{"type":"command","command":cmd}]} if kind=="gemini": entry["matcher"]="startup" arr.append(entry) if kind=="gemini": data.setdefault("hooksConfig", {})["enabled"]=True text=json.dumps(data, indent=2)+"\n" d=os.path.dirname(f) or "." fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as out: out.write(text) os.replace(tmp,f) # atomic: original stays intact until the full write succeeds except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.stderr.write(f"[agent-primer] {f}: write failed ({ex})\n"); sys.exit(3) PY then note "registered $kind SessionStart hook in $file" else FAILED=1; note "could not merge into $file (left untouched) — add this hook manually:"; printf ' %s SessionStart -> %s\n' "$kind" "$cmd"; fi else note "python3 not found — add this to $file manually:" printf ' SessionStart command hook -> %s\n' "$cmd" fi } with_policy_frontmatter() { # with_policy_frontmatter "" [POLICY_FILE] (emits frontmatter+policy to stdout) printf '%s\n' "$1"; cat "${2:-$POLICY_SRC}" } selected() { case ",$AGENTS," in *",$1,"*) return 0 ;; *) return 1 ;; esac } # Once-mode is the default. With --always, every wired hook command gets the flag so the # check stays verbose every session. The leading space lets us append it directly inside # the existing quoted command strings (empty => byte-identical to the default). HOOK_FLAGS="" [ "$ALWAYS" = 1 ] && HOOK_FLAGS=" --always" # Opt-in policy bundles (--with). Each is a HOOKLESS markdown policy distributed into the # same instruction channels as the core 3, via place_policy. Registry rows: id|src|marker|desc. EXTRA_REGISTRY=" mcp|$MCP_SRC|agent-primer-mcp|MCP servers — Context7 docs, GitHub, Playwright (complements CodeGraph) tools|$TOOLS_SRC|agent-primer-tools|Code tools — ast-grep + repomix; when to use each vs CodeGraph rules|$RULES_SRC|agent-primer-rules|Security + 12-Factor-Agents + commit/PR hygiene guardrails skills|$SKILLS_SRC|agent-primer-skills|Skill registries — Anthropic skills, skills.sh, VoltAgent agent-extensions|$EXT_SRC|agent-primer-extensions|Per-agent first-party plugins/skills/tools " # Distribute one hookless policy doc into a single agent's channel(s), honoring scope. # Mirrors the core per-agent placement; opt-in bundles use generic frontmatter from $desc. place_policy() { # place_policy AGENT SRC MARKER DESC local agent="$1" src="$2" marker="$3" desc="$4" kdir case "$agent" in claude) if [ "$CLAUDE_RULE_MODE" = "append" ]; then append_marked "$CLAUDE_RULE" "$src" "$marker" else putfile "${CLAUDE_RULE%/*}/$marker.md" < "$src"; fi ;; codex) append_marked "$CODEX_INSTR" "$src" "$marker" ;; opencode) append_marked "$OPENCODE_INSTR" "$src" "$marker" ;; gemini) append_marked "$GEMINI_INSTR" "$src" "$marker" ;; cursor) [ -n "$CURSOR_MDC" ] && with_policy_frontmatter "--- description: $desc alwaysApply: true ---" "$src" | putfile "${CURSOR_MDC%/*}/$marker.mdc" ;; antigravity) [ -n "$ANTI_RULE" ] && putfile "${ANTI_RULE%/*}/$marker.md" < "$src" append_marked "$ANTI_INSTR" "$src" "$marker" ;; kimi) if [ "$SCOPE" = "project" ]; then kdir="$ROOT/.kimi-code/skills/$marker/SKILL.md"; else kdir="$HOME/.kimi-code/skills/$marker/SKILL.md"; fi with_policy_frontmatter "--- name: $marker description: $desc whenToUse: When this bundle's tools/rules are relevant to the task. ---" "$src" | putfile "$kdir" ;; qoder) [ -n "$QODER_RULE" ] || return 0 with_policy_frontmatter "" "$src" | putfile "${QODER_RULE%/*}/$marker.md" append_marked "$QODER_INSTR" "$src" "$marker" ;; esac } # --- place the kit ------------------------------------------------------------- note "scope=$SCOPE target=$ROOT agents=$AGENTS dry-run=$DRYRUN" if [ "$DRYRUN" = 0 ]; then if mkdir -p "$KIT_DEST" \ && cp "$SCRIPT_SRC" "$KIT_DEST/codegraph-session-check.sh" && chmod +x "$KIT_DEST/codegraph-session-check.sh" \ && cp "$POLICY_SRC" "$KIT_DEST/codegraph-policy.md" \ && cp "$KARPATHY_SRC" "$KIT_DEST/karpathy-policy.md" \ && cp "$SUPERPOWERS_SRC" "$KIT_DEST/superpowers-policy.md"; then note "placed kit in $KIT_DEST" else FAILED=1; note "ERROR: failed to place kit in $KIT_DEST" fi else note "would place kit in $KIT_DEST" fi # --- per-agent wiring ---------------------------------------------------------- if selected claude; then if [ "$SCOPE" = "project" ]; then SETTINGS="$ROOT/.claude/settings.json"; else SETTINGS="$HOME/.claude/settings.json"; fi if [ "$CLAUDE_RULE_MODE" = "append" ]; then append_marked "$CLAUDE_RULE" # global: ~/.claude/CLAUDE.md (auto-loaded) else putfile "$CLAUDE_RULE" < "$POLICY_SRC"; fi # project: .claude/rules/*.md if [ "$CLAUDE_RULE_MODE" = "append" ]; then append_marked "$CLAUDE_RULE" "$KARPATHY_SRC" "karpathy-guidelines" else putfile "${CLAUDE_RULE%/*}/karpathy-guidelines.md" < "$KARPATHY_SRC"; fi if [ "$CLAUDE_RULE_MODE" = "append" ]; then append_marked "$CLAUDE_RULE" "$SUPERPOWERS_SRC" "superpowers" else putfile "${CLAUDE_RULE%/*}/superpowers.md" < "$SUPERPOWERS_SRC"; fi json_hook "$SETTINGS" claude "bash \"$SCRIPT_CLAUDE\" --format json$HOOK_FLAGS" fi if selected codex; then append_marked "$CODEX_INSTR" append_marked "$CODEX_INSTR" "$KARPATHY_SRC" "karpathy-guidelines" append_marked "$CODEX_INSTR" "$SUPERPOWERS_SRC" "superpowers" if [ "$SCOPE" = "project" ]; then CFILE="$ROOT/.codex/hooks.json"; else CFILE="$HOME/.codex/hooks.json"; fi json_hook "$CFILE" codex "bash \"$SCRIPT_OTHER\" --format text$HOOK_FLAGS" fi if selected cursor; then if [ "$SCOPE" = "project" ]; then HFILE="$ROOT/.cursor/hooks.json"; else HFILE="$HOME/.cursor/hooks.json"; fi if [ -n "$CURSOR_MDC" ]; then with_policy_frontmatter "--- description: CodeGraph session-startup rule — verify install/index/freshness before work alwaysApply: true ---" | putfile "$CURSOR_MDC" with_policy_frontmatter "--- description: Karpathy coding guidelines — think before coding, simplicity first, surgical changes, goal-driven execution alwaysApply: true ---" "$KARPATHY_SRC" | putfile "${CURSOR_MDC%/*}/karpathy-guidelines.mdc" with_policy_frontmatter "--- description: Superpowers — install the skills plugin + its TDD/systematic/simplicity/evidence methodology alwaysApply: true ---" "$SUPERPOWERS_SRC" | putfile "${CURSOR_MDC%/*}/superpowers.mdc" else note "Cursor global rules are UI-only (User Rules); the global hook covers Cursor. Add the rule via Cursor Settings > Rules if you want the doc." fi json_hook "$HFILE" cursor "bash \"$SCRIPT_OTHER\" --format cursor$HOOK_FLAGS" fi if selected gemini; then append_marked "$GEMINI_INSTR" append_marked "$GEMINI_INSTR" "$KARPATHY_SRC" "karpathy-guidelines" append_marked "$GEMINI_INSTR" "$SUPERPOWERS_SRC" "superpowers" if [ "$SCOPE" = "project" ]; then GS="$ROOT/.gemini/settings.json"; else GS="$HOME/.gemini/settings.json"; fi json_hook "$GS" gemini "bash \"$SCRIPT_OTHER\" --format json$HOOK_FLAGS" # Make Gemini also read AGENTS.md (so the shared policy applies). if [ "$HAVE_PY" = 1 ] && [ "$DRYRUN" = 0 ]; then if CG_FILE="$GS" "$PY" - <<'PY' import os, json, sys, tempfile f=os.environ["CG_FILE"] raw=open(f,encoding="utf-8").read() if os.path.exists(f) else "" data={} if raw.strip(): try: data=json.loads(raw) except Exception as ex: sys.stderr.write(f"[agent-primer] {f}: invalid JSON ({ex}); refusing to modify it\n"); sys.exit(2) if not isinstance(data, dict): sys.exit(2) ctx=data.setdefault("context", {}) names=ctx.get("fileName") want=["AGENTS.md","GEMINI.md"] if isinstance(names, str): names=[names] if not isinstance(names, list): names=[] for w in want: if w not in names: names.append(w) ctx["fileName"]=names text=json.dumps(data, indent=2)+"\n" d=os.path.dirname(f) or "." fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as out: out.write(text) os.replace(tmp,f) except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.exit(3) PY then note "set context.fileName=[AGENTS.md,GEMINI.md] in $GS" else FAILED=1; note "ERROR: failed to set context.fileName in $GS"; fi fi fi if selected opencode; then if [ "$SCOPE" = "project" ]; then PLUG="$ROOT/.opencode/plugins/codegraph-session-check.js"; SREF="$SCRIPT_OTHER" else PLUG="$HOME/.config/opencode/plugins/codegraph-session-check.js"; SREF="$SCRIPT_OTHER"; fi SREF_JS="$(json_str "$SREF")" # JSON-encode the path → Bun's $ tag quotes it safely (no injection / space-safe) putfile "$PLUG" < ({ "session.created": async () => { try { const out = await \$\`bash \${SCRIPT} --format text$HOOK_FLAGS --project \${directory}\`.quiet().nothrow(); const text = (out.stdout || "").toString().trim(); if (text) console.log(text); } catch (_) { /* never block session start */ } }, }); JS # opencode reads AGENTS.md natively — ensure the shared policies are there. append_marked "$OPENCODE_INSTR" append_marked "$OPENCODE_INSTR" "$KARPATHY_SRC" "karpathy-guidelines" append_marked "$OPENCODE_INSTR" "$SUPERPOWERS_SRC" "superpowers" fi if selected antigravity; then [ -n "$ANTI_RULE" ] && putfile "$ANTI_RULE" < "$POLICY_SRC" # project: .agents/rules/*.md append_marked "$ANTI_INSTR" # global: ~/.gemini/GEMINI.md (shared) [ -n "$ANTI_RULE" ] && putfile "${ANTI_RULE%/*}/karpathy-guidelines.md" < "$KARPATHY_SRC" append_marked "$ANTI_INSTR" "$KARPATHY_SRC" "karpathy-guidelines" [ -n "$ANTI_RULE" ] && putfile "${ANTI_RULE%/*}/superpowers.md" < "$SUPERPOWERS_SRC" append_marked "$ANTI_INSTR" "$SUPERPOWERS_SRC" "superpowers" if [ "$SCOPE" = "project" ]; then AH="$ROOT/.agents/hooks.json"; else AH="$HOME/.gemini/antigravity-cli/plugins/agent-primer/hooks.json"; fi json_hook "$AH" antigravity "bash \"$SCRIPT_OTHER\" --format text$HOOK_FLAGS" fi if selected kimi; then if [ "$SCOPE" = "project" ]; then SKILL="$ROOT/.kimi-code/skills/codegraph-startup/SKILL.md"; else SKILL="$HOME/.kimi-code/skills/codegraph-startup/SKILL.md"; fi with_policy_frontmatter "--- name: codegraph-startup description: At session start, verify CodeGraph is installed, indexed, and fresh before substantive work. whenToUse: At the very start of every session, before doing substantive work on a task. ---" | putfile "$SKILL" if [ "$SCOPE" = "project" ]; then KSKILL="$ROOT/.kimi-code/skills/karpathy-guidelines/SKILL.md"; else KSKILL="$HOME/.kimi-code/skills/karpathy-guidelines/SKILL.md"; fi with_policy_frontmatter "--- name: karpathy-guidelines description: Reduce common LLM coding mistakes — surface assumptions, keep it simple, make surgical changes, define verifiable success criteria. whenToUse: When writing, reviewing, or refactoring code on non-trivial tasks. ---" "$KARPATHY_SRC" | putfile "$KSKILL" if [ "$SCOPE" = "project" ]; then SPSKILL="$ROOT/.kimi-code/skills/superpowers/SKILL.md"; else SPSKILL="$HOME/.kimi-code/skills/superpowers/SKILL.md"; fi with_policy_frontmatter "--- name: superpowers description: Install the superpowers skills plugin and follow its TDD / systematic / simplicity / evidence methodology. whenToUse: At session start, and when planning or implementing non-trivial coding work. ---" "$SUPERPOWERS_SRC" | putfile "$SPSKILL" # Kimi supports hooks ONLY in the global ~/.kimi-code/config.toml — so the hook is # written on --global only. A --project install writes the skill and prints the snippet, # never silently mutating your global config. KCONF="$HOME/.kimi-code/config.toml" KCMD="bash \"$SCRIPT_OTHER\" --format text$HOOK_FLAGS" # quote the path so the shell command survives spaces in $HOME if [ "$SCOPE" = "global" ]; then if [ "$DRYRUN" = 1 ]; then note "would append Kimi SessionStart hook to $KCONF" elif grep -q "codegraph-session-check.sh" "$KCONF" 2>/dev/null; then note "Kimi SessionStart hook already in $KCONF" else mkdir -p "$(dirname "$KCONF")" 2>/dev/null if printf '\n# codegraph-session-startup\n[[hooks]]\nevent = "SessionStart"\ncommand = "%s"\ntimeout = 10\n' "$(toml_esc "$KCMD")" >> "$KCONF"; then note "appended Kimi SessionStart hook to $KCONF (global)" else FAILED=1; note "ERROR: failed to append Kimi hook to $KCONF"; fi fi else note "Kimi hooks are global-only — wrote the project skill. To enable Kimi's hook, run:" note " $0 --global --agents kimi (or add a [[hooks]] SessionStart with command: $KCMD to ~/.kimi-code/config.toml)" fi fi if selected qoder; then # Qoder reads AGENTS.md + .qoder/rules; it has NO SessionStart event, so the rule is the carrier. if [ -n "$QODER_RULE" ]; then with_policy_frontmatter "" | putfile "$QODER_RULE" append_marked "$QODER_INSTR" with_policy_frontmatter "" "$KARPATHY_SRC" | putfile "${QODER_RULE%/*}/karpathy-guidelines.md" append_marked "$QODER_INSTR" "$KARPATHY_SRC" "karpathy-guidelines" with_policy_frontmatter "" "$SUPERPOWERS_SRC" | putfile "${QODER_RULE%/*}/superpowers.md" append_marked "$QODER_INSTR" "$SUPERPOWERS_SRC" "superpowers" else note "Qoder has no SessionStart hook and no documented global rules dir — wire Qoder per-project (install.sh --project)." fi fi # --- opt-in bundles (--with) ---------------------------------------------------- # Read the registry via a here-doc-fed `while read` (NOT `… | while read`): a here-doc # redirect keeps the loop in the CURRENT shell, so FAILED set by place_policy/append_marked # propagates to the exit code. Per selected bundle: copy its doc into the kit dir once, then # distribute it into each selected agent via place_policy. while IFS='|' read -r _bid _bsrc _bmarker _bdesc; do [ -n "$_bid" ] || continue policy_on "$_bid" || continue if [ "$DRYRUN" = 0 ]; then cp "$_bsrc" "$KIT_DEST/$(basename "$_bsrc")" 2>/dev/null || { FAILED=1; note "ERROR: failed to copy $(basename "$_bsrc") to $KIT_DEST"; } fi for _ag in claude codex cursor gemini opencode antigravity kimi qoder; do selected "$_ag" && place_policy "$_ag" "$_bsrc" "$_bmarker" "$_bdesc" done done < "$DEST/uninstall.sh" <<'CG_EOF_UNINSTALL' #!/usr/bin/env bash # uninstall.sh — cleanly reverse what install.sh wired into your AI coding agents: # the marker-delimited policy blocks, the SessionStart hook entries, the standalone # rule/skill files, the opencode plugin, and the kit dir. Mirrors install.sh's flags. # # Idempotent and safe to run when nothing is installed. Refuses to touch a config it # can't parse; writes atomically (temp file + rename) so it never half-writes a file. # # Usage: # ./uninstall.sh --project [DIR] remove from a project (default: current dir) # ./uninstall.sh --global remove from your user-level (~/) configs # ./uninstall.sh ... --agents a,b only these agents (comma-separated; default: all) # ./uninstall.sh ... --dry-run show what would happen, change nothing # ./uninstall.sh --version # ./uninstall.sh -h | --help set -u VERSION="0.1.0" SCOPE="" TARGET="" AGENTS="claude,codex,cursor,gemini,opencode,antigravity,kimi,qoder" KNOWN_AGENTS="claude codex cursor gemini opencode antigravity kimi qoder" DRYRUN=0 FAILED=0 MARKERS="codegraph-session-startup karpathy-guidelines superpowers agent-primer-mcp agent-primer-tools agent-primer-rules agent-primer-skills agent-primer-extensions" # Standalone rule/skill basenames install.sh writes (core 3 + opt-in bundles). One list, used by # every per-agent removal loop (was duplicated 5×). Kimi's codegraph skill dir is the lone exception. STANDALONE_NAMES="codegraph-session-startup karpathy-guidelines superpowers agent-primer-mcp agent-primer-tools agent-primer-rules agent-primer-skills agent-primer-extensions" KIMI_SKILL_NAMES="codegraph-startup karpathy-guidelines superpowers agent-primer-mcp agent-primer-tools agent-primer-rules agent-primer-skills agent-primer-extensions" HOOK_TAG="codegraph-session-check.sh" # identifies the hook entries/commands we added usage() { cat <<'EOF' agent-primer uninstaller — reverse what install.sh wired into AI coding agents. Usage: uninstall.sh --project [DIR] remove from a project (default: current dir) uninstall.sh --global remove from your user-level (~/) configs uninstall.sh ... --agents a,b only these agents (comma-separated; default: all) uninstall.sh ... --dry-run show what would happen, change nothing uninstall.sh --version print version and exit uninstall.sh -h | --help show this help Agents: claude, codex, cursor, gemini, opencode, antigravity, kimi, qoder EOF } while [ "$#" -gt 0 ]; do case "$1" in --project) SCOPE="project"; if [ "${2:-}" ] && [ "${2#-}" = "$2" ]; then TARGET="$2"; shift; fi; shift ;; --global) SCOPE="global"; shift ;; --agents) AGENTS="${2:-}"; shift; [ "$#" -gt 0 ] && shift ;; --agents=*) AGENTS="${1#*=}"; shift ;; --dry-run) DRYRUN=1; shift ;; --version) echo "agent-primer $VERSION"; exit 0 ;; -h|--help) usage; exit 0 ;; *) echo "error: unknown arg: $1" >&2; usage >&2; exit 2 ;; esac done [ -z "$SCOPE" ] && { echo "error: pass --project [DIR] or --global" >&2; usage >&2; exit 2; } AGENTS="$(printf '%s' "$AGENTS" | tr -d '[:space:]')" [ -z "$AGENTS" ] && { echo "error: --agents is empty" >&2; exit 2; } _bad=""; _oldifs="$IFS"; IFS=',' for _a in $AGENTS; do case " $KNOWN_AGENTS " in *" $_a "*) ;; *) _bad="$_bad $_a" ;; esac; done IFS="$_oldifs" [ -n "$_bad" ] && { echo "error: unknown agent(s):$_bad" >&2; echo "known agents: $KNOWN_AGENTS" >&2; exit 2; } # Scope-aware paths — MUST match install.sh exactly so we remove what it placed. if [ "$SCOPE" = "project" ]; then TARGET="${TARGET:-$PWD}" TARGET="$(cd "$TARGET" 2>/dev/null && pwd || echo "$TARGET")" ROOT="$TARGET" KIT_DEST="$TARGET/tools/agent-primer" CLAUDE_RULE_DIR="$ROOT/.claude/rules"; CLAUDE_RULE_MODE="file" CODEX_INSTR="$ROOT/AGENTS.md"; OPENCODE_INSTR="$ROOT/AGENTS.md" GEMINI_INSTR="$ROOT/GEMINI.md" ANTI_INSTR="$ROOT/AGENTS.md"; ANTI_RULE_DIR="$ROOT/.agents/rules" QODER_RULE_DIR="$ROOT/.qoder/rules"; QODER_INSTR="$ROOT/AGENTS.md" CURSOR_RULE_DIR="$ROOT/.cursor/rules" SETTINGS="$ROOT/.claude/settings.json"; CFILE="$ROOT/.codex/hooks.json" HFILE="$ROOT/.cursor/hooks.json"; GS="$ROOT/.gemini/settings.json" AH="$ROOT/.agents/hooks.json" OPENCODE_PLUG="$ROOT/.opencode/plugins/codegraph-session-check.js" KIMI_SKILLS="$ROOT/.kimi-code/skills" else ROOT="$HOME" KIT_DEST="$HOME/.agent-primer" CLAUDE_RULE="$HOME/.claude/CLAUDE.md"; CLAUDE_RULE_MODE="append" CODEX_INSTR="$HOME/.codex/AGENTS.md"; OPENCODE_INSTR="$HOME/.config/opencode/AGENTS.md" GEMINI_INSTR="$HOME/.gemini/GEMINI.md" ANTI_INSTR="$HOME/.gemini/GEMINI.md"; ANTI_RULE_DIR="" QODER_RULE_DIR=""; QODER_INSTR="" CURSOR_RULE_DIR="" SETTINGS="$HOME/.claude/settings.json"; CFILE="$HOME/.codex/hooks.json" HFILE="$HOME/.cursor/hooks.json"; GS="$HOME/.gemini/settings.json" AH="$HOME/.gemini/antigravity-cli/plugins/agent-primer/hooks.json" OPENCODE_PLUG="$HOME/.config/opencode/plugins/codegraph-session-check.js" KIMI_SKILLS="$HOME/.kimi-code/skills" KCONF="$HOME/.kimi-code/config.toml" fi PY="$(command -v python3 || true)" HAVE_PY=0; [ -n "$PY" ] && HAVE_PY=1 note() { printf '[agent-primer] %s\n' "$*"; } selected() { case ",$AGENTS," in *",$1,"*) return 0 ;; *) return 1 ;; esac } rm_path() { # rm_path PATH (file or dir; quiet if absent) local p="$1"; { [ -e "$p" ] || [ -L "$p" ]; } || return 0 if [ "$DRYRUN" = 1 ]; then note "would remove $p"; return 0; fi if rm -rf "$p"; then note "removed $p"; else FAILED=1; note "ERROR: failed to remove $p"; fi } # Strip our marker blocks from a shared markdown file; delete the file if it ends up empty. strip_markers() { # strip_markers FILE local file="$1"; [ -f "$file" ] || return 0 if [ "$DRYRUN" = 1 ]; then note "would strip policy blocks from $file"; return 0; fi if [ "$HAVE_PY" = 0 ]; then note "python3 not found — remove the blocks from $file by hand"; return 0; fi if CG_FILE="$file" CG_MARKERS="$MARKERS" "$PY" - <<'PY' import os, re, sys, tempfile f=os.environ["CG_FILE"]; markers=os.environ["CG_MARKERS"].split() txt=open(f,encoding="utf-8").read(); orig=txt for m in markers: s=re.escape(f""); e=re.escape(f"") txt=re.sub(r"\n*"+s+r".*?"+e+r"\n?", "\n", txt, flags=re.S) if txt==orig: sys.exit(0) if not txt.strip(): try: os.remove(f) except OSError as ex: sys.stderr.write(f"[agent-primer] {f}: {ex}\n"); sys.exit(1) sys.exit(0) d=os.path.dirname(f) or "."; fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as o: o.write(txt.lstrip("\n")) os.replace(tmp,f) except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.stderr.write(f"[agent-primer] {f}: write failed ({ex})\n"); sys.exit(1) PY then note "stripped policy blocks from $file" else FAILED=1; note "ERROR: failed to strip blocks from $file"; fi } # Remove our SessionStart hook entries from a JSON config (atomic; refuses malformed). unhook_json() { # unhook_json FILE KIND local file="$1" kind="$2"; [ -f "$file" ] || return 0 if [ "$DRYRUN" = 1 ]; then note "would remove hook from $file"; return 0; fi if [ "$HAVE_PY" = 0 ]; then note "python3 not found — remove the SessionStart entry running $HOOK_TAG from $file by hand"; return 0; fi if CG_FILE="$file" CG_KIND="$kind" CG_TAG="$HOOK_TAG" "$PY" - <<'PY' import os, json, sys, tempfile f=os.environ["CG_FILE"]; kind=os.environ["CG_KIND"]; tag=os.environ["CG_TAG"] raw=open(f,encoding="utf-8").read() if not raw.strip(): sys.exit(0) try: data=json.loads(raw) except Exception as ex: sys.stderr.write(f"[agent-primer] {f}: invalid JSON ({ex}); refusing to modify it\n"); sys.exit(2) if not isinstance(data, dict): sys.exit(0) key="sessionStart" if kind=="cursor" else "SessionStart" hooks=data.get("hooks") if not isinstance(hooks, dict) or not isinstance(hooks.get(key), list): sys.exit(0) arr=hooks[key] def ours(e): if not isinstance(e, dict): return False if tag in str(e.get("command","")): return True return any(isinstance(h,dict) and tag in str(h.get("command","")) for h in (e.get("hooks") or [])) new=[e for e in arr if not ours(e)] if len(new)==len(arr): sys.exit(0) if new: hooks[key]=new else: del hooks[key] if not hooks: data.pop("hooks", None) text=json.dumps(data, indent=2)+"\n" d=os.path.dirname(f) or "."; fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as o: o.write(text) os.replace(tmp,f) except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.stderr.write(f"[agent-primer] {f}: write failed ({ex})\n"); sys.exit(3) PY then note "removed hook from $file" else FAILED=1; note "ERROR: failed to remove hook from $file"; fi } # Remove our [[hooks]] block (and its leading comment) from Kimi's config.toml. unhook_kimi() { # unhook_kimi FILE local file="$1"; [ -f "$file" ] || return 0 if [ "$DRYRUN" = 1 ]; then note "would remove Kimi hook from $file"; return 0; fi if [ "$HAVE_PY" = 0 ]; then note "python3 not found — remove the [[hooks]] block running $HOOK_TAG from $file by hand"; return 0; fi if CG_FILE="$file" CG_TAG="$HOOK_TAG" "$PY" - <<'PY' import os, re, sys, tempfile f=os.environ["CG_FILE"]; tag=os.environ["CG_TAG"] txt=open(f,encoding="utf-8").read(); orig=txt # Drop an optional leading "# codegraph-session-startup" comment + the [[hooks]] block # (up to the next table header or EOF) when that block references our hook. def repl(m): return "" if tag in m.group(0) else m.group(0) txt=re.sub(r"(?:^[ \t]*#[^\n]*\n)?^\[\[hooks\]\][^\[]*", repl, txt, flags=re.M) if txt==orig: sys.exit(0) txt=re.sub(r"\n{3,}", "\n\n", txt).lstrip("\n") d=os.path.dirname(f) or "."; fd,tmp=tempfile.mkstemp(dir=d, prefix=".ap-", suffix=".tmp") try: with os.fdopen(fd,"w",encoding="utf-8") as o: o.write(txt) os.replace(tmp,f) except Exception as ex: try: os.unlink(tmp) except OSError: pass sys.stderr.write(f"[agent-primer] {f}: write failed ({ex})\n"); sys.exit(1) PY then note "removed Kimi hook from $file" else FAILED=1; note "ERROR: failed to remove Kimi hook from $file"; fi } note "uninstall scope=$SCOPE target=$ROOT agents=$AGENTS dry-run=$DRYRUN" if selected claude; then if [ "$CLAUDE_RULE_MODE" = "append" ]; then strip_markers "$CLAUDE_RULE" else for n in $STANDALONE_NAMES; do rm_path "$CLAUDE_RULE_DIR/$n.md"; done; fi unhook_json "$SETTINGS" claude fi if selected codex; then strip_markers "$CODEX_INSTR"; unhook_json "$CFILE" codex; fi if selected cursor; then [ -n "$CURSOR_RULE_DIR" ] && for n in $STANDALONE_NAMES; do rm_path "$CURSOR_RULE_DIR/$n.mdc"; done unhook_json "$HFILE" cursor fi if selected gemini; then strip_markers "$GEMINI_INSTR"; unhook_json "$GS" gemini; fi # leaves context.fileName (harmless, user may rely on it) if selected opencode; then rm_path "$OPENCODE_PLUG"; strip_markers "$OPENCODE_INSTR"; fi if selected antigravity; then [ -n "$ANTI_RULE_DIR" ] && for n in $STANDALONE_NAMES; do rm_path "$ANTI_RULE_DIR/$n.md"; done strip_markers "$ANTI_INSTR"; unhook_json "$AH" antigravity fi if selected kimi; then for n in $KIMI_SKILL_NAMES; do rm_path "$KIMI_SKILLS/$n"; done [ "$SCOPE" = "global" ] && unhook_kimi "$KCONF" fi if selected qoder; then [ -n "$QODER_RULE_DIR" ] && for n in $STANDALONE_NAMES; do rm_path "$QODER_RULE_DIR/$n.md"; done [ -n "$QODER_INSTR" ] && strip_markers "$QODER_INSTR" fi # Remove the kit dir last (it's wholly owned by agent-primer). rm_path "$KIT_DEST" # agent-primer's own wiring is now gone. It does NOT remove the tools its policies had # agents *install* — those are independent (esp. the CodeGraph CLI, which you may use # outside this kit). Print exact teardown commands for them; never run them. cat <<'EOF' [agent-primer] Removed agent-primer's wiring. The tools its policies bootstrapped were left in place. To remove those yourself (optional): CodeGraph CLI — only if you don't use it outside this kit: codegraph uninstall # unregister its MCP server from your agents codegraph uninit # run inside a repo to delete that project's .codegraph/ index npm uninstall -g @colbymchenry/codegraph # remove the CLI itself (if installed via npm) Superpowers: plugin — remove via your agent's plugin manager (e.g. /plugin in Claude Code; the marketplace UI in Cursor / Codex / Gemini / …): https://github.com/obra/superpowers skills — delete the obra/superpowers skills that 'npx skills add' wrote into your skills directory (run 'npx skills --help' for a remove command) EOF if [ "$FAILED" = 0 ]; then note "uninstall done."; else note "uninstall done — but some steps FAILED (see ERROR lines above)."; fi exit "$FAILED" CG_EOF_UNINSTALL chmod +x "$DEST/codegraph-session-check.sh" "$DEST/install.sh" "$DEST/uninstall.sh" echo "[agent-primer] kit extracted to $DEST" if [ "${1:-}" = "--uninstall" ]; then shift; exec bash "$DEST/uninstall.sh" "$@"; fi exec bash "$DEST/install.sh" "$@"