Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ jobs:
- run: bun run lint:skills
- run: bun run lint:agents
- run: bun run lint:profiles
- name: lint:knowledge (no-op without KNOWLEDGE_REPO_PATH)
# No team-knowledge repo checkout lives in this CI job — the fixture
# question (a real knowledge-note dir to lint here) is deliberately
# left open, see issue #91 / PR body. Without KNOWLEDGE_REPO_PATH set
# this only proves the script runs cleanly (prints a "no directory
# specified" message and exits 0); it does not lint real notes yet.
run: bun run lint:knowledge
- name: lint:knowledge (fixture dir, not the real team-knowledge repo)
# No team-knowledge repo checkout lives in this CI job, so this lints
# tests/fixtures/knowledge/ instead — a small schema-valid note kept
# in-repo so the linting logic (schema checks, name/filename
# mismatch, supersedes-unknown warning) actually runs in CI rather
# than short-circuiting on "no directory specified".
run: bun run lint:knowledge tests/fixtures/knowledge

schemas:
# Fails if `bun run schemas:emit` would produce a diff — enforces that the
Expand Down
5 changes: 2 additions & 3 deletions MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ cc-settings ships a **core** set of MCP servers — installed automatically by `
| `tldr` | Semantic codebase analysis (call graphs, impact) | `/tldr`, `/explore` |
| `figma` | Figma Dev Mode MCP — design tokens, component props | Auto-triggered by the server's own instructions on figma.com URLs; `/qa` for design-fidelity checks |
| `chrome-devtools` | Chrome DevTools (perf traces, network, console, screenshots, a11y tree, click/fill, lighthouse) | `/lighthouse`, `/qa`, `/fix`, `tester` agent, Figma-MCP design-vs-implementation diffs |
| `Sanity` | Sanity CMS operations (GROQ queries, etc.) | satus / novus projects with Sanity integration |

**Optional** servers — not installed by default; add manually to `~/.claude.json` if you want them. Listed in `mcp-configs/recommended.json`:

Expand All @@ -478,6 +477,7 @@ cc-settings ships a **core** set of MCP servers — installed automatically by `
| `github` | GitHub issues / PRs / projects | `gh` CLI covers most of this with lower context cost |
| `vercel` | Deployment management | Stack-specific (Vercel-only) |
| `memory` | Persistent cross-session memory | cc-settings has its own `~/.claude/memory/` system |
| `Sanity` | Sanity CMS operations (GROQ queries, etc.) | Project-specific (satus / novus with Sanity) — not in `config/20-mcp.json`, so `setup.sh` never installs it |

The post-install summary groups MCP servers by status (`core` / `optional` / `user-added`) so a new joiner can tell which came from cc-settings vs which they added themselves.

Expand All @@ -489,7 +489,7 @@ These are enforced automatically — no skill needed:

