Claude Skill

codebase-memory

Use the codebase knowledge graph for structural code queries, trace call paths and dependencies, inspect architecture, assess change impact, or activate the bundled Claude Code discovery hooks.

LLM Mart · 0 points · 19 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ConnorGriffin-skills-skills_tools_codebase-memory-872be56.zip · 7 KB
Part of connorgriffin/skills — 25 skills

Install

skills CLI npx skills add https://github.com/ConnorGriffin/skills/tree/main/skills/tools/codebase-memory
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install connorgriffin-skills@llmmart
Git git clone https://github.com/ConnorGriffin/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole connorgriffin/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Codebase Memory

Follow reminder.md for the standing discovery policy; it is the one authored policy shared by this skill, the profile pointer, and the installed SessionStart hook.

When ensure reports unavailable, use its cbm-onboard diagnostic and bounded retry sequence rather than guessing at a project or treating every failure as a sandbox denial. The reminder owns the details.

Activate discovery guidance in Claude Code

After the standard skills installer copies this directory, run:

python3 ~/.claude/skills/codebase-memory/scripts/install.py

For a non-default Claude home:

python3 <installed-skill>/scripts/install.py --claude-home PATH

The installer owns three files under the selected Claude home's hooks/ directory and merges the skill's registrations into settings.json. It preserves unrelated settings and hook registrations. Malformed settings, conflicting registrations, symlinks, and unowned managed-path files are no-write failures. Its scope is the bundled files and registrations.

To merge the registrations into a settings file kept outside the Claude home, for example one versioned in a dotfiles checkout:

python3 <installed-skill>/scripts/install.py --claude-home PATH \
  --settings-file PATH/TO/settings.json

--settings-file moves only the settings file. The three hook files, and the paths rendered into the registrations, still follow --claude-home. The named target must be a regular non-symlink file, and its parent directory must already exist, be a directory, and be writable and searchable; a symlinked target or an unusable parent is a no-write failure. A parent reached through a symlinked directory is fine, which is how a versioned checkout is usually wired.

For a consumer that manages its own settings.json hook registrations, add --skip-settings:

python3 <installed-skill>/scripts/install.py --claude-home PATH --skip-settings

This installs the three managed hook files and stops there: it does not read, parse, validate, merge, or write settings.json at all. The consumer owns registering the installed hooks in its own settings. All the other guards still run: source ownership, the hooks-directory symlink check, and the unowned-managed-file check. --skip-settings and --settings-file name two different settings targets, so the installer refuses both together at the command-line level rather than picking one.

