System diagrams
Five maps of how CCGM actually works. Every box names a file, script, hook, or mechanism that exists in the repo, and every diagram lists what it was drawn from.
Links resolve to this site where the file is part of an ingested module, and to a SHA-pinned GitHub blob otherwise.
Install flow
One bash entry point walks a fixed sequence: resolve, back up, prune, install, merge, verify. Every stage below is a numbered step in start.sh and a named library under lib/.
- start.sh parses --preset, --link, --scope, and --add, then checks prerequisites (git, jq, python3).
- lib/modules.sh discovers every modules/*/module.json and closes the dependency set; a preset is just a bare array of module names in presets/*.json.
- lib/backup.sh snapshots whatever already lives at the target paths into ~/.claude/backups/ before anything is written.
- lib/repair.sh removes symlinks left dangling by an earlier install, because ln -s refuses to overwrite one.
- Each files{} entry becomes one install-plan action: copy, link (under --link), or merge. lib/template.sh expands __PLACEHOLDER__ values from .ccgm.env into template:true targets.
- lib/merge.sh deep-merges every settings.partial.json into settings.json with jq -- permissions.allow/deny concatenate and dedupe, hooks combine by event type.
- start.sh writes .ccgm-manifest.json, then verifies: every installed file exists, no __PLACEHOLDER__ survived in a template target, and settings.json still parses.
Drawn from
-
start.shThe installer itself: 15 numbered steps from prerequisites to the final verify. -
lib/modules.shdiscover_modules(), validate_module(), resolve_dependencies(), load_preset(). -
lib/backup.shcreate_backup() snapshots the managed paths under ~/.claude/backups/. -
lib/repair.shrepair_dangling_symlinks() clears links a rename left pointing nowhere. -
lib/template.shexpand_templates() substitutes __PLACEHOLDER__ from .ccgm.env. -
lib/merge.shmerge_settings() deep-merges a settings fragment into settings.json via jq. -
presets/standard.jsonA preset is a bare JSON array of module names -- no wrapper object.
Module anatomy
A module is a directory with a manifest. module.json is the only thing that decides what installs and where -- the directory layout is a convention, files{} is the contract.
- module.json carries name, displayName, description, category, scope, dependencies[], tags[], configPrompts[], and files{}.
- files{} is a keyed object, not an array: each key is a source path inside the module, and its value names the target, the type, whether it is a template, and whether it merges.
- rules/*.md install to ~/.claude/rules/ -- these are the always-loaded instruction files a module contributes.
- commands/*.md and agents/*.md become slash commands and subagent prompts under ~/.claude/commands/ and ~/.claude/agents/.
- hooks/*.py, lib/*.py, and bin/* install to the matching ~/.claude directories; hooks are what settings.json points its event registrations at.
- settings.partial.json is never copied. It carries merge: true, so lib/merge.sh folds it into ~/.claude/settings.json instead of overwriting it.
- Anything not named in files{} -- README.md, tests/ -- stays in the repo and never reaches ~/.claude.
Drawn from
-
modules/branch-guard/module.jsonA complete manifest: three files{} entries, one of them merge: true. -
modules/branch-guard/rules/branch-guard.mdA rule file -- installs to ~/.claude/rules/ and is always in context. -
modules/branch-guard/hooks/branch-guard.pyA hook -- installs to ~/.claude/hooks/ and is invoked by settings.json. -
modules/branch-guard/settings.partial.jsonThe merge fragment: registers the hook on PreToolUse without owning settings.json. -
modules/git-worktrees/commands/worktree-start.mdA command file -- installs to ~/.claude/commands/ as a slash command. -
modules/subagent-patterns/agents/implementer.mdAn agent file -- installs to ~/.claude/agents/ as a subagent prompt. -
lib/merge.shWhy settings.partial.json is merged rather than copied over settings.json.
Hook gate pipeline
On a standard install the Bash matcher holds two entries, and one of them is a dispatcher that runs ten checks in a single process instead of six. One precedence rule -- not array order across modules -- decides the outcome.
- A Bash tool call raises the PreToolUse event. ~/.claude/settings.json holds the registrations, assembled by lib/merge.sh from every installed module settings.partial.json.
- The matcher "Bash" selects every entry registered against it. On a standard install that is two: hooks/pretooluse-bash-dispatch.py from the hooks module, and hooks/branch-guard.py from branch-guard. This diagram follows the dispatcher.
- The dispatcher is one process where there used to be six. lib/hook_dispatcher.py runs a declarative manifest by priority: git_workflow (10), destructive (20), force_branch_delete (25), smart_rules (30), port_advisory (40), agent_tracking (50), migration_timestamp (60), force_push_main (70), careful (80), pattern (90).
- A check declaring runs_in_bypass=true still runs in bypass mode; a check declaring short_circuit=true is emitted the instant it fires.
- DECISION_RANK resolves what fired: hard_block beats deny, deny beats allow, allow beats ask. Advisory output never changes the decision.
- A hard_block leaves via hook_utils.hard_block() and exit 2 -- the one signal honoured regardless of permission mode. Everything else leaves as JSON on stdout via hook_utils.emit_decision().
- The same event also has a file-edit arm: branch-guard.py on Edit, MultiEdit, Write, NotebookEdit, and the filesystem-MCP write tools; auto-approve-file-ops.py on Read, Edit, and Write; check-freeze.py on Edit and Write. branch-guard.py is the one hook on both arms -- its second entry is the Bash one above, covering git commit, add, stage, and apply.
Drawn from
-
modules/hooks/settings.partial.jsonThe registrations themselves: which hook runs on which event, behind which matcher. -
modules/hooks/hooks/pretooluse-bash-dispatch.pyThe hooks module's PreToolUse:Bash entry point and the declarative manifest it builds. -
modules/hooks/lib/hook_dispatcher.pyDECISION_RANK, the bypass short-circuit, and the priority walk. -
modules/hooks/lib/pretooluse_bash_checks.pyThe ten check functions the manifest names. -
modules/hooks/lib/hook_utils.pyhard_block(), emit_decision(), is_bypass_mode(), redact_secrets(), file_locked_append(). -
modules/branch-guard/hooks/branch-guard.pyThe Bash matcher's other entry, and the file-edit arm: Edit, MultiEdit, Write, NotebookEdit, and the filesystem-MCP write tools, plus git commit/add/stage/apply on Bash. -
modules/hooks/hooks/auto-approve-file-ops.pyThe file-edit arm on Read, Edit, and Write; check-freeze.py runs alongside it on Edit and Write.
Multi-agent worktree flow
A worktree is created for exactly one unit of work and removed when that unit merges. Teardown is the load-bearing half: a worktree an agent built in never auto-removes.
- A delegating command splits the work into independent units and gives each sub-agent its own worktree, via the Agent/Workflow isolation: "worktree" option or /worktree-start.
- Each worktree lives under .claude/worktrees/<name>, shares the parent .git, and has its own index and HEAD -- so parallel builds and commits never collide.
- The sub-agent implements on a feature branch cut from origin/main and opens a PR.
- The PR is reviewed and squash-merged onto main.
- The delegator tears that unit down: worktree-sweep.sh --worktree <path> does a non-force git worktree remove, then git worktree prune. Non-force is itself a gate -- git refuses on uncommitted or untracked files.
- /worktree-sweep is the orphan backstop for anything the happy path missed. It removes only CLEAN worktrees and preserves any with uncommitted work, untracked files, an in-progress rebase, a lock, or a detached HEAD carrying commits reachable from no ref.
- The branch is deleted only when the default branch already contains its work -- either as an ancestor, or patch-equivalent after a squash merge. Anything else is kept and reported.
Drawn from
-
modules/git-worktrees/lib/worktree-sweep.shThe janitor: CLEAN vs PRESERVE classification, non-force removal, and the squash-aware branch check. -
modules/git-worktrees/rules/git-worktrees.mdThe lifecycle contract, the four decided cases, and the 237 GB incident that produced it. -
modules/git-worktrees/commands/worktree-start.mdHands-on creation: gitignore verification, uniqueness checks, project setup. -
modules/git-worktrees/commands/worktree-finish.mdThe four-option end gate: merge locally, push and PR, keep, or discard. -
modules/git-worktrees/commands/worktree-sweep.mdThe repo-wide backstop sweep. -
modules/multi-agent/rules/multi-agent.mdWhy worktrees are the default isolation and when a separate clone is the right call instead.
Memory and learnings loop
Learnings are op-events appended to a per-project, per-agent JSONL shard. Two paths write to it -- an in-session reflection and a nightly transcript mining run -- and one opt-in hook reads it back at session start.
- A session leaves a transcript under ~/.claude/projects/**. That is the raw material for both write paths.
- In-session capture: /reflect and /consolidate, plus hooks/reflection-trigger.py (after a merge) and hooks/precompact-reflection.py (before context compaction).
- bin/ccgm-learnings-log appends an op-event through lib/learnings_store.py -- add, verify, contradict, supersede, or deprecate. Content passes a prompt-injection sanitizer on the way in.
- The other path is nightly: bin/dream-daily.sh runs lib/transcript_miner.py over the same transcripts, and lib/dream_analyze.py map-reduces them.
- Its output lands in ~/.claude/dreaming/proposals/{date}.jsonl and stays pending. /dream-apply is the always-available human gate; opt-in optimistic integration writes behind a dwell window instead.
- Both paths converge on one append-only JSONL shard at ~/.claude/learnings/{project-slug}/agents/{agent-id}.jsonl. Reads project the op chain into current heads and apply confidence decay, staleness, and any dwell window.
- hooks/learnings-inject.py returns the top-ranked learnings at SessionStart -- but only when the source is startup and CCGM_LEARNINGS_INJECT is set. Unset, it reads stdin and exits having printed nothing.
Drawn from
-
modules/self-improving/lib/learnings_store.pyThe store: shard layout, op-event projection, confidence decay, sanitizer, dwell window. -
modules/self-improving/bin/ccgm-learnings-logThe write CLI: add, verify, contradict, supersede, deprecate. -
modules/self-improving/bin/ccgm-learnings-searchThe ranked, token-budgeted read path. -
modules/self-improving/hooks/learnings-inject.pyThe opt-in SessionStart injection and its two no-op conditions. -
modules/self-improving/hooks/reflection-trigger.pyFires the reflection checklist after a PR merge. -
modules/dreaming/lib/transcript_miner.pydiscover(), mine(), cluster(), budget(), schema_canary() over ~/.claude/projects/**. -
modules/dreaming/lib/dream_analyze.pyThe map-reduce that turns mined evidence into per-change proposals. -
modules/dreaming/bin/dream-daily.shThe nightly chain: analyze, eval-refresh, optimistic-integrate, digest, reconcile, retention. -
modules/dreaming/commands/dream-apply.mdThe always-available human gate for a pending proposal.