Gives Claude the code graph of a project from the CodeGraph CLI `codegraph`. Syncs the index at session start, after an edit, and after a `git` command that…

dotclaude changes Claude Code through its official extension points: settings, hooks, skills, agents, and an output style. It does not patch the Claude Code binary. Its goal is the highest quality for the lowest token cost.
The wiki explains each part and the reason for it. Start with the Home page, or read the Install page for the full setup.
dotclaude is a marketplace with one core plugin and seven add-on plugins. Each add-on works without the core plugin.
| Plugin | What it gives |
|---|---|
dotclaude | The core: the settings snippet, the status line, the output style, six agents, ten skills, the guard and asks hooks, the spec store, and the second_opinion tool. |
dotclaude-codegraph | The code graph of a project, from the CLI codegraph. |
dotclaude-browser | The browse skill, and a headless browser search for each WebSearch call. |
dotclaude-docs | The docs skill, with current library docs from the CLI ctx7. |
dotclaude-sessions | The start-cost and usage skills, which measure the session start and the usage. |
dotclaude-lab | Tools for plugin authors: the sandbox, capture, schema, and eval compare. |
dcc-plugin-agents-md | Loads AGENTS.md files where Claude Code loads CLAUDE.md, in place of the built-in cc-plugin-agents-md. |
dcc-plugin-you-should-know | Opt-in. Shows one thing to know before Claude changes something, in place of the built-in cc-plugin-you-should-know. |
/plugin marketplace add xsyetopz/dotclaude
/plugin install dotclaude@dotclaude
Then run /dotclaude:setup, and restart Claude Code. The setup skill shows each change to your settings file, and it writes the change after you confirm.
Requirements:
2.1.296 or later. dotclaude is tested only on 2.1.296.git.1.4.3 or later on PATH, for the status line, the setup command, and the second_opinion tool.PATH, for the npm installs of the add-ons.A plugin cannot set permissions, environment variables, or models. So the core plugin gives these settings as a settings snippet. The setup skill applies the snippet for you. You can also copy the keys that you want into your own settings.json.
| Part | Effect |
|---|---|
| Compaction | Off. autoCompactEnabled is false, and DISABLE_COMPACT is 1, which also turns off /compact. |
| Context window | CLAUDE_CODE_MAX_CONTEXT_TOKENS and CLAUDE_CODE_AUTO_COMPACT_WINDOW are 300000. A session that reaches the limit stops, and you run /clear. |
| Memory | autoMemoryEnabled is false. The spec store holds the state instead. |
| Output style | outputStyle is dotclaude:Concise. Your own outputStyle setting can change it. |
| Permissions | Normal work passes. Work like sudo asks. Destructive actions are denied by autoMode.hard_deny. Reads of secret files are denied. |
| Sandbox | Bash runs in the sandbox. Its network reaches GitHub and the package registries only. |
| Guard | Asks before the first call of a session to a GitHub repository of another owner that has an AI policy file. |
| Asks | Asks only when a change weakens a check. It never asks for a read. |
| Status line | The folder, git, model, effort, context against the window, cost, the 5-hour, 7-day, and spend limits, and the progress of a spec change. The percent turns yellow at 75% and red at 90%. |
| Handoff | With the default manual value of mainHandover, a hint at 60% of the window tells you to run /dotclaude:handoff. |
| Checks | The CLI dotclaude-run-checks runs a batch of check commands, with no model call. |
The core plugin has one option, mainHandover. Its values are auto, manual, and off, and the default is manual.
| Agent | Model | Effort |
|---|---|---|
planner, reviewer | claude-opus-5-5 | high |
debugger | claude-sonnet-5-5 | high |
implementer, researcher | claude-sonnet-5-5 | medium |
investigator | claude-haiku-5-5 | medium |
The main session uses claude-opus-5-5, from the settings snippet. The maxEffortLevel setting is max, so /effort can go up to max for a hard check. See Agents.
The ten skills are /dotclaude:handoff, /dotclaude:rules-creator, /dotclaude:setup, and the seven spec phases /dotclaude:specify, /dotclaude:plan, /dotclaude:implement, /dotclaude:review, /dotclaude:merge, /dotclaude:accept, and /dotclaude:archive. See Skills.
Each model call reads the whole context again, so the size of the context sets the cost of each turn.
/dotclaude:specify <id>, or run dotclaude-spec specify <id>./clear, run dotclaude-spec status <id>. In the fresh session, run dotclaude-spec next <id>, which prints the brief of the next package.See Context and handoff and Spec store.
The second_opinion tool of the core plugin asks TypeSafe Jev for a pick and a confidence on a decision. Export TYPESAFE_API_KEY before Claude Code starts. Each call sends the data of the decision to the TypeSafe API. With no key, or when Jev gives no answer, a small Claude model answers instead. The result says that the answer is not from Jev. See Second opinion.
Install each add-on that you need.
/plugin install dotclaude-codegraph@dotclaude
/plugin install dotclaude-browser@dotclaude
/plugin install dotclaude-docs@dotclaude
/plugin install dotclaude-sessions@dotclaude
/plugin install dotclaude-lab@dotclaude
/plugin install dcc-plugin-agents-md@dotclaude
/plugin install dcc-plugin-you-should-know@dotclaude
dotclaude-codegraph needs the CLI codegraph on PATH, and an index that codegraph init -y builds in the project. It syncs the index at session start, and it adds no MCP server. See Code graph.dotclaude-browser uses agent-browser. Its backend option can select CloakBrowser, for sites with bot detection. See Browser.dotclaude-docs needs no key. See Library docs.dotclaude-sessions measures the start of a session and the usage of each run. See Sessions.dotclaude-lab is for plugin authors, and it works alone. See Lab.dcc-plugin-agents-md replaces the built-in cc-plugin-agents-md, so turn the built-in plugin off. See AGENTS.md.dcc-plugin-you-should-know is opt-in, because each check makes one Haiku call, on an API key or a subscription. It replaces the built-in cc-plugin-you-should-know, so turn the built-in plugin off. See You should know.claude plugin marketplace update dotclaude
claude plugin update dotclaude@dotclaude
Run the claude plugin update line again for each add-on that you installed, with its name in place of dotclaude. Then restart Claude Code, read the changelog, and run /dotclaude:setup again. The status line commands hold the path of the plugin version, so each update needs the new setup run.
To try an unreleased checkout, run:
claude --plugin-dir /path/to/dotclaude/plugins/dotclaude
Edit the source in src/, and run just generate. Do not edit plugins/ by hand. just check runs the lint, the type check, the check of the generated files, the tests, the validation, and the hook labs. just sandbox runs Claude Code with this checkout in a separate config. See Development and Sandbox.
MIT.
hooks/register.ts 17 lines1// Generated by `just generate` from `src/plugins/dotclaude-codegraph/slices/*/slice.ts`. Edit the source and run `just generate`.
2import type { On } from "claude-code";
3import { onTreeChange as h0 } from "../slices/graph/adapters/mod.ts";
4
5export const register = (on: On): void => {
6 // eval: tool.call graph
7 on(
8 "tool.call",
9 {
10 tool: "Bash",
11 command:
12 /(?:^|[;&|])\s*git(?:\s+(?:-[Cc]\s+(?:[^\s"']|"[^"]*"|'[^']*')+|--(?:git-dir|work-tree|namespace)(?:=|\s+)(?:[^\s"']|"[^"]*"|'[^']*')+|--(?!(?:git-dir|work-tree|namespace)(?:=|\s))[a-z][\w-]*|-[pP]))*\s+(?:commit|merge|rebase|pull|checkout|switch|reset|cherry-pick|revert|stash|apply|am)(?:\s|$)/,
13 },
14 h0,
15 );
16};
17slices/graph/adapters/mod.ts 53 lines1// Generated by `just generate` from `src/plugins/dotclaude-codegraph/slices/graph/adapters/mod.ts`. Edit the source and run `just generate`.
2// The mod handler of the `graph` slice: a `tool.call` on a `Bash` command with a `git` command that changes the tree syncs the index after the command.
3// The filter in `slice.ts` is `TREE_CHANGE`, which the mod runtime tests in process, so a `Bash` call of any other kind starts no process.
4// This replaces 12 classic `PostToolUse` hooks, each with an `if` for one `git` command.
5// The sync runs with `$.process.run` and no shell, in a project with `.codegraph/codegraph.db` only.
6// The hook fails open: a missing `codegraph`, a timeout, or a deny of the call adds nothing and never changes the result of the call.
7// Each `$` call is in this file, because `$` is not followed across an import.
8
9import type { EngineInterface } from "claude-code";
10import type { Handler } from "../../../kernel/slice.ts";
11import { INDEX_PATH } from "../domain/graph.ts";
12import { GIT_SYNC_TIMEOUT_MS } from "../limits.ts";
13
14/**
15 * The `next` of a `tool.call` hook, with a method signature so that its parameter is bivariant.
16 * The generated `register` passes this handler to `on` with the filter of `slice.ts`, which gives `next` the narrow type of that call.
17 * The generator does not narrow the handler type, so the method signature lets both `next` types fit.
18 */
19type PassOn = {
20 pass(
21 e: Parameters<Parameters<Handler<"tool.call">>[2]>[0],
22 ): ReturnType<Parameters<Handler<"tool.call">>[2]>;
23}["pass"];
24
25/** The project folder: `CLAUDE_PROJECT_DIR`, or the folder of the session. */
26const projectRoot = async ($: EngineInterface): Promise<string> =>
27 (await $.env.get("CLAUDE_PROJECT_DIR")) || ".";
28
29/** Syncs the index when the project has one. The catch has no narrower test, because `$.process.run` documents no error type. */
30const syncIndex = async ($: EngineInterface): Promise<void> => {
31 const root = await projectRoot($);
32 if (!(await $.fs.exists(`${root}/${INDEX_PATH}`))) return;
33 try {
34 await $.process.run(["codegraph", "sync", "-q"], {
35 cwd: root,
36 timeoutMs: GIT_SYNC_TIMEOUT_MS,
37 });
38 } catch {
39 // A missing `codegraph` or a timeout leaves the index as it is. The next sync catches up.
40 }
41};
42
43/** The `tool.call` handler on a `git` command that changes the tree. It runs the command, then syncs the index. */
44export const onTreeChange = async (
45 $: EngineInterface,
46 e: Parameters<Handler<"tool.call">>[1],
47 next: PassOn,
48) => {
49 const out = await next(e);
50 if (out.deny === undefined) await syncIndex($);
51 return out;
52};
53kernel/slice.ts 114 lines1// Generated by `just generate` from `src/kernel/slice.ts`. Edit the source and run `just generate`.
2// The contract of a slice manifest: what a slice folder exports from its `slice.ts`.
3// The generator reads each manifest, and the types of the mod API come from `claude-code`.
4// This file has no runtime import, so the hooks module can load it.
5
6import type {
7 Args,
8 EventName,
9 Events,
10 Matcher,
11 PluginOptions,
12} from "claude-code";
13
14/** The order of the phases of a mod handler, from the first to the last. The generator sorts by it. */
15export const PHASES = [
16 "observe",
17 "normalize",
18 "rewrite",
19 "check",
20 "answer",
21] as const;
22
23/** One phase of a mod handler, from `PHASES`. */
24export type Phase = (typeof PHASES)[number];
25
26/** The filter of a mod handler: a matcher over the argument of the event `E`. */
27export type EventFilter<E extends EventName = EventName> = Matcher<Args<E>>;
28
29/** The hook signature of the event `E`, as the mod API declares it. */
30export type Handler<E extends EventName = EventName> = Events[E];
31
32/**
33 * Keeps the `userConfig` values of the plugin, which the mod runtime gives to `register`, for a hook to read.
34 * The generated `register` calls it once, before the `on()` calls.
35 * The hook itself goes to `on()` by its name, because `claude plugin validate --strict` follows `$` only into a function of `register.ts`.
36 */
37export type Configure = (options: PluginOptions) => void;
38
39/**
40 * The eval kinds of one hook, which the generator writes to `evals/hooks.json` under the id `<event> <slice>`.
41 * A test is `<file from the repository root> > <test name>`.
42 * A hook that runs a model has `requests` above 0 and names its `claude plugin eval` case in `eval`.
43 * A hook that can block also names `block` and `false`.
44 */
45export interface HookEvals {
46 readonly requests: number;
47 readonly tokens: number;
48 readonly blocks: boolean;
49 readonly allow: string;
50 readonly text: string;
51 readonly block?: string;
52 readonly false?: string;
53 readonly eval?: string;
54}
55
56/**
57 * One mod handler of a slice. A slice can have more than one for one event, each with its own filter.
58 * It has `run`, the hook, and `configure`, when the hook reads the `userConfig` values of the plugin.
59 */
60export interface ModHandler<E extends EventName = EventName> {
61 readonly event: E;
62 readonly filter?: EventFilter<E>;
63 readonly phase: Phase;
64 /**
65 * The hook. A hook with a `filter` can take the event that the filter narrows, as `MatchedHook` of the mod API types it.
66 * The generated `register.ts` gives the hook and the filter to `on()`, and the compiler checks the pair there.
67 */
68 readonly run: Handler<E> | ((...args: never[]) => unknown);
69 readonly configure?: Configure;
70 /** The eval kinds of the hook. One entry covers each handler of the slice on the same event. */
71 readonly evals?: HookEvals;
72}
73
74/** One classic command hook, for an event that the mod API does not cover. */
75export interface ClassicHook {
76 readonly event: string;
77 readonly matcher?: string;
78 readonly command: string;
79 readonly timeout?: number;
80 readonly async?: boolean;
81 /** The eval kinds of the hook. Its id is the event and the command, without the `bun` launcher and the plugin root. */
82 readonly evals?: HookEvals;
83}
84
85/** One segment of the status line. The `row` is the row of the status line that holds it. */
86export interface StatusSegment {
87 readonly id: string;
88 readonly row: number;
89 readonly render: (status: unknown) => string | null;
90}
91
92/** One `userConfig` field of a plugin manifest, such as the `backend` of `dotclaude-browser`. */
93export interface UserConfigOption {
94 readonly type: "string" | "number" | "boolean";
95 readonly title: string;
96 readonly description?: string;
97 readonly default: string | number | boolean;
98 readonly options?: readonly string[];
99}
100
101/** The manifest of one slice: the name, and each part that the slice adds to the plugin. */
102export interface Slice {
103 readonly name: string;
104 readonly mod?: readonly ModHandler[];
105 readonly hooks?: readonly ClassicHook[];
106 readonly cli?: { readonly bin: string };
107 readonly segments?: readonly StatusSegment[];
108 readonly skills?: readonly string[];
109 readonly agents?: readonly string[];
110 readonly outputStyles?: readonly string[];
111 readonly userConfig?: Readonly<Record<string, UserConfigOption>>;
112 readonly settings?: Readonly<Record<string, unknown>>;
113}
114slices/graph/domain/graph.ts 77 lines1// Generated by `just generate` from `src/plugins/dotclaude-codegraph/slices/graph/domain/graph.ts`. Edit the source and run `just generate`.
2// The pure logic of the code-graph hooks: the text that goes to Claude, and the parse of the payload.
3// No function here runs a command or reads a file.
4
5/** The fields of a hook payload that the hooks read. */
6export interface HookPayload {
7 cwd?: string;
8}
9
10/** The install command that the check prints. The add-on never runs it. */
11export const INSTALL_COMMAND = "bun i -g @colbymchenry/codegraph";
12
13/** The command that builds the first index. It is the opt-in of a project. */
14export const INIT_COMMAND = "codegraph init -y";
15
16/** The path of the index database in a project. A project with this file has opted in. */
17export const INDEX_PATH = ".codegraph/codegraph.db";
18
19/**
20 * The line that `SessionStart` adds.
21 * `impact` has a default depth of 2, so the line gives `-d 5`.
22 * A run on the fixture of the comparison missed 2 of 8 affected functions at depth 2.
23 */
24export const SESSION_LINE =
25 "This project has a code graph. " +
26 "`codegraph impact <symbol> -d 5` lists the code that a change to a symbol affects. " +
27 "The default depth of 2 misses some of it. " +
28 "`codegraph callers <symbol>` and `codegraph callees <symbol>` list who calls a symbol and what it calls.";
29
30/** One word of an option value, with its quoted parts, such as `"my dir"`. */
31const GIT_WORD = String.raw`(?:[^\s"']|"[^"]*"|'[^']*')+`;
32
33/**
34 * A global option of `git` before its verb: `-C <path>`, `-c <k=v>`, `--git-dir=<p>`, `--git-dir <p>`, `--work-tree`, `--namespace`, or a flag such as `--no-pager` or `-p`.
35 */
36const GIT_OPTION = String.raw`(?:-[Cc]\s+${GIT_WORD}|--(?:git-dir|work-tree|namespace)(?:=|\s+)${GIT_WORD}|--(?!(?:git-dir|work-tree|namespace)(?:=|\s))[a-z][\w-]*|-[pP])`;
37
38/**
39 * A `Bash` command with a `git` command that changes the tree: `commit`, `merge`, `rebase`, `pull`, `checkout`, `switch`, `reset`, `cherry-pick`, `revert`, `stash`, `apply`, or `am`.
40 * The `git` command starts the command or follows `;`, `&`, or `|`, so `cd x && git commit` matches.
41 * Global options before the verb are skipped, so `git -C dir commit` matches too.
42 * The mod handler uses it as its filter, so a command of any other kind starts no process.
43 */
44export const TREE_CHANGE = new RegExp(
45 String.raw`(?:^|[;&|])\s*git(?:\s+${GIT_OPTION})*\s+(?:commit|merge|rebase|pull|checkout|switch|reset|cherry-pick|revert|stash|apply|am)(?:\s|$)`,
46);
47
48/** `text` as a parsed payload, or `null` when it is not JSON. */
49export const parseJson = (text: string): HookPayload | null => {
50 try {
51 return JSON.parse(text);
52 } catch (err) {
53 if (!(err instanceof SyntaxError)) throw err;
54 return null;
55 }
56};
57
58/** What the check prints, as lines, from whether the CLI answers and whether the project has an index. */
59export const checkLines = (
60 found: boolean,
61 version: string,
62 indexed: boolean,
63): string[] => {
64 if (!found)
65 return [
66 "`codegraph` is not on `PATH`, so the code-graph hooks do nothing.",
67 `To install it, run \`${INSTALL_COMMAND}\`.`,
68 "The add-on does not install it.",
69 ];
70 return [
71 `\`codegraph\` ${version} is on \`PATH\`.`,
72 indexed
73 ? "This project has an index, so the code-graph hooks run."
74 : `This project has no index, so the hooks do nothing. To opt in, run \`${INIT_COMMAND}\` in the project.`,
75 ];
76};
77slices/graph/limits.ts 24 lines1// Generated by `just generate` from `src/plugins/dotclaude-codegraph/slices/graph/limits.ts`. Edit the source and run `just generate`.
2// The bounds of the `graph` slice.
3
4/** How long `codegraph --version` may run for the check. */
5export const QUERY_TIMEOUT_MS = 2000;
6
7/** The hook timeout of `SessionStart`, in seconds, as `slice.ts` gives it to Claude Code. */
8export const SESSION_START_HOOK_TIMEOUT_S = 10;
9
10/**
11 * How long the sync at session start may run.
12 * On Windows the sync also gets `TREE_GRACE_MS` more, so the total is 9 s, which is 1 s under the hook timeout.
13 */
14export const START_SYNC_TIMEOUT_MS = 6000;
15
16/** The time that `run-tree.ts` gets to start and to end the process tree after the limit on Windows. */
17export const TREE_GRACE_MS = 3000;
18
19/** How long the background sync after an edit may run. The hook timeout in `slice.ts` is longer. */
20export const BACKGROUND_SYNC_TIMEOUT_MS = 60000;
21
22/** How long the sync after a `git` command may run. The `git` result waits for it, so it is shorter. */
23export const GIT_SYNC_TIMEOUT_MS = 15000;
24