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..0a4e8d3 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,139 @@ 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 _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. @@ -915,6 +1049,38 @@ 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 + 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 meta_block: + err(f"templates/{rel}", f"workflow `meta` is missing `{field}`") + # 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"(?.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', + })), +} diff --git a/test/schema-hygiene/test-workflow-meta-rule.sh b/test/schema-hygiene/test-workflow-meta-rule.sh new file mode 100755 index 0000000..87d7cae --- /dev/null +++ b/test/schema-hygiene/test-workflow-meta-rule.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Verify the --check rule for dynamic-workflow scripts (rule 4b) actually holds. +# Two regression classes, both found by review of the original implementation: +# 1. `meta` was located with text.split("}", 1)[0], which truncates at the +# first nested brace — a `phases: [{...}]` entry ahead of `name:` made the +# rule report a missing field that was present. +# 2. the static-import guard was text.lstrip().startswith("import "), which +# only fires when `import` is the first token in the whole file; a mid-file +# `import x from "y"` sailed through, and the runtime rejects it. +# Asserts both are caught now, that the shipped workflow stays clean, and that +# a brace inside a description string doesn't unbalance the brace matcher. +set -euo pipefail + +python3 - <<'EOF' +import re +import sys +sys.path.insert(0, '.') +from configure import _js_meta_block + +def missing(text, field): + return field not in _js_meta_block(text) + +def has_import(text): + return bool(re.search(r"(?m)^\s*import\s", text) + or re.search(r"(?