Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b36457d
fix(build-skill): pre-approve sentinel commands, drop phantom continu…
victorkzam Jul 22, 2026
dfe8601
fix(ship-skill): add Agent to allowed-tools for the pre-PR review panel
victorkzam Jul 22, 2026
67d2c45
chore(gitignore): allowlist installer paths, forbid repo-local settings
victorkzam Jul 22, 2026
1155718
fix(agents): align effort tiers with policy, add disallowedTools to r…
victorkzam Jul 22, 2026
01073c7
fix(settings): config-dir-aware hook paths, guarded notification, sta…
victorkzam Jul 22, 2026
39b8cdf
docs(workflow): align loop caps, hook caveats, and frontmatter fields…
victorkzam Jul 22, 2026
dbe6662
fix(hooks): config-dir-aware plans allowlist in orchestrator delegate…
victorkzam Jul 22, 2026
f339a71
feat(scripts): add shared lib, enforced manifest, and doctor-style ch…
victorkzam Jul 22, 2026
47ce431
feat(scripts): add marker-block CLAUDE.md merge with non-destructive …
victorkzam Jul 22, 2026
29a531d
feat(scripts): add idempotent manifest-driven install.sh with diffs a…
victorkzam Jul 22, 2026
53c8016
feat(scripts): add deterministic settings.json merge with array-conca…
victorkzam Jul 22, 2026
c60dc62
feat(setup): add repo-scoped /setup router skill with consent-gated a…
victorkzam Jul 22, 2026
6852390
ci: add shellcheck, bats, e2e, and manifest gates on linux and macos
victorkzam Jul 22, 2026
c073f16
docs(readme): rewrite onboarding around /setup with script fallback
victorkzam Jul 22, 2026
fa466c8
fix(install): reject manifest paths escaping the config dir
victorkzam Jul 22, 2026
03159d3
fix(install): restore lost executable bit on unchanged hook files
victorkzam Jul 22, 2026
75e3ee5
fix(merge-settings): refuse malformed nested hook shapes instead of c…
victorkzam Jul 22, 2026
4e34484
docs(workflow): correct defaultMode claim to match shipped template
victorkzam Jul 22, 2026
367e5c0
ci: install bats on ubuntu via pinned git checkout, not npm -g
victorkzam Jul 23, 2026
832d4da
test(install): exercise the traversal guard with a category-prefixed …
victorkzam Jul 23, 2026
40c0b3b
test(merge-claude-md): cover END-before-BEGIN and duplicate-marker re…
victorkzam Jul 23, 2026
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
184 changes: 184 additions & 0 deletions .claude/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
name: setup
description: "Install this repo's Claude Code setup into the current machine's config dir. Runs ONLY when the user types /setup inside a clone of claude-code-setup — never model-invoked. Orchestrates the check/install/merge scripts through a preflight → install → merge → handoff flow, with a diff shown and explicit consent taken before every write."
disable-model-invocation: true
argument-hint: (no arguments — run inside a fresh clone of this repo)
allowed-tools:
- Read
- Glob
- Grep
- Bash(bash scripts/check.sh:*)
- Bash(bash scripts/install.sh --dry-run:*)
- Bash(bash scripts/merge-settings.sh --dry-run:*)
- Bash(bash scripts/merge-claude-md.sh --dry-run:*)
---

# Setup Router — Adopter Install Flow