- **2-iteration limit** — fails twice? Stop, pivot, present alternatives
- **Bug fix scope** — only touch files related to the bug
- **Pre-commit verification** — tsc + build + tests must pass before any commit
- **Pre-commit verification** — `tsc --noEmit` is hooked and blocks `git commit` on type errors; build + tests are not hooked — run them yourself via `bun run proof` or the relevant skill before committing
- **Post-compaction recovery** — re-read plan + active files after compaction
- **Neutral exploration** — agents investigate without bias toward expected outcomes
- **No AI attribution** — stealth mode in all commits and PRs
Expand Down Expand Up @@ -565,7 +565,6 @@ Hook types: `command` (shell), `prompt` (LLM yes/no), `agent` (subagent with too
| Event | What Happens |
|-------|-------------|
| Session start | Load learnings, check TLDR index |
| Setup | Runs on `--init`, `--init-only`, or `--maintenance` CLI flags |
| User prompt | Delegation breadth detection (`delegation-detector.ts`), session title |
| Pre-tool (Bash) | Safety net, pre-commit TSC, docs check before install |
| Pre-tool (Edit) | Stale file detection |
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/dar

Both one-liners clone the repo and run the installer. To pass flags (`--light`, `--dry-run`, …), clone first and run the bootstrap from the checkout: `bash setup.sh --light` or `.\setup.ps1 --light`.

Requires [Bun](https://bun.sh) ≥ 1.2.21 and git — the bootstrap installs Bun automatically if missing. Re-installs are non-destructive: existing permissions, custom hooks, and local overrides survive.
Requires [Bun](https://bun.sh) ≥ 1.2.21 and git — the bootstrap installs Bun automatically if missing. The full profile also installs `jq` if missing; no runtime hook shells out to it (all hooks are TypeScript) — it's for the `jq` one-liners used in team-knowledge remediation runbooks. Re-installs are non-destructive: existing permissions, custom hooks, and local overrides survive.

Restart Claude Code after install.

Expand All @@ -57,7 +57,7 @@ No custom CLAUDE.md, agents, rules, profiles, MCP servers, hooks (beyond the sta
├── CLAUDE.md # Claude-Code-specific config
├── settings.json # Composed from config/*.json (permissions, hooks, MCP)
├── agents/ # 10 specialized subagents
├── skills/ # 36 auto-invocable skills
├── skills/ # 37 auto-invocable skills
├── profiles/ # Stack contexts: nextjs, react-native, tauri, webgl, maestro, react-router
├── rules/ # Path-conditioned rules (load on-demand by file type)
└── src/ # Hook + script implementations (TypeScript)
Expand Down
83 changes: 83 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ Exit code is non-zero on any suspicious finding so CI can gate on this. Stale-on

Source: `src/lib/audit-hooks.ts`, `src/scripts/audit-hooks.ts`.

**Also scanned, in their own report section:** `env` values and `mcpServers`
command/args are matched against the same malware-signature bank
(`curl | sh`, base64 decode + exec, `node -e`, etc.) and printed under a
separate `ENV/MCP SUSPICIOUS` heading when something matches. This is
**pattern-match classification only** — there is no shipped-pattern/manifest
concept for env vars or MCP server definitions, so nothing here is ever
"trusted", and a clean scan is not an integrity guarantee the way a "trusted"
hook finding is. See the scope note below for exactly what this does and
doesn't buy you.

### 4. The allowlist convention

Every hook command that cc-settings ships starts with
Expand Down Expand Up @@ -194,6 +204,36 @@ Consequences of that stance:
- Anything that MUST never run belongs in `config/30-permissions.json`,
not (only) in safety-net.

**Keeping the deny-list honest against the allow-list's wholesale
subcommands:** `config/30-permissions.json` wholesale-allows several git
subcommands (`git branch:*`, `git checkout:*`, `git push:*`, …) because
requiring per-flag confirmation on every `git status`/`git log`/routine
`git push` would make the deny-list unusable. That tradeoff only holds if
the deny-list has an explicit override for every destructive *form* of a
wholesale-allowed subcommand — otherwise the dangerous form is silently
auto-approved by the broad allow, with only fail-open safety-net.ts between
it and the user, contradicting the "deny-list is the boundary" claim above.
Concretely: `git push -uf`/`-fu` (bundled short flags that still carry
`-f`, distinct from the literal `--force`/`-f` token forms already denied)
and `git branch -D`/`-d -f` (force branch delete) each have their own deny
entry alongside the existing `--force`/`-f`/`-D` forms, mirroring exactly
what `safety-net.ts`'s `analyzeGitAfterVerb` already treats as must-block.
When adding a new wholesale-allowed git (or other) subcommand, enumerate its
destructive forms against safety-net.ts's checks and close the gap in the
deny-list rather than narrowing the allow — narrowing breaks the common
case (an interactive human confirming a legitimate `git push --force` to
their own fork), closing the gap doesn't.

**Edit/MultiEdit are wildcard-allowed (`Edit(*)`, `MultiEdit(*)`) alongside
`Write(*)`.** The deny-list's `Write(~/.claude/settings.json)` /
`Write(~/.claude.json)` entries only close the `Write` tool surface for
those two files — `Edit`/`MultiEdit` are separate tool names and are not
covered by a `Write`-scoped deny rule. `Edit(~/.claude/settings.json)`,
`MultiEdit(~/.claude/settings.json)`, and the `.claude.json` equivalents
mirror the `Write` denies for exactly this reason: all three mutation tools
must be denied for a path, not just one of them, or a prompt-injected
"use Edit instead of Write" instruction bypasses the protection entirely.

## What cc-settings deliberately does not do

- **Auto-quarantine on suspicious match.** The session-start hook only
Expand Down Expand Up @@ -226,6 +266,49 @@ What the content manifest does **not** cover:
worms automate against defaults — but a targeted attacker with full
user-privilege write access is outside this threat model.

## Scope: the fingerprint and content manifest cover the `hooks` block only

Layers 1 and 2 above (the hooks-block fingerprint and the src content
manifest) are precisely scoped: `hashHooks()` hashes `settings.hooks` and
nothing else, and the content manifest verifies script *files*, not other
`settings.json` keys. Concretely, **`mcpServers`, `permissions`, `env`, and
every other top-level key of `settings.json` are entirely outside what those
two layers verify** — a change to any of them, however large, never trips
the fingerprint mismatch warning, even though `mcpServers` entries run
arbitrary local commands with full startup (a persistence primitive at
least as strong as a hook).

Why this boundary, deliberately, rather than widening the hash: `mcpServers`,
`permissions`, and `env` are all things a user is *expected* to hand-edit
routinely (adding a personal MCP server, tightening a permission rule,
setting a project env var) — unlike the `hooks` block, which cc-settings
owns end-to-end and where any drift from the installed state is inherently
suspect. Folding those keys into the fingerprint would mean every legitimate
edit trips the same "tampering" warning as an actual injected payload,
training users to click through it — the opposite of what a security signal
is for.

What actually covers `mcpServers`/`env`: layer 3's auditor (above) runs a
**best-effort pattern-match scan** (`auditEnvAndMcp` in
`src/lib/audit-hooks.ts`) over `env` values and `mcpServers` command/args
against the same malware-signature bank used for hooks, reported in its own
`ENV/MCP SUSPICIOUS` section. This is classification only — it catches an
*obvious* injected payload (a `curl | sh`, a base64-decode-and-exec, an
inline `node -e`), the same way the hook auditor's `suspicious` tier does.
It does **not** give `mcpServers`/`env` the same guarantee `hooks` gets from
the fingerprint + manifest pair: there's no "this exact value hasn't changed
since install" check, no content-hash verification of anything an
`mcpServers.command` points at, and no `trusted` tier — silence means "no
known signature matched," not "unchanged" or "verified."

**Manual-review guidance:** if you see an `mcpServers` entry or `env` value
you don't recognize — whether or not the auditor flagged it — treat it the
same as an unrecognized hook: check whether you or a tool you installed
added it, and if not, treat it as a compromise signal and follow the
remediation steps above. `permissions` (the allow/deny lists themselves) has
no scanner at all yet; review changes to it the same way you'd review any
other settings.json diff.

## Reporting

Suspicious activity, false positives, or signature gaps — open an issue at
Expand Down
2 changes: 1 addition & 1 deletion agents/deslopper.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description: |
- "before push" / "pre-push review" / "final cleanup"

RETURNS: Dead code auto-removed, consolidation recommendations for approval
tools: [Read, Edit, Grep, Glob, LS, Bash, Agent, AskUserQuestion, TeamCreate, SendMessage, TaskCreate, TaskUpdate, TaskList, TaskGet]
tools: [Read, Edit, Grep, Glob, LS, Bash, Agent, AskUserQuestion, TeamCreate, TeamDelete, SendMessage, TaskCreate, TaskUpdate, TaskList, TaskGet]
disallowedTools: ["Bash(git push:*)", "Bash(git commit:*)", "Bash(rm:*)"]
maxTurns: 40
effort: high
Expand Down
2 changes: 1 addition & 1 deletion agents/maestro.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: |
- Complex debugging requiring multiple perspectives

RETURNS: Orchestration status, synthesized results from sub-agents, progress tracking
tools: [Read, Write, Edit, MultiEdit, Bash, Grep, Glob, LS, TodoWrite, Agent]
tools: [Read, Write, Edit, MultiEdit, Bash, Grep, Glob, LS, TodoWrite, Agent, TeamCreate, TeamDelete, SendMessage, TaskCreate, TaskUpdate, TaskList, TaskGet]
disallowedTools: ["Bash(git push:*)", "Bash(rm:*)"]
maxTurns: 60
effort: max
Expand Down
3 changes: 1 addition & 2 deletions agents/reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ disallowedTools: ["Bash(git commit:*)", "Bash(git push:*)", "Bash(rm:*)"]
maxTurns: 15
permissionMode: plan
effort: high
isolation: worktree
color: yellow
initialPrompt: |
Run `git diff origin/main...HEAD --stat` then `git diff origin/main...HEAD` (fall back to `git diff HEAD~1` if no upstream) to see the scope of changes before critiquing. Also run `git log --oneline -10` for recent context. Then proceed with the user's review request.
Run `git diff origin/main...HEAD --stat` then `git diff origin/main...HEAD` (fall back to `git diff HEAD~1` if no upstream) to see the scope of changes before critiquing — this runs in the live working tree, so uncommitted and unpushed changes are visible. Also run `git log --oneline -10` for recent context. Then proceed with the user's review request.
---

You are an expert code reviewer for Darkroom Engineering projects.
Expand Down
3 changes: 1 addition & 2 deletions agents/security-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ disallowedTools: ["Bash(git commit:*)", "Bash(git push:*)", "Bash(rm:*)", "Bash(
maxTurns: 30
permissionMode: plan
effort: high
isolation: worktree
color: red
initialPrompt: |
Start with automated scans before targeted review: secret detection grep (AKIA|sk-|sk_live|ghp_|shpat_), dangerous-pattern grep (eval|dangerouslySetInnerHTML|innerHTML|exec\(), `bun audit` if a lockfile exists, and `grep NEXT_PUBLIC_.*SECRET` for env leaks. Report the quick-scan baseline first, then proceed with the user's deep-review request.
Start with automated scans before targeted review, over the live working tree (not a fresh worktree, so uncommitted and unpushed changes are in scope): secret detection grep (AKIA|sk-|sk_live|ghp_|shpat_), dangerous-pattern grep (eval|dangerouslySetInnerHTML|innerHTML|exec\(), `bun audit` if a lockfile exists, and `grep NEXT_PUBLIC_.*SECRET` for env leaks. Report the quick-scan baseline first, then proceed with the user's deep-review request.
---

You are an expert security reviewer for Darkroom Engineering projects.
Expand Down
8 changes: 8 additions & 0 deletions config/30-permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
"Write(~/.gnupg/*)",
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push -uf:*)",
"Bash(git push -fu:*)",
"Bash(git branch -D:*)",
"Bash(git branch -d -f:*)",
"Bash(git reset --hard:*)",
"Bash(git clean -f:*)",
"Bash(git checkout -- .:*)",
Expand Down Expand Up @@ -133,6 +137,10 @@
"Write(~/.bash_profile)",
"Write(~/.claude/settings.json)",
"Write(~/.claude.json)",
"Edit(~/.claude/settings.json)",
"MultiEdit(~/.claude/settings.json)",
"Edit(~/.claude.json)",
"MultiEdit(~/.claude.json)",
"Bash(gh repo delete:*)",
"Bash(gh secret:*)",
"Bash(gh api -X DELETE:*)",
Expand Down
5 changes: 3 additions & 2 deletions docs/agent-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ Routing principle: **explore and execute on the cheaper tiers, decide on the top
|-------|-------|-----------|
| `maestro` | **opus[1m]** | Orchestration needs the strongest available reasoning (was `fable`) |
| `planner` | **opus[1m]** | Architecture decisions need depth (was `fable`) |
| `oracle` | **opus[1m]** | Expert Q&A needs nuance (was `fable`) |
| `oracle` | *(session model)* (skill, not an agent — `skills/oracle/SKILL.md` runs as a `context: fork` of the main session, no agent binding) | Not a dedicated opus[1m] agent despite the name; the fork inherits the session's model, so on an `opus[1m]` session oracle already thinks at the top tier |
| `reviewer` | **sonnet** | Diff-reading is bulk work; cross-model `codex-verifier` provides the independent second gate |
| `implementer` | **sonnet** | Executes already-made plans; Sonnet 5 is near-Opus on coding, and plans come from the top tier |
| `security-reviewer` | **opus** | Analysis feeding the session's decision |
| `tester` | **sonnet** | Test writing follows clear patterns |
| `scaffolder` | **sonnet** | Boilerplate generation is mechanical |
| `explore` | **sonnet** | The highest-volume agent — routine investigation is Sonnet-fine; bump per-invocation to Opus for genuinely hard blast-radius/architecture work |
| `deslopper` | **sonnet** | Deletions are tool-grounded (tldr call graph) and guard-railed (no rm/commit/push, conservative auto-fix) |
| `codex-verifier` | **sonnet** | Independent cross-model check via the Codex CLI — the value is a different model family reviewing the diff, not raw reasoning depth on the Claude side |

The `sonnet` tier is now Claude Sonnet 5 — near-Opus quality on coding/agentic work — which reinforces the split above: `tester`, `scaffolder`, `explore`, `deslopper`, `implementer`, and `reviewer` stay on Sonnet for fan-out/mechanical/execution work at a fraction of Opus cost, while the judgment-bearing agents that gate a decision (`maestro`, `planner`, `oracle`, `security-reviewer`) stay on the top tier.
The `sonnet` tier is now Claude Sonnet 5 — near-Opus quality on coding/agentic work — which reinforces the split above: `tester`, `scaffolder`, `explore`, `deslopper`, `implementer`, `reviewer`, and `codex-verifier` stay on Sonnet for fan-out/mechanical/execution/consult work at a fraction of Opus cost, while the judgment-bearing agents that gate a decision (`maestro`, `planner`, `security-reviewer`) stay on the top tier.

Override per-invocation when a specific task warrants it: bump a cheap agent up — `Agent(explore, "...", model: "opus")` for a hard investigation — or drop a decision agent down for a trivial pass. The table is the default, not a ceiling.

Expand Down
Loading
Loading