The external tool codebase-memory-mcp installs its own hooks at two of the same names (cbm-code-discovery-gate, cbm-session-reminder). Activation does not reclaim a name another tool already owns; it stops and names the likely owner and the repair (move the foreign files out of hooks/, remove that tool's registrations from settings.json, then rerun).

Graph-query vocabulary

  • list_projects and index_status report indexed-project inventory and health.
  • get_architecture summarizes repository structure and architectural relationships.
  • search_graph locates symbols by name, label, or qualified-name pattern.
  • trace_path follows callers, callees, data flow, and cross-service paths.
  • get_code_snippet returns exact source for a resolved symbol.
  • query_graph handles complex multi-hop graph questions.
  • search_code searches literal source text.
  • detect_changes maps a Git diff to its structural impact.
  • index_repository creates or refreshes a repository graph.
Files (skills)
  • agents
    • openai.yaml 209 B
      interface:
        display_name: "Codebase Memory"
        short_description: "Explore indexed code through its knowledge graph"
        default_prompt: "Use $codebase-memory to explore this repository through the code graph."
      
  • config
    • claude-settings.json 1.1 KB
      {
        "hooks": {
          "PreToolUse": [
            {
              "matcher": "Grep|Glob",
              "hooks": [
                {
                  "type": "command",
                  "command": "<CLAUDE_HOME>/hooks/cbm-code-discovery-gate",
                  "timeout": 5
                }
              ]
            }
          ],
          "SessionStart": [
            {
              "matcher": "startup",
              "hooks": [
                {
                  "type": "command",
                  "command": "<CLAUDE_HOME>/hooks/cbm-session-reminder"
                }
              ]
            },
            {
              "matcher": "resume",
              "hooks": [
                {
                  "type": "command",
                  "command": "<CLAUDE_HOME>/hooks/cbm-session-reminder"
                }
              ]
            },
            {
              "matcher": "clear",
              "hooks": [
                {
                  "type": "command",
                  "command": "<CLAUDE_HOME>/hooks/cbm-session-reminder"
                }
              ]
            },
            {
              "matcher": "compact",
              "hooks": [
                {
                  "type": "command",
                  "command": "<CLAUDE_HOME>/hooks/cbm-session-reminder"
                }
              ]
            }
          ]
        }
      }
      
  • hooks
    • cbm-code-discovery-gate 523 B · in bundle
    • cbm-session-reminder 196 B · in bundle
  • scripts
    • install.py 10.6 KB
      #!/usr/bin/env python3
      """Activate the codebase-memory skill's Claude Code discovery hooks."""
      
      from __future__ import annotations
      
      import argparse
      import json
      import os
      import shlex
      import stat
      import tempfile
      from pathlib import Path
      
      
      SKILL_DIRECTORY = Path(__file__).resolve().parents[1]
      OWNERSHIP = "Managed by codebase-memory skill installer."
      MANAGED_FILES = {
          "cbm-code-discovery-gate": (SKILL_DIRECTORY / "hooks" / "cbm-code-discovery-gate", 0o755),
          "cbm-session-reminder": (SKILL_DIRECTORY / "hooks" / "cbm-session-reminder", 0o755),
          "cbm-code-discovery-reminder.md": (SKILL_DIRECTORY / "reminder.md", 0o644),
      }
      
      
      def lstat_or_none(path: Path) -> os.stat_result | None:
          try:
              return path.lstat()
          except FileNotFoundError:
              return None
      
      
      def arguments() -> argparse.Namespace:
          parser = argparse.ArgumentParser(
              description="Activate codebase-memory discovery hooks in Claude Code."
          )
          parser.add_argument(
              "--claude-home",
              type=Path,
              default=Path.home() / ".claude",
              help="Claude Code home directory (default: $HOME/.claude)",
          )
          settings_target = parser.add_mutually_exclusive_group()
          settings_target.add_argument(
              "--settings-file",
              type=Path,
              default=None,
              help=(
                  "settings file to merge registrations into "
                  "(default: <claude-home>/settings.json)"
              ),
          )
          settings_target.add_argument(
              "--skip-settings",
              action="store_true",
              help=(
                  "Install the managed hook files only; do not read, parse, validate, "
                  "merge, or write settings.json. For a consumer that owns its own "
                  "hook registrations. Not allowed together with --settings-file: a "
                  "named settings target is meaningless when no settings file is "
                  "touched at all."
              ),
          )
          return parser.parse_args()
      
      
      def atomic_write(path: Path, content: bytes, mode: int) -> None:
          if path.exists() and path.read_bytes() == content and stat.S_IMODE(path.stat().st_mode) == mode:
              return
          descriptor, temporary_name = tempfile.mkstemp(
              prefix=".codebase-memory-install-", dir=path.parent
          )
          temporary = Path(temporary_name)
          try:
              with os.fdopen(descriptor, "wb") as handle:
                  handle.write(content)
                  handle.flush()
                  os.fsync(handle.fileno())
              os.chmod(temporary, mode)
              os.replace(temporary, path)
          finally:
              try:
                  temporary.unlink()
              except FileNotFoundError:
                  pass
      
      
      def command_target(command: object) -> str | None:
          if not isinstance(command, str):
              return None
          try:
              words = shlex.split(command)
          except ValueError:
              return None
          if not words:
              return None
          return os.path.abspath(os.path.expandvars(os.path.expanduser(words[0])))
      
      
      def unresolved_managed_reference(command: object) -> str | None:
          """Detect a registration whose target still contains an unresolved
          variable (an unset one, since $HOME/${HOME} now resolve above) but whose
          final path component names a managed file. Such a registration cannot be
          matched to a resolved target, so it must fail closed as a conflict rather
          than silently pass the merge as an unrelated command.
          """
          if not isinstance(command, str):
              return None
          try:
              words = shlex.split(command)
          except ValueError:
              return None
          if not words:
              return None
          expanded = os.path.expandvars(os.path.expanduser(words[0]))
          if "$" not in expanded:
              return None
          if os.path.basename(expanded) not in MANAGED_FILES:
              return None
          return command
      
      
      def merge_settings(existing: dict, canonical: dict) -> dict:
          if not isinstance(existing, dict):
              raise ValueError("settings root must be an object")
          hooks = existing.setdefault("hooks", {})
          if not isinstance(hooks, dict):
              raise ValueError("settings hooks must be an object")
      
          expected_by_target: dict[str, list[tuple[str, dict]]] = {}
          for event, entries in canonical["hooks"].items():
              for entry in entries:
                  for hook in entry["hooks"]:
                      target = command_target(hook["command"])
                      if target is None:
                          raise ValueError("canonical managed hook command is not executable")
                      expected_by_target.setdefault(target, []).append((event, entry))
      
          for event, entries in hooks.items():
              if not isinstance(entries, list):
                  raise ValueError(f"settings hooks.{event} must be an array")
              for entry in entries:
                  if not isinstance(entry, dict):
                      raise ValueError(f"settings hooks.{event} entries must be objects")
                  entry_hooks = entry.get("hooks", [])
                  if not isinstance(entry_hooks, list):
                      raise ValueError(f"settings hooks.{event} entry hooks must be an array")
                  for hook in entry_hooks:
                      if not isinstance(hook, dict):
                          continue
                      unresolved = unresolved_managed_reference(hook.get("command"))
                      if unresolved is not None:
                          raise ValueError(
                              f"conflicting managed hook registration for {unresolved}"
                          )
                  managed_targets = {
                      command_target(hook.get("command"))
                      for hook in entry_hooks
                      if isinstance(hook, dict)
                      and command_target(hook.get("command")) in expected_by_target
                  }
                  for target in managed_targets:
                      if not any(
                          event == expected_event and entry == expected_entry
                          for expected_event, expected_entry in expected_by_target[target]
                      ):
                          raise ValueError(
                              f"conflicting managed hook registration for {target}"
                          )
      
          for event, required_entries in canonical["hooks"].items():
              current_entries = hooks.setdefault(event, [])
              for required in required_entries:
                  matches = [index for index, entry in enumerate(current_entries) if entry == required]
                  if not matches:
                      current_entries.append(required)
                      continue
                  for index in reversed(matches[1:]):
                      del current_entries[index]
          return existing
      
      
      def main() -> int:
          options = arguments()
          claude_home = options.claude_home.expanduser().absolute()
          skip_settings = options.skip_settings
          hooks_directory = claude_home / "hooks"
          settings_path = None
          settings_label = None
          settings_stat = None
          if not skip_settings:
              if options.settings_file is None:
                  settings_path = claude_home / "settings.json"
                  settings_label = "settings.json"
              else:
                  settings_path = options.settings_file.expanduser().absolute()
                  settings_label = str(settings_path)
                  container = settings_path.parent
                  if not container.is_dir() or not os.access(container, os.W_OK | os.X_OK):
                      raise SystemExit(
                          f"settings file needs an existing writable directory: {container}"
                      )
              settings_stat = lstat_or_none(settings_path)
              if settings_stat is not None and not stat.S_ISREG(settings_stat.st_mode):
                  raise SystemExit(f"{settings_label} must be a regular non-symlink file")
          hooks_stat = lstat_or_none(hooks_directory)
          if hooks_stat is not None and not stat.S_ISDIR(hooks_stat.st_mode):
              raise SystemExit("hooks must be a real non-symlink directory")
          planned_files = {}
          for name, (source, mode) in MANAGED_FILES.items():
              content = source.read_bytes()
              if OWNERSHIP.encode() not in content:
                  raise SystemExit(f"source is missing ownership text: {source}")
              planned_files[name] = (content, mode)
      
          if hooks_stat is not None:
              for name in MANAGED_FILES:
                  destination = hooks_directory / name
                  destination_stat = lstat_or_none(destination)
                  if destination_stat is None:
                      continue
                  if not stat.S_ISREG(destination_stat.st_mode):
                      raise SystemExit(
                          f"managed target must be a regular non-symlink file: {destination}"
                      )
                  if OWNERSHIP.encode() not in destination.read_bytes():
                      raise SystemExit(
                          f"managed target is not owned: {destination} "
                          "(codebase-memory-mcp is known to install its own hooks at "
                          "this name; move its files out of the hooks directory, "
                          "remove its registrations from settings.json, then rerun)"
                      )
      
          if skip_settings:
              claude_home.mkdir(parents=True, exist_ok=True)
              hooks_directory.mkdir(exist_ok=True)
              for name, (planned_content, mode) in planned_files.items():
                  atomic_write(hooks_directory / name, planned_content, mode)
              print(
                  f"Installed codebase-memory discovery hook files in {claude_home}; "
                  "no settings.json registrations were written."
              )
              return 0
      
          template = json.loads(
              (SKILL_DIRECTORY / "config" / "claude-settings.json").read_text(
                  encoding="utf-8"
              )
          )
          quoted_home = shlex.quote(str(claude_home))
          for entries in template["hooks"].values():
              for entry in entries:
                  for hook in entry["hooks"]:
                      hook["command"] = hook["command"].replace(
                          "<CLAUDE_HOME>", quoted_home
                      )
          canonical = template
          if settings_stat is not None:
              try:
                  settings = json.loads(settings_path.read_text(encoding="utf-8"))
              except json.JSONDecodeError as error:
                  raise SystemExit(
                      f"{settings_label} is not valid JSON: {error.msg}"
                  ) from error
              settings_mode = stat.S_IMODE(settings_stat.st_mode)
          else:
              settings = {}
              settings_mode = 0o600
          try:
              merged = merge_settings(settings, canonical)
          except ValueError as error:
              raise SystemExit(str(error)) from error
          content = (json.dumps(merged, indent=2) + "\n").encode()
      
          claude_home.mkdir(parents=True, exist_ok=True)
          hooks_directory.mkdir(exist_ok=True)
          for name, (planned_content, mode) in planned_files.items():
              atomic_write(hooks_directory / name, planned_content, mode)
          atomic_write(settings_path, content, settings_mode)
          print(f"Activated codebase-memory discovery hooks in {claude_home}")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
  • reminder.md 1.7 KB
    Managed by codebase-memory skill installer.
    
    # Code discovery policy
    
    Use codebase-memory-mcp graph tools first for structural code exploration, against
    exactly one project: the one that belongs to the checkout you are working in.
    
    Establish that project before querying. When a workflow supplies a `project` for
    the checkout it verified, use exactly that name as given. Otherwise resolve the
    canonical current checkout through the supported structured interface,
    `python3 <cbm-onboard-skill-directory>/scripts/cbm-lifecycle.py ensure <checkout
    path>`, and use the `project` it prints. Never pick the graph by project name,
    branch-like label, list order, apparent recency, or because it was the only result;
    `list_projects` is an inventory, not a way to choose the current checkout. A
    reported `unavailable`, or no usable interface at all, means ordinary search and
    file reads for the rest of the session. Follow cbm-onboard's bounded diagnostic and
    retry sequence: distinguish a missing or unsupported binary from an unable-to-respond
    CLI; retry the same `ensure` command with its documented local-only sandbox rationale
    only after the normal workspace-write attempt. An active-generation conflict means
    wait and retry, never a sandbox denial or permission to close another session.
    
    With that project established, use `search_graph` to find symbols, `trace_path` for
    callers and callees, `get_code_snippet` for exact source, `query_graph` for
    multi-hop questions, and `get_architecture` for orientation. Use `search_code` or
    ordinary search and file reads for literal text, configuration, non-code files, and
    unindexed projects.
    
    Activating this skill never indexes a project. Run `index_repository` only when
    indexing is explicitly requested or required by the target repository.
    
  • SKILL.md 3.6 KB
    ---
    name: codebase-memory
    description: Use the codebase knowledge graph for structural code queries, trace call paths and dependencies, inspect architecture, assess change impact, or activate the bundled Claude Code discovery hooks.
    ---
    
    # Codebase Memory
    
    Follow [reminder.md](reminder.md) for the standing discovery policy; it is the one
    authored policy shared by this skill, the profile pointer, and the installed
    SessionStart hook.
    
    When `ensure` reports unavailable, use its cbm-onboard diagnostic and bounded retry
    sequence rather than guessing at a project or treating every failure as a sandbox
    denial. The reminder owns the details.
    
    ## Activate discovery guidance in Claude Code
    
    After the standard skills installer copies this directory, run:
    
    ```sh
    python3 ~/.claude/skills/codebase-memory/scripts/install.py
    ```
    
    For a non-default Claude home:
    
    ```sh
    python3 <installed-skill>/scripts/install.py --claude-home PATH
    ```
    
    The installer owns three files under the selected Claude home's `hooks/`
    directory and merges the skill's registrations into `settings.json`. It preserves
    unrelated settings and hook registrations. Malformed settings, conflicting
    registrations, symlinks, and unowned managed-path files are no-write failures.
    Its scope is the bundled files and registrations.
    
    To merge the registrations into a settings file kept outside the Claude home, for
    example one versioned in a dotfiles checkout:
    
    ```sh
    python3 <installed-skill>/scripts/install.py --claude-home PATH \
      --settings-file PATH/TO/settings.json
    ```
    
    `--settings-file` moves only the settings file. The three hook files, and the
    paths rendered into the registrations, still follow `--claude-home`. The named
    target must be a regular non-symlink file, and its parent directory must already
    exist, be a directory, and be writable and searchable; a symlinked target or an
    unusable parent is a no-write failure. A parent reached through a symlinked
    directory is fine, which is how a versioned checkout is usually wired.
    
    For a consumer that manages its own `settings.json` hook registrations, add
    `--skip-settings`:
    
    ```sh
    python3 <installed-skill>/scripts/install.py --claude-home PATH --skip-settings
    ```
    
    This installs the three managed hook files and stops there: it does not read,
    parse, validate, merge, or write `settings.json` at all. The consumer owns
    registering the installed hooks in its own settings. All the other guards
    still run: source ownership, the hooks-directory symlink check, and the
    unowned-managed-file check. `--skip-settings` and `--settings-file` name two
    different settings targets, so the installer refuses both together at the
    command-line level rather than picking one.
    
    The external tool `codebase-memory-mcp` installs its own hooks at two of the
    same names (`cbm-code-discovery-gate`, `cbm-session-reminder`). Activation does
    not reclaim a name another tool already owns; it stops and names the likely
    owner and the repair (move the foreign files out of `hooks/`, remove that
    tool's registrations from `settings.json`, then rerun).
    
    ## Graph-query vocabulary
    
    - `list_projects` and `index_status` report indexed-project inventory and health.
    - `get_architecture` summarizes repository structure and architectural relationships.
    - `search_graph` locates symbols by name, label, or qualified-name pattern.
    - `trace_path` follows callers, callees, data flow, and cross-service paths.
    - `get_code_snippet` returns exact source for a resolved symbol.
    - `query_graph` handles complex multi-hop graph questions.
    - `search_code` searches literal source text.
    - `detect_changes` maps a Git diff to its structural impact.
    - `index_repository` creates or refreshes a repository graph.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related