π foundry β Claude Code Plugin¶
Changelog records version-by-version changes, including the plugin's earlier directory history.
Local Git operations that need authorization (commit, staging owned changes, a local merge and its merge commit, cherry-pick, branch/worktree prep) ask one question: Approve (this operation only), Approve always (this operation, plus a standing grant for this project) or Deny (skip it and leave changes as they are). The approval-guard.js hook records an Approve always answer in claude-git-approval.json inside git's own common directory (git rev-parse --git-common-dir) β once per project; it applies to every worktree, and git never tracks or checks out anything there. While a grant passes the rule's check (node <foundry>/hooks/commit-guard.js --check-grant: a regular, non-linked file with the expected version, scope and answer, in the session project's own common directory β resolved with Git's discovery environment removed and bare repositories refused, so a tracked bare-repository fixture, a planted .git file or a steered GIT_DIR supplies nothing), later sessions skip the question for task-scoped non-destructive local operations, and committing the agent's own completed task is the default on completion; the reply that commits states grant .git/claude-git-approval.json@<created_at> or no grant. Only the hook writes the grant, from your actual answer to a question whose Approve always option tells you it persists, what it covers and how to revoke it. The hook blocks an agent tool call that names the grant directly; it sees names, not contents, so a patch, script or archive that writes the file is not blocked and stays forbidden by the rule alone. rm "$(git rev-parse --git-common-dir)/claude-git-approval.json" revokes it. A grant never covers push (asked every time), force operations, history rewriting or destructive recovery; a checkout under review gains nothing from it; your narrower instructions (no commit, leave unstaged, read-only, wait) still win, and spawned subagents, teammates and bridge children never use it. Without a grant or an active skill workflow, the question is asked.
Every push asks its own question, git-push: Approve (this push, once) or Deny. The question shows the branch, the target and the command the agent will run. Your Approve makes the approval-guard.js hook record a single-use token, claude-push-approval.json in the same common directory. The token holds the current branch, HEAD and the id of the question you answered, and expires after 15 minutes; a later Approve replaces it and a later Deny removes it. commit-guard.js lets one push of that branch at that HEAD to a configured remote run β git push, git push origin, -u, origin <branch>, origin HEAD:<branch>, "$(git branch --show-current)", git lfs push origin <branch> β after checking that your Approve to that very question is in the session transcript, and consumes the token as it does; of parallel push calls only one runs, and a spawned agent never spends it. The push must run from the checkout itself, be the only push in its command, and have no commit, reset or checkout beside it. A second push, a new commit or another branch needs a new Approve. Anything else β deleting a remote branch, --prune, --all, --tags, another branch or ref, a URL, path or variable destination, cd, send-pack, http-push, subtree push β no Approve can cover: the agent gives you the command to run yourself. A dry run (--dry-run) needs no approval. Force pushes stay forbidden even with a token. A new session or /clear deletes a pending token β per project, so a second session in the same project clears the first one's. The former ! touch /tmp/claude-push-auth-β¦ sentinel no longer authorizes anything.
Foundry alone ships the git/gh guards, approval records and one-time tokens. GitHub reads through gh run without a question. Every GitHub write through gh, on any repository, asks its own question, gh-write: Approve (this exact command, once) or Deny. The question names the command once in an inline code span; your Approve makes the approval-guard.js hook record a single-use token, claude-gh-write-approval.json in the same common directory, holding that command for 15 minutes. gh-write-guard.js then lets exactly that text run once as the whole Bash command and spends the token; other text, a second run or a spawned agent's call stays blocked. A later Approve replaces the token, a later Deny removes it, and a new session or /clear deletes it. Outside a git repository nothing is recorded, so you run the command yourself. Claude Code still applies a settings deny rule after a hook allows a call, so the plugins no longer ship gh write deny entries; re-run /foundry:setup once to remove the ones an earlier setup merged.
Review loops fix feasible authorized findings, investigate repeated signatures, and continue whenever the weighted score decreases; structural labels alone do not stop remediation. Completion prompts ask only for a missing decision or authorization, regardless of report length, and already-authorized next steps continue. Concrete gates for fresh scope, paid execution, sensitive or destructive actions, and remote changes remain in force.
Independent challenge loops show the cumulative old/new table once after each newly validated challenge, before fixes or another review. Pending reviews and unrelated status updates do not repeat it; final handoffs retain their canonical Results table. Each open finding needs a recorded fix, escalation, or justified deferral; chat table timing has no machine-readable delivery receipt.
OSS Claude Code configuration for Python/ML projects: 10 specialist agents, 11 skills, event-driven hooks, and repeatable audit and calibration workflows.
The audit summary, configuration-health, investigation, and session report templates use short descriptive fields; writing requirements sit in separate notes and are omitted from completed reports. Other audit modes and calibrate/brainstorm/distill templates keep their earlier placeholder style.
Workflow audits and adversarial reviews trace producer guarantees through downstream consumers and the next ordinary user action. They challenge identity, ownership, destination, lifetime, and resume assumptions with concrete counterexamples and report untested handoffs. The shared review/fix loop inventories first-pass coverage, checks each fix against its invariant and a sibling route, and distinguishes carried defects from later discoveries.
Each round challenges and collects findings, reports the cumulative old/new table, resolves feasible authorized findings, escalates unfixable security/critical/high findings, then repeats until clean or plateau/non-convergence, subject to authority, evidence, recurrence and stricter caller budgets. The table is Iteration | Critical | High | Medium | Low | Nits | Weighted score; open and fixed-pending-verification signatures split into old and new, while verified-fixed and rejected findings stay excluded. Security and critical combine only for display; score weights remain 20/10/6/4/2/1. Empty rounds print no progress table. A 1,500-output-token reviewer target is hard only when the runtime supports it; findings are never dropped to meet it.
Optional Codemap guidance is shipped locally with foundry; loading it does not read another plugin's private shared directory. Codemap queries still require the codemap-py plugin, with file-read fallback when the CLI or local contract is unavailable.
Specialist Codemap prompts reuse only matching, successful, fresh answers, inspecting each batch child's metadata and preserving valid empty results. Supplied answers avoid duplicate pre-flight calls; source and independent-review reads remain allowed. QA distinguishes static test/mock relationships from measured line coverage and treats missing measurements as unknown.
Optional integrations:
/oss:reviewand/oss:releaseadd release/review workflows (requires theossplugin);/develop:featureand/develop:fixadd implementation workflows (requires thedevelopplugin);/research:runand/research:topicadd ML research workflows (requires theresearchplugin).
skills/_shared/quality-stack.md is the canonical copy of the quality-gate stack develop's feature/fix/refactor skills run at close, propagated byte-identical into develop's own _shared/ via propagate_shared.py.
Its flaky-test retry now re-runs only the failing node-ids instead of the whole directory twice, doctests fold into the main pytest run when a collection probe confirms it's safe (report status not-merged/pass/fail, replacing the old binary pass/fail line), and a pytest-xdist capability probe runs the wide pass in parallel when available β a failure that reproduces only under that parallel pass is reported PARALLEL_FAIL_SERIAL_PASS (likely a test-isolation bug) rather than marked flaky. ! BREAKING: the doctest report line's format changed to the 3-state form above.
π Contents
- What is foundry?
- Why foundry?
- Install
- Quick start
- Current boundaries
- Skills reference
/foundry:setup/foundry:audit/foundry:calibrate/foundry:manage/foundry:brainstorm/foundry:investigate/foundry:profile/foundry:distill/foundry:session/foundry:create/foundry:humanizer- Agents reference
- foundry:sw-engineer
- foundry:solution-architect
- foundry:qa-specialist
- foundry:linting-expert
- foundry:perf-optimizer
- foundry:doc-scribe
- foundry:web-explorer
- foundry:curator
- foundry:challenger
- foundry:creator
- Agent relationships
- Rules installed
- Configuration
- Troubleshooting
- Plugin structure
- Bin helper inventory
- Upgrade
- Uninstall
- Development / testing
- Contributing / feedback
π€ What is foundry?¶
foundry is the configuration and workflow layer for Claude Code on Python/ML OSS projects. It gives Claude Code ten specialist agents, eleven namespaced skills, rules, and event-driven hooks for routing work, checking configuration, measuring instruction quality, and recording lessons for review.
The problem it solves is workflow drift: a generalist session can mix architecture, implementation, tests, docs, and tooling without clear ownership, while corrections disappear between sessions. Foundry provides explicit role boundaries, repeatable audits and calibration runs, and project-local state for the parts it supports. It does not replace application-code, release, or ML-research plugins.
π― Why foundry?¶
Without: one model handles architecture, implementation, docs, linting, testing, performance β no boundary enforcement. Corrections evaporate per session. No way to know if agent accuracy drifted.
With:
/foundry:auditcatches config drift before it becomes debugging session/foundry:calibratemeasures recall vs stated confidence β know exactly where agents fall short/foundry:managecreates, renames, deletes agents with full cross-reference propagation, one command/foundry:brainstormturns vague idea into approved spec before any code written/foundry:distillconverts accumulated corrections into durable rules + agent instruction updates- Hooks keep lint, task tracking, teammate quality gates, and report checks running around Claude tool events (including Write/Edit operations)
Use /foundry:audit for structural checks, /foundry:calibrate for measured routing and agent behavior, and /foundry:distill to turn recurring corrections into proposed instruction updates.
π¦ Install¶
Prerequisites: Claude Code with plugin support; Python 3.10+ and git (setup runs from a project repository); jq on PATH for setup/audit JSON work; and Node.js for the plugin's hooks. Each hook runs node from PATH, then /opt/homebrew/bin/node or /usr/local/bin/node; when none exists, the hooks skip without blocking and one SessionStart check reports once per session that they are inactive. The plugin's bin/python and native Windows bin/python.cmd preserve the shipped python command and probe candidate runtimes for Python 3.10+ before executing the workload once. Plugin bin/ follows system directories on PATH, so an eligible system python takes precedence; missing eligible runtimes produce a clear diagnostic.
Companion plugins for full workflow suite:
claude plugin install oss@borda-ai-rig
claude plugin install develop@borda-ai-rig
claude plugin install research@borda-ai-rig
One-time setup β run inside Claude Code after install:
Merges statusLine, permissions.allow, permissions.deny, enabledPlugins, the shipped env defaults, and (when pinned by the project) advisorModel into ~/.claude/settings.json; removes the obsolete Bash(gh api repos/*:*) allow entry and the retired deny entries (gh writes, local branch/tag deletion; permissions-deny-retired.json) while preserving every other existing allow and deny entry. It also symlinks all rule files into ~/.claude/rules/ as foundry-<name>.md and TEAM_PROTOCOL.md into ~/.claude/. Run it from the project repository root. Idempotent β safe to re-run.
After any plugin upgrade, re-run /foundry:setup β auto-replaces stale foundry symlinks, removes rules gone from new version. No prompt for old-version symlinks.
β‘ Quick start¶
Run setup once, then check the installation from the project repository root:
The first useful workflow is:
It produces a structured report of system config checks (hooks, settings.json, plugin integration, and symlinks). Treat any critical findings as blockers and resolve the report's suggested next action.
Follow with:
Quick routing benchmark β measures whether Claude Code dispatches synthetic tasks to the right agent. The calibration threshold is 90%; use the measured result to identify routing gaps rather than treating it as a guarantee.
π§ Current boundaries¶
- Foundry manages Claude Code configuration and workflow guidance; it does not implement application code or provide release-management and ML-research workflows. For those workflows, install
develop(requires thedevelopplugin),oss(requires theossplugin), orresearch(requires theresearchplugin). - Calibration uses synthetic problems with quasi-ground-truth. It is an instruction-quality signal, not a production benchmark or guarantee of agent correctness.
- Hooks are event-driven and depend on Claude Code loading the enabled plugin plus Node.js. Most report or nudge; selected guards intentionally gate unsafe pushes, direct agent self-grants of an approval record, or incomplete follow-up prompts.
- Setup intentionally leaves some state behind on uninstall: merged settings,
foundry-*.mdrule links, andTEAM_PROTOCOL.mdneed manual review or removal; see Uninstall. - Profile cost output is best-effort and uses public list rates, not an account billing statement; when transcripts are unavailable, the clock report can still run without the cost section.
These are current boundaries of the shipped plugin, not promises about future support.
π§ Skills reference¶
/foundry:setup¶
Post-install setup. Merges settings, creates symlinks. Run once after install, again after any upgrade.
What it does:
- Detects Python 3.10+ (
python/py -3/python3); installs~/.local/bin/pythonshim whenpythonabsent or resolves to Windows Store stub - Backs up
~/.claude/settings.jsonbefore touching - Merges
statusLine,permissions.allow,permissions.deny,enabledPlugins,advisorModel(copied from project.claude/settings.jsonwhen pinned); removes the obsoleteBash(gh api repos/*:*)allow entry and the retired deny entries (gh writes, local branch/tag deletion;permissions-deny-retired.json) while preserving all other existing allow and deny entries - Merges shipped
envdefaults from.claude-plugin/env-defaults.jsonβ currentlyCLAUDE_CODE_ENABLE_TODO_TOOLS=1, without which Claude Code keeps the task tools disabled and every skill that mandates task tracking silently no-ops; existing values are never overwritten - Copies
permissions-guide.mdto.claude/(only if absent β preserves project-local edits) - Symlinks all
plugins/cc_foundry/rules/*.mdinto~/.claude/rules/asfoundry-<name>.md, plusTEAM_PROTOCOL.mdinto~/.claude/; on upgrade, auto-replaces stale foundry symlinks, removes rules gone from the current version, and migrates pre-namespace unprefixed links it provably owns. - Removes stale
hooksblock from settings if present (hooks now register via plugin manifest)
Hooks (hooks.json) register automatically when plugin enabled β /foundry:setup never touches them directly.
Codemap integration metadata uses the codemap-py.integration.v2 managed-block body while retaining the v1 sentinel format; use codemap-py integrate audit for read-only integration evidence.
/foundry:audit¶
Full-sweep quality audit of .claude/ config + all plugins/*/ agent and skill files. Catches broken cross-references, inventory drift, model-tier mismatches, description overlap, doc staleness. Reports findings by severity; fix level chosen from always-fire follow-up gate after report. Adversarial mode challenges every claim via foundry:challenger + Codex.
/foundry:audit # full sweep, report only β gate offers fix options
/foundry:audit --upgrade # fetch latest Claude Code docs, apply improvements with A/B testing
/foundry:audit --adversarial # adversarial review with foundry:challenger + Codex
/foundry:audit --efficiency # cost and efficiency sweep: model tiers, effort levels, unbounded spawn patterns, token bloat, boilerplate duplication, ceremony turns (E10: setup blocks a single turn could carry, small-doc loader calls, bookkeeping-only responses); outputs P1β4 plan + code-block extraction candidates; extraction gate offers EXTRACT / EXTRACT+RECOMMENDED / skip
# Tier 1 β group scopes
/foundry:audit agents # all agents
/foundry:audit skills # all skills
/foundry:audit rules # all rules
/foundry:audit communication # communication governance files
/foundry:audit setup # system config: settings.json, hooks, plugin integration
/foundry:audit plugin # foundry plugin integration checks only
/foundry:audit plugins # deep audit of all installed plugins
# Tier 2 β plugin name (shorthand for 'plugins <name>')
/foundry:audit oss # oss plugin agents + skills only
/foundry:audit foundry # foundry plugin only (same as 'plugins foundry')
/foundry:audit oss research # oss + research plugins
# Tier 3 β specific agent or skill name
/foundry:audit shepherd # single agent
/foundry:audit curator challenger # two agents
/foundry:audit review resolve # two skills
# Combine scope + flags
/foundry:audit oss --adversarial # oss plugin, adversarial review
/foundry:audit agents --adversarial # all agents, adversarial review
# Preserve key context across session compaction
/foundry:audit --keep "task-123, .reports/audit/2026-07-03T10-00-00Z"
fix and upgrade mutually exclusive β never combine.
Behaviour change (0.40.1): the dead-model-spec check and Check 21 (skill frontmatter conflicts) were removed, and --efficiency's missing-model check no longer exempts skills declaring disable-model-invocation: true. All three rested on the premise that disable-model-invocation: true nullifies a co-declared model: or context: fork; it does not β the flag blocks Claude-automatic invocation, subagent preloading, and scheduled dispatch, while a user-typed /skill-name still runs and still honours model:. The removed checks would have recommended deleting a field that has real effect. Dropping the exemption widens missing-model detection from 14 to 35 skills, so expect more findings under --efficiency than before.
Sweep checks (30 checks):
- Inventory drift: MEMORY.md roster vs files on disk
- Broken cross-references between agents and skills
- Hardcoded absolute user paths
- Model-tier appropriateness (reasoning vs execution roles)
- Agent description routing overlap (40%+ consecutive step overlap flagged)
- settings.json permissions vs Bash calls in skills
- Hook event names vs documented schema
- Claude Code docs freshness (spawns
foundry:web-explorerfor live docs) - Plugin integration correctness (codex plugin, foundry plugin)
- File length, heading hierarchy, LLM context minimality
- Config token overhead: total always-loaded config >100 KB, single rules file >10 KB (rules/ loads at session start; agents/skills lazy-loaded)
- Codemap index guards: every file resolving a codemap index path must be MANIFEST-managed, declared in the guard registry with its shape, or call the provider CLI (which never spells the path); registry entries whose file no longer holds a guard fail too, so the inventory cannot rot
Outputs structured report. With fix level: delegates fixes to sub-agents (never edits inline), re-audits modified files to confirm fixes held. Convergence loop up to 5 passes.
/foundry:calibrate¶
Benchmarks agents and skills against synthetic problems with defined ground truth. Primary signal = calibration bias β gap between self-reported confidence and actual recall. Well-calibrated agent reports 0.9 when finds ~90% of issues.
/foundry:calibrate all --fast # quick benchmark across all modes (3 problems each)
/foundry:calibrate all --full # thorough benchmark (10 problems each)
/foundry:calibrate routing --fast # routing accuracy only β run after any agent description change
/foundry:calibrate agents --full --ab-test # agents + general-purpose baseline comparison
/foundry:calibrate all --fast --apply # benchmark then immediately apply improvement proposals
/foundry:calibrate --apply # apply proposals from the most recent past run
/foundry:calibrate foundry:sw-engineer --fast # single agent (tier 3 by full name)
# Tier 2 β plugin name
/foundry:calibrate oss --fast # all oss plugin agents + calibratable skills
/foundry:calibrate oss research --fast # oss + research plugins
# Tier 3 β specific agent or skill (bare name or plugin-prefixed)
/foundry:calibrate curator --fast # single agent by bare name
/foundry:calibrate curator shepherd # two agents (default --fast)
# Multiple targets
/foundry:calibrate agents skills --fast # agents + skills in one run
# Preserve key context across session compaction
/foundry:calibrate --keep "task-456, .reports/calibrate/2026-07-03T10-00-00Z"
Large fan-out gate: broad scopes (all, agents, skills, plugins, <plugin-name>) always confirm via AskUserQuestion before spawning β expand to dozens of agent/skill pipelines only inside Step 2, no exact count upfront. Narrow scopes (single agent/skill) confirm only when exact spawn count exceeds 50. --apply no longer bypasses gate alone; only --skip-gate does.
Thresholds:
- Routing accuracy: 90% (hard-problem accuracy: 80%)
- Recall per agent: 0.70 (below = instruction improvement needed)
- Calibration bias: within +/-0.15 (beyond = confidence decoupled from quality)
Modes:
agentsβ all specialist agentsskillsβ/foundry:auditand/oss:reviewroutingβ orchestrator dispatch accuracy for synthetic task promptscommunicationβ team protocol compliance, file-handoff protocol violationsrulesβ rule adherence across global + path-scoped rule filesplugins/<plugin-name>β all agents + calibratable skills, one or all plugins<agent-name>/<skill-name>β single target, bare or plugin-prefixed nameallβ all above
Results saved to .reports/calibrate/<timestamp>/<target>/. Improvement proposals written to proposal.md per target dir, applied with --apply.
Agents and skills modes use dual-source evaluation: Claude + Codex generate problems and score responses independently, Claude 51% tiebreaker.
/foundry:manage¶
Create, update, or delete agents, skills, and rules; update or delete hooks. Full cross-reference propagation. Keeps MEMORY.md, README, settings.json in sync automatically.
/foundry:manage create agent security-auditor "Vulnerability scanning specialist for OWASP Top 10 and supply chain threats"
/foundry:manage create skill benchmark "Benchmark orchestrator for measuring performance across commits"
/foundry:manage create rule torch-patterns "PyTorch coding patterns β compile, AMP, distributed"
/foundry:manage update my-agent "add a section on error handling patterns"
/foundry:manage update my-agent new-agent-name # rename
/foundry:manage update my-agent docs/spec.md # apply spec file as change directive
/foundry:manage delete old-agent-name
/foundry:manage add perm "Bash(jq:*)" "Parse and filter JSON" "Extract fields from REST API responses"
/foundry:manage remove perm "Bash(jq:*)"
Create: fetches latest Claude Code agent/skill frontmatter schema, picks unused color, assigns model tier by role complexity, delegates content generation to foundry:curator. Checks overlap with existing agents first.
Update: auto-detects type from disk. Rename atomic (write-before-delete). Content edits delegated to foundry:curator (agents/skills) or foundry:sw-engineer (hooks). Propagates description changes to cross-references when more than 3 files affected.
Delete: removes file, cleans broken references across .claude/, updates MEMORY.md and README.
Permissions: add perm / remove perm update both settings.json and permissions-guide.md atomically β never one without other.
After any create or update: /foundry:calibrate routing --fast to confirm routing accuracy unaffected.
/foundry:brainstorm¶
Turns fuzzy idea into approved exploration tree, then spec, then ordered action plan. Nothing implemented until user approves design.
/foundry:brainstorm "add caching layer to the data pipeline"
/foundry:brainstorm "add caching layer to the data pipeline" --tight # fewer questions and operations
/foundry:brainstorm "add caching layer to the data pipeline" --deep # more exploration
/foundry:brainstorm "add caching layer to the data pipeline" --type workflow
/foundry:brainstorm breakdown .plans/blueprint/2026-04-01-caching-layer.md # tree -> spec
/foundry:brainstorm breakdown .plans/blueprint/2026-04-01-caching-layer-spec.md # spec -> action plan
# Preserve key context across session compaction
/foundry:brainstorm "add caching layer to the data pipeline" --keep "task-789, .plans/blueprint/2026-04-01-caching-layer.md"
Idea mode (default):
- Scans codebase for relevant existing code + constraints
- Asks up to 10 clarifying questions (5 with
--tight, 15 with--deep) β the first call batches 2-3 orientation questions, the rest follow one at a time so each can adapt to the prior answer - Presents 3-5 initial branches: core idea, tension resolved, what it trades away
- Interactive operations loop: deepen, reject, resolve, merge, add β up to 10 rounds
- Saves tree to
.plans/blueprint/YYYY-MM-DD-<slug>.mdwithStatus: tree - Live tree viewer at URL printed during Step 1 (serve project root with
python -m http.server 8000)
Breakdown mode (breakdown <file>):
Status: treefile: distillation questions, then section-by-section spec, savedStatus: draftStatus: draftfile: resolves blocking open questions, produces ordered action plan with tagged invocations
--type hint (application, workflow, utility, config, research) shapes question framing + codebase scan patterns in idea mode.
/foundry:investigate¶
Systematic diagnosis for unknown failures. Gathers signals, ranks hypotheses, probes top candidates, reports confirmed root cause + recommended next action.
/foundry:investigate "hooks not firing on Save"
/foundry:investigate "CI fails but passes locally"
/foundry:investigate "codex agent exits 127 on this machine"
/foundry:investigate "/calibrate times out every run"
/foundry:investigate "hooks not firing" --keep "task-999" # preserve across compaction
Covers: broken local setup, environment mismatches, tool misconfigurations, hook misbehavior, CI vs local divergence, permission errors, runtime anomalies.
Not for: known Python test failures with traceback (use /develop:debug); .claude/ config quality sweep (use /foundry:audit).
Workflow: parse symptom -> gather signals in parallel (tool versions, PATH, recent git changes, config state, logs) -> rank hypotheses -> optional Codex adversarial review for ambiguous cases -> probe top hypotheses -> report root cause + recommended next skill.
Output always includes: confirmed root cause (or narrowed suspects), key evidence, what was ruled out, single recommended next action.
Auto-invokes when (MAYBE): unknown failure, no Python traceback β hook not firing, CI passes locally but fails remotely, behavior inconsistent with config; "not working but config looks right", "hook not triggering", "why isn't X running".
/foundry:profile¶
Buckets session clock time from ~/.claude/logs/{timings,invocations}.jsonl into local-tool, agent-spawn, Skill, AskUserQuestion idle, main-loop reasoning residual, and session tokens/cost from Claude Code transcripts (~/.claude/projects/**, main-loop + subagent files) into main vs subagent spend by model tier β merged into one report. Pure log/transcript read β no instrumentation, no LLM calls, no skill edits.
/foundry:profile # last 24h, top 5 slowest calls + cost ranking
/foundry:profile --since 7d # last 7 days
/foundry:profile --session-id 9c1bded7 # drill one session: clock breakdown + cost/agent-roster/cache-rebuild detail
/foundry:profile --top-n 20 # 20 longest single calls
Covers: per-session clock breakdown (local% / agent% / skill% / reasoning% / idle), per-skill clock rollup (runs, total, mean, median, p90), top-N longest single calls, headline split over window; ## Tokens & cost section β sessions ranked by cost (main $ / subagent $ / total $), per-command rollup, or (with --session-id) cost by main/sidechain Γ model tier, agent roster, top cache-rebuild calls, cold-start share. Subagent spend is read directly from each session's subagents/agent-*.jsonl transcripts, not inferred.
Not for: per-line Python perf (use foundry:perf-optimizer); known failure diagnosis (use /foundry:investigate); a real billing statement (prices are public list rates, not effective plan rates).
Reads JSONL logs foundry task-log.js hook already writes for the clock side (bin/timing_analyzer.py) and Claude Code transcripts for the cost side (bin/cost_analyzer.py) β answers "where did wall clock go in /oss:resolve?" and "what did this session cost?" alike. Agents run in the background, so a spawn-only row carries a false-zero duration_ms; the real span is recovered by joining the matching startedβcompleted pair in invocations.jsonl. The cost side is best-effort: if no transcripts fall in the window, the ## Tokens & cost section is omitted and the clock report still ships.
cost_analyzer.py --composition writes a ## Context composition Markdown section with per-window usage, injection bytes (including instructions), estimated tool-result tokens and duplicate bytes, and likely large-input triggers. It accepts --projects-root, --session-id (full id or prefix), --since, and --output; main and subagent contexts are reported independently. Explicit compaction records define windows when present; otherwise a post-call context drop below 70% starts a window. --runtime claude is the default and also reports repeated Read content flags, hook counts and sizes per prompt, large cache-write triggers, and the session's 5-minute/1-hour cache-write split. Claude sessions also show a timestamp-ordered Logged compaction events table from ~/.claude/logs/compactions.jsonl (override with --compactions-log), including hook metadata and gate estimates. Each gate is paired with the next logged precompact event and compared with the following main-transcript window: estimated versus realised calls, and the current gate's 120,000-token floor versus realised first-call context (prompt + output). Missing timing or a matching window is reported as unavailable; gates without a following precompact event say no compaction followed. Missing logs add a no logged compaction events note; malformed rows warn and are skipped, and unknown events are ignored. --runtime codex reports tokens only, without prices, from $CODEX_HOME/sessions (default ~/.codex/sessions); --projects-root overrides discovery. Codex deduplicates native response_id usage records or legacy sequential cumulative token-count events, reports subagent shares across linked rollout threads, flags attempted code edits, and estimates each thread's AGENTS.md bytes Γ calls. Native response records take precedence for an entire thread; historical event-only usage in mixed-format threads is unavailable. Injection kinds include AGENTS.md, skills, model switches, multi-agent instructions and tagged hook developer messages. This analyzer ships in the Claude foundry plugin.
Auto-invokes when (MAYBE): user asks where wall-clock time or tokens/cost go, why a skill is slow or expensive, what dominates session runtime or spend; "where does time go", "why so slow", "what did this cost", "token spend", "profile last session", "clock breakdown", "session timing", "which skill burns tokens".
/foundry:distill¶
Extracts patterns from work history + corrections, distills into durable improvements β new agent/skill suggestions, roster quality review, memory pruning, promoting lessons into rules, or analysing external plugins/agentic resources for adoption.
/foundry:distill # analyze project patterns, suggest new agents/skills
/foundry:distill review # review existing roster for quality and gaps (no new suggestions)
/foundry:distill prune # trim stale/redundant entries from all project MEMORY.md files
/foundry:distill prune --project # interactive picker: select which project(s) to prune
/foundry:distill memory # promote patterns from all projects into rules/agents/skills
/foundry:distill memory --project # interactive picker: select which project(s) to distill from
/foundry:distill "external https://..." # analyse external plugin/skill/agent resource, produce adoption proposal
/foundry:distill "external ./path/to/plugin" # same β local path or directory
/foundry:distill "I keep doing X manually" # use description as context for suggestions
/foundry:distill --keep "task-111" # preserve key context across compaction
--project triggers interactive project picker β lists all slugs under ~/.claude/projects/*/memory/ with MEMORY.md size in tokens; select one or more. Without --project, both modes run across all projects automatically. Both prune and memory run parallel across selected projects (one agent per project), then consolidate into single confirmation step. memory mode also enriches each project's feedback with project-level context (CLAUDE.md, recent git log, active plans) for better classification accuracy.
lessons mode consolidates feedback_*.md memory files and a legacy .notes/lessons.md when present: clusters by domain, classifies each entry as β rule, β agent update, β skill update, β already covered, or β too narrow, generates proposals. Before applying: conflict pre-check β greps each target file for section delta lands in, flags cross-proposal collisions with β . Confirmed changes applied, followed by git diff gate β inspect or revert before committing.
external mode does fast + slow read of source (URL, file, directory), extracts mental model + standout implementation details, compares against live local setup, splits candidates into two groups: Align + improve (maps cleanly onto existing agents/skills/rules) and Differentiated highlights (novel, structurally different β interesting but larger work). Each candidate scored, assigned adoption lane: adopt-as-is / tweak / discuss / skip. When Group A thin or cumulative edit effort large, recommends installing source as standalone plugin with justification, not cherry-picking. Nothing written until confirmed.
After applying: run /foundry:setup to propagate new rule files to ~/.claude/.
Run monthly or after any correction burst.
/foundry:session¶
Session state that outlives a context reset β a handover document plus a parking lot for open loops. dump sweeps the live conversation and composes goal, decisions + why, lessons, standing instructions, a files-touched table, outstanding items and an artifacts table, writes it to .claude/state/session/<slug>.md, and ends by printing /clear as the last line of the reply. The session-restore.js hook injects that document back on the other side, so restore costs nothing.
/foundry:session dump [name] # sweep, write the handover doc, then print /clear
/foundry:session recall [name] # print a stored handover back into context (named, or latest)
/foundry:session list # handovers (slug / age / consumed) + open parked items
/foundry:session park <idea> # stash one open loop without derailing the current task
/foundry:session sweep # audit the conversation for unlanded ideas and questions
/foundry:session drop <item> # close a parked item and log the closure
Auto-invokes when: user says "dump the session", "handover before clear", "save state before clearing", "carry this over", "park this for later", "what did we defer", "anything unfinished before I close".
Runs inline, not forked β the conversation history is the authoritative source for what changed, what was decided, and what never landed. Implementation detail (diffs, tool output, exploration transcript, abandoned approaches) is dropped on purpose.
The hook auto-restores only an unconsumed handover under 30 minutes old, so an old dump never ambushes an unrelated session; anything outside that window is reachable via recall, which has no age gate. Over ~8000 chars the hook injects ## Goal + files table + ## Next step + a pointer instead of the whole doc. Handover docs older than 14 days list as β stale; older than 30 days are swept during list.
Parked items live in .claude/state/session/PARKED.md, are copied into every dump rather than consumed by it, and have no TTL β only drop removes one, appending an audit line to dropped.jsonl. Parking is an explicit command, not an automatic behaviour.
The mode is recall, not resume, to stay clear of Claude Code's native /resume β which revives a whole past conversation and is the better tool whenever it applies.
Not auto-compact survival (the skill contract in .temp/state/skill-contract.md, per rules/compaction.md) β the two are distinguished in a boundary table in the skill.
/foundry:create¶
Interactive outline co-creation for developer advocacy content. Collects format, audience profile, four-beat arc, voice/tone via structured questions; detects out-of-scope requests; surfaces editorial conflicts; writes approved outline for foundry:creator to execute.
/foundry:create "tracing Python microservices with OpenTelemetry"
/foundry:create "why your CI pipeline is lying to you"
/foundry:create # no topic β skill asks interactively
Supported formats: blog post, Marp slide deck (conference/meetup talk), social thread (Twitter/LinkedIn), talk abstract (CFP submission), lightning talk (5β10 min).
Out-of-scope detection: refuses FAQs, comparison tables, reference docs at Step 1, redirects to foundry:doc-scribe.
Editorial conflict detection: brief implies expert-level topic for beginner audience (or reverse) β skill surfaces mismatch explicitly before writing.
Writes .plans/content/<slug>-outline.md. Hand off to foundry:creator after approval:
Max 5 AskUserQuestion interactions for well-specified brief (format, audience, arc, voice). Skips interactive steps if all choices in initial brief.
/foundry:humanizer¶
Strips AI-writing tells from human-facing prose β docs, PR/commit bodies, reports, release notes, blog posts. Removes LLM-vocabulary clichΓ©s, banned constructions (rule-of-three triads, "not just X but Y"), formatting tells (title-case headings, em-dash overuse). Final pass β preserves meaning + structure; no rewrite from scratch.
/foundry:humanizer <text or file path> # humanize in place
/foundry:humanizer check <file> # report tells without editing
Primarily model-initiated self-review pass: foundry may invoke before finalizing substantial prose artifact drafted during task. Best-effort, not guaranteed intercept. Skips code, config, machine-parsed envelopes, inter-agent handover files.
π€ Agents reference¶
All ten agents available by full plugin-prefixed name. In spawn directives and subagent_type values, always use full prefix (foundry:sw-engineer, not sw-engineer).
foundry:sw-engineer¶
Role: senior software engineer β writing/refactoring Python.
Use for: implementing features, fixing bugs, TDD/test-first, SOLID, type safety, production-quality Python for OSS libraries.
Model: opus
Not for: docstrings (foundry:doc-scribe), ruff/mypy config (foundry:linting-expert), system design (foundry:solution-architect), test quality analysis (foundry:qa-specialist), perf profiling (foundry:perf-optimizer), ML paper implementations (research:scientist), .claude/ config edits (foundry:curator), CI/CD pipeline config β GitHub Actions, pre-commit hooks, CI YAML (oss:cicd-steward β requires oss plugin).
Auto-invokes when: user asks to implement, build, write, modify, fix code across 3+ files; phrases: "implement", "build", "write the code for", "add feature", "fix this bug".
Runs in the caller's working tree by default, so its edits and reports land where the calling skill reads them. Worktree isolation is chosen per spawn, not by the agent: a caller passes isolation="worktree" only for parallel or speculative writers whose changes it transplants back β /foundry:distill executables extraction, and /oss:resolve Phase 2 (requires oss plugin). An isolated run cannot write into the main checkout or another worktree, so its caller hands it a temporary path or reads the result from its return envelope.
foundry:solution-architect¶
Role: system design specialist β ADRs, API surface design, interface specs, migration plans, coupling analysis.
Use for: architectural trade-offs, public API contracts, deprecation strategies, assessing architectural feasibility of AI-generated hypotheses against codebase constraints.
Model: opus
Not for: implementation code (foundry:sw-engineer), release management (oss:shepherd), perf profiling or DataLoader throughput tuning (foundry:perf-optimizer).
Auto-invokes when (MAYBE): architecture questions involving 3+ components; "how should I structure this", "ADR for", "migration plan for". Not for simple design questions.
Produces documentation β ADRs, interface contracts, migration plans, component diagrams β not production code. Hands off to foundry:sw-engineer for execution. Spec with multiple unresolved decision branches: resolves one at a time via AskUserQuestion with recommended answer each, not one bulk ask.
foundry:qa-specialist¶
Role: QA specialist β writing, reviewing, fixing tests. Rigorous black-box end-user tester: public API surface by default, expectations from docs/type hints β not implementation, tests represent realistic user workflows.
Use for: new pytest tests, public-API coverage gaps, edge-case matrices, fixing failing tests, integration test design. Auto-embeds OWASP Top 10 security review when task scope includes auth, payment flows, or PII β all modes.
Model: sonnet
Not for: linting, type checking, annotation fixes (foundry:linting-expert), production implementation (foundry:sw-engineer), slow test suite profiling (foundry:perf-optimizer), testing private/internal methods or mocking internals by default β opt-in only when the caller explicitly asks, or when a bug is unreachable through any public path.
Auto-invokes when: user asks to write tests, assess coverage, define test strategy; "write tests for", "add unit tests", "test coverage for".
Writes deterministic, parametrized, behavior-focused tests. Progression: happy path β edge cases β error cases β boundary values β adversarial inputs. Applies public-API coverage checklist before done.
foundry:linting-expert¶
Role: static analysis + tooling specialist for Python.
Use for: ruff rules, mypy strictness, pre-commit hooks, fixing lint/type violations, missing type annotations, lint/type content of quality gates. Final code sanitization before handover.
Model: sonnet (effort: medium)
Not for: CI pipeline structure or runner strategy (oss:cicd-steward), test logic (foundry:qa-specialist), implementation beyond annotation/style (foundry:sw-engineer), docstrings or API reference (foundry:doc-scribe).
Auto-invokes when: after code edits when user asks "is this clean", "any lint issues", "check formatting", "check types"; lint or type errors visible in output.
Always downstream of foundry:sw-engineer β never lints unimplemented code.
foundry:perf-optimizer¶
Role: performance engineer β profiling + optimizing CPU, GPU, memory, I/O bottlenecks.
Use for: profiling Python/ML workloads, DataLoader bottlenecks, mixed precision, vectorizing loops, PyTorch throughput tuning.
Model: opus
Not for: general refactoring (foundry:sw-engineer), architectural redesign (foundry:solution-architect), DataLoader correctness or reproducibility audits (research:data-steward β requires research plugin), doc writing (foundry:doc-scribe), annotation fixes (foundry:linting-expert).
Auto-invokes when: user asks to profile, benchmark, optimize Python/ML workload; mentions slow training, GPU underutilization, DataLoader bottleneck; phrases: "why is this slow", "profile this", "optimize training speed". Not for general code changes (foundry:sw-engineer).
Strictly profile-first: measure before change, change one thing, measure again. Optimization order: algorithm -> data structure -> I/O -> memory -> concurrency -> vectorization -> compute -> caching. Never GPU tuning before checking I/O.
foundry:doc-scribe¶
Role: documentation specialist β docstrings, API references, README files.
Use for: auditing missing docstrings, writing Google-style (Napoleon) docstrings from code, README content, doc/code inconsistencies.
Model: sonnet
Not for: CHANGELOG entries or release notes (oss:shepherd for lifecycle/format decisions, /oss:release for automated generation), linting code examples (foundry:linting-expert), implementation (foundry:sw-engineer), outward-facing narrative artifacts β blog posts, talk slides, social threads (foundry:creator).
Auto-invokes when: user asks for documentation; "write docs for", "add docstrings to", "update the README", "document this function".
Always downstream β documents finalized code, never shapes design. After foundry:doc-scribe produces content, follow with foundry:linting-expert to sanitize code examples.
foundry:web-explorer¶
Role: web fetch + content extraction specialist.
Use for: fetching live library docs, API references, changelogs, migration guides, package version lookups, GitHub release extraction. Used internally by /foundry:audit upgrade and /foundry:manage create.
Model: sonnet
Not for: code analysis or implementation (foundry:sw-engineer), ML paper analysis (research:scientist), README/docstring writing (foundry:doc-scribe), dependency upgrade lifecycle decisions (oss:shepherd), perf profiling or benchmarking recommendations (foundry:perf-optimizer).
Auto-invokes when: user asks about library docs, external API, URL content; "what does X docs say", "look up", "find the docs for", "latest version of"; user pastes URL + asks question about it.
Feeds research:scientist β fetches current docs + papers; scientist interprets.
foundry:curator¶
Role: quality guardian of Claude config markdown files β agents, skills, rules.
Plugin-layout audits require new executable helpers to use Python and treat legacy shell helpers as portability debt.
Use for: auditing .claude/ config files β verbosity creep, cross-agent duplication, broken cross-references, structural violations, outdated content, roster overlap. Used internally by /foundry:audit and /foundry:manage.
Model: opus
Not for: hook files (*.js) β belong to foundry:sw-engineer. Not for creating/scaffolding new agents or skills (/foundry:manage create). Not for routing new tasks to other agents.
Auto-invokes when (MAYBE): user explicitly references specific agent/skill config file by path; "audit this agent", "check this skill file", "is this config valid". Narrow trigger β see description.
Generally not invoked directly. /foundry:audit spawns it in batches across all config files; /foundry:manage delegates content generation + editing to it.
foundry:challenger¶
Role: adversarial reviewer β implementation plans, architecture proposals, significant code reviews.
Use for: red-teaming plan before committing, challenging architectural decisions before ship, adversarial code review on security-sensitive or irreversible ops. Every claim unproven until backed by evidence. Attacks across 6 dimensions (Assumptions, Missing Cases, Security Risks, Architectural Concerns, Complexity Creep, Root Cause) β drills to bedrock per standing challenge (keeps asking "why?" until root cause, not surface symptom). Mandatory refutation step keeps it objective: accepts refutation when evidence warrants.
When bridge@borda-ai-rig is installed and enabled, challenger calls the read-only Codex adversarial review track for the same target and aggregates its findings. Pass --no-codex to skip. An absent or disabled bridge is surfaced rather than silently falling back.
Model: opus
Not for: designing plans or ADRs (foundry:solution-architect), writing tests or coverage review (foundry:qa-specialist), config file quality review (foundry:curator).
Auto-invokes when: user asks to challenge, stress-test, critique design or plan; "challenge this", "what are the weaknesses", "devil's advocate", "poke holes in", "second opinion on".
Read-only β never writes or edits files. Runs by default in all /develop:* skills and /oss:review β skip with --no-challenge.
foundry:creator¶
Role: developer advocacy content specialist β outward-facing narrative artifacts.
Use for: complete blog posts, Marp slide decks, social threads, talk abstracts, lightning talk outlines in one autonomous pass. Imagines ideal reader experience first, works backwards to structure + form β questions status-quo conventions, pushes for fresh angles. Reads approved outline file (.plans/content/<slug>-outline.md) from /foundry:create. Applies four-beat story arc (ProblemβJourneyβInsightβAction) calibrated to target audience level.
Model: sonnet
Not for: in-code documentation, docstrings, API references (foundry:doc-scribe), release notes/changelogs (oss:shepherd β requires oss plugin), structured reference content β FAQs, comparison tables (redirect to foundry:doc-scribe).
Auto-invokes when: outline file .plans/content/<slug>-outline.md approved; user asks for blog post, Marp slide deck, social thread, talk abstract, lightning talk outline. Skip when: outline file not found (run /foundry:create first); code documentation (foundry:doc-scribe); release notes (oss:shepherd).
Always downstream of /foundry:create β reads approved outline, generates full artifact. Two-phase system: /foundry:create (interactive intake β outline) then foundry:creator (autonomous generation β artifact).
π Agent relationships¶
Agents = directed pipeline, not flat pool:
foundry:linting-expertalways downstream offoundry:sw-engineerβ never lints unimplemented codefoundry:doc-scribealways downstream β documents finalized code, never shapes designfoundry:qa-specialistruns parallel tofoundry:sw-engineerduring review, or downstream after implementationfoundry:challengeris pre-implementation β challenges plans + proposals before any code; use beforefoundry:sw-engineerfoundry:curatoris orthogonal β audits.claude/config files, not user codefoundry:web-explorerfeedsresearch:scientistβ fetches current docs + papers; scientist interpretsfoundry:creatoralways downstream of/foundry:createβ reads approved outline file; never generates without prior outline
Model tiering:
- reasoning agents (
foundry:sw-engineer,foundry:perf-optimizer) useopus - adversarial reasoning (
foundry:challenger) usesopus - high-stakes design/config roles (
foundry:solution-architect,foundry:curator) useopus - execution agents (
foundry:doc-scribe,foundry:web-explorer,foundry:creator,foundry:qa-specialist,foundry:linting-expert) usesonnet
Agent and skill frontmatter uses aliases only, never a pinned model ID, so each follows the newest release. On the Anthropic API the aliases resolve to the 5.5 family (opusβOpus 5.5, sonnetβSonnet 5.5, haikuβHaiku 5.5); third-party providers map haiku to Haiku 4.5. Haiku 5.5 accepts effort. opusplan as subagent frontmatter runs sonnet (subagents never enter plan mode) β use opus or sonnet explicitly. fable is the reserve tier for demanding long-horizon reasoning, with no standing agent assignment. Within the 5 family the tiers sit closer together than the 4.x tiers did, so effort is the primary cost knob and tier the blunt one β tune effort before moving an agent's tier, and prove any downgrade with /foundry:calibrate <agent> --full --ab-test rather than a generation-level parity claim.
/foundry:audit's fix dispatch (audit/modes/fix.md, audit/templates/audit-fix-prompt.md) overrides per-call to haiku for PARALLEL_SAFE_CATEGORIES findings (pure transcription of an already-known replacement) regardless of the spawned agent's own frontmatter tier β judgment-bearing and cross-file fixes keep the agent's default.
π Rules installed¶
/foundry:setup symlinks all 18 rule files from plugins/cc_foundry/rules/ into ~/.claude/rules/, each renamed to foundry-<source-name>.md. They govern Claude behavior globally, in all sessions after install.
~/.claude/rules/ is one flat directory shared by every installed plugin, and four of this marketplace's plugins ship a rules/quality-gates.md, so source basenames would silently overwrite one another. Each plugin therefore namespaces its own rules with its plugin name: quality-gates.md installs as foundry-quality-gates.md, alongside develop-quality-gates.md, oss-quality-gates.md, and research-quality-gates.md from the sibling plugins' own setup skills. The prefix is inert β verified against Claude Code 2.1.220 that it changes neither unconditional rule loading nor paths: frontmatter matching.
Re-running setup after an upgrade migrates any pre-namespace unprefixed link, but only when that link provably belongs to foundry; a link into another marketplace, a source checkout, or a dotfiles tree is preserved and reported as a conflict instead.
Stub + on-demand split (token diet):
debugging.md,external-data.md,artifact-lifecycle.md,quality-gates.md,communication.md,claude-config.md,public-github.md,task-lifecycle.md,notebooks.md(full body:notebook-style.md) = thin always-loaded stubs with hard constraints only; full procedural bodies live inrules/_full/(resolved from plugin cache, NOT symlinked or injected), Read on demand at the trigger point named in each stub β drafting a commit, multi-file fix, multi-page fetch, defining new output dirs, writing a report, building anAskUserQuestioncall, setting a Bash timeout, a GitHub write, a subagent spawn, writing a notebook cell. Sections that matter only while touching one kind of file sit behind a narrowerpathsglob instead (markdown.mdfor*.md, including the full link-verification wording and the instruction-file compression gate,python-code.mdfor pinned lint/format hooks and complexity limits), each leaving a one-line eager stub where the obligation must hold outside the trigger. Rules that matter only during one activity are injected rather than loaded:hooks/rule-inject.jsaddsagent-spawn.md(spawn slots, end-turn-after-spawn, parallel spawn ceilings) on the firstAgent()call andgit-commit.mdon the firstgit add/commit/pushof each session and subagent, again after compaction or/clear; the always-loaded files keep compact stubs carrying the hard limits (task-lifecycle.mdspawn stubs,claude-config.mdspawn ceilings and git hard bans).tests/rules/test_rule_context_budget.pypins the always-loaded byte total.Zero constraint loss: every named rule survives in the stub or its
_full/body, verified by heading-set diff.Measured (2026-08-07): the always-injected preamble is ~20.5K tokens per agent and is re-sent every turn β a 56-turn agent re-sends ~1.15M tokens of it. The splits above cut ~2.5K of that, plus ~1.3K from
plugins/CLAUDE.mdβplugins/AUTHORING.md. Rule savings are realized only aftermake sync-claudepropagates to the plugin cache β~/.claude/rules/*.mdare symlinks into a version-pinned cache directory, so edits to this repo'srules/do not affect running agents until installed.
| Rule file | Applies to | Governs |
|---|---|---|
communication.md |
all | Re: anchor format, progress narration, tone, output routing, breaking-findings format, terminal colors, confidence display, Markdown no-hard-wrap stub (structure selection in markdown.md) |
quality-gates.md |
all | Confidence block format, independent adversarial review/fix loop, link verification stub (full wording in markdown.md), Python complexity-limit stub (rule in python-code.md), output routing (long output to file) |
git-commit.md |
injected by rule-inject.js after the first commit-creating git call (add/commit/push/merge/revert/cherry-pick/am/rebase, annotated tag) runs; **/.git/COMMIT_EDITMSG, **/.gitmessage |
Commit message format, diff-gathering before writing, co-author trailers, branch + push safety (hard bans stay always-loaded in claude-config.md) |
claude-config.md |
all | Bash timeouts (3x P90), turn batching (one response per independent call set), directory navigation rules, no hardcoded absolute paths, agent/skill spawn discipline, parallel spawn ceilings stub (table in agent-spawn.md), pinned pre-commit hooks over bare lint tools (stub; detail in python-code.md), instruction-file compression gate (stub; gate in foundry-config.md), git commit/push hard bans |
artifact-lifecycle.md |
all | Canonical artifact layout (.plans/, .reports/, .temp/), run directory naming, TTL policy, plan and benchmark evidence kept out of shipped content |
external-data.md |
all | Pagination rules: GitHub CLI, REST APIs, GraphQL, Cloud APIs β never work on partial result set |
foundry-config.md |
.claude/** |
Plan-mode gate before any .claude/ edit, post-edit checklist, XML tag conventions, distribution rules |
python-code.md |
**/*.py |
Google-style docstrings, closed option sets as enums, dataclass/TypedDict selection and record-type boundaries, deprecation API, multi-OS portability, complexity limits, pinned lint/format hook usage |
python-testing.md |
tests/**/*.py, **/test_*.py |
pytest design: TDD process, fixtures, parametrization, marker selection, pytest-xdist runs, behaviour-vs-bookkeeping check classification, mutation checks on a copy, cross-OS tests, mocking, what to test in priority order |
notebooks.md |
**/*.ipynb, **/*.py |
Notebook cell granularity, markdown narrative depth, plot framing, shell magics, docstring placement β .ipynb and Jupytext # %% scripts |
public-github.md |
all | GitHub reads run free; every gh write needs the user's one-time gh-write approval of the exact command |
untrusted-content.md |
all | Ingested external text is data, never instruction β delimiters, no permission widening, memory propagation |
markdown.md |
**/*.md |
Never hard-wrap prose, structure selection (bullets, numbered lists, tables, blockquotes), behavior-sensitive reformatting, instruction-file compression gate, link verification (fetch + read + match per URL) |
agent-spawn.md |
injected by rule-inject.js after the first Agent() call runs; **/skills/**/*.md, **/agents/**/*.md |
Agent() spawn slots (three-slot delta-first labels, pre-spawn check), end the turn after spawning with per-agent deadlines, parallel spawn ceilings by model tier |
βοΈ Configuration¶
settings.json keys merged by /foundry:setup¶
| Key | What it does |
|---|---|
statusLine.command |
Runs statusline.js β active agent count in Claude Code status bar |
permissions.allow |
Adds pre-approved Bash commands, git operations, WebFetch domains |
permissions.deny |
Adds denied operations (force push, curl writes, rm -rf, sudo); removes retired gh write and local branch/tag deletion denies |
enabledPlugins["bridge@borda-ai-rig"] |
Enables bridge-backed Codex review in /foundry:calibrate and /foundry:audit |
advisorModel |
Copied from project .claude/settings.json when pinned β advisor tool uses chosen model |
env.CLAUDE_CODE_ENABLE_TODO_TOOLS |
Re-enables the task tools (TaskCreate/TaskList/TaskUpdate/TaskGet), off by default |
Env defaults ship in .claude-plugin/env-defaults.json and merge per key with //= β a key already present in ~/.claude/settings.json keeps the user's value, including a deliberate "0".
Optional flags and knobs¶
--approve on /foundry:setup: skips all interactive prompts, auto-accepts recommended choices. For scripted or CI setups.
--skip-audit on /foundry:manage: skips trailing /foundry:audit validation step. Use inside audit-initiated fix sessions to avoid recursion.
Calibration pace: --fast (3 problems per target, default) vs --full (10 per target). --fast for routine checks after agent edits; --full for thorough benchmarks before releases or after major instruction changes.
Brainstorm ceremony: --tight (5/5/1 caps, well-scoped ideas), default (10/10/2), --deep (15/15/3, genuinely ambiguous problems).
Environment¶
No environment variables required. foundry reads ~/.claude/settings.json + plugin's installed cache path, both resolved automatically by /foundry:setup.
π Troubleshooting¶
/foundry:audit reports broken symlinks (Check I3)
Symlinks in ~/.claude/rules/ point to previous plugin cache path after upgrade. Re-run /foundry:setup β Step 10 detects stale symlinks as conflicts, offers replacement.
Hooks not firing
Run /foundry:investigate "hooks not firing on Save". Most common cause: hooks block still in ~/.claude/settings.json from pre-plugin-migration install (hooks now register via plugin manifest, not hooks key). /foundry:setup Step 3 detects + removes stale block.
/foundry:calibrate times out
Each pipeline subagent has 10-minute hard cutoff (15 minutes when Codex active). Target consistently times out: run in isolation: /foundry:calibrate foundry:sw-engineer --fast. Persistent: /foundry:investigate "/calibrate times out every run".
/foundry:manage create picks wrong model tier
Model tier chosen by role complexity at creation: opus for high-stakes quality review and complex reasoning (e.g. foundry:curator, foundry:sw-engineer), sonnet for focused execution, haiku for mechanical high-frequency work. opusplan as subagent frontmatter runs sonnet (subagents never enter plan mode) β use opus or sonnet explicitly. Fix after creation: /foundry:manage update <name> "change model to sonnet".
foundry:curator returns low confidence during /foundry:audit
Audit re-runs agent with specific gap named in Gaps: field. Confidence still below 0.7 after one retry: gap surfaced with warning in final report for manual review. Recurring gaps: add to foundry:curator antipatterns section: /foundry:manage update foundry-curator "add gap X to antipatterns-to-flag".
jq not found warning during /foundry:audit setup
Check 4 (permissions-guide drift) requires jq. Install: brew install jq on macOS, apt install jq on Linux. Audit continues without β only Check 4 skipped.
/foundry:audit's fix-level question is denied
Blocks only audit's fix follow-up until aggregate.md and valid summary.jsonl exist and Step 7 delivers Audit Report, exact Total, and every finding. A valid empty summary permits a zero-finding audit. Recovery questions remain available. Finish the producer, print the required current output, then retry the workflow follow-up. Existing sentinel expiry remains unchanged; it is not a completion receipt.
/foundry:profile's drill-down question is denied
Blocks only audit's fix follow-up until aggregate.md and valid summary.jsonl exist and Step 7 delivers Audit Report, exact Total, and every finding. A valid empty summary permits a zero-finding audit. Recovery questions remain available. Finish the producer, print the required current output, then retry the workflow follow-up. Existing sentinel expiry remains unchanged; it is not a completion receipt.
ποΈ Plugin structure¶
plugins/cc_foundry/
βββ .claude-plugin/
β βββ plugin.json version + metadata
β βββ permissions-allow.json allow-list merged by /foundry:setup
β βββ permissions-deny.json deny-list merged by /foundry:setup
β βββ permissions-deny-retired.json retired denies /foundry:setup removes from user settings
βββ agents/ 10 specialist agent files (flat β a nested `agents/<x>/y.md` would register as a dispatchable `foundry:<x>:y` agent)
βββ references/ agent sidecar fragments (`references/<agent>/*.md`), `cat`-loaded on demand; deliberately outside `agents/` so they are never scanned as agents
βββ skills/ 11 skill directories (audit, brainstorm, calibrate, create, distill, humanizer, investigate, manage, profile, session, setup)
βββ rules/ 18 rule files symlinked to ~/.claude/rules/foundry-*.md by /foundry:setup (+ 12 on-demand bodies in rules/_full/)
βββ CLAUDE.src.md workflow rules; /foundry:setup Step 10 copies β ~/.claude/CLAUDE.md
βββ TEAM_PROTOCOL.md AgentSpeak v2 inter-agent protocol
βββ permissions-guide.md annotated allow/deny reference (copied to .claude/ by /foundry:setup)
βββ hooks/
βββ hooks.json hook registrations (${CLAUDE_PLUGIN_ROOT} paths), including PreCompact and PostCompact; the two SessionEnd entries (task-log.js, audit-close.js) carry an explicit 15 s timeout β the cap every plugin's SessionEnd audit-close.js registration shares
βββ task-log.js SubagentStart/Stop tracking to /tmp/claude-state-<session>/; PreCompact/PostCompact append metadata to ~/.claude/logs/compactions.jsonl (no instruction or summary text); PreCompact writes the session's edited-file list from `timings.jsonl` (Write/Edit/MultiEdit/NotebookEdit, whole session) into session-context.md and saves skill-contract.md verbatim with verified/unverified status from the writer's session + raw-byte SHA-256 owner stamp, skipping matching foreign-session stamps; SessionEnd sweeps idle, clean, registered agent-*/oss-* worktrees (2 h) and keeps dev-*, dirty or output-holding trees
βββ statusline.js status bar agent counts
βββ teammate-quality.js TaskCompleted/TeammateIdle teammate output quality gate
βββ lint-on-save.js runs pre-commit after every Write/Edit; async + cross-session lock; 15s timeout; skips .temp/
βββ artifact-guard.js PostToolUse Write/Edit on .reports/**/*.md, .temp/**/*.md; soft 10K-token cap + .temp/ ultra-caveman article-density heuristic; feedback only, never blocks
βββ rtk-rewrite.js transparently rewrites CLI calls for token compression
βββ agent-router.js PreToolUse Agent hook; 3-tier routing fallback (worktree β cache β local)
βββ commit-guard.js PreToolUse Bash guard; git commit is prompt-discipline only, not hook-gated; `--check-grant` (the rule's local Git grant check, reads no stdin) prints `grant .git/claude-git-approval.json@<created_at>` for a record `lib/approval-grants.js` accepts, else `no grant: <reason>` β Foundry only, along with its grant writer; force push (any `--force*`, `-f` cluster, `+refspec`, `--mirror`, any abbreviation git accepts such as `--mir`, and an unquoted glob argument that a planted file could turn into one) blocked unconditionally on any branch for `push`, `send-pack`, `http-push`, `subtree β¦ push` and `lfs β¦ push` (program name matched lower-cased); every `remote-<transport>` helper and every subcommand under an enabling inline `help.autocorrect` count as force; a dry run (`--dry-run`, `-n`) passes without a token; regular push runs only by spending the single-use push token `claude-push-approval.json` (scope `push-once`, 15 min) through `lib/approval-grants.js` `spendToken` β an exclusive reservation and a rename claim per `tool_use_id`, so of parallel calls exactly one runs, after proving the approving `git-push` question and the user's Approve in the session transcript; a spawned agent's call or one without `tool_use_id` never spends it; by the user's decision it covers one push of the approved branch at the approved HEAD to a configured remote name (`git push [-u] [<remote>] [HEAD|<branch>][:<branch>]`, a current-branch substitution, `lfs push`), while a delete (`:ref`, `--delete`, `-d`), `--prune`, `--all`/`--branches`, `--tags`, `--follow-tags`, any other option or abbreviation, another ref, a URL, path, unknown or variable destination, `cd`/`pushd`/`popd`, a `-C`/`--git-dir`/`--work-tree`/`GIT_DIR` switch, inline config redirecting the push, `send-pack`, `http-push` and `subtree push` are never covered (blocked, token kept, the user runs them by hand); a second push, a loop, function or repeating runner, HEAD-moving git beside the push (commit, reset, checkout, update-ref, a config or remote write), a new commit or another branch is blocked (no auto-arm); any exception on the push path exits 2; SessionStart and `/clear` delete both tokens with their claims (per project, not per session), SessionStart also this repository's leftover `claude-push-auth-*` sentinels, which no longer authorize anything; invocations found through grouping, prefixes, quoting, brace expansion, any-case git names, inline `-c`/`--config-env` aliases, here-strings and heredocs fed to a shell, nested shell source and `python -c`/`perl -e`/`ruby -e`/`node -e` code via `lib/shell-git.js`; fails closed when the command cannot be parsed or the library is missing; accepted limits (third-party `git-<name>` programs, GitHub API force updates left to `gh-write-guard.js`) listed in its header and the lib header
βββ gh-write-guard.js PreToolUse Bash; blocks (exit 2) every GitHub write made through `gh` the user did not approve, before any allow rule applies: known write subcommands (`pr create/merge/comment`, `issue close`, `release`/`gist` writes, `repo edit/delete/sync/rename`, `run rerun/cancel`, `workflow run/enable/disable`, `label`/`secret`/`variable` writes and the rest of gh 2.102's write set, every documented group and verb alias included) wherever flags sit or however quoted (`$'β¦'` escapes bash and zsh read alike decoded, quoting they read differently blocked; a variable or substitution where a write verb could stand blocks), and `gh api` failing closed on any `-X`/`--method` but a literal GET (run-time values included), `--input`, a method-override or run-time header name, REST `-f`/`-F` fields without an explicit GET, a GraphQL `mutation`, a query file or a query the command text does not show, and unknown options, flags placed before the `api` word included; reads and unknown subcommands pass to normal permission handling; invocations found via `lib/shell-git.js`, which drops redirections while quoting is known; fails closed without the library or an exact argv (quoting the lexer cannot follow, gh named in interpreter code); inactive without Node.js (launcher exits 0, SessionStart diagnostic warns); an exact-argv write whose whole command text, trimmed, equals a valid `gh-write-once` token's `command` runs once: `lib/approval-grants.js` `spendToken` reserves the token before renaming it to a claim keyed by `tool_use_id`, so every loaded Foundry copy allow that call and no other, and the hook prints a PreToolUse `allow`; other text, an invalid, expired or foreign token, a spawned agent, no `tool_use_id`, no `lib/approval-grants.js` or a write found without an exact argv stays blocked and keeps the token; ships in Foundry only, along with both libs
βββ allow-dispatch.js PreToolUse Bash; the only registered auto-allow hook β calls the two required decision modules plus Foundry's optional gh-read module below as libraries in rank order (provenance, shape, gh read), writes the first allow's payload, and appends one audit record naming the effective verdict plus every module's own verdict; a module that throws contributes "none" and never suppresses the others; exits 0 on every path, because an allow-only hook that crashes grants nothing and falls back to normal permission handling; propagated to every plugin except codemap-py
βββ sentinel-read-allow.js decision module (shape lane), no longer registered as a hook of its own; auto-allows blueprint idioms ($(cat "${TMPDIR:-/tmp}/β¦") sentinel reads, $(date -u +FMT) stamps, substitution-free `IFS= read -r VAR < sentinel` form) when every segment is read-only β kills "Contains expansion" prompts for pre-canned skill code; whole-line `#` comments are skipped in segment validation (a separator after one still validates as live, and a quoted `#` stays data), and `..` rejects only as a real path component so `...`/`v1.2..v1.3` are not read as traversal; verdicts are invariant under runtime value substitution, which is what this module covers that `blueprint-allow.js` structurally cannot; keeps its standalone entry point, which `bin/audit_hook_coverage.py` still subprocesses; everything else falls through to normal permission checks; propagated to every plugin except codemap-py
βββ blueprint-allow.js decision module (provenance lane, rank 1), no longer registered as a hook of its own; normalizes the command, hashes it, and exact-matches it against the plugin's committed `blueprint-manifest.json` (generated by `bin/build_blueprint_manifest.py` from every verbatim bash block/command shipped in skills/agents/rules); a hit plus an independent danger re-check allows without a prompt, covering `$(python β¦)`/`$(git β¦)` capture-substitutions the shape-based module cannot reach; any deviation from the blueprint text misses and falls through to a normal prompt; propagated to every plugin except codemap-py
βββ github-read-allow.js decision module (gh-read lane, rank 3), not registered as a hook of its own; auto-allows a Bash command made only of allowlisted gh reads (read verbs, help and local config reads, `gh api` GETs incl. `?query`, explicit `-X GET` fields and double-quoted endpoint variables, GraphQL queries the text shows), each optionally piped into a closed filter set (`jq`, `head`, `tail`, `wc`, `sort`, `uniq`, `grep`, `cut`, `tr`, `column`) with no file, program or environment access, in every session and spawned agent, with no grant or record; never allows a gh write; ships in Foundry only; the shared dispatcher skips this optional lane where its module is absent
βββ audit-close.js PostToolUse/PostToolUseFailure (Bash) + SessionStart/SessionEnd; appends one record observing that a Bash call completed, and one per session boundary; writes nothing to stdout on any event (SessionStart stdout is injected into the model's context); every installed plugin writes its own row with nothing coordinating them, so four rows for one completion are corroboration rather than duplicates; propagated to every plugin except codemap-py
βββ lib/audit-log.js shared record writer for the two hooks above: canonical serialization (a cross-language contract with `bin/verify_blueprint_audit.py`), self-hash, and a single append into `~/.claude/logs/audit/s-<key>.jsonl`; append-only by design β it holds no lock, marker or state directory and has no code path that deletes or rewrites a file; never throws, so a logging failure cannot change a permission decision; `RIG_AUDIT=0` disables writing only; propagated to every plugin except codemap-py
βββ stale-plugin-check.js SessionStart (every source); when the plugin root this session loaded carries `.orphaned_at` or is not the installed path, prints one JSON object: a `systemMessage` the user sees (plugin, loaded vs installed version, `exit, then claude --resume <session_id>`) and `additionalContext` telling the model not to run that plugin's workflows on the stale copy until the user chooses, through AskUserQuestion, to restart or continue β asked once per session and plugin version: a compact/clear warning after that ask keeps the user notice but drops the ask, while a first detection at compact/clear (plugin replaced mid-session) still asks; each copy checks only its own plugin, because plugins update independently; silent when current or outside the plugin cache; never blocks; propagated to every plugin except codemap-py
βββ rules-check.js SessionStart (every source); when setup's `foundry-<name>.md` rule links are missing (setup never ran or failed), names each rule file to read and injects the commit rule so commits keep their format and co-author trailers
βββ rule-inject.js PostToolUse + PostToolUseFailure `Agent|Bash`, SessionStart `compact|clear`, PreCompact; injects `rules/agent-spawn.md` as `additionalContext` after the first `Agent()` call and `rules/git-commit.md` after the first Bash call running a commit-creating git command (via `lib/shell-git.js`), once per session and per subagent (`agent_id`); a call blocked by a PreToolUse hook never spends the slot; the context arrives beside the call's result, so commit-format bullets stay always-loaded in `claude-config.md`; reads the plugin's own `rules/` at run time; SessionStart `compact` restores the skill contract as verified or advisory via the writer's owner stamp (matching session + raw-byte SHA-256), skips matching foreign-session stamps, and uses a Read path above the injection cap; `clear` only resets sentinels
βββ approval-guard.js PostToolUse AskUserQuestion + PreToolUse Bash/Write/Edit/MultiEdit/NotebookEdit; runs the writer and the self-grant guard of `lib/approval-grants.js`. Writer: the user's `Approve always` answer to the exact `git-approve` question (options `Approve` / `Approve always` / `Deny`) writes `claude-git-approval.json` (version, scope, created_at, question, answer) in the git common dir β one grant per project, shared by every worktree; `git-push` answered `Approve` writes the single-use push token `claude-push-approval.json` (scope `push-once`, adding branch, head, expires_at) for one push of the current branch at the current HEAD (a detached HEAD records nothing), and `Deny` removes a pending token; `gh-write` answered `Approve` writes the single-use token `claude-gh-write-approval.json` (scope `gh-write-once`, adding command and expires_at) for the one gh command the question names in an inline code span (none, two, a fenced block or a span across lines records nothing), and `Deny` removes it; an `Approve always` description not naming that file, an answer the model pre-filled in its own call (read from the session transcript's `tool_use` block named by `tool_use_id`, tail-first, bounded to 16 MiB β never from PostToolUse `tool_input`, which the harness may fill with the user's pick), a spawned-agent question, a missing transcript, id or call, a rejected location, or a record path that is a symbolic link or not a regular file records nothing and says why; written through an exclusive temp file and a rename, and an identical record (times aside) is left unchanged by a silent copy, so the loaded Foundry copies leave one record and report it once. Guard: exit 2 on file-tool writes to any approval-record name or to a path named `.git` (other paths inside `.git` pass), and on Bash commands naming a record as a write target (a redirection or output option onto it, or an operand of anything but an exact read, delete, print or mention-only git subcommand; a glob counts only in the common dir; scope ids never count), the common dir as a whole where a command writes it (copy, move, sync, extract or clone destination, any `ln` operand, a redirect onto it, an operand of a command not known to only read), a brace or glob word that could expand to a record or the git dir, a claim or temp record name through a file tool, or that could run the writer β names matched as the shell and filesystem resolve them (quotes, escapes and `$'β¦'` removed, NFKC + case folded, Windows trailing dot/space/`::$DATA`/8.3 names, globs and braces, `.git//` and `.git/./` collapsed); exclusion operands (`--exclude`, `--ignore`, find's `-path`/`-name` without a writing action, `tree -I`, `zip -x`) are not targets; it sees names, never contents, so a patch, `git apply`, archive, script or forged payload stays prompt discipline; ships in Foundry only
βββ lib/approval-grants.js approval-record scope table (`local-git-non-destructive`, `push-once` and `gh-write-once`, each written), record store, branch and HEAD reader (`gitHead`, shared by the writer and the spend), single-use spend shared by every loaded Foundry copy (`spendToken`, `removeRecord`) and guard logic behind `approval-guard.js`, read by `commit-guard.js` (`--check-grant`, push token spend, token wipe) and `gh-write-guard.js` (gh write token spend); one `approval-record` family at version 1, one file per scope in the git common dir, resolved with `GIT_DIR`/`GIT_COMMON_DIR`/`GIT_WORK_TREE`/`GIT_CEILING_DIRECTORIES`/`GIT_DISCOVERY_ACROSS_FILESYSTEM`/`GIT_INDEX_FILE` removed and `-c safe.bareRepository=explicit`, refusing a bare repository, a missing work tree, a common dir not named `.git` or not linked back from its work tree, and one other than `CLAUDE_PROJECT_DIR`'s; a reader rejects an unknown record version or scope, so copies of different releases fail closed; mirrored as prose in codex-rig's `native-skill-contract.md`; ships in Foundry only
βββ lib/shell-git.js program-agnostic quote-aware shell lexer shared by `commit-guard.js`, `rule-inject.js` (git readings, `gitSubcommandArgs`) and `gh-write-guard.js` (`programInvocations`), shipped only in Foundry: finds every invocation in a Bash command through `{ }`/`if`/`time`/`sudo`/`xargs`, quoted args, brace expansion, substitutions, heredocs, here-strings and `bash -c`/`eval`/`env -S` source; unquoted redirections are dropped with their target and fd number while quoting is known (a quoted `>` stays an argument); the program name ignores case and `.exe`; inline `-c`/`--config-env` aliases expanded (run-time values read as a force push); an enabling inline `help.autocorrect` reads every subcommand as a force push; an unquoted glob argument adds a reading as `--force`; `python -c`/`perl -e`/`ruby -e`/`node -e` code naming git and push reads as a push, and code naming any other program makes `programInvocations` answer null (no exact argv, the caller fails closed); out-of-scope spellings listed as accepted limits in its header; for git a coarse operator split is unioned so detection only grows; fails closed on unparsable quoting or oversized brace expansion
βββ session-restore.js SessionStart matcher `clear`; reads `<cwd>/.claude/state/session/LATEST` from the hook payload's own `cwd`, and injects that handover doc as raw stdout (the documented SessionStart context channel) when it is unconsumed and under 30 min old β then marks it consumed and unlinks the pointer so a second `/clear` never re-injects; over ~8000 chars only `## Goal` + files table + `## Next step` + a `/foundry:session recall` pointer go in; every other path is a silent exit 0, so it can never block a session start
βββ write-guard.js PreToolUse Edit/Write/NotebookEdit; guard-only inverse of the two allow hooks above β grants nothing, only forces a confirmation (`permissionDecision: "ask"`) on writes to a small repository-independent protected set: `.github/**`, `CLAUDE.md`/`AGENTS.md`, `.claude/settings*.json`, `.pre-commit-config.yaml`, `CHANGELOG.md`, dependency lockfiles, `pyproject.toml`; a protected file git already tracks passes through, because every write to it is diffable and revertible β except `.claude/settings*.json` and `.pre-commit-config.yaml`, which always ask because they take effect before any diff review; untracked, new and gitignored protected files ask, and a failed git probe counts as untracked; source and tests are deliberately unprotected, so pairing with `acceptEdits` removes routine friction while untracked protected files and permission config stay gated; patterns are anchored on a path-separator boundary (`src/my_changelog_helper.py` passes through), matched after POSIX normalization so Windows backslashes cannot disable it, and case-insensitive because macOS/Windows filesystems case-fold (`changelog.md` writes the real `CHANGELOG.md`); everything else is silent passthrough; propagated to every plugin except codemap-py
βββ md-compress.js normalizes markdown whitespace/table-padding in place on Edit
βββ batch-nudge.js PreToolUse/PostToolUse/UserPromptSubmit; tracks a streak of sequential batchable calls (Read/Grep/Glob, read-only Bash prefixes) separated by model-round-trip-sized gaps (β₯1.5s); at streak 4, nudges via PostToolUse exit 2 (stderr feedback, never blocks β PreToolUse always exits 0)
βββ enforce-audit-header.js Blocks only audit's fix follow-up until aggregate.md and valid summary.jsonl exist and the follow-up call's first question text is a short plain summary (at most 1,500 characters) naming the `summary.jsonl` path and the Audit Report headline; option descriptions and previews never supply it, and a reply-text copy before the call does not count. A valid empty summary permits a zero-finding audit. Recovery questions remain available. On Stop: keeps a turn going once per aggregate when its final reply (the text after the turn's last tool call) lacks the full Audit Report; a shown follow-up does not satisfy it.
βββ enforce-profile-header.js Blocks only the owning workflow follow-up until its report exists and the follow-up call's first question text is a short plain summary (report path, `Title`, `Outcome`/`Verdict`; at most 1,500 characters, no table); option descriptions and previews never supply it, and a reply-text table before the call does not count. Diagnostic/recovery questions remain available; unreadable delivery evidence is not success. On Stop: keeps a turn going once per report when its final reply (the text after the turn's last tool call) lacks the full header table; a shown follow-up does not satisfy it.
βββ report-header-table.js Shared local module: matches workflow-specific questions and current report headers; at question time checks only the call's first question text for the report path and the `Title` and `Outcome`/`Verdict` values within 1,500 characters; at Stop only the final reply after the turn's last tool call counts for the full header table.
Single-use push and GitHub-write approvals use an exclusive reservation in the git common directory while validating, spending, replacing or revoking a token. Concurrent calls wait at most one second, then deny if it remains busy; copies of the same tool call share its proven claim. The reservation is removed on normal completion. If a hook crashes, stop active hooks before manually deleting <token-file>.spent-lock from the git common directory; Foundry also removes these reservations at SessionStart and /clear.
π§° Bin helper inventory (53 shipped deterministic helpers)¶
These Python helpers are installed workflow support and maintainer surfaces, not additional slash-command skills. They resolve paths, validate plugin documents, prepare reports, or perform bounded deterministic transforms; invoke them through the owning skill or with the installed plugin root.
propagate_shared.py and check_orphaned_bin.py use ASCII status labels so success, no-op propagation, and orphan warnings work with legacy console encodings such as Windows cp1252.
Audit, consistency, and document checks¶
| Helper | Purpose |
|---|---|
audit_churn.py |
Emit git-history recurrence signals for /foundry:audit. |
audit_preflight.py |
Parse /foundry:audit arguments and prepare its run state. |
audit_static.py |
Run and aggregate the deterministic Layer-1 audit checks. |
check_agent_waits.py |
Flag skill/agent text that polls for agents or sends task bookkeeping as its own turn. |
check_bash_persistence.py |
Detect shell variables referenced across separate Bash tool calls. |
check_bin_test_coverage.py |
Require every bin/ script to have a real test file (audit Check R4). |
check_cli_flag_drift.py |
Detect drift between documented flags and argparse options. |
check_codemap_guard.py |
Detect unmanaged codemap index-guard copies. |
check_bridge.py |
Detect whether the bridge plugin is installed and enabled. |
check_fence_symmetry.py |
Validate Markdown code-fence pairing and nesting. |
check_gitignored_refs.py |
Reject tracked-file references to this repo's gitignored documents. |
check_install_state.py |
Validate post-install state under ~/.claude for /foundry:audit, including native Windows symlink targets. |
check_mode_dispatch.py |
Detect dangling mode-dispatch references in skill files. |
check_orphaned_bin.py |
Find bin scripts not referenced by plugin Markdown. |
check_plugin_layout.py |
Validate a plugin's on-disk layout for /foundry:audit Check 8. |
check_plugin_module_docs.py |
Require a module docstring on every shipped plugin module. |
check_plugin_version_sync.py |
Require matching dual-host manifests, retain tracked host manifests in shipped plugins, and check changed shipped JSON version, schema, and protocol fields plus matching Python dictionary literals and module-level *_VERSION, _SCHEMA, and PROTOCOL integers against committed HEAD. |
check_readme_drift.py |
Detect README version and bin-reference drift from disk. |
check_routing_links.py |
Validate computed paths in skills and agent files. |
check_rtk_alignment.py |
Compare the RTK rewrite hook's prefix list against the installed RTK. |
check_skill_contracts.py |
Run structural contract checks over skill Markdown for /foundry:audit. |
check_spawn_prompt_vars.py |
Detect unresolved variables in Markdown spawn prompts. |
check_tag_symmetry.py |
Check XML-tag symmetry in agent and skill Markdown. |
classify_resolver_sites.py |
Classify resolver call sites as inline-required or cat-only. |
scan_efficiency_signals.py |
Emit deterministic cost signals for /foundry:audit --efficiency. |
Plugin manifests accept numeric
X.Y.Zversions: unchanged or one patch, minor, or major bump from committed HEAD. Python dictionary literals bind to an enclosing class/function and a direct named assignment when present, then compare by version field name. A newly inserted named assignment does not shift another producer's baseline. Changed duplicate inline fields without stable assignment names fail closed because the checker cannot identify which producer changed. Computed keys, dynamic values, and reassignment through a dictionary variable are outside this static check.
Session, install, and state helpers¶
| Helper | Purpose |
|---|---|
find-polluter.py |
Binary-search a test suite for test-isolation contamination. |
agent_watch.py |
Report each spawned agent's deliverable and deadline in one call β no polling. |
append_ledger.py |
Append staged .rec records (or --from files) to a growing ledger without rewriting it. |
compact_gate.py |
Decide whether boundary compaction saves read-token cost from live session usage. |
get_plugin_install_path.py |
Resolve the active plugin path from Claude's registry. |
heal_git_artifacts.py |
Reclaim stale skill locks and orphaned git worktrees. |
jq_write.py |
Apply an atomic jq JSON edit through temp-file replacement. |
load_mode.py |
Resolve a skill mode/template directory and emit one file. |
load_shared_doc.py |
Resolve the plugin shared-doc directory and emit one file. |
make_run_dir.py |
Create a UTC-timestamped run directory. |
purge_plugin_cache.py |
Remove orphaned versions from the plugin cache. |
remove_hook_from_registry.py |
Remove matching hook commands from a JSON registry. |
resolve_agent_file.py |
Locate the source file for a calibration target. |
resolve_memory_dir.py |
Resolve the project-scoped Claude memory directory. |
resolve_plugin_root.py |
Resolve and validate the current installed plugin root. |
resolve_shared_path.py |
Resolve a plugin's shared directory portably (own, or the active codemap-py install). |
resolve_skill_subdir.py |
Resolve a skill modes/ or templates/ subdirectory. |
state.py |
Persist small shell values across Bash tool-call boundaries. |
symlink_with_guard.py |
Scan and safely manage foundry-init symlink conflicts. |
write_skill_contract.py |
Write contract + owner stamp; optional -- calls_ahead=N drop=... interactive=false tail. |
Reporting, synchronization, and transforms¶
The shared-file checker also keeps the short Codex question guide and conditional details identical across Codex Rig, Codemap and Bridge. Copies ship independently; installed plugins do not depend on one another.
| Helper | Purpose |
|---|---|
audit_hook_coverage.py |
Measure how often the auto-allow hooks fire across session transcripts. |
build_blueprint_manifest.py |
Hash shipped bash blueprints into the per-plugin allow manifest. |
cost_analyzer.py |
Bucket Claude session token usage and estimated cost. |
extract-keep-flag.py |
Parse --keep "<items>" and clear a stale compaction contract. |
extract_code_blocks.py |
Extract and classify fenced Markdown code blocks. |
list_audit_files.py |
Inventory the config files /foundry:audit covers, with block counts. |
measure_config_size.py |
Size the config Claude loads, for /foundry:audit. |
parse-skill-flags.py |
Parse boolean and value skill flags into shell assignments. |
propagate_shared.py |
Keep byte-identical cross-plugin shared files synchronized. |
resolve_centrality.py |
Convert codemap centrality output into a resolver map. |
timing_analyzer.py |
Bucket Claude session clock time into a Markdown report. |
verify_blueprint_audit.py |
Verify and prune the auto-allow audit log in ~/.claude/logs/audit/. |
verify_perm.py |
Verify permission presence across settings and the permissions guide. |
π Upgrade¶
Then, inside Claude Code:
Re-run /foundry:setup after upgrade required β symlinks point to versioned cache path, go stale after reinstall.
ποΈ Uninstall¶
Claude Code runs no cleanup hook on uninstall, so nothing /foundry:setup created is removed by claude plugin uninstall or by make clear-all. Settings keys merged into ~/.claude/settings.json (statusLine, permissions.allow, permissions.deny, enabledPlugins, advisorModel, env) remain β remove manually if desired. The ~/.claude/rules/foundry-*.md symlinks and ~/.claude/TEAM_PROTOCOL.md also persist and dangle once the plugin cache version is gone; delete them by hand.
Auto-allow audit log¶
allow-dispatch.js and audit-close.js append one JSON line each per Bash call to ~/.claude/logs/audit/: one file per session (s-<key>.jsonl, the key being a hash of the session id) plus a shared _no-session.jsonl for calls that arrive without one. A record names the deciding plugin and hook, the session and tool-call ids, the working directory, the effective decision with its lane and provenance, and both decision modules' own verdicts. Command text is never written β only a digest, and for a provenance allow the manifest src it matched. Note the limit honestly: task-log.js already writes the first 200 characters of every Bash command into timings.jsonl under the same tool_use_id, so the guarantee is about this file rather than about the log directory.
With several plugins installed each writes its own rows and nothing coordinates them, so one completed call yields one row per plugin at each end. That is corroboration, not duplication.
python "${CLAUDE_PLUGIN_ROOT}/bin/verify_blueprint_audit.py" verify
python "${CLAUDE_PLUGIN_ROOT}/bin/verify_blueprint_audit.py" prune --older-than 30 --dry-run
verify exits 1 only when a record's own hash fails to verify; torn framing from concurrent appends and every classification are warnings. record_hash detects a record that changed after it was written β it is not tamper-evidence, because the same user owns the log, the hooks and the checker.
Read the classifications as observations rather than proof: a call with a decision row and no completion row is reported as completion-unobserved when the session later ended and in-flight-or-hard-kill when it did not, and neither distinguishes a refusal at the prompt from a deny rule elsewhere or a closer that died. Equally, that a tool ran never establishes that a human approved it.
prune is the only component that deletes a log file, is never run by a hook, and never prunes _no-session.jsonl by age β a growing one means the host stopped sending a session id. Nothing schedules prune.
RIG_AUDIT=0 disables audit writing without changing any permission decision.
Records follow the twelve mandatory fields of "Agent Audit Trail: A Standard Logging Format for Autonomous AI Systems", an active individual-submission Internet-Draft that is not endorsed by the IETF and has no standing in its standards process. Deliberate deviations: outcome adds pending, because a decision that has not executed yet has no outcome in the draft's vocabulary; trust_level is plugin/unknown rather than the draft's L0βL4, because what is observed is which component proposed an allow, not an authentication level; agent_id is <plugin>/<hook> rather than a URI; session_id is the host's id verbatim rather than a UUID; and parent_record_id and prev_hash are always null, because this log is not chained β lineage is derived on read instead.
π§ͺ Development / testing¶
One runner (pytest) covers both Python bin/ scripts and JS hooks. Current test count: grep -rc 'def test_' plugins/cc_foundry/tests/*.py | awk -F: '{s+=$2} END{print s}'.
Apply pytest markers and conditional skips directly to test functions or existing test classes, as described in the bin authoring guide.
Run locally (requires Python β₯ 3.10 and Node β₯ 18 β run from repo root):
JS hook tests only:
JS hook tests (subprocess, black-box)¶
Each test spawns node <hook>.js with JSON payload on stdin, asserts filesystem side-effects under /tmp/claude-state-<session_id>/ + exit codes.
| File | Hook | Tests | Covered |
|---|---|---|---|
test_task_log_js.py |
task-log.js |
35 | Agent lifecycle (Pre/PostToolUse, SubagentStart/Stop), tool_use_idβagent_id re-key on SubagentStart (direct + agent_type-fallback match, including named agents matched by name) so SubagentStop's unlink actually lands, background agents kept past dispatch-time PostToolUse, pending/ race condition, worktree last_active refresh on tool activity, codex skill tracking, per-tool counters (last stamp per call, inflight opened at PreToolUse and released on PostToolUse/Failure, floored at 0 on duplicate fire), SessionEnd stale-worktree sweep keeps dev-*, uncommitted and fresh trees |
test_stale_plugin_check_js.py |
stale-plugin-check.js |
77 | Runs every shipped copy (foundry, oss, develop, research) under its own plugin name: silent for the installed root, a source-tree root, a missing registry, or another event; for an orphaned, replaced or uninstalled root prints exactly one JSON object whose systemMessage names the plugin, both versions and the claude --resume <session_id> step (a plain restart when the payload has no session id) and whose additionalContext requires an AskUserQuestion before that plugin's workflows run β dropped on a compact/clear after this session already got it, kept on first detection at compact/clear, on resume, and without a session id; each plugin registers its own copy once on SessionStart, and every copy is in the propagation manifest |
test_rules_check_js.py |
rules-check.js |
4 | Blank config names every rule and injects the commit rule without frontmatter; silent when all rules are linked or on another event; a partial install names only the missing rule |
test_rule_inject_js.py |
rule-inject.js |
80 | Agent injects the rule body once (exact PostToolUse/PostToolUseFailure additionalContext shape, no frontmatter), second Agent silent, each agent_id and each session injects once; git add/commit/push in any segment or spelling inject (x && git commit, git -C dir push, env VAR=1 git push, heredoc message), git status/echo git commit-ish/git commit-tree/git stash push silent; compact/clear reset sentinels; compact restores contracts, clear silent; startup leaves them; a rule over 9,500 chars injects summary + rule and _full paths; malformed/empty/foreign payloads, missing rule file and unwritable temp dir exit 0 silently; both shipped rules inject in full under the cap; grouped/wrapped/nested, GIT, inline-alias, bash <<<, inline help.autocorrect and redirection-before-subcommand (git 2>/dev/null commit) forms and merge/revert/cherry-pick/am/rebase/annotated tag inject; PreToolUse claims nothing; a commit-guard-blocked push leaves the slot; subagent PreCompact re-arms only that subagent; missing lib fails open; registration matchers |
test_statusline_js.py |
statusline.js |
23 | π€ segment: empty/active/stale agents, non-worktree agent stays visible past 10 min (30-min backstop) and drops past 30 min, long-running agent kept visible by fresh last_active, codex agent type label (via agents/ and the codex/ dir), missing-agents/-dir tolerance. π οΈ segment: freshness from last (a call 5 s into a 40 s-old window stays shown), in-flight call visible past the 30 s window, finished call and leaked in-flight slot dropped, legacy since-only record still renders |
test_commit_guard_js.py |
commit-guard.js |
403 | Push guard: every remote write needs a single-use push-once token covering one push of the approved branch at the approved HEAD to a configured remote, remote and branch written out (delete, prune, tags, other ref, URL, variable target, cd, inline push redirects blocked); spent once by rename-claim with transcript proof, never by a spawned agent; a dry run spends nothing. Force and mirror pushes blocked even with a token. Shell readings: heredoc data vs code, substitutions, zsh-only syntax failing closed with the syntax named. Local Git (staging, merge, cherry-pick, branch/tag delete, reset, stash, worktree) passes. --check-grant, SessionStart and /clear token wipe, fail closed on internal errors. |
test_approval_guard_js.py |
approval-guard.js |
411 | Writer: records only from the user's own answer to the exact question (git-approve Approve always with the record file disclosed; git-push and gh-write Approve with question id, branch and HEAD or the exact command and file digests); refuses spawned agents, prefilled answers, drifted option sets, hidden characters and compound gh spans; Deny revokes pending tokens; only the writing copy reports. Two and four Foundry installs in temporary roots race on the same answer and tool-call id, leaving one record and spending one token. Self-grant guard: blocks Bash and file-tool writes of record, claim and temp names (brace, glob and symlink spellings), writes onto the git dir as a whole, running the writer, and named writes to a session transcript; reads, revoke, memory files and local Git reads of .git pass. |
test_gh_write_guard_js.py |
gh-write-guard.js |
447 | Blocks every gh write without a gh-write-once token for the exact command; the token runs once across all loaded Foundry copies (rename-claim), only with its transcript proof, unchanged file operands and a single gh invocation. Force updates (repo sync --force, pr update-branch --rebase, force fields including nested GraphQL keys, --input to refs or GraphQL) blocked even with a token. Reads pass; wrappers, substitutions, heredocs and zsh-only syntax (named) are covered. |
test_agent_router_js.py |
agent-router.js |
12 | Builtin/plugin-agent passthrough (tier 1), unknown-agent reroute to general-purpose (tier 3), SessionStart index build, tier-2B LLM request (resolved model ID, thinking disabled) and response parse (typed text block; error/non-2xx/no-text raise, never a silent "none"), runtime Haiku resolution (env override, 24h cache, Models API pick by line, logged fallback, session-scoped cache path) |
test_batch_nudge_js.py |
batch-nudge.js |
9 | PreToolUse never blocks regardless of streak; no nudge below threshold; nudge (PostToolUse exit 2 + stderr) fires exactly on the call crossing threshold; small-gap calls (same-message dispatch) never accumulate; non-batchable tool (Edit) resets streak; UserPromptSubmit resets streak; read-only Bash prefix (git log) counted batchable, compound and mutating Bash commands are not |
test_sentinel_read_allow_js.py |
sentinel-read-allow.js |
146 | Blueprint sentinel reads + date stamps + substitution-free IFS= read -r form allowed (observed prompt commands, unquoted variant, defaults, pipes into whitelisted tokens); passthrough on non-whitelisted tokens, non-TMPDIR paths, nested/second substitutions, backticks, process subst, heredocs, write redirects, quote smuggling, Codex bypass PoCs; whole-line comments skipped without letting a separator hide a live command, and a quoted # stays data; .. matched as a path component so .../v1.2..v1.3/file..txt allow while leading/mid/trailing/bare/${V:-..} traversal rejects; verdict invariant across runtime value substitution (sentinel basename, var name, CSID form, target filename, subdir path) but flips on injection through the varied slot; no updatedInput emitted; non-Bash + malformed JSON hygiene; requiring the module attaches no stdin listener and does not exit, and evaluate labels allow / passthrough(shape-mismatch) / none(not-applicable) without changing the payload decide produces |
test_blueprint_allow_js.py |
blueprint-allow.js |
119 | Verbatim block/command/trailing-comment/trailing-whitespace/CRLF variants allowed with src provenance, no updatedInput; one-char deviations and recombined halves of two manifest entries passthrough; missing manifest, malformed JSON, wrong-typed entries, non-Bash tool, malformed stdin fail open; independent danger re-check refuses even a tampered danger-token entry the generator would never emit; normalization vectors shared with the generator via blueprint_normalization_vectors.json (digests agree across JS/Python); evaluate labels each decline by the check that made it (danger, manifest-unavailable, bailout, no-match) and carries a digest exactly when the command was normalized |
test_write_guard_js.py |
write-guard.js |
39 | Every protected class asks with its labelled reason (CI, agent instructions, permission config, lint gate, changelog, lockfile, package metadata); in a git repository a tracked protected file (relative or absolute path) passes through while untracked, new and gitignored ones still ask, and tracked .claude/settings.json and .pre-commit-config.yaml ask anyway; absolute paths, Windows backslash separators and case variants (changelog.md, UV.LOCK β case-folding filesystems) match; Write and NotebookEdit (via notebook_path) guarded alongside Edit; routine source/test edits and anchored near-misses (src/my_changelog_helper.py, src/github/client.py) pass through; unguarded tools, missing path and malformed stdin fail open; the only permissionDecision in the source is ask, and no updatedInput is emitted |
test_enforce_audit_header_js.py |
enforce-audit-header.js |
β | Current aggregate summary in the first question's text (summary path and Audit Report headline; option fields and reply text alone denied), 1,500-character cap, a shown follow-up never settling final-reply delivery, zero findings, missing/unreadable evidence, recovery questions, and sentinel lifecycle. |
test_enforce_profile_header_js.py |
enforce-profile-header.js |
β | Current report summary in the first question's text (report path, Title, Outcome; option fields and reply text alone denied), 1,500-character cap, a shown follow-up never settling final-reply delivery, recovery boundary, state-file parsing, and sentinel lifecycle. |
test_report_header_table_js.py |
report-header-table.js |
β | Question-time summary (report path, Title, Outcome or Verdict in the first question's text; later questions, option descriptions, previews, tables and HTML denied; 1,500-character cap; optional previews held to the host preview cap), Stop judged on the final reply only and never settled by a shown follow-up, once-per-report continuation, inline-markdown value cells and workflow recognition. |
run_hook and state_dir = pytest fixtures in conftest.py; test methods receive as parameters β no from conftest import needed.
Why Python tests for JS hooks? Black-box contract tests: each spawns real node process, sends JSON payload on stdin, asserts filesystem side-effects + exit codes. Python = orchestrator only β JS runs for real. One runner (pytest) covers all plugin tests (Python bin/ scripts + JS hooks), no package.json or node_modules required. Tradeoff: covers hook interface contract, not individual JS function internals. Jest = right addition if internal logic ever warrants unit testing.
Python bin/ script tests¶
| File | Script | Tests | Covered |
|---|---|---|---|
test_check_routing_links.py |
check_routing_links.py |
42 | Computed path resolution, orphan-risk detection (R2), bin-ref integrity (R3), security path guard |
test_check_codemap_guard.py |
check_codemap_guard.py |
42 | MANIFEST-managed vs registry-declared vs provider-CLI classification, registry rot detection, git-root anchoring and raw-project-name invariants |
test_build_blueprint_manifest.py |
build_blueprint_manifest.py |
37 | Normalization (CRLF, quote-aware comment strip, blank-run collapse, spacing variants never collide), logical-command splitting (never on ;/&&, continuation-safe, escaped-backslash-aware), heredoc/quote-span bail-out to whole-block-only, danger-token detection (deferred + block-level, dropped count reported), block+command dual hashing with src at the opening fence, cross-OS path posix-ification (no backslash ever in emitted JSON), --update/--check drift + determinism, byte-identical two-run reproducibility |
test_symlink_with_guard.py |
symlink_with_guard.py |
35 | Create/update/remove symlinks, guard against stale links, unconditional purge of ~/.claude/skills/ links |
test_extract_code_blocks.py |
extract_code_blocks.py |
30 | Fence parsing, lang normalisation, heuristic code/prose classification, token filtering |
test_check_agent_waits.py |
check_agent_waits.py |
28 | Waiting-tool, fixed-poll and bookkeeping-turn detection vs prohibitions; repo-wide zero-finding gate over every plugin; foundry spawn sites use the shared deadline protocol; quality-stack nested index freshness |
test_check_bash_persistence.py |
check_bash_persistence.py |
28 | Cross-block variable reference detection, env-var filtering, multi-block files |
test_find_polluter.py |
find_polluter.py |
30 | Safe/unsafe node-id validation, isolation test runner, bisect loop, path-traversal + report guidance |
test_check_cli_flag_drift.py |
check_cli_flag_drift.py |
35 | AST flag extraction, invocation-scoped matching, docstring Usage:-block scan vs own argparse surface, REMAINDER passthrough, no-exec guarantee |
test_audit_log_js.py |
hooks/lib/audit-log.js |
62 | Canonical serialization and record hash matched against committed cross-language vectors (key order, explicit null versus absence, non-ASCII and above-BMP characters, integers only, ill-formed strings rejected); filesystem keys hashed so a/b and a?b cannot merge and lone-surrogate ids are rejected before hashing; directory 0700 and file 0600; append never rewrites earlier bytes; disabled, unwritable, symlinked and unserializable cases return _unwritten rather than throwing; _no-session soft cutoff refuses and leaves the file byte-identical; identity read from plugin.json, not the version-named directory; the export surface contains no file-removing API |
test_shell_git_js.py |
hooks/lib/shell-git.js |
259 | Shell lexer shared by the guards: simple commands across quoting, substitutions ($(β¦), backticks, process substitution), heredocs (quoted bodies as data to data commands, bodies fed to a shell as code, a heredoc inside $( that bash 3.2 could close early failing closed), wrappers (env, xargs, sudo, find -exec), zsh-only and bash/zsh-divergent syntax marked unreadable with a named reason, and the coarse fallback pass. |
test_hook_stdout_captures.py |
both decision modules + allow-dispatch.js |
37 | Replays fixtures/hook_stdout_captures.json, the raw stdout bytes both modules wrote before the refactor, across the whole command corpus and six manifest scenarios: each module's standalone driver is byte-unchanged, and the dispatcher writes `blueprint |
test_allow_dispatch_js.py |
allow-dispatch.js |
30 | One row per call carrying the effective verdict plus every lane's verdict; blueprint outranks shape when both allow; a gh read is allowed at rank 3 with no record, and blueprint outranks it; real dispatcher rows verify with three lanes, or two when the optional gh-read module is absent; whitespace splits the lanes (passthrough versus none); both-none writes no row; a module that throws or fails to load leaves the other deciding and is recorded module-error; a stalled shape lane does not delay an already-written blueprint allow; RIG_AUDIT=0 never loads the library, and a broken library leaves stdout and exit code untouched; raw command text never reaches the log and nothing outside the audit directory is created; integration-marked concurrent writers all verify their own hashes |
test_github_read_allow_js.py |
github-read-allow.js |
429 | Allowlisted gh reads (read verbs with any option, help and local config reads, gh api GETs incl. ?query, explicit -X GET fields and double-quoted endpoint variables after a literal /, GraphQL queries the text shows), each optionally piped into the closed filter set, are allowed in a repository, outside one and in a spawned agent with no record, and gh-write-guard passes every one; filters reading or writing a file, running a program or reading the environment, run-time text outside the endpoint, GET fields that may reach GraphQL, config get oauth_token, extension help, gh writes and unreadable quoting are not; without the lexer nothing is allowed |
test_heredoc_in_substitution_js.py |
lib/shell-git.js |
9 | A heredoc inside $(β¦) whose body closes the substitution early in bash 3.2 is unreadable syntax (fail closed); the git commit -m "$(cat <<'EOF' β¦ EOF )" idiom with balanced parentheses or an apostrophe stays readable. |
test_audit_close_js.py |
audit-close.js |
32 | Success and failure rows with the same status words timings.jsonl uses; both events write both rows; authority is never inferred from execution; non-Bash and unhandled events write nothing; lifecycle rows are skipped without a usable session id while completion rows still land in the shared stream; four simulated plugins produce four retained rows naming their own versions; stdout empty on every event; nothing created outside the log |
test_verify_blueprint_audit.py |
bin/verify_blueprint_audit.py |
106 | Table-driven against fixtures/audit/ β a corpus and its expectations built before the verifier existed. Exit 1 only on a failed record hash; torn framing and every classification stay warnings. Covers the classifications that were wrong in earlier designs: a healthy four-plugin file is clean, four closers are corroboration while a missing one is incomplete-closure, a four-plugin shape allow is clean apart from multi-allow, abstention requires a real passthrough, and none/module-error are counted apart from it. Plus the reader's own robustness: a stray directory or dead symlink in the log dir must not raise, since an uncaught raise exits 1 and that code means corrupt-record. Plus acceptance of two or three distinct known verdict lanes and rejection of missing, extra, duplicate or unknown lanes and values this system never writes, both limits, the JSON output shape and totals, and prune's dry run, age cutoff, --keep-session and refusal to prune _no-session.jsonl |
test_audit_registration.py |
hooks.json + propagation + docs | 38 | Every plugin registers the dispatcher, registers neither decision module directly, and registers the closer on all four events with the right matcher; the four new files are in the propagation manifest with all three copies and the check-only gate passes; the verifier is named in all four READMEs (a gap check_orphaned_bin.py does not cover) and no document still claims a registration order; the shape module still runs as a subprocess for audit_hook_coverage.py; a claude-state-<session> teardown cannot touch the audit log |
test_audit_hook_coverage.py |
audit_hook_coverage.py |
22 | Manifest union across plugins (single-plugin probe under-reports), newest-version selection by numeric key at any nesting depth, mechanism attribution with blueprint winning over shape, memoization, shape-hook argv/payload dispatch, transcript parsing (Bash-only, malformed-line and vanished-file tolerance), --since/--project/--skills-only filters |
test_verify_perm.py |
verify_perm.py |
21 | Settings allow-entry detection, missing/malformed JSON, CLI exit codes |
test_check_orphaned_bin.py |
check_orphaned_bin.py |
21 | Orphaned bin/ script detection, consumer-reference parsing, multi-plugin scan |
test_cost_analyzer.py |
cost_analyzer.py |
30 | Message-id dedupe (3x-inflation guard), per-model pricing (future id of a known family at that family's newest price with a stderr warning, no-family id and retired id without its own row at per-field max, cache write/read ratio per model, 1h vs 5m write split, Haiku 5.5 prompt-size band), main/sidechain bucketing with per-call cost, subagent-transcript merge, session discovery no-double-count, CLI session-id + window modes |
test_jq_write.py |
jq_write.py |
18 | Arg parsing, JSON path writes, merge semantics |
test_resolve_shared_path.py |
resolve_shared_path.py |
23 | Plugin/subdir validation, path traversal rejection, tier-1/2/3 resolution cascade, registry record beating a newer cache dir, un-prefixed provider source fallback |
test_heal_git_artifacts.py |
heal_git_artifacts.py |
67 | PID liveness on both platform branches (Windows simulated on every host, POSIX probe patched so signal 0 is asserted rather than sent), lock verdicts, worktree tiering, CLI exit codes, and a guard that no doctest calls the POSIX probe β signal 0 is CTRL_C_EVENT on Windows, so one did abort every Windows CI leg with a KeyboardInterrupt |
test_check_fence_symmetry.py |
check_fence_symmetry.py |
17 | Unclosed fences, nested/interleaved fences, multi-file scan |
test_check_gitignored_refs.py |
check_gitignored_refs.py |
12 | Token extraction, existence+check-ignore classification in a real tmp checkout, waiver marker, placeholder templates, CLI exit codes |
test_check_bridge.py |
check_bridge.py |
9 | nested registry parsing, installPath requirement, stale-cache rejection, settings opt-out |
test_make_run_dir.py |
make_run_dir.py |
16 | Portability invariants (no /tmp literals, no utcnow), timestamp format |
test_check_spawn_prompt_vars.py |
check_spawn_prompt_vars.py |
16 | $VAR in markdown code blocks, caller-substituted-var whitelist, multi-file scan |
test_check_tag_symmetry.py |
check_tag_symmetry.py |
14 | Empty/whitespace XML blocks, unbalanced open/close tags, multi-file scan |
test_resolve_skill_subdir.py |
resolve_skill_subdir.py |
11 | Tier-1/2/3 subdir resolution cascade, local-override flag, fallback ordering |
test_purge_plugin_cache.py |
purge_plugin_cache.py |
17 | Report-vs-apply contract, seven deletion guards, --expect-count abort, argument rejection |
test_resolve_memory_dir.py |
resolve_memory_dir.py |
10 | Path slugification, PROJECT_ROOT override, git fallback, missing-git fallback |
test_resolve_plugin_root.py |
resolve_plugin_root.py |
9 | Registry lookup, cache-scan fallback, orphaned-version skip, security gates (cache-dir + manifest-name) |
test_get_plugin_install_path.py |
get_plugin_install_path.py |
18 | Registry lookup, multiple-entry tie-breaking, missing plugin exit code, stale loaded-root warning (orphaned or not the installed path) |
test_classify_resolver_sites.py |
classify_resolver_sites.py |
5 | Directory scan for extractable/non-extractable resolver sites, CLI report + --list-extractable β maintainer-only, ad hoc invocation, not wired into any skill |
test_c33_dir_resolution.py |
(C33 dir-resolution logic) | 4 | Latest-version selection, older-version exclusion, no-cache fallback |
test_audit_preflight.py |
audit_preflight.py |
18 | Bare-scope/challenge-alias parsing, --keep flag extraction, tool-presence marker caching (fresh/expired/corrupt), session-scoped state files, mutually-exclusive-flag and missing-dir exits, template resolution |
test_check_bin_test_coverage.py |
check_bin_test_coverage.py |
17 | Real-function counting vs stub bodies (pass/ellipsis), async and nested-class tests counted, non-test functions ignored, syntax-error files treated as covered, missing/empty/stub-only test files flagged, private scripts skipped, --local clean/dirty tree gating |
test_check_install_state.py |
check_install_state.py |
20 | Nested/flat registry shape resolution, missing registry/foundry/cache-dir severity, settings merge completeness, statusline and allow-list checks, bridge enablement, stale-hook and rule-link staleness, native Windows and POSIX symlink-target checks, --check dispatch |
test_check_plugin_layout.py |
check_plugin_layout.py |
26 | Manifest presence/validity/required-fields/name checks, real dir/file existence severities, reference resolution, hooks.json presence and validity, hooks-registry sync across plugin/settings, setup-skill keyword coverage, absent-CLI and claude plugin validate timeout degrading to a skip so 8f/8g still run, absent-plugin-dir and broken-plugin runs both reporting on stdout at exit 0, plugin-root reference extraction |
test_check_rtk_alignment.py |
check_rtk_alignment.py |
17 | Single/double-quoted prefix-array parsing, missing/unreadable file fallback, indented-command extraction, aligned-list pass, unknown/unlisted/meta-command classification, grep -w word-boundary semantics (prose-only mention valid, hyphen a boundary, bare substring still invalid), RTK/hook-absent skip, unparsable list skip, rtk --help timeout skip, misaligned prefixes reported on stdout |
test_check_skill_contracts.py |
check_skill_contracts.py |
27 | Bare-comment and shell-timeout exemption detection, single/multiline Bash-call timeout presence, add_argument exemption that a docstring mention does not satisfy, config-file discovery across both roots (installed .claude/agents/*.md, source plugins/*/agents/*.md, nested rules/_full/, unrelated Markdown excluded), mode-file shadow and overlap detection, --check dispatch, non-local 32f skip |
test_list_audit_files.py |
list_audit_files.py |
14 | Local sweep finds skill entrypoints/modes/templates/shared, excludes nested agent sidecars and rules/_full, installed sweep narrower, dedupe+sort, block counting per file, header/relative-path labelling, local vs installed root selection |
test_measure_config_size.py |
measure_config_size.py |
17 | Empty-dir header-only output, agent/skill/rules size thresholds (warn/fail), skill path labelling, tighter rules budget, global CLAUDE.md counted once, delivered ~/.claude/rules/ counted and per-file flagged, symlinked rule counted once, --mode required and inventory/overhead dispatch |
test_resolve_agent_file.py |
resolve_agent_file.py |
12 | Bare/plugin-prefixed agent and skill resolution, leading-slash skill selection, cc-prefixed vs bare plugin directory precedence, single/ambiguous/no-match glob resolution, resolved vs unresolved CLI output, --local failure labelling |
test_scan_efficiency_signals.py |
scan_efficiency_signals.py |
18 | Frontmatter block extraction, Agent-in-loop detection (for/while) with batch-guard/head-limit exemption, declared-model presence, file-type inclusion (SKILL.md/agent files, modes excluded), report section ordering, boilerplate file counting, missing-model reporting, CLI exit code |
test_append_ledger.py |
append_ledger.py |
9 | Staged record appended after existing lines, staged file consumed, missing trailing newline supplied, parallel <ledger>.<writer>.rec swept in name order, absent ledger created, empty flush no-op, --from sources appended and kept, missing source skipped, --from leaves staged records alone |
test_write_skill_contract.py |
write_skill_contract.py |
14 | Five-field contract write, optional seven-field form appending a labelled ledger list (empty items omit the block, blank lines dropped), parent-dir creation, overwrite of previous contract, cwd-relative path, verbatim placeholder survival, wrong-arity/empty-skill exit codes, --help usage, no file written on usage error |
CI runs full test suite on every push to main and on PRs touching plugins/ (see .github/workflows/ci-tests.yml).
π Contributing / feedback¶
foundry = part of Borda-AI-Rig repository. Suggest improvement or report bug:
- Run
/foundry:brainstorm "your idea"to develop idea before filing - File issue on repository β include output of
/foundry:audit setup+ Claude Code version - Plugin updates propagate to users via
claude plugin install foundry@borda-ai-rig+/foundry:setup
New agent or skill: use /foundry:manage create β handles scaffolding, README sync, MEMORY.md updates automatically.
Evidence and real-case acceptance: shipped rules/quality-gates.md requires cited proof for factual, causal and completion claims, a representative real case alongside TDD, and unchanged user-authorized behavior. Mocks are supplementary polishing after real behavior is validated; unavailable real acceptance stays explicit. Review limits remain factual process evidence; an out-of-scope extra review needs one explicit user decision with its gap, expected output and stop condition.
The shared cross-validation protocol requires parent validation at every severity before findings publication, then capped independent critical verification. Review presentation uses role names only and square-bracketed scores, such as Software engineer [3]; execution provenance stays in evidence and limits. Version-one review envelopes distinguish source judgment, verification readiness and durable open concerns. Static proof may settle a claim; unavailable evidence never refutes it, and incomplete feasible work never becomes unconditional approval. Missing coverage and other review limits never become source findings or automatic extra-review requests; declined optional reviews are omitted from findings and action items while factual limits remain disclosed.