From e40d3f31c50c7ed28f6bf1cf3e97b10bb9f57e3c Mon Sep 17 00:00:00 2001 From: tigers1997 Date: Tue, 25 Aug 2026 10:34:42 -0400 Subject: [PATCH 1/2] feat: generate templates/INDEX.md, scaffold .claude/workflows/, document teams and routines Three gaps the currency audit left open. 1. templates/INDEX.md was hand-maintained and had gone stale enough to mislead: it still referenced a configurator.html that no longer exists and was missing half the modules. It is now generated from MODULES by `python3 configure.py --write-index`, and --check fails when the committed copy and the generator disagree, so it cannot drift again. 2. Dynamic workflows have been a first-class Claude Code surface since 2.1.154 and the configurator scaffolded nothing for them. The multi-agent module now ships .claude/workflows/spec-fanout.js, which runs as /spec-fanout: it generates N variants of one spec into disjoint slots, then screens each variant against the spec and the diversification axis before reporting. It is the workflow-native successor to the /infinite skill in the same module -- same job, but the runtime holds the loop and the intermediate results, the run is resumable, and the screening pass is a real gate rather than a suggestion. Project workflows under .claude/workflows/ are shared with everyone who clones the repo. --check gained a rule validating that every shipped workflow declares a usable `meta` block and uses no import(); the runtime rejects both, and a broken workflow would otherwise fail at invocation time in the user's project rather than at scaffold time here. workflowSizeGuideline is stubbed in settings.local.json.example. 3. docs/04 gains a table comparing the five ways to run work in parallel (subagent / skill / agent team / workflow / worktree session) by who holds the plan and where intermediate results live -- and states plainly why the configurator ships no templates for agent teams, channels or routines: - Teams are spawned in conversation and live for a session, so there is no project-level file that defines one. The only knob worth setting is teammateMode, a per-machine terminal preference, now stubbed in settings.local. - The channel gate keys channelsEnabled and allowedChannelPlugins are managed-settings only, so a project cannot enable them; an administrator does. Once enabled a channel is an MCP server like any other. - Routines are scheduled cloud agents that run against a repo rather than from your checkout, so a committed template has nothing to bind to. A Stop or SessionStart hook is the project-scoped equivalent, and the git-workflow module already ships one. The microbit-enforcer routing rule in target_path_for is generalized from ".sh" to ".sh"/".ps1" so the directory's scripts route to .claude/hooks/ by kind rather than by extension. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JVndNviHZSnbKJnWP7jFbV --- CHANGELOG.md | 2 + README.md | 2 + config_schema.py | 6 +- configure.py | 133 +++++++++ docs/04-subagents-mcp-orchestration.md | 51 ++++ examples/persona-small-team/expected-tree.txt | 1 + .../.claude/.cc-manifest.json | 4 +- .../.claude/settings.local.json.example | 6 + templates/INDEX.md | 254 ++++++++++++++---- .../dot-claude/settings.local.json.example | 6 + .../dot-claude/workflows/spec-fanout.js | 119 ++++++++ 11 files changed, 529 insertions(+), 55 deletions(-) create mode 100644 templates/multi-agent/dot-claude/workflows/spec-fanout.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f13a3..27e3d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project. Format: [Keep a Changelog](https://keepacha ## Unreleased +- **feat: generate `templates/INDEX.md`, scaffold `.claude/workflows/`, and document teams / channels / routines.** Three gaps the currency audit left open. **(1)** `templates/INDEX.md` was hand-maintained and had gone stale enough to mislead — it still referenced a `configurator.html` that no longer exists and was missing half the modules. It is now **generated** from `MODULES` by `python3 configure.py --write-index`, and `--check` fails when the committed copy and the generator disagree, so it cannot drift again. **(2)** Dynamic workflows have been a first-class Claude Code surface since 2.1.154 and the configurator scaffolded nothing for them. The `multi-agent` module now ships `.claude/workflows/spec-fanout.js` (runs as `/spec-fanout`), which generates N variants of one spec into disjoint slots and then **screens each variant against the spec** before reporting. It is the workflow-native successor to the `/infinite` skill in the same module — same job, but the runtime holds the loop and the intermediate results, the run is resumable, and the screening pass is a real gate rather than a suggestion. Project workflows under `.claude/workflows/` are shared with everyone who clones the repo. `--check` gained a rule validating that every shipped workflow declares a usable `meta` block and uses no `import()` (the runtime rejects both). `workflowSizeGuideline` is stubbed in `settings.local.json.example`. **(3)** `docs/04` gains a table comparing the five ways to run work in parallel (subagent / skill / agent team / workflow / worktree session) by *who holds the plan*, and states plainly why the configurator ships no templates for agent teams, channels or routines: teams are spawned in conversation and live for a session (only `teammateMode` is worth setting, and it's a per-machine terminal preference — stubbed in settings.local); the channel gate keys `channelsEnabled` and `allowedChannelPlugins` are **managed-settings only**, so a project cannot enable them; and routines are scheduled cloud agents that run against a repo rather than from your checkout, where a `Stop` or `SessionStart` hook is the project-scoped equivalent. + - **fix(commands): rename `/review` → `/review-branch` so it stops shadowing the bundled `/code-review`.** CC 2.1.223 made `/review` the alias of the bundled `/code-review` — Claude Code's multi-agent reviewer, including the cloud `ultra` mode. A project skill of that name wins it (verified headlessly on 2.1.241: a project skill named `review` ran for `/review`, and the same held for `plan` against the built-in `/plan`), so **every scaffolded project was silently hiding the better built-in behind this simpler single-pass skill** — overlap that turned into a real capability loss the day the alias shipped. The skill moves to `templates/commands/review-branch/` with `name: review-branch`, and its description now positions it honestly ("a quick single-pass review; Claude Code's bundled `/code-review` is the deeper multi-agent one"). Both are reachable again. Updated across `config_schema.py`, `configure.py`'s pattern-integration map, `templates/INDEX.md`, the `/investigate` and `/plan-eng-review` cross-references, docs 02/03/05/09/10/11, README, and the example project. **Migration:** the configurator has no mechanism to delete a file it previously wrote, so an upgraded project keeps the old `.claude/skills/review/` alongside the new one — and the stale copy still shadows the alias. New `/verify-setup` **check 13** detects exactly that pair and tells the user to `rm -rf .claude/skills/review`. `/plan` is left alone deliberately: it shadows a built-in *command* rather than a bundled skill, and plan mode stays reachable via Shift+Tab, so it's a name clash rather than a lost capability — README now says so and points at the rename if you'd rather keep the shortcut. - **docs: re-baseline the MCP context claims against tool search, and scope `/infinite` against dynamic workflows.** Two of the project's headline claims had been overtaken by Claude Code and were overstating what the modules buy. **MCP.** README claimed per-task profiles "drop a bloated 4-MCP baseline from ~49% context to under 5%", and `docs/04` asserted "every MCP tool is a chunk of JSON schema loaded at session start". Tool search defers MCP schemas by default (`alwaysLoad: true` is the opt-*out*), so the premise no longer holds. Measured rather than re-guessed — four local stdio servers advertising twelve tools each (48 total) against an otherwise identical one-turn session on CC 2.1.245: **26,665 tokens with no MCP servers, 27,361 deferred (+696, ~14/tool), 40,993 with `alwaysLoad: true` (+14,328, ~298/tool)** — deferral removes ~95% of the schema cost, and the numbers reproduced exactly across runs. The claim was also embedded in four *shipped* templates, which is worse than in the docs because it lands in every user's project: `check-context/SKILL.md` (its budget guardrails and the "MCP > 10%" flag), `claude-ctx.sh`'s rationale comment, `servers-cookbook.md` (which already explained deferral correctly a few sections earlier, so it contradicted itself), and the `mcp.minimal.json` profile comment. All corrected. Profiles are now documented for what they still genuinely buy — which servers *connect*: startup time, auth prompts, cold start, and the blast radius `--strict-mcp-config` enforces — and `docs/06` picks up the same correction. The dated `experiments-memory` example keeps its original result with a superseding **addendum** rather than a rewrite, because an experiment log records what was true when it ran. **`/infinite`.** Dynamic workflows now do staged, resumable, budgeted fan-out with structured output between stages; hand-rolled wave batching is the weaker instrument for that job. The skill opens with a decision table sending staged / merge-heavy / resumable work to a workflow, and keeps the one case it is genuinely good at — N variants of a single spec into disjoint slots with no cross-iteration coordination. README's module row and a new `docs/04` section say the same. diff --git a/README.md b/README.md index 150fa12..d7e2c34 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,8 @@ All five preflight checks are silent on a clean default scaffold; informational --force Kill-switch: skip the deep-merge AND the collision strategy. Every existing file is overwritten with .bak- (the pre-Tier-2 behavior). Implies --on-collision=overwrite. +--write-index Regenerate templates/INDEX.md from MODULES (maintainer + tool; --check fails when the committed index is stale). --save-config FILE Save answers to FILE (plus scaffolding) --save-config-only FILE Save answers only, no scaffolding --check Static validation of templates + MODULES (CI gate); diff --git a/config_schema.py b/config_schema.py index c5a44e5..944ab7c 100644 --- a/config_schema.py +++ b/config_schema.py @@ -184,6 +184,7 @@ "paths": [ "multi-agent/dot-claude/rules/multi-agent-guardrails.md", "multi-agent/dot-claude/agents/parallel-generator.md", + "multi-agent/dot-claude/workflows/spec-fanout.js", "commands/merge-worktrees/SKILL.md", "commands/infinite/SKILL.md", ], @@ -1100,10 +1101,11 @@ def target_path_for(template_rel: str): if rest_path.startswith("agents/"): agent_name = rest_path[len("agents/"):] return f".claude/agents/{agent_name}" - # microbit-enforcer.sh routes to .claude/hooks/, not .claude/skills/. + # microbit-enforcer.sh / .ps1 route to .claude/hooks/, not .claude/skills/. # The directory's settings-patch.json is consumed via extraSettingsPatch # and never copied as a file (so no special-case skip needed). - if rest_path.startswith("microbit-enforcer/") and rest_path.endswith(".sh"): + if (rest_path.startswith("microbit-enforcer/") + and rest_path.endswith((".sh", ".ps1"))): fname = rest_path[len("microbit-enforcer/"):] return f".claude/hooks/{fname}" return f".claude/skills/{rest_path}" diff --git a/configure.py b/configure.py index 429af08..b0f1258 100755 --- a/configure.py +++ b/configure.py @@ -603,6 +603,7 @@ def compute_merged_settings(form_values: dict, selected: set, module_flags: dict # Final pass: strip any `//`-prefixed keys at any nesting depth. Catches # nested doc labels / stubs that escape the shallow per-merge filters. settings = _strip_doc_labels(settings) + return settings @@ -680,6 +681,106 @@ def _frontmatter_block(text: str) -> str: return "" +INDEX_PATH = TEMPLATE_DIR / "INDEX.md" + +INDEX_FOOTER = """## How settings merge works + +Several modules contribute `hooks` entries, and they all land in one +`.claude/settings.json`. The CLI merges them (see `deep_merge_settings` and +`_merge_hook_groups` in `configure.py`): groups are keyed by `matcher`, inner +hooks are unioned by `command`, and a user's own entries are never rewritten. +If you're hand-copying instead, the shape is: + +```json +{ + "hooks": { + "PreToolUse": [ ...all matchers from all modules... ], + "PostToolUse": [ ... ], + "Stop": [ ... ] + } +} +``` + +Within one event, hooks from different modules concatenate; Claude Code runs +every matching entry. + +## Path rewrites + +- `*/dot-claude/*` -> `.claude/*` and `*/dot-github/*` -> `.github/*`. The + template tree avoids real dotfolders so it browses and syncs cleanly on + tools that special-case them. +- `mcp/mcp.json` -> `.mcp.json`; `mcp/profiles/mcp..json` -> + `.mcp..json` at the repo root; `mcp/servers-cookbook.md` -> + `docs/mcp-servers.md`; `mcp/claude-ctx.sh` -> `claude-ctx` (executable). +- Hook scripts are written executable, and with LF endings on every platform. +""" + + +def render_template_index() -> str: + """Render templates/INDEX.md from MODULES — the same source the CLI + scaffolds from, so the index cannot drift from what actually ships. + + The hand-maintained version went stale (it still referenced a + `configurator.html` that no longer exists and was missing half the + modules); `--check` now fails when this output and the file disagree. + Regenerate with `python3 configure.py --write-index`. + """ + lines = [ + "# Template library index", + "", + "**Generated — do not edit by hand.** Produced from `MODULES` in", + "`config_schema.py` by `python3 configure.py --write-index`, and verified", + "by `python3 configure.py --check`.", + "", + "Every module contributes drop-in files for a target project. `cc-configure`", + "composes the selected modules; you can also copy any file directly.", + "", + ] + for m in MODULES: + flags = m.get("flags") or {} + lines.append(f"## `{m['id']}`" + (" *(required)*" if m.get("required") else "")) + lines.append("") + desc = (m.get("description") or "").strip() + if desc: + lines.append(desc.split(". ")[0].rstrip(".") + ".") + lines.append("") + paths = m.get("paths") or [] + if paths: + lines.append("| Template | Installs to |") + lines.append("|---|---|") + for p in paths: + try: + target = target_path_for(p) + except Exception: + target = "(computed at scaffold time)" + lines.append(f"| `{p}` | `{target}` |") + lines.append("") + extras = [] + if m.get("settingsPatch"): + extras.append(f"`{m['settingsPatch']}` merges into `.claude/settings.json`") + if m.get("extraSettingsHook"): + extras.append("registers hook entries in `.claude/settings.json`") + if m.get("gitignoreSource"): + extras.append(f"`{m['gitignoreSource']}` appends to `.gitignore`") + if m.get("gitattributesSource"): + extras.append(f"`{m['gitattributesSource']}` appends to `.gitattributes`") + if m.get("dependsOn"): + extras.append("depends on " + ", ".join(f"`{d}`" for d in m["dependsOn"])) + for name, spec in flags.items(): + opts = spec.get("options") + extras.append( + f"flag `{name}`" + (f" ({' | '.join(map(str, opts))}" + + f", default `{spec.get('default')}`)" if opts else "") + ) + for e in extras: + lines.append(f"- {e}") + if extras: + lines.append("") + lines.append(INDEX_FOOTER.rstrip("\n")) + lines.append("") + return "\n".join(lines) + + def _find_bash(): """Return a bash that can actually execute, or None. @@ -915,6 +1016,31 @@ def warn(source, msg): if f"include {pat}" not in text: err(f"templates/{skill_rel}", f"missing required pattern include: {pat}") + # --- 4b. Dynamic-workflow scripts declare a usable meta block --- + # The runtime rejects a script whose `meta` isn't a pure literal with name + + # description, and a broken workflow fails at invocation time rather than at + # scaffold time — so catch it here instead of in the user's project. + for f in sorted(TEMPLATE_DIR.rglob("workflows/*.js")): + rel = f.relative_to(TEMPLATE_DIR) + text = f.read_text(encoding="utf-8") + if "export const meta" not in text: + err(f"templates/{rel}", "workflow script has no `export const meta` block") + continue + for field in ("name:", "description:"): + if field not in text.split("}", 1)[0]: + err(f"templates/{rel}", f"workflow `meta` is missing `{field}`") + if "import(" in text or text.lstrip().startswith("import "): + err(f"templates/{rel}", + "workflow scripts cannot use import()/import — the runtime rejects them") + + # --- 5. Generated index is current --- + if INDEX_PATH.exists(): + if INDEX_PATH.read_text(encoding="utf-8").replace("\r\n", "\n") != render_template_index(): + err("templates/INDEX.md", + "out of date — regenerate with `python3 configure.py --write-index`") + else: + err("templates/INDEX.md", "missing — generate with `python3 configure.py --write-index`") + # --- Report --- errors = [i for i in issues if i[0] == "ERR"] for _sev, _src, _msg in issues: @@ -2677,6 +2803,9 @@ def parse_args(): help="Static validation of templates + MODULES registry (CI-friendly). " "Exits 0 on clean, 1 with a per-issue summary otherwise. " "Skips all other processing — no scaffolding, no prompts.") + p.add_argument("--write-index", action="store_true", + help="Regenerate templates/INDEX.md from MODULES (maintainer tool). " + "--check fails when the committed index and this output disagree.") p.add_argument("--whats-new", action="store_true", help="Read-only: compare this project's .cc-manifest.json " "version/SHA against the current configurator build and " @@ -2781,6 +2910,10 @@ def cmd_whats_new(target_dir): def main(): args = parse_args() # --check short-circuits everything else: no scaffolding, no target dir creation. + if args.write_index: + write_text_lf(INDEX_PATH, render_template_index()) + print(green(f"wrote {INDEX_PATH.relative_to(REPO_ROOT)}")) + return if args.check: sys.exit(run_check()) # --whats-new is read-only: report the configurator-vs-manifest delta and diff --git a/docs/04-subagents-mcp-orchestration.md b/docs/04-subagents-mcp-orchestration.md index c73c8c2..4236a55 100644 --- a/docs/04-subagents-mcp-orchestration.md +++ b/docs/04-subagents-mcp-orchestration.md @@ -165,6 +165,57 @@ Main session edits; `code-reviewer` subagent runs in parallel after every commit ### Desktop + cloud Local Claude Code for interactive work; Claude Code Web for long-running cloud jobs. Worktrees bridge the two. Useful for "run this large refactor while I go do something else." After both branches converge, use `/merge-worktrees` (ships in `multi-agent`) to integrate safely via a disposable branch. +### Five ways to run work in parallel + +Claude Code grew several parallelism primitives, and they are not +interchangeable. The difference is **who holds the plan** and **where +intermediate results live**: + +| Primitive | Who decides what runs next | Results live in | Reach for it when | +|---|---|---|---| +| **Subagent** | Claude, turn by turn | Claude's context | A few delegated tasks inside one turn | +| **Skill** | Claude, following the prompt | Claude's context | The procedure is the same every time | +| **Agent team** | A lead agent supervising peer sessions | A shared task list | A handful of long-running peers that outlive a turn | +| **Workflow** | The script | Script variables | Dozens to hundreds of agents; you want the orchestration itself repeatable | +| **Worktree session** | You | Separate checkouts | Genuinely independent branches of work | + +The configurator ships templates for the first two and for worktrees +(`/merge-worktrees`), plus one starter workflow (below). It ships nothing for +agent teams, channels, or routines — deliberately, and the reasons are worth +knowing: + +- **Agent teams** are spawned in conversation and live for a session. There is + no project-level file that defines a team, so there is nothing to commit. The + one knob worth setting is `teammateMode` (how teammates are displayed: split + panes under tmux/iTerm2, or in-process), and it's a per-machine terminal + preference — stubbed in `.claude/settings.local.json.example`, not in the + committed settings. +- **Channels** push external events (CI results, chat messages, alerts) into a + running session through an MCP server. Their gate keys — `channelsEnabled` + and `allowedChannelPlugins` — are **managed-settings only**, so a project + cannot turn them on for you; your administrator does. Once enabled, a channel + is an MCP server like any other and belongs in `.mcp.json`. +- **Routines** are scheduled cloud agents (`/schedule`). They run on Anthropic's + infrastructure against a repo, not from your checkout, so a committed template + would have nothing to bind to. If you want a recurring local check, a `Stop` + or `SessionStart` hook is the project-scoped equivalent — and the + `git-workflow` module already ships one. + +### The starter workflow + +The `multi-agent` module scaffolds `.claude/workflows/spec-fanout.js`, which +runs as `/spec-fanout`. It generates N variants of one spec into disjoint output +slots, then screens each variant against the spec and the diversification axis +before reporting. It is the workflow-native successor to the `/infinite` skill +in the same module: same job, but the runtime holds the loop and the +intermediate results, the run is resumable, and the screening pass is a real +gate. Workflows saved under `.claude/workflows/` are shared with everyone who +clones the repo. + +Set `workflowSizeGuideline` (stubbed in `.claude/settings.local.json.example`, +honored from any settings file) if you want the workflows Claude writes for this +repo to stay small. + ### When to reach for a workflow instead Dynamic workflows (`/workflows`) run a script Claude writes over many agents, diff --git a/examples/persona-small-team/expected-tree.txt b/examples/persona-small-team/expected-tree.txt index fd6713f..12a1ade 100644 --- a/examples/persona-small-team/expected-tree.txt +++ b/examples/persona-small-team/expected-tree.txt @@ -61,6 +61,7 @@ ./.claude/skills/verify-setup/SKILL.md ./.claude/skills/writing-plans/SKILL.md ./.claude/skills/writing-plans/plan-document-reviewer-prompt.md +./.claude/workflows/spec-fanout.js ./.gitattributes ./.github/workflows/claude.yml ./.gitignore diff --git a/examples/python-uv-fastapi/.claude/.cc-manifest.json b/examples/python-uv-fastapi/.claude/.cc-manifest.json index 1f4b20b..c1a2291 100644 --- a/examples/python-uv-fastapi/.claude/.cc-manifest.json +++ b/examples/python-uv-fastapi/.claude/.cc-manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 2, - "written_at": "2026-08-25T13:35:25Z", + "written_at": "2026-08-25T14:28:16Z", "written_by": "cc-configure 2.7.0", - "written_by_sha": "62b6afb", + "written_by_sha": "594d7f7", "mcp_servers": [], "stack_manifests": [], "check_commands": { diff --git a/examples/python-uv-fastapi/.claude/settings.local.json.example b/examples/python-uv-fastapi/.claude/settings.local.json.example index 22d53a6..58e07fd 100644 --- a/examples/python-uv-fastapi/.claude/settings.local.json.example +++ b/examples/python-uv-fastapi/.claude/settings.local.json.example @@ -60,6 +60,12 @@ "symlinkDirectories": ["node_modules"] }, + "// workflowSizeGuideline": "medium", + "//workflowSizeGuideline-notes": "workflowSizeGuideline (CC 2.1.219+) — how many agents Claude aims for in a dynamic workflow it writes: unrestricted | small (<5) | medium (<15) | large (<50). Advice to the model, not an enforced cap; the runtime's own limits (16 concurrent, 1,000 per run) still apply. A settings value takes precedence over the /config row and hides it. Honored from any settings file, so it can be a project default — useful when a repo's workflows should stay small. The sibling `disableWorkflows: true` turns dynamic workflows and the bundled workflow commands off entirely.", + + "// teammateMode": "auto", + "//teammateMode-notes": "teammateMode — how agent-team teammates are displayed: auto (split panes under tmux/iTerm2, in-process otherwise) | in-process | tmux | iterm2. A machine-and-terminal preference, which is why it belongs in this file rather than the committed settings.json; a tmux value on a machine without tmux just degrades. Agent teams are session-scoped and spawned in conversation, so there is nothing for the configurator to scaffold beyond this knob — see docs/04-subagents-mcp-orchestration.md.", + "// agent": "code-reviewer", "//agent-notes": "agent — name of a subagent (built-in or .claude/agents/.md) whose system prompt, tool restrictions and model the MAIN thread takes on for every session in this project (same as `claude --agent `; the choice persists on resume). Never a configurator default: it replaces the default Claude Code system prompt entirely. Useful for a review-only checkout or a docs-only worktree.", diff --git a/templates/INDEX.md b/templates/INDEX.md index 2163878..9fc1afd 100644 --- a/templates/INDEX.md +++ b/templates/INDEX.md @@ -1,65 +1,217 @@ # Template library index -Every module produces one or more drop-in files for a new Claude Code project. The configurator (`configurator.html`) composes these into a selectable bundle. You can also copy files directly. - -**Note on folder names in this library:** `dot-claude/` means `.claude/` in the target project. `mcp.json` in `mcp/` becomes `.mcp.json` at the project root. This is a workaround for the storage layer — the generated setup scripts handle the rename automatically. - -## Core (always included) -- `core/CLAUDE.md` → `./CLAUDE.md` -- `core/dot-claude/settings.json` → `.claude/settings.json` -- `core/dot-claude/settings.local.json.example` → `.claude/settings.local.json.example` -- `core/.gitignore.append` → append to `./.gitignore` - -## Safety & permissions -- `safety/hooks/block-dangerous-bash.sh` → `.claude/hooks/block-dangerous-bash.sh` -- `safety/hooks/scan-secrets.sh` → `.claude/hooks/scan-secrets.sh` -- `safety/settings-patch.json` → merge into `.claude/settings.json` under `hooks` - -## Git workflow -- `git-workflow/hooks/format-on-write.sh` → `.claude/hooks/format-on-write.sh` -- `git-workflow/hooks/stop-run-checks.sh` → `.claude/hooks/stop-run-checks.sh` -- `git-workflow/settings-patch.json` → merge into `.claude/settings.json` under `hooks` - -## Token efficiency -- `token-efficiency/dot-claude/rules/_scoping-guide.md` → `.claude/rules/_scoping-guide.md` (docs; safe to delete after reading) -- `token-efficiency/dot-claude/rules/frontend.md` → `.claude/rules/frontend.md` -- `token-efficiency/dot-claude/rules/backend.md` → `.claude/rules/backend.md` -- `token-efficiency/dot-claude/rules/tests.md` → `.claude/rules/tests.md` -- `token-efficiency/hooks/pre-compact-snapshot.sh` → `.claude/hooks/pre-compact-snapshot.sh` - -## Slash commands (skills) -- `commands/plan/SKILL.md` → `.claude/skills/plan/SKILL.md` -- `commands/review-branch/SKILL.md` → `.claude/skills/review-branch/SKILL.md` -- `commands/commit/SKILL.md` → `.claude/skills/commit/SKILL.md` -- `commands/ship/SKILL.md` → `.claude/skills/ship/SKILL.md` -- `commands/sync-docs/SKILL.md` → `.claude/skills/sync-docs/SKILL.md` - -## Subagents -- `agents/code-reviewer.md` → `.claude/agents/code-reviewer.md` -- `agents/test-runner.md` → `.claude/agents/test-runner.md` -- `agents/doc-writer.md` → `.claude/agents/doc-writer.md` -- `agents/security-auditor.md` → `.claude/agents/security-auditor.md` - -## MCP -- `mcp/mcp.json` → `./.mcp.json` (project-scoped) -- `mcp/servers-cookbook.md` → `docs/mcp-servers.md` (reference, optional) - -## UI / status -- `ui/statusline.sh` → `.claude/hooks/statusline.sh` -- `ui/output-styles/plan.md` → `.claude/output-styles/plan.md` +**Generated — do not edit by hand.** Produced from `MODULES` in +`config_schema.py` by `python3 configure.py --write-index`, and verified +by `python3 configure.py --check`. + +Every module contributes drop-in files for a target project. `cc-configure` +composes the selected modules; you can also copy any file directly. + +## `core` *(required)* + +CLAUDE.md template (populated from the intake form), .claude/settings.json with balanced permissions, .gitignore additions. + +| Template | Installs to | +|---|---| +| `core/CLAUDE.md` | `CLAUDE.md` | +| `core/dot-claude/settings.json` | `.claude/settings.json` | +| `core/dot-claude/settings.local.json.example` | `.claude/settings.local.json.example` | + +- `core/.gitignore.append` appends to `.gitignore` +- `core/.gitattributes.append` appends to `.gitattributes` + +## `safety` + +PreToolUse hooks (block dangerous bash: rm -rf, sudo, curl | sh, force push, hard reset; gate apt/brew/dnf/yum/pacman/apk install for packages not in any configured repo) + scan Write/Edit for secrets. + +| Template | Installs to | +|---|---| +| `safety/hooks/block-dangerous-bash.sh` | `.claude/hooks/block-dangerous-bash.sh` | +| `safety/hooks/scan-secrets.sh` | `.claude/hooks/scan-secrets.sh` | +| `safety/hooks/check-package-availability.sh` | `.claude/hooks/check-package-availability.sh` | +| `safety/hooks/_lib/availability_check.sh` | `.claude/hooks/_lib/availability_check.sh` | +| `safety/hooks/_lib/detect_tool_versions.sh` | `.claude/hooks/_lib/detect_tool_versions.sh` | + +- `safety/settings-patch.json` merges into `.claude/settings.json` +- flag `lockdown` +- flag `slop_scan` +- flag `slop_scan_action` (warn | block, default `warn`) +- flag `slop_scan_density` +- flag `slop_scan_imports` + +## `git-workflow` + +PostToolUse formats files after Claude writes (prettier/ruff/gofmt/rustfmt). + +| Template | Installs to | +|---|---| +| `git-workflow/hooks/format-on-write.sh` | `.claude/hooks/format-on-write.sh` | +| `git-workflow/hooks/stop-run-checks.sh` | `.claude/hooks/stop-run-checks.sh` | + +- `git-workflow/settings-patch.json` merges into `.claude/settings.json` + +## `token-efficiency` + +Path-scoped .claude/rules/ starters + PreCompact snapshot. + +| Template | Installs to | +|---|---| +| `token-efficiency/dot-claude/rules/_scoping-guide.md` | `.claude/rules/_scoping-guide.md` | +| `token-efficiency/dot-claude/rules/frontend.md` | `.claude/rules/frontend.md` | +| `token-efficiency/dot-claude/rules/backend.md` | `.claude/rules/backend.md` | +| `token-efficiency/dot-claude/rules/tests.md` | `.claude/rules/tests.md` | +| `token-efficiency/hooks/pre-compact-snapshot.sh` | `.claude/hooks/pre-compact-snapshot.sh` | + +- registers hook entries in `.claude/settings.json` +- flag `tier` (basic | pro, default `basic`) + +## `commands` + +Bundled commands (plan/review-branch/commit/ship/sync-docs/check-context/session-retro/verify-setup/retrofit) + 4 subagents (code-reviewer/test-runner/doc-writer/security-auditor). + +| Template | Installs to | +|---|---| +| `commands/plan/SKILL.md` | `.claude/skills/plan/SKILL.md` | +| `commands/review-branch/SKILL.md` | `.claude/skills/review-branch/SKILL.md` | +| `commands/commit/SKILL.md` | `.claude/skills/commit/SKILL.md` | +| `commands/ship/SKILL.md` | `.claude/skills/ship/SKILL.md` | +| `commands/sync-docs/SKILL.md` | `.claude/skills/sync-docs/SKILL.md` | +| `commands/check-context/SKILL.md` | `.claude/skills/check-context/SKILL.md` | +| `commands/session-retro/SKILL.md` | `.claude/skills/session-retro/SKILL.md` | +| `commands/verify-setup/SKILL.md` | `.claude/skills/verify-setup/SKILL.md` | +| `commands/retrofit/SKILL.md` | `.claude/skills/retrofit/SKILL.md` | +| `commands/agents/code-reviewer.md` | `.claude/agents/code-reviewer.md` | +| `commands/agents/test-runner.md` | `.claude/agents/test-runner.md` | +| `commands/agents/doc-writer.md` | `.claude/agents/doc-writer.md` | +| `commands/agents/security-auditor.md` | `.claude/agents/security-auditor.md` | +| `commands/freeze/SKILL.md` | `.claude/skills/freeze/SKILL.md` | +| `commands/unfreeze/SKILL.md` | `.claude/skills/unfreeze/SKILL.md` | +| `commands/guard/SKILL.md` | `.claude/skills/guard/SKILL.md` | +| `commands/careful/SKILL.md` | `.claude/skills/careful/SKILL.md` | +| `commands/microbit-enforcer/microbit-enforcer.sh` | `.claude/hooks/microbit-enforcer.sh` | + +- flag `subset` (curated | full | rigorous, default `full`) + +## `recommend-plugins` + +Generates docs/recommended-plugins.md listing official Claude Code plugins recommended for your stack: always-recommended set (claude-code-setup, claude-md-management, feature-dev, commit-commands, superpowers, etc.) + stack-specific picks computed from your form answers (language → LSP plugin, database → DB plugin, framework → framework-specific plugin, MCP toggles → official replacements). + +| Template | Installs to | +|---|---| +| `recommend-plugins/recommended-plugins.md` | `docs/recommended-plugins.md` | + +## `experiments-memory` + +Scaffolds memory/experiments/CLAUDE.md — a nested memory file that injects ONLY when Claude reads files under memory/experiments/. + +| Template | Installs to | +|---|---| +| `experiments-memory/memory/experiments/CLAUDE.md` | `memory/experiments/CLAUDE.md` | +| `experiments-memory/memory/experiments/2026-04-24-example-profile-budget.md` | `memory/experiments/2026-04-24-example-profile-budget.md` | + +## `multi-agent` + +Path-scoped guardrails rule (loads when touching .claude/agents/**), /merge-worktrees for safe integration of parallel branches, and /infinite + parallel-generator subagent for fanout-style spec expansion (generate N variants in parallel). + +| Template | Installs to | +|---|---| +| `multi-agent/dot-claude/rules/multi-agent-guardrails.md` | `.claude/rules/multi-agent-guardrails.md` | +| `multi-agent/dot-claude/agents/parallel-generator.md` | `.claude/agents/parallel-generator.md` | +| `multi-agent/dot-claude/workflows/spec-fanout.js` | `.claude/workflows/spec-fanout.js` | +| `commands/merge-worktrees/SKILL.md` | `.claude/skills/merge-worktrees/SKILL.md` | +| `commands/infinite/SKILL.md` | `.claude/skills/infinite/SKILL.md` | + +- `multi-agent/settings-patch.json` merges into `.claude/settings.json` + +## `github-actions` + +.github/workflows/claude.yml — triggers anthropics/claude-code-action@v1 on @claude mentions in issues, PR comments, and PR reviews. + +| Template | Installs to | +|---|---| +| `github-actions/dot-github/workflows/claude.yml` | `.github/workflows/claude.yml` | + +## `mcp` + +Writes .mcp.json with only the MCP servers you enabled. + +| Template | Installs to | +|---|---| +| `mcp/mcp.json` | `.mcp.json` | +| `mcp/servers-cookbook.md` | `docs/mcp-servers.md` | +| `mcp/claude-ctx.sh` | `claude-ctx` | +| `mcp/profiles/mcp.research.json` | `.mcp.research.json` | +| `mcp/profiles/mcp.frontend.json` | `.mcp.frontend.json` | +| `mcp/profiles/mcp.minimal.json` | `.mcp.minimal.json` | +| `mcp/hooks/sessionstart-drift-check.sh` | `.claude/hooks/sessionstart-drift-check.sh` | + +- registers hook entries in `.claude/settings.json` + +## `discipline-skills` + +Seven discipline skills forked from the MIT-licensed obra/superpowers v6.3.0 plugin: brainstorming, writing-plans, executing-plans, verification-before-completion, using-git-worktrees, subagent-driven-development, finishing-a-development-branch. + +| Template | Installs to | +|---|---| +| `discipline-skills/LICENSE` | `.claude/skills/_LICENSE-discipline-skills.md` | +| `discipline-skills/brainstorming/SKILL.md` | `.claude/skills/brainstorming/SKILL.md` | +| `discipline-skills/brainstorming/spec-document-reviewer-prompt.md` | `.claude/skills/brainstorming/spec-document-reviewer-prompt.md` | +| `discipline-skills/writing-plans/SKILL.md` | `.claude/skills/writing-plans/SKILL.md` | +| `discipline-skills/writing-plans/plan-document-reviewer-prompt.md` | `.claude/skills/writing-plans/plan-document-reviewer-prompt.md` | +| `discipline-skills/executing-plans/SKILL.md` | `.claude/skills/executing-plans/SKILL.md` | +| `discipline-skills/verification-before-completion/SKILL.md` | `.claude/skills/verification-before-completion/SKILL.md` | +| `discipline-skills/using-git-worktrees/SKILL.md` | `.claude/skills/using-git-worktrees/SKILL.md` | +| `discipline-skills/subagent-driven-development/SKILL.md` | `.claude/skills/subagent-driven-development/SKILL.md` | +| `discipline-skills/subagent-driven-development/implementer-prompt.md` | `.claude/skills/subagent-driven-development/implementer-prompt.md` | +| `discipline-skills/subagent-driven-development/task-reviewer-prompt.md` | `.claude/skills/subagent-driven-development/task-reviewer-prompt.md` | +| `discipline-skills/subagent-driven-development/re-review-prompt.md` | `.claude/skills/subagent-driven-development/re-review-prompt.md` | +| `discipline-skills/subagent-driven-development/scripts/review-package` | `.claude/skills/subagent-driven-development/scripts/review-package` | +| `discipline-skills/subagent-driven-development/scripts/task-brief` | `.claude/skills/subagent-driven-development/scripts/task-brief` | +| `discipline-skills/subagent-driven-development/scripts/sdd-workspace` | `.claude/skills/subagent-driven-development/scripts/sdd-workspace` | +| `discipline-skills/finishing-a-development-branch/SKILL.md` | `.claude/skills/finishing-a-development-branch/SKILL.md` | +| `discipline-skills/hooks/sessionstart-discipline.sh` | `.claude/hooks/sessionstart-discipline.sh` | + +- registers hook entries in `.claude/settings.json` + +## `ui` + +Status line script (project dir | branch | model | context % | OS+tool-version chip), an alternative 'last-prompt' status line, and a 'plan' output style. + +| Template | Installs to | +|---|---| +| `ui/statusline.sh` | `.claude/hooks/statusline.sh` | +| `ui/statusline-last-prompt.sh` | `.claude/hooks/statusline-last-prompt.sh` | +| `ui/output-styles/plan.md` | `.claude/output-styles/plan.md` | + +- flag `no_version_chip` ## How settings merge works -When multiple modules add `hooks`, they must be merged into one `settings.json`. The configurator does this automatically. If you're hand-copying, the pattern is: +Several modules contribute `hooks` entries, and they all land in one +`.claude/settings.json`. The CLI merges them (see `deep_merge_settings` and +`_merge_hook_groups` in `configure.py`): groups are keyed by `matcher`, inner +hooks are unioned by `command`, and a user's own entries are never rewritten. +If you're hand-copying instead, the shape is: ```json { "hooks": { - "PreToolUse": [ ...all matchers from all modules... ], + "PreToolUse": [ ...all matchers from all modules... ], "PostToolUse": [ ... ], - "Stop": [ ... ] + "Stop": [ ... ] } } ``` -Within one event name, hooks from different modules concatenate. Claude runs all matching entries. +Within one event, hooks from different modules concatenate; Claude Code runs +every matching entry. + +## Path rewrites + +- `*/dot-claude/*` -> `.claude/*` and `*/dot-github/*` -> `.github/*`. The + template tree avoids real dotfolders so it browses and syncs cleanly on + tools that special-case them. +- `mcp/mcp.json` -> `.mcp.json`; `mcp/profiles/mcp..json` -> + `.mcp..json` at the repo root; `mcp/servers-cookbook.md` -> + `docs/mcp-servers.md`; `mcp/claude-ctx.sh` -> `claude-ctx` (executable). +- Hook scripts are written executable, and with LF endings on every platform. diff --git a/templates/core/dot-claude/settings.local.json.example b/templates/core/dot-claude/settings.local.json.example index 22d53a6..58e07fd 100644 --- a/templates/core/dot-claude/settings.local.json.example +++ b/templates/core/dot-claude/settings.local.json.example @@ -60,6 +60,12 @@ "symlinkDirectories": ["node_modules"] }, + "// workflowSizeGuideline": "medium", + "//workflowSizeGuideline-notes": "workflowSizeGuideline (CC 2.1.219+) — how many agents Claude aims for in a dynamic workflow it writes: unrestricted | small (<5) | medium (<15) | large (<50). Advice to the model, not an enforced cap; the runtime's own limits (16 concurrent, 1,000 per run) still apply. A settings value takes precedence over the /config row and hides it. Honored from any settings file, so it can be a project default — useful when a repo's workflows should stay small. The sibling `disableWorkflows: true` turns dynamic workflows and the bundled workflow commands off entirely.", + + "// teammateMode": "auto", + "//teammateMode-notes": "teammateMode — how agent-team teammates are displayed: auto (split panes under tmux/iTerm2, in-process otherwise) | in-process | tmux | iterm2. A machine-and-terminal preference, which is why it belongs in this file rather than the committed settings.json; a tmux value on a machine without tmux just degrades. Agent teams are session-scoped and spawned in conversation, so there is nothing for the configurator to scaffold beyond this knob — see docs/04-subagents-mcp-orchestration.md.", + "// agent": "code-reviewer", "//agent-notes": "agent — name of a subagent (built-in or .claude/agents/.md) whose system prompt, tool restrictions and model the MAIN thread takes on for every session in this project (same as `claude --agent `; the choice persists on resume). Never a configurator default: it replaces the default Claude Code system prompt entirely. Useful for a review-only checkout or a docs-only worktree.", diff --git a/templates/multi-agent/dot-claude/workflows/spec-fanout.js b/templates/multi-agent/dot-claude/workflows/spec-fanout.js new file mode 100644 index 0000000..13b53d3 --- /dev/null +++ b/templates/multi-agent/dot-claude/workflows/spec-fanout.js @@ -0,0 +1,119 @@ +export const meta = { + name: 'spec-fanout', + description: + 'Generate N distinct variants of one spec, each into its own output slot, then screen every variant against the spec before reporting.', + whenToUse: + 'N independent variants of a single spec — landing pages, prompt variants, config shapes. Not for staged pipelines or work that has to be merged.', + phases: [ + { title: 'Generate', detail: 'one agent per variant, each writing only its own slot' }, + { title: 'Screen', detail: 'check each variant against the spec and the diversification axis' }, + ], +} + +// Invoke with a config object, e.g. +// Run /spec-fanout with {"spec":"docs/specs/card.md","outDir":"variants","count":6,"axis":"visual density"} +// +// This is the workflow-native successor to the `/infinite` skill this module +// also ships. Prefer this one: the runtime holds the loop and the intermediate +// results, so the controller's context stays clean, the run is resumable, and +// the screening pass is a real gate rather than a suggestion. +const cfg = args || {} +const spec = cfg.spec +const outDir = cfg.outDir || 'variants' +const count = Math.max(1, Number(cfg.count || 5)) +const axis = cfg.axis || 'overall approach' + +if (!spec) { + return [ + 'Missing `spec`. Invoke with a config object, for example:', + ' Run /spec-fanout with {"spec":"docs/specs/card.md","outDir":"variants","count":6,"axis":"visual density"}', + '', + 'Fields: spec (required, path to the spec file), outDir (default "variants"),', + 'count (default 5), axis (what must differ between variants).', + ].join('\n') +} + +const SCREEN_SCHEMA = { + type: 'object', + required: ['compliant', 'distinct', 'notes'], + properties: { + compliant: { type: 'boolean', description: 'Meets every must-be-true item in the spec.' }, + distinct: { type: 'boolean', description: 'Genuinely differs from the other variants on the stated axis.' }, + violations: { type: 'array', items: { type: 'string' }, description: 'Spec requirements this variant misses.' }, + notes: { type: 'string', description: 'One or two sentences a human can act on.' }, + }, +} + +const slots = Array.from({ length: count }, (_, i) => ({ + index: i + 1, + slug: 'iter-' + String(i + 1).padStart(2, '0'), +})) + +log('Fanning out ' + count + ' variants of ' + spec + ' into ' + outDir + '/, diversified on: ' + axis) + +// pipeline, not parallel: a variant is screened the moment it is written, so a +// slow generator never holds up the screening of the ones already finished. +const reviewed = await pipeline( + slots, + (slot) => + agent( + [ + 'Read the spec at ' + spec + ' in full.', + '', + 'Produce EXACTLY ONE variant of it and write your files to ' + outDir + '/' + slot.slug + '/.', + 'That directory is yours alone. Never read, write, or reference any other', + 'slot under ' + outDir + '/ — sibling agents are working there concurrently.', + '', + 'You are variant ' + slot.index + ' of ' + count + '. What must be different', + 'about yours: ' + axis + '. Honor that literally; if the spec makes it', + 'impossible, say so in your summary rather than emitting a near-duplicate.', + '', + 'Every must-be-true requirement in the spec applies to your variant. If a', + 'spec requirement conflicts with the diversification axis, follow the spec', + 'and flag the conflict.', + '', + 'Return under 10 lines: what you built, the axis choice you made, and any', + 'requirement you could not satisfy.', + ].join('\n'), + { label: slot.slug, phase: 'Generate' }, + ), + (summary, slot) => + agent( + [ + 'Screen one generated variant against its spec. Read the spec at ' + spec + ',', + 'then read what the generator wrote under ' + outDir + '/' + slot.slug + '/.', + '', + "The generator's own summary (treat as an unverified claim, not evidence):", + summary === null ? '(the generator produced no summary)' : summary, + '', + 'Judge two things:', + '1. compliant — does it meet every must-be-true item in the spec?', + '2. distinct — does it genuinely differ on this axis: ' + axis + '?', + '', + 'Judge the files, not the summary. Do not fix anything.', + ].join('\n'), + { label: 'screen:' + slot.slug, phase: 'Screen', schema: SCREEN_SCHEMA }, + ).then((verdict) => ({ slot: slot.slug, verdict })), +) + +const results = reviewed.filter(Boolean) +const clean = results.filter((r) => r.verdict && r.verdict.compliant && r.verdict.distinct) +const problems = results.filter((r) => !clean.includes(r)) + +log(clean.length + '/' + count + ' variants passed screening') + +return { + spec, + outDir, + axis, + requested: count, + generated: results.length, + passed: clean.map((r) => r.slot), + needsAttention: problems.map((r) => ({ + slot: r.slot, + compliant: r.verdict ? r.verdict.compliant : null, + distinct: r.verdict ? r.verdict.distinct : null, + violations: r.verdict ? r.verdict.violations || [] : [], + notes: r.verdict ? r.verdict.notes : 'screening agent returned nothing', + })), +} From dfe8323a65db967d40ad3c0e864fc718887e9844 Mon Sep 17 00:00:00 2001 From: tigers1997 Date: Tue, 25 Aug 2026 11:50:40 -0400 Subject: [PATCH 2/2] fix(check): brace-match the workflow meta block; catch imports on any line Two defects in rule 4b, both raised as advisory findings on #95. The meta block was located with text.split("}", 1)[0], which truncates at the first nested brace. `phases: [{ title, detail }]` is a legitimate meta field, so a workflow that wrote `phases:` ahead of `name:` would be reported as missing a field it actually had. Replaced with a brace matcher that skips string literals, so a brace inside a description no longer unbalances the scan either. The static-import guard was text.lstrip().startswith("import "), which only fires when `import` is the first token in the entire file. A mid-file `import x from "y"` passed the check and then failed at invocation time, which is exactly what this rule exists to prevent. Now matched at the start of any line, with the dynamic-import test tightened so it doesn't fire on an identifier that merely ends in "import". Adds test/schema-hygiene/test-workflow-meta-rule.sh -- the rule had no test at all. Covers both regressions, the string-brace case, a genuinely missing field, an unterminated literal, and asserts the shipped spec-fanout.js stays clean. --- configure.py | 44 ++++++++++- .../schema-hygiene/test-workflow-meta-rule.sh | 79 +++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100755 test/schema-hygiene/test-workflow-meta-rule.sh diff --git a/configure.py b/configure.py index b0f1258..0a4e8d3 100755 --- a/configure.py +++ b/configure.py @@ -781,6 +781,39 @@ def render_template_index() -> str: return "\n".join(lines) +def _js_meta_block(text: str) -> str: + """Return the `export const meta = { ... }` object literal, brace-matched. + + Deliberately not `text.split("}", 1)[0]`: a `meta` block legitimately nests + objects — `phases: [{ title, detail }]` — so splitting on the first `}` + truncates it and would miss a `name:`/`description:` written after `phases:`. + String literals are skipped so a brace inside a description can't unbalance + the scan. Returns "" when there is no terminated literal. + """ + i = text.find("export const meta") + if i == -1: + return "" + start = text.find("{", i) + if start == -1: + return "" + depth, j, n = 0, start, len(text) + while j < n: + c = text[j] + if c in "'\"`": + quote = c + j += 1 + while j < n and text[j] != quote: + j += 2 if text[j] == "\\" else 1 + elif c == "{": + depth += 1 + elif c == "}": + depth -= 1 + if depth == 0: + return text[start:j + 1] + j += 1 + return "" + + def _find_bash(): """Return a bash that can actually execute, or None. @@ -1026,10 +1059,17 @@ def warn(source, msg): if "export const meta" not in text: err(f"templates/{rel}", "workflow script has no `export const meta` block") continue + meta_block = _js_meta_block(text) + if not meta_block: + err(f"templates/{rel}", + "workflow `meta` is not a terminated object literal") + continue for field in ("name:", "description:"): - if field not in text.split("}", 1)[0]: + if field not in meta_block: err(f"templates/{rel}", f"workflow `meta` is missing `{field}`") - if "import(" in text or text.lstrip().startswith("import "): + # Static `import` on ANY line, not only the first: the runtime rejects + # a mid-file `import x from "y"` just as hard as one at the top. + if re.search(r"(?m)^\s*import\s", text) or re.search(r"(?