You are the session executing `/setup` inside a **clone of this repo** (this skill is
repo-scoped: it lives under `.claude/skills/` and is discovered only when Claude Code runs
in the clone — it is never installed onto an adopter's machine). Your job is to install this
setup's managed artifacts (agents/hooks/rules/skills), then merge `settings.json` and
`CLAUDE.md`, into the current machine's Claude config dir — **safely, with a diff shown and
explicit consent taken before every write**.

## Consent model (read before you start)

This flow has **two consent gates by design**, and you must not collapse either:

1. **Every write is preceded by a diff + an explicit ask.** You dry-run first, show the
plan/diff, and ask the user to proceed.
2. **The writing commands are deliberately NOT pre-approved in `allowed-tools`.** Only the
read-only and `--dry-run` forms are pre-approved. When you run the real install, a
real `--force=<category>`, or a real `--apply`, Claude Code will raise a **permission
prompt** — that prompt is the *intended* second gate, not a bug. Tell the user to expect
it and let it happen; never try to route around it.

Do **not** invent Edit/Write shortcuts to config paths, and do not seek broader permission
grants to suppress those prompts.

## Script contract (shared by all four scripts)

- Machine lines `CCS-STATUS:<action>:<path>` print on **stdout**; human diffs/plan text also
on stdout; refusals/errors on **stderr**.
- Exit codes: **0** success · **1** refusal / partial · **2** usage / internal.
- `scripts/install.sh` (non-dry-run) prints `CCS-STATUS:backup-dir:<path>` as its **first**
status line. **Capture that path** — that ONE run dir is threaded via `--backup-dir <path>`
into every later `--apply`/`--force` this session, so the whole install produces a single
backup run dir and a single receipt.

## Global error rules (apply in every phase)

- **Any script exits 2** → STOP the entire flow. Report the script's stderr **verbatim**;
do not attempt later phases.
- **Any script exits 1** → report its stderr **verbatim** and ask the user how to proceed
(it is a refusal/partial, not a hard stop — the user decides).
- **Abort-safe / idempotent:** every phase is idempotent. If the flow is aborted partway,
re-running `/setup` resumes safely — already-installed, identical files no-op
(`CCS-STATUS:unchanged:...`), and merges recompute from current on-disk state. There is no
state file; a declined step is simply remembered in this conversation.

---

## P1 — Preflight

Run:

```
bash scripts/check.sh
```

- **Exit 2 (FAIL)** → STOP. Present the `FAIL:` lines **verbatim** plus the fixes `check.sh`
suggests. Do not proceed to P2.
- **Exit 1 (WARN)** → present the `WARN:` lines and **ask the user whether to proceed** given
the warnings. Only continue if they say yes.
- **Exit 0 (PASS)** → proceed to P2.

## P2 — Install managed artifacts

1. **Dry-run first:**

```
bash scripts/install.sh --dry-run --diff
```

Present the plan: what would be installed (missing → install), what is identical
(`unchanged`), and per-category **diffs for any differing files** (differing files are
**skipped** by default, per category).

2. **Real install** (this is NOT pre-approved — expect a permission prompt; explain that to
the user before running it):

```
bash scripts/install.sh
```

**Capture the first status line** `CCS-STATUS:backup-dir:<path>`. Save `<path>` — you will
pass `--backup-dir <path>` to every later apply/force so the session shares one backup run
dir and one receipt.

3. **Resolve skipped (differing) files.** Any file that differed was left untouched. For each
affected **category**, show that category's diff and ask the user **keep vs overwrite**.
Overwrite only the categories the user approves, one category per run:

```
bash scripts/install.sh --force=<category> --backup-dir <path>
```

(`<category>` is one of `agents`, `hooks`, `rules`, `skills`.) This run is also NOT
pre-approved — the permission prompt is again the intended consent gate.

## P3 — Merge config files (one target at a time)

Handle **`settings.json` first, then `CLAUDE.md`**. For each target, always: dry-run → show
the unified diff → take **explicit consent** → apply with the shared `--backup-dir <path>`.

### P3a — settings.json

```
bash scripts/merge-settings.sh --dry-run
```

Show the unified diff. Ask for explicit consent. On yes:

```
bash scripts/merge-settings.sh --apply --backup-dir <path>
```

If the target already exists (an existing-config merge, not a fresh verbatim write), also
surface the **withheld-convenience-keys note** to the user:

> Convenience keys (model, statusLine, defaultMode) were not merged to avoid overwriting
> yours — copy from `settings.example.json` if wanted.

### P3b — CLAUDE.md

```
bash scripts/merge-claude-md.sh --dry-run
```

Show the unified diff. Ask for explicit consent. On yes:

```
bash scripts/merge-claude-md.sh --apply --backup-dir <path>
```

**Declines are respected.** If the user declines either merge, do not write, do not re-ask in
a loop, and do not nag — just record the decline in the conversation and move on.

## P4 — Handoff

The install is done from this session's side. Print, for the user:

- **Receipt location** and the **backup run dir** (`<path>` captured in P2).
- A short **"what behaves differently now"** summary (3 lines):
1. Pushes to `main`/`master` are blocked (branch-protection hook).
2. The delegation guard is active during `/build` (orchestrator can't hand-edit source).
3. Design + pre-PR human checkpoints are enforced.

Then give the user the steps **they run AFTER restarting Claude Code**. The restart **severs
this session**, so the skill must NOT try to run these itself — hand them over as
instructions:

1. **Restart Claude Code** (so the newly installed settings/hooks/skills load).
2. Verify the install landed:

```
bash scripts/check.sh --post
```

3. Confirm hooks loaded by running **`/hooks`** and checking the managed entries appear.

---

## Anti-patterns to avoid

- Do NOT skip the dry-run — a diff is shown before **every** consent, no exceptions.
- Do NOT collapse the two consent gates: never seek a broader grant to suppress the
install/force/apply permission prompts.
- Do NOT thread multiple backup dirs — capture the ONE `CCS-STATUS:backup-dir:<path>` from
P2 and reuse it for every apply/force.
- Do NOT re-ask a declined merge in a loop.
- Do NOT run the post-restart steps yourself — the restart ends this session; they are the
user's to run.
- Do NOT continue past an exit-2 from any script — STOP and report verbatim.
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: CI

# push covers every branch (including feature branches, per project convention
# of feature-branch-only work); pull_request targeting main covers PRs.
on:
push:
pull_request:
branches: [main]

jobs:
lint-and-test:
name: shellcheck + bats + manifest (ubuntu)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install shellcheck
run: sudo apt-get update -y && sudo apt-get install -y shellcheck

# Same pinned git-checkout install as the macos job below. `npm install -g
# bats` broke on the ubuntu-24.04 runner image: npm's global prefix is
# /usr/local, whose man dirs the runner user cannot mkdir (EACCES).
- name: Install bats-core from a git checkout
run: |
git clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git "$RUNNER_TEMP/bats-core"
echo "$RUNNER_TEMP/bats-core/bin" >> "$GITHUB_PATH"

- name: shellcheck scripts + hooks
# -x: scripts/*.sh source scripts/lib.sh; a bare single-file shellcheck
# trips info-level SC1091 (can't follow non-constant/relative source).
# House convention: always lint with -x here.
#
# --severity=error: hooks/*.sh (owned by an earlier task, out of scope
# here) carry pre-existing info/warning-level nits (SC2086, SC2015,
# SC2221/2222) that are stylistic, not bugs. Gating on errors keeps this
# a real correctness gate without failing CI on unrelated existing
# style notes. Judgment call — revisit if those files get cleaned up.
run: shellcheck -x --severity=error scripts/*.sh hooks/*.sh

- name: bats (full suite, incl. e2e)
run: bats tests/*.bats

- name: manifest.txt == git ls-files agents/ hooks/ rules/ skills/ (C3)
run: |
diff <(git ls-files agents/ hooks/ rules/ skills/) scripts/manifest.txt

bats-macos:
name: bats (macos system /bin/bash 3.2 + BSD userland)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

# bats-core from a git checkout, invoked directly out of bin/ — NOT via
# brew. A brewed bats would pull in a modern GNU-flavored PATH/toolchain
# and mask exactly the GNUisms this job exists to catch. shellcheck is
# intentionally skipped here: the ubuntu job already lints every script.
- name: Install bats-core from a git checkout
run: |
git clone --depth 1 --branch v1.11.0 https://github.com/bats-core/bats-core.git "$RUNNER_TEMP/bats-core"
echo "$RUNNER_TEMP/bats-core/bin" >> "$GITHUB_PATH"

- name: bats (full suite, incl. e2e) under system bash
run: |
echo "bash: $(command -v bash) ($(bash --version | head -n1))"
bats tests/*.bats
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@
!skills/**
!hooks/
!hooks/**
!scripts/
!scripts/**
!tests/
!tests/**
!.github/
!.github/**

# Allow the repo-scoped setup skill under .claude/, but nothing else in
# .claude/ (agent-memory, local settings, etc. must stay ignored). Gitignore
# requires un-ignoring each parent directory level before its children, so
# the chain below is intentionally explicit rather than a single !.claude/**.
!.claude/
!.claude/skills/
!.claude/skills/setup/
!.claude/skills/setup/**

# Never track local, personal, or generated Claude Code state, even if a
# future copy step drops it in here by mistake.
Expand All @@ -30,3 +45,7 @@ projects/
history.jsonl
*.log
.DS_Store

# Security rail: this repo must never ship an active repo-local settings
# file, even though .claude/ is partially un-ignored above.
.claude/settings.json
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ your own" are personal-preference blocks you should adapt or delete; the
## Identity (example — replace with your own)
PM-turned-engineer. Quality and learning over speed. Always explain WHY behind architectural decisions, not just WHAT.

<!-- BEGIN claude-code-setup workflow-rules v1 -->
## Workflow Rules
- Never push directly to main/master. Feature branches only.
- Conventional Commits: feat:, fix:, refactor:, docs:, chore:, test:
Expand All @@ -31,6 +32,7 @@ PM-turned-engineer. Quality and learning over speed. Always explain WHY behind a
- Use subagents for tasks whose output would bloat the main context (exploration, review, large diffs).
- Suggest /clear between unrelated tasks.
- For large features: suggest one session per phase.
<!-- END claude-code-setup workflow-rules v1 -->

## Stack Preferences (example — replace with your own; used only when a project's own CLAUDE.md is silent)
- iOS: SwiftUI, async/await, no completion handlers, // MARK: sections
Expand Down
Loading
Loading