Contributing to Citadel

March 31, 2026 · View on GitHub

Contributions are welcome. Issues, bug reports, new skills, and hook improvements all help.

Reporting Issues

Open an issue on GitHub. Include:

  • What you expected to happen
  • What actually happened
  • Error messages (full text, not screenshots of text)
  • Your OS, shell, and Node version

Submitting Pull Requests

  1. Fork the repo
  2. Create a branch from main (e.g., fix/issue-10-description or feat/new-skill)
  3. Make your changes
  4. Run node hooks_src/smoke-test.js to verify hooks are healthy
  5. Open a PR against main

Branch protection is enabled. All changes go through a PR. Direct pushes to main are blocked.

What to watch out for

Cross-platform compatibility. Citadel runs on Windows, macOS, and Linux. Before submitting:

  • Hook definitions live in hooks/hooks-template.json and are installed per-project via scripts/install-hooks.js
  • The Claude installer is compatibility-aware by default; use --hook-profile latest in tests or fixtures when you need the full modern hook surface deterministically
  • Do NOT hardcode /bin/bash, /bin/sh, or other Unix-only paths
  • Do NOT assume forward-slash path separators in Node scripts (use path.join())
  • Test on your platform and note which platform you tested on in the PR

Plugin architecture. Citadel is distributed as a Claude Code plugin. Users install it once and it works across all projects — no per-project file copying required.

  • Hook scripts live in hooks_src/ and are installed per-project via scripts/install-hooks.js
  • The init-project SessionStart hook auto-scaffolds per-project state (.planning/, .citadel/scripts/)
  • Per-project configuration lives in .claude/harness.json (generated by /do setup)

Adding a New Skill

Skills live in skills/{name}/SKILL.md (one directory per skill). Every skill needs:

---
name: skill-name
description: >-
  One or two sentences explaining what the skill does.
user-invocable: true
auto-trigger: false
---

Follow the patterns in existing skills. Read 2-3 before writing your own.

Users can also create project-level custom skills in their project's .claude/skills/ directory using /create-skill.

Adding a New Hook

Hooks live in hooks_src/. Before adding one:

  1. Read harness-health-util.js for shared utilities (telemetry, config, validation)
  2. Use execFileSync (not execSync) to avoid shell injection
  3. Use require('./harness-health-util') for the project root path
  4. Use process.env.CLAUDE_PROJECT_DIR || process.cwd() for the user's project root
  5. Add your hook to hooks/hooks-template.json — the installer resolves ${CLAUDE_PLUGIN_ROOT} at install time
  6. If the hook requires a newer Claude Code event, update the compatibility gating in runtimes/claude-code/generators/hook-support.js
  7. Run node hooks_src/smoke-test.js to make sure the smoke test picks it up

Plugin Directory Structure

citadel/
  .claude-plugin/       # Plugin manifest
  skills/               # Built-in skill definitions ({name}.md per skill)
  agents/               # Sub-agent definitions
  hooks/
    hooks-template.json # Hook definitions (resolved by install-hooks.js)
  hooks_src/            # Hook script implementations
  scripts/              # Utility scripts (synced to projects by init-project)
  .planning/
    _templates/         # Templates (copied to projects by init-project)
  .claude/
    agent-context/      # Rules injected into sub-agents
  docs/                 # Reference documentation

Opt-in Hooks

Some hooks are not included in the default hooks/hooks-template.json. They are available in hooks_src/ for users who want them:

  • external-action-gate.js — Blocks git push, PR creation, issue comments, and other external actions until the user approves. Add to your project's .claude/settings.local.json:

    {
      "hooks": {
        "PreToolUse": [{
          "matcher": "Bash",
          "hooks": [{ "type": "command", "command": "node '${CLAUDE_PLUGIN_ROOT}/hooks_src/external-action-gate.js'", "timeout": 5 }]
        }]
      }
    }
    
  • issue-monitor.js — Checks for new GitHub issues on session start. Add to .claude/settings.local.json:

    {
      "hooks": {
        "SessionStart": [{
          "hooks": [{ "type": "command", "command": "node '${CLAUDE_PLUGIN_ROOT}/hooks_src/issue-monitor.js'", "timeout": 20 }]
        }]
      }
    }
    

Migrating from copy-based install? These hooks previously lived at .claude/hooks/. The paths in your settings.local.json need to change from node .claude/hooks/external-action-gate.js to the ${CLAUDE_PLUGIN_ROOT} form shown above.

Code Style

  • Node.js scripts use CommonJS (require), not ESM
  • Keep hooks fast (under 5s for PreToolUse, under 30s for PostToolUse)
  • Fail-closed for security hooks (exit 2 on error), fail-open for non-critical hooks (exit 0 on error)
  • No external dependencies. Hooks use only Node built-ins.