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/.

How start.sh turns a cloned ccgm repo into a configured ~/.claude A single vertical pipeline of six stages, each labelled with the shell library that performs it, feeding a dashed box that stands for the ~/.claude install target. ./start.sh --preset · --link · --scope · --add Resolve modules and dependencies presets/*.json · lib/modules.sh Back up what is already there lib/backup.sh → ~/.claude/backups/ Prune stale symlinks lib/repair.sh Install plan: copy · link · merge lib/template.sh expands __PLACEHOLDER__ lib/merge.sh folds settings.partial.json Write the manifest, then verify .ccgm-manifest.json · placeholder check rules/ commands/ hooks/ lib/ scripts/ agents/ skills/ settings.json · .ccgm.env · .ccgm-manifest.json ~/.claude/
  1. start.sh parses --preset, --link, --scope, and --add, then checks prerequisites (git, jq, python3).
  2. 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.
  3. lib/backup.sh snapshots whatever already lives at the target paths into ~/.claude/backups/ before anything is written.
  4. lib/repair.sh removes symlinks left dangling by an earlier install, because ln -s refuses to overwrite one.
  5. 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.
  6. 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.
  7. 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

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.

What a ccgm module directory holds and where each file installs Two dashed columns. On the left, one module directory with its manifest above the file groups it declares. On the right, the matching directories under ~/.claude, joined by five arrows. module.json name · category · scope dependencies[] · tags[] files{} · configPrompts[] rules/*.md commands/*.md · agents/*.md hooks/*.py · lib/*.py · bin/* skills/** settings.partial.json README.md · tests/ rules/ commands/ · agents/ hooks/ · lib/ · bin/ skills/ settings.json (deep-merged) modules/{name}/ ~/.claude/ Only paths declared in files{} install; README.md and tests/ stay in the repo.
  1. module.json carries name, displayName, description, category, scope, dependencies[], tags[], configPrompts[], and files{}.
  2. 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.
  3. rules/*.md install to ~/.claude/rules/ -- these are the always-loaded instruction files a module contributes.
  4. commands/*.md and agents/*.md become slash commands and subagent prompts under ~/.claude/commands/ and ~/.claude/agents/.
  5. hooks/*.py, lib/*.py, and bin/* install to the matching ~/.claude directories; hooks are what settings.json points its event registrations at.
  6. settings.partial.json is never copied. It carries merge: true, so lib/merge.sh folds it into ~/.claude/settings.json instead of overwriting it.
  7. Anything not named in files{} -- README.md, tests/ -- stays in the repo and never reaches ~/.claude.

Drawn from

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.

How a Bash tool call is gated on PreToolUse A vertical funnel: a tool call enters a settings.json event registration, reaches the dispatcher, passes a dashed box listing ten priority-ordered checks, then narrows to a precedence rule that splits into two outcomes. Bash tool call hooks.PreToolUse, matcher "Bash" ~/.claude/settings.json (built by lib/merge.sh) dispatcher + branch-guard.py (standard) Dispatcher: one process, not six hooks/pretooluse-bash-dispatch.py hard_block > deny > allow > ask hook_dispatcher.DECISION_RANK exit 2 (bypass-proof) hook_utils.hard_block() decision on stdout hook_utils.emit_decision() lib/hook_dispatcher.py runs the manifest in priority order 10 git_workflow 20 destructive 25 force_branch_delete 30 smart_rules 40 port_advisory 50 agent_tracking 60 migration_timestamp 70 force_push_main 80 careful 90 pattern runs_in_bypass=true survives bypass mode; short_circuit emits at once
  1. 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.
  2. 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.
  3. 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).
  4. A check declaring runs_in_bypass=true still runs in bypass mode; a check declaring short_circuit=true is emitted the instant it fires.
  5. DECISION_RANK resolves what fired: hard_block beats deny, deny beats allow, allow beats ask. Advisory output never changes the decision.
  6. 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().
  7. 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

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.

The lifecycle of a git worktree used to isolate one unit of delegated work A loop that runs down the right column from delegation to teardown, crosses left to a backstop sweep, and drops into a full-width box stating the rule for deleting the branch. Delegator splits the work Agent/Workflow isolation: worktree One worktree per unit .claude/worktrees/<name> Sub-agent implements own index + HEAD, feature branch PR opened and merged squash merge onto origin/main Teardown (mandatory) worktree-sweep.sh --worktree <path> non-force remove, then prune /worktree-sweep backstop removes only CLEAN worktrees preserves uncommitted / mid-rebase Branch deleted only when its work is already upstream ancestor of the default branch, or patch-equivalent after a squash merge git branch -D is denied; the sweep is the permitted path.
  1. 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.
  2. 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.
  3. The sub-agent implements on a feature branch cut from origin/main and opens a PR.
  4. The PR is reviewed and squash-merged onto main.
  5. 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.
  6. /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.
  7. 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

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.

How a session writes durable learnings and how they come back A cycle. A session forks into two write paths -- in-session capture on the left, nightly transcript mining on the right -- which converge on the append-only store, and the store returns to the session through an opt-in SessionStart injection along the right rail. Claude Code session ~/.claude/projects/** Capture /reflect · /consolidate hooks/reflection-trigger.py hooks/precompact-reflection.py Nightly mining bin/dream-daily.sh lib/transcript_miner.py lib/dream_analyze.py Write an op-event bin/ccgm-learnings-log lib/learnings_store.py add · verify · contradict Evidence-tagged proposals ~/.claude/dreaming/proposals/ /dream-apply human gate Learnings store (append-only JSONL) ~/.claude/learnings/{slug}/agents/{id}.jsonl confidence decay · staleness · dwell_until SessionStart injection (opt-in) hooks/learnings-inject.py CCGM_LEARNINGS_INJECT · source=startup in-session transcripts injected at session start
  1. A session leaves a transcript under ~/.claude/projects/**. That is the raw material for both write paths.
  2. In-session capture: /reflect and /consolidate, plus hooks/reflection-trigger.py (after a merge) and hooks/precompact-reflection.py (before context compaction).
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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