Skip to content

feat(setup): manifest-driven installer, safe merges, and /setup skill - #1

Merged
victorkzam merged 21 commits into
mainfrom
feat/adopter-setup
Jul 23, 2026
Merged

victorkzam merged 21 commits into
mainfrom
feat/adopter-setup

Conversation

@victorkzam

@victorkzam victorkzam commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Adds an idempotent, manifest-driven scripts/install.sh (diffs, backups, receipts, symlink refusal, path-containment guard) so adopters can install the shipped agents/hooks/rules/skills into ~/.claude safely and repeatably.
  • Adds deterministic, consent-friendly merge scripts: merge-settings.sh (jq narrow merge of hooks + permissions.allow, array-concat dedup, strict 0/1/2 exit contract incl. nested-shape validation) and merge-claude-md.sh (marker-block CLAUDE.md merge with non-destructive fallbacks).
  • Adds scripts/lib.sh (shared guards), enforced scripts/manifest.txt, and doctor-style scripts/check.sh (--pre/--post).
  • Adds a repo-scoped /setup router skill with two consent gates (dry-run previews pre-approved; applies require explicit approval) — never installed onto the adopter's machine.
  • CI on linux + macos (system bash 3.2 / BSD userland): shellcheck --severity=error, full bats suite, manifest-sync gate.
  • README onboarding rewritten around /setup with a script-only fallback; WORKFLOW.md aligned with shipped behavior (loop caps, hook caveats, defaultMode).
  • Fixes from the pre-PR 3-lens review panel (all evidence-verified): manifest path-traversal guard, exec-bit self-heal on unchanged hooks, nested hook-shape validation, defaultMode doc correction.
  • Post-PR 4-lens subagent review (installer, merge scripts, adopter docs, test quality — all evidence-gated): code approved by all lenses; test-quality found the traversal regression tests were satisfied by the category allowlist alone — now strengthened with a category-prefixed escape case proven non-vacuous by mutation, plus coverage for the two untested malformed-marker refusal branches.

