Repository navigation
Design
This page gives the design principles of dotclaude 1.0.0. Read it to learn the reason behind each part of the plugin. For each principle, it gives what the principle is, why it holds, and how the code applies it. Principles 1 to 9 are on this page. Principles 10 to 18 are on Design: Principles, Continued. The decisions behind each principle are in Decisions and its group pages. Architecture explains how the parts work together.
The source of the 1.0.0 design is .dotclaude/spec/changes/1.0.0/change.md and design.md.
dotclaude 1.0.0 starts again from an empty tree. The previous release failed in three ways:
- It did not ship as a product. Its first follow-up plan added one task for each session. After 40 of 113 tasks, it had no release.
- Its architecture was not modular. Entry points grew by accretion, and one capability had 11 files.
- OpenSpec was coupled into the setup, the status line, the settings snippet, the tests, and the wiki.
The goal is a full customization of Claude Code through its extension points. It replaces the built-in prompt and tool texts with shorter text that keeps the needed facts. It turns off each built-in part that dotclaude does not need. It sends each decision that facts settle to Jev, and not to the user.
What: dotclaude changes Claude Code with the mod API, settings, hooks, skills, agents, and output styles. It never patches the Claude Code binary or its npm package.
Why: A patch breaks at each release. It also breaks the terms of service of Anthropic. Reading the bundle for facts is permitted.
How in the code:
-
plugins/dotclaude/hooks/hooks.jsonholds one entry, themoduleslist with./register.ts. The core plugin has no classic hook. -
plugins/dotclaude/hooks/register.tsis generated. It callson()for 11 events. - The output style
dotclaude:Concisecomes fromsrc/plugins/dotclaude/slices/styles/slice.ts. - The transforms in
src/plugins/dotclaude/slices/prompt/transforms/are keyed by the id of each built-in section. The ids come from a reading of the Claude Code 2.1.296 bundle. The extracted texts stay in the git-ignoredexternal/folder. No transform copies them.
What: For each need, dotclaude uses the component type with the least cost.
- A fixed operation on files is a CLI.
- A workflow that the user starts is a user-invoked skill that calls a CLI.
- Knowledge that the model looks for is a model-invoked skill.
- A check before or after a tool call is a mod handler.
- A prompt change is a mod handler on
prompt.composeorprompt.section. - A classic hook is used only when no mod event can do the work.
- The status line is a
statusLinecommand.
Why: Each skill adds its description to the start of each session.
A mod handler runs in the process, with no Bun start (design.md).
A classic hook starts a process for each event that it matches.
How in the code:
- The spec CLI is
src/plugins/dotclaude/slices/spec/adapters/cli.ts. - User-invoked skills, such as
src/plugins/dotclaude/slices/spec/skills/spec/SKILL.md, setdisable-model-invocation. -
src/plugins/dotclaude-codegraph/slices/graph/adapters/hook.tsis the onlyadapters/hook.tsinsrc/.
What: A hook, a script, a setting, or a status-line command does work that needs no judgment.
Why: Each model call reads the whole context again.
A check without judgment therefore spends tokens for no gain.
This is an inference from change.md.
How in the code:
- Each
dotclaude-speccommand that changes a state appends one line toevents.jsonl, after its gate passes. No model call runs (src/plugins/dotclaude/slices/spec/adapters/transition.ts). Schema 2 replaced thelanecommand of schema 1 with these commands, and it removedpackages.md. - The
asksslice is acheckmod handler (src/plugins/dotclaude/slices/asks/slice.ts).
What: A slice holds one capability, from pure domain code to its entry points. To add a capability, add a slice. To remove it, remove the slice.
Why: design.md records that Jev picked this pattern over clean architecture in layers, with confidence 0.82.
Layers spread one capability over four folders.
So parallel agents conflict on shared folders.
How in the code:
A slice has domain/ for pure code, ports.ts, adapters/, and limits.ts.
Its contract is src/kernel/slice.ts.
-
biome.jsonenforces the import rules. A slice imports onlykernel/and its own folder. Domain code imports no adapter and nokernel/node/.
What: just generate writes the shared files of each plugin.
These files are hooks/register.ts, hooks/hooks.json, and the settings snippet.
No one edits these files by hand.
Why: A hand-kept list of slices needs an edit for each new slice. Parallel agents conflict on that list.
How in the code:
-
tools/generate/discover.tsfinds eachsrc/plugins/<plugin>/slices/*/slice.ts. -
tools/generate/order.tsorders the mod handlers by event, phase, slice name, and filter. It throws on two handlers of one slice with the same event, filter, and phase. -
tools/generate/emit/register.tswrites one literalon()call for each handler. The mod runtime reads the event name only as a literal. -
just checkrunsgenerate --check.
What: A plugin imports nothing from another plugin. Each add-on works with no core plugin installed.
Why: A user debugs a session with dotclaude-lab.
So that tool cannot need the core plugin.
How in the code:
-
tools/generate/closure.tsgives each plugin its own copy of thesrc/kernel/modules that it imports. The copies go inplugins/<plugin>/kernel/. -
just generate --checkfails when a copy differs from its source. - The rule for editors is in
.claude/rules/plugin-imports.md.
What: A shared runtime bound is in src/kernel/budget.ts.
A slice bound is in slices/<s>/limits.ts.
A repository bound is in tools/limits.ts.
Why: A number that is kept in two places drifts apart. Tests pin each copy that must match, in code and config, and not in prose.
How in the code:
-
CONTEXT_WINDOWis300_000insrc/kernel/budget.ts. The settings snippet setsCLAUDE_CODE_MAX_CONTEXT_TOKENSto the same value. -
MAX_TURNSinsrc/plugins/dotclaude/slices/agents/limits.tsgives the turn bound of each agent. -
FILE_MAX_LINESis300intools/limits.ts.tests/support/file-size.test.tschecks it. -
START_TOKENSis9_960intools/limits.ts.tools/measure.tschecks the first request against it.
What: The prompt slice replaces each built-in section with shorter text.
The trim slice turns off built-in parts that dotclaude does not need.
It does so with settings and env entries.
Why: Each request sends the system prompt and the tool descriptions again.
Shorter text leaves more context for the work (change.md).
How in the code:
-
src/plugins/dotclaude/slices/prompt/transforms/index.tslists one transform for each section. It keys each transform by the section id. -
src/plugins/dotclaude/slices/trim/domain/removed.tslists each removed part and its switch. - Each settings key has one owner.
The settings merge fails when two slices give one key two different values (
src/plugins/dotclaude/slices/trim/slice.ts).
What: The main session keeps a window of 300_000 tokens, and it does not compact.
The handoff flow writes a file, runs /clear, and submits a resume prompt.
Why: change.md puts the handoff in place of compaction.
A fixed window gives a known point where the session stops.
This is an inference.
How in the code:
-
CONTEXT_WINDOWandCONTEXT_BLOCK_MARGINare insrc/kernel/budget.ts. - The settings snippet sets
DISABLE_COMPACTto1. It setsautoCompactEnabledtofalse(wiki/reference/Settings-Snippet.md). - The
handoffslice reads nostate.json(src/plugins/dotclaude/slices/handoff/slice.ts). - Claude Code 2.1.296 shows a 200K window for the Opus 5.5 model.
It does so unless
CLAUDE_CODE_AUTO_COMPACT_WINDOWis set. The budget file records this fact, found by reading the bundle.
- Start
- How dotclaude Works
- Add-On Plugins
- How Claude Code Works
- Reference
- Rationale
- Evidence
- Development
- Archive