Test Plan

  • bats tests/*.bats — 84/84 green locally (install, both merges, check, hooks, e2e)
  • shellcheck -x --severity=error scripts/*.sh hooks/*.sh — clean
  • diff <(git ls-files agents/ hooks/ rules/ skills/) scripts/manifest.txt — in sync
  • CI green on both ubuntu and macos jobs
  • Mutation check: traversal test fails with ccs_validate_manifest_rel neutered, passes restored
  • Manual pass of tests/manual-setup-checklist.md on a scratch CLAUDE_CONFIG_DIR

Known follow-ups (deliberately deferred — all minor, evidence-backed, none block merge)

  • merge-claude-md.sh marker matching is exact-line; a trailing space after a <!-- BEGIN ... --> marker is misclassified as malformed and refused (fails safe, no corruption).
  • install.sh refuses a symlinked leaf destination but follows a pre-existing symlinked category directory (e.g. ~/.claude/hooks → elsewhere), letting a valid install land outside the config dir. Arguably intended for stow/chezmoi layouts — decide, then either guard or document.
  • install.sh validates manifest path shape but not source existence; a shape-valid line with no matching source (e.g. CRLF working tree) aborts mid-install with a raw cp: error instead of an upfront refusal. Hardening: existence check in validation + .gitattributes forcing LF on scripts/manifest.txt.
  • merge-settings.sh silently drops a foreign hook group whose hooks array is empty (scaffolding in progress), despite the header's "foreign hooks preserved verbatim" promise — preserve already-empty groups or soften the claim.
  • check.sh matches managed hook basenames as substrings of any command string, so a contrived config can earn a spurious "references all managed hooks" PASS — tighten to structural matching.

Design Reference

Local design docs: ~/.claude/plans/adopter-setup-design-draft.md + adopter-setup-tasks.md (16 tasks, 6 tagged risk: high; 3-lens pre-PR panel + 4-lens post-PR subagent review, criticals/majors fixed on-branch).

🤖 Generated with Claude Code

victorkzam and others added 21 commits July 22, 2026 14:54
…e arg, un-pin beta header

Co-Authored-By: Claude <noreply@anthropic.com>
Un-ignore scripts/, tests/, .claude/skills/setup/, and .github/ so the
adopter-setup installer, bats tests, repo-scoped setup skill, and CI
workflows can be tracked. The .claude/ allowlist chain un-ignores only
the setup skill's parent directories, so agent-memory and other .claude/
content stay ignored by default. Adds an explicit .claude/settings.json
ignore as a security rail, guaranteeing the repo never ships an active
settings file even though .claude/ is partially un-ignored.

Co-Authored-By: Claude <noreply@anthropic.com>
…ead-only agents

Aligns agent frontmatter with the effort policy in WORKFLOW.md: review-type
agents (design-reviewer, reviewer) get effort: xhigh, research agents
(researcher) get effort: high. Adds disallowedTools: [Write, Edit] to
Explore and researcher to enforce the read-only contract already claimed
in their descriptions.

Co-Authored-By: Claude <noreply@anthropic.com>
…tusline caveat

Rewrite all four hook command strings to resolve scripts via
${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/ instead of a hardcoded ~/.claude
path, so the template works for CLAUDE_CONFIG_DIR adopters with no
merge-time rewriting. Guard the macOS-only osascript Notification hook
with a command -v check so it no-ops gracefully on Linux. Extend the
statusLine _comment to flag that reading .oauthAccount from the local
config JSON relies on an undocumented internal interface that may break
in future Claude Code versions.

Co-Authored-By: Claude <noreply@anthropic.com>
… with shipped behavior

Replace the three "≤3" design-reviewer loop claims with convergence
language (escalates after 5 iterations, matching the shipped design
skill), add the effort field to the agent frontmatter list, flag the
Notification hook as macOS-only, and point Quick Start at the README's
/setup flow instead of implying manual copying.

Co-Authored-By: Claude <noreply@anthropic.com>
… guard

The delegate guard's editable-path allowlist only recognized
$HOME/.claude/plans/*, so adopters running with CLAUDE_CONFIG_DIR set had
their plan-file edits under $CLAUDE_CONFIG_DIR/plans/ wrongly blocked during
/build. Accept both forms and mention both in the block message. Adds
tests/hooks.bats covering allow/block behavior under both layouts.

Co-Authored-By: Claude <noreply@anthropic.com>
…eck.sh

Foundation for the adopter installer: scripts/lib.sh (config-dir resolution
with absolutization, symlinked-dir accept / regular-file + source==destination
refusals, refuse_symlink, threaded backup dir, incremental receipts, jq
fail-closed, atomic writes), an enforced scripts/manifest.txt (== git ls-files
agents/ hooks/ rules/ skills/), scripts/check.sh (default severity map with the
2.1.53 CVE floor and 2.1.132 / 2.1.198 degradation tiers, plus --post artifact
verification with filename-match hook checks), and the frozen shared bats
helper.

Co-Authored-By: Claude <noreply@anthropic.com>
…fallbacks

Merge the repo's managed workflow-rules block into the adopter's
CLAUDE.md: fresh copy of the block for a new file, version-agnostic
in-place replace when markers exist, append when they do not, and a
non-destructive refusal on malformed marker states or symlinked targets.
Dry-run/apply modes with backup-dir threading, atomic writes, receipts,
and idempotent re-runs, per the shared script contract in lib.sh.

Co-Authored-By: Claude <noreply@anthropic.com>
…nd receipts

Adds the manifest-driven installer (agents/hooks/rules/skills): missing files
install, byte-identical files are no-ops, differing files are skipped by
default and reported per category, and --force/--force=<category> overwrite
after backing up the adopter's file. --dry-run (optionally with --diff)
previews every action with zero mutations; a symlinked destination file is
refused per-file while the rest of the run continues; receipts and backups
thread through scripts/lib.sh.

Co-Authored-By: Claude <noreply@anthropic.com>
…t dedup

Add scripts/merge-settings.sh, the sole writer of $CLAUDE_DIR/settings.json:
a missing target receives the template verbatim (M3), while an existing target
gets a narrow jq merge limited to .hooks and .permissions.allow. Hook
reconciliation is basename-keyed against manifest.txt, so old manual-cp-era
command strings are upgraded to the config-dir-aware form without duplication.
permissions.allow is concat + unique. The merge is byte-stable idempotent, so a
re-run is a CCS-STATUS:unchanged no-op. --dry-run previews a unified diff and
mutates nothing; refusals (symlink, missing jq, unparseable, non-object) exit 1.

Co-Authored-By: Claude <noreply@anthropic.com>
…pplies

Add .claude/skills/setup/SKILL.md — a repo-scoped, non-model-invocable
router that orchestrates check.sh/install.sh/merge-*.sh through a
preflight -> install -> merge -> handoff flow. Only read-only and
--dry-run forms are pre-approved in allowed-tools; the writing forms
(install, --force, --apply) deliberately trigger a permission prompt as
the second consent gate. A diff is shown before every write and a single
install backup dir is threaded into every apply.

Add tests/manual-setup-checklist.md — the pre-PR human gate covering the
conversational behaviors the .bats suites cannot: declined-merge respect,
exit-2 stop, diff-before-every-consent, backup-dir threading, the
withheld-convenience-keys note, idempotent re-run, and post-restart verify.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds a CI workflow with two jobs: an ubuntu job that lints scripts/*.sh
and hooks/*.sh with shellcheck -x, runs the full bats suite, and enforces
manifest.txt == git ls-files agents/ hooks/ rules/ skills/ (C3); and a
macos job that runs the full bats suite under the system /bin/bash 3.2 +
BSD userland (via a bats-core git checkout, not brew) to catch GNUisms.

Adds tests/e2e.bats, the M4 happy-path integration test: check -> install
-> merge-settings -> merge-claude-md -> check --post, run twice to prove
the whole pipeline is byte-stable on repeat runs. The real `claude`
binary is stripped from PATH so the pre-install check's expected failure
(claude CLI not found) is deterministic on every machine, not just CI.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Validate every manifest.txt line (absolute path, or a ".." path
component) before any writes occur, closing a PoC-verified traversal
where a crafted manifest line wrote outside the adopter's config dir
while still reporting CCS-STATUS:installed.

Co-Authored-By: Claude <noreply@anthropic.com>
A byte-identical hook file whose +x bit was stripped fell into the
unchanged path, which never chmods, so no run (not even --force)
could repair it and check.sh --post failed forever. handle_unchanged
now restores +x for the hooks category before reporting unchanged,
still no backup/receipt churn for byte-identical content, and a
second run stays a full no-op.

Co-Authored-By: Claude <noreply@anthropic.com>
…rashing

ccs_validate_object now walks every nested shape the merge jq touches
(.hooks.<event> arrays, their elements, inner .hooks lists, and .command
strings), so the documented 0/1/2 exit contract holds for nested shapes
too instead of a raw jq exit-5 crash.

Co-Authored-By: Claude <noreply@anthropic.com>
Plan-first is enforced by /design's plan mode and the post-design checkpoint (exit-plan-mode boundary), not by a global "plan" default.

Co-Authored-By: Claude <noreply@anthropic.com>
npm's global prefix on the ubuntu-24.04 runner image is /usr/local, whose
man dirs the runner user cannot create (EACCES), so the install step died
before any gate ran. Mirrors the macos job's pinned v1.11.0 checkout.

Co-Authored-By: Claude <noreply@anthropic.com>
…escape

The prior traversal-regression tests were toothless: both PoC lines lacked a
valid category prefix, so install.sh's category allowlist refused them before
ccs_validate_manifest_rel ever ran; mutation testing (guard neutered to a
no-op) proved both stayed green.

Co-Authored-By: Claude <noreply@anthropic.com>
…fusals

Two of the three malformed-marker shapes documented in the script's header
comment (END-before-BEGIN, duplicate BEGIN/END pairs) had no test coverage;
only the lone-BEGIN case was exercised.

Co-Authored-By: Claude <noreply@anthropic.com>
@victorkzam
victorkzam merged commit e87cac6 into main Jul 23, 2026
4 checks passed
@victorkzam
victorkzam deleted the feat/adopter-setup branch July 24, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant