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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project. Format: [Keep a Changelog](https://keepacha

## Unreleased

- **fix(commands): scope the microbit-enforcer SessionStart marker-clear to fresh-slate sources so a long session keeps its freezes (dogfood F1).** The `freeze`/`guard`/`careful` micro-behaviors drop `.claude/.frozen` / `.guarded` / `.careful` markers that the `microbit-enforcer.sh` PreToolUse hook reads to reject Write/Edit. The companion SessionStart hook that clears those markers shipped with **no `matcher`**, so it fired on *every* SessionStart source — including `resume` and `compact`, not just `startup`. Net effect: in a long session you `/freeze` a file, context compacts (which fires a SessionStart with `source: compact`) or you `--resume`, and the marker is silently deleted mid-task — the enforcer stops protecting the file exactly when you'd most want it to. **Fix:** the SessionStart entry in `templates/commands/microbit-enforcer/settings-patch.json` now carries `"matcher": "startup|clear"` (verified against `code.claude.com/docs/en/hooks-guide` — SessionStart sources are `startup`, `resume`, `clear`, `compact`, and the matcher filters on source), so markers clear only on a brand-new session or an explicit `/clear` and persist across `--resume`/`--continue` and compaction. New `test/microbit-enforcer/test-sessionstart-matcher.sh` asserts the merged settings carry the matcher (red against the unmatched form). `examples/python-uv-fastapi/.claude/settings.json` updated to match; the `templates/core/CLAUDE.md` gitignore-block note softened from "transient state cleared by SessionStart" to "transient session state," and the freeze/guard/careful `SKILL.md` lifecycle notes + the `microbit-enforcer.sh` header corrected to match — across both the templates and the `python-uv-fastapi` example mirror (markers clear on a fresh session or `/clear`, persist across `--resume`/compaction). Surfaced by dogfooding the configurator into a containerized project.

- **feat(preflight): `[ STACK WARNINGS ]` — flag a configured check stack that doesn't exist at the repo root (dogfood F2).** A user who keeps the default Node/pnpm intake answers (or otherwise configures `pnpm`/`uv`/`cargo`/… check commands) on a project whose manifest lives in a subdir or behind a container got **zero feedback** that the scaffolded Stop hook would never run: `stop-run-checks.sh` skips any check whose stack manifest is absent at the root (skip rule 3), so the whole typecheck/lint/test loop self-disables silently. New `check_stack_reality(target_dir, form_values)` preflight maps each configured check command's (typecheck/lint/test) first-binary to its stack manifest (new `BINARY_TO_MANIFEST`, mirroring `manifest_for()` in `stop-run-checks.sh`) and warns when that manifest is missing from the project root — naming the subdir when the manifest is one level down (`frontend/package.json` in a split frontend/backend layout) and adding a note when a `docker-compose.yml`/`Dockerfile` is present that the host-side format/check hooks call host tools and will no-op if the toolchain lives in the container. Warning-only; never blocks scaffolding; stays silent when the stack matches the root or when no command maps to a known manifest (bare `tsc`/`pytest`/`ruff`). Scoped to the three commands `stop-run-checks.sh` actually runs (typecheck/lint/test), so the warning never misattributes an install/build/dev skip to the Stop hook; command parsing is guarded so a malformed quote degrades to a plain split instead of crashing (same guard applied to the pre-existing `extract_first_binaries`). Rendered as a `[ STACK WARNINGS ]` block alongside the existing preflights. New `test/cc-manifest/test-stack-reality-preflight.sh` (matched-silent, unmappable-silent, monorepo-names-subdir, containerized-note, absent-everywhere, install-only-silent, malformed-quote-safe) + a malformed-quote case in `test-check-commands-extracted.sh`. Surfaced by dogfooding the configurator into a Python + FastAPI + Vite project that runs entirely via Docker.

- **chore(skills): sync discipline-skills v5.1.0 → v6.0.2 (obra/superpowers).** First sync since the module landed (PR #59, 2026-05-24); upstream shipped **v6.0.0** on 2026-06-16 (major), then **v6.0.1** + **v6.0.2** on 2026-06-17 as fast-follow patch releases. Skill inventory unchanged (14/14 upstream; we still fork the same 7). **Structural change in `subagent-driven-development`**: the two per-task reviewer prompts upstream removed (`spec-reviewer-prompt.md` + `code-quality-reviewer-prompt.md`) are replaced by a single `task-reviewer-prompt.md` that returns spec-compliance + quality verdicts in one pass; two new bash scripts (`scripts/review-package` + `scripts/task-brief`) move the review diff and task text to files so they don't park in the controller's context (file-handoff, not paste-handoff). Our v5-era "embed code-reviewer.md inline in code-quality-reviewer-prompt.md" SYNC.md edit is RETIRED — the new `task-reviewer-prompt.md` has no external dependency. **Configurator change**: `configure.py`'s `executable` heuristic widened from `rel.endswith(".sh")` to also honor the source file's user-execute bit, so the new `scripts/review-package` and `scripts/task-brief` (no `.sh` extension upstream) arrive at user installs with `+x` set. Other ports are mostly clean re-applies: `using-git-worktrees` + `finishing-a-development-branch` inherit the v6 drop of the global `~/.config/superpowers/worktrees/` path (project-local `.worktrees/` only) and `finishing-a-development-branch`'s forge-neutral rewrite (no hardcoded `gh pr create`); `writing-plans` gains v6's Global Constraints + per-task Interfaces template sections; `brainstorming`'s checklist drops the "offer visual companion just-in-time" step (the v6 process-flow digraph no longer carries the visual-companion nodes either); `executing-plans` re-applies the "This skill works much better" reframe (project-neutral). `superpowers:` prefix re-stripped across all SKILL.md and `*-prompt.md` files — `test-no-superpowers-prefix.sh` clean. Path list in `config_schema.py` updated (removed 2, added 3 → 15 total); test fixtures (`test-module-files-exist.sh` + `test-scaffold-installs-skills.sh`) updated to match. SYNC.md pin bumped to v6.0.2 with a fresh delta paragraph and revised canonical-edit list (5 items → 4; the retired embed-template item documented as removed). README's discipline-skills paragraphs refreshed (v5.1.0 → v6.0.2; carve-out section + module description). Known papercut documented: `subagent-driven-development/SKILL.md` retains one "final whole-branch review" line referencing `../requesting-code-review/code-reviewer.md` (a skill we don't ship) — pre-existing in v5.1.0, not addressed in this sync to keep port scope tight.

- **chore(compat): CC 2.1.176–2.1.181 survey — held subagent-nesting wording lifted; no `tested_up_to` bump.** SchemaStore sync PR #5723 still open/draft (untouched since 2026-05-24 despite dep #5728 merged 2026-06-01), so `tested_up_to` stays pinned at 2.1.150 per the no-lone-bumps rule. **Held constraint resolved:** v2.7.0's 2.1.172–175 survey held the subagent-nesting wording on a CHANGELOG/docs contradiction (CHANGELOG said subagents can nest ≤5 levels; live docs said they cannot). The contradiction is now reconciled — `code.claude.com/docs/en/sub-agents` documents nested subagents under `min-version: 2.1.172`, and CC 2.1.181 explicitly unifies the foreground+background depth cap at 5 levels (*"Fixed foreground subagents spawning unbounded nested chains; they now respect the same 5-level depth limit as background subagents"*). Three shipped-template wording sites updated: `docs/04-subagents-mcp-orchestration.md` lines 59 + 140 and `templates/commands/infinite/SKILL.md` line 65. Leaf-design guidance preserved and remains the recommended pattern within the testing envelope (`tested_up_to < 2.1.172` means most users on the tested band don't have nesting; the `/infinite` skill's no-sibling-coordination invariant still holds because subagent contexts don't share even when nesting is available). **2.1.176–181 versions surveyed** (2.1.177 + 2.1.180 not present in CHANGELOG — skipped or internal-only per the verbatim-pull discipline): no new in-territory `settings.json` keys; out-of-territory by precedent — `footerLinksRegexes` + `language` (2.1.176, UI-pref tier), `sandbox.allowAppleEvents` (2.1.181, macOS-only opt-in). Future opt-in stub candidate: the new `Tool(param:value)` permission-rule form with `*` wildcard (2.1.178, e.g. `Agent(model:opus)`) — no shipped template uses it yet, SchemaStore acceptance status unverified, parking for the next compat batch. Nested `.claude/skills` collision handling (2.1.178) gains `<dir>:<name>` namespacing — reinforces the existing plugin-skill carve-out pattern, no shipped-template impact. MCP server-level specs in subagent `disallowedTools` (`mcp__server`, `mcp__server__*`, `mcp__*`) silently-ignored bug fixed in 2.1.178 — grep against shipped templates clean. Compat comment refreshed in `config_schema.py` with the full survey.
Expand Down
113 changes: 112 additions & 1 deletion configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,23 @@ def check_settings_validates(settings: dict) -> list:
)


# Maps a check-command's first binary to the stack manifest that signals
# "this stack is scaffolded at this directory." Mirrors manifest_for() in
# templates/git-workflow/hooks/stop-run-checks.sh — keep the two in sync.
# Values are a subset of KNOWN_STACK_MANIFESTS.
BINARY_TO_MANIFEST = {
"pnpm": "package.json", "npm": "package.json",
"yarn": "package.json", "bun": "package.json",
"uv": "pyproject.toml", "poetry": "pyproject.toml",
"pip": "pyproject.toml", "pip3": "pyproject.toml",
"cargo": "Cargo.toml", "rustc": "Cargo.toml",
"go": "go.mod",
"bundle": "Gemfile", "gem": "Gemfile",
"mvn": "pom.xml",
"gradle": "build.gradle", "./gradlew": "build.gradle",
}


def detect_stack_manifests(target_dir: Path) -> list:
"""Return sorted list of known stack-manifest filenames present at target_dir.

Expand All @@ -967,12 +984,95 @@ def extract_first_binaries(typecheck: str = None, lint: str = None, test: str =
for kind, cmd in (("typecheck", typecheck), ("lint", lint), ("test", test)):
if not cmd or not cmd.strip():
continue
parts = shlex.split(cmd)
try:
parts = shlex.split(cmd)
except ValueError:
parts = cmd.split() # unbalanced quote etc. — degrade, don't crash
if parts:
out[kind] = parts[0]
return out


def check_stack_reality(target_dir, form_values: dict) -> list:
"""Warn when a configured check command's stack manifest is missing from the
project root — the exact condition that makes the Stop hook
(stop-run-checks.sh, skip rule 3) silently skip that check at runtime.

For each missing manifest, note whether it exists one directory level down
(monorepo / split frontend-backend layout) so the user knows the toolchain
is present but the root-anchored hook won't find it. When the project also
looks containerized (a compose file or root Dockerfile is present), append a
note that the host-side format/check hooks call host tools and will no-op if
the real toolchain lives in the container.

Warning-only; never blocks scaffolding. Returns a list of strings for the
caller to render under a [ STACK WARNINGS ] block. Silent when every
configured command's manifest is present at the root, or when no configured
command maps to a known manifest (e.g. bare tsc / pytest / ruff)."""
import shlex
fv = form_values or {}
# Only the three commands the Stop hook (stop-run-checks.sh) actually runs —
# naming install/build/dev here would misattribute the runtime skip to it.
bins = set()
for key in ("cmd_typecheck", "cmd_lint", "cmd_test"):
val = fv.get(key)
if isinstance(val, str) and val.strip():
try:
parts = shlex.split(val)
except ValueError:
parts = val.split() # unbalanced quote etc. — degrade, don't crash
if parts:
bins.add(parts[0])

wanted = {} # manifest -> set of configured binaries that imply it
for b in bins:
manifest = BINARY_TO_MANIFEST.get(b)
if manifest:
wanted.setdefault(manifest, set()).add(b)
if not wanted:
return []

try:
subdirs = [p for p in target_dir.iterdir()
if p.is_dir() and not p.name.startswith(".")]
except OSError:
subdirs = []

warnings = []
for manifest in sorted(wanted):
if (target_dir / manifest).exists():
continue # configured stack matches the repo root — fine
bins_str = "/".join(sorted(wanted[manifest]))
found_in = sorted(p.name for p in subdirs if (p / manifest).exists())
if found_in:
where = ", ".join(d + "/" for d in found_in[:3])
warnings.append(
"configured `{b}` checks but no {m} at the project root "
"(found in {w}). stop-run-checks.sh runs from the root and "
"will silently skip these — point your cmd_* values at the "
"subdir (e.g. `cd {d} && ...`) or scaffold it separately.".format(
b=bins_str, m=manifest, w=where, d=found_in[0]))
else:
warnings.append(
"configured `{b}` checks but no {m} anywhere in the tree — "
"these checks can't run here. Fix the stack in "
".claude-config.json or re-run cc-configure.".format(
b=bins_str, m=manifest))

if warnings:
container = next(
(n for n in ("docker-compose.yml", "docker-compose.yaml",
"compose.yml", "compose.yaml", "Dockerfile")
if (target_dir / n).exists()), None)
if container:
warnings.append(
"detected {c} — if your toolchain runs in containers, the "
"format-on-write and stop-run-checks hooks call host tools and "
"will no-op. Point cmd_* at `docker compose exec <svc> ...`.".format(
c=container))
return warnings


def _configurator_sha():
"""Short git SHA of the configurator's OWN checkout (REPO_ROOT), or None
when unavailable (not a git repo, git missing, timeout, any error).
Expand Down Expand Up @@ -2612,6 +2712,17 @@ def main():
for w in env_warnings:
print(f" {yellow('!')} {w}")

# Surface a configured check stack that doesn't exist at the repo root —
# the runtime condition that makes stop-run-checks.sh silently self-disable.
stack_warnings = check_stack_reality(target_dir, config["formValues"])
if stack_warnings:
print()
print(bold(yellow("[ STACK WARNINGS ]")))
for w in stack_warnings:
print(f" {yellow('!')} {w}")
print(dim(" stop-run-checks.sh skips any check whose stack manifest is absent at the"))
print(dim(" project root, so a mismatched stack disables the typecheck/lint/test loop."))

# Surface pre-existing design docs (typical output of a prior superpowers
# brainstorming session). Informational, not a warning — the install is
# fine; we just want to nudge the user to fold their design into CLAUDE.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
# .claude/.careful — newline-separated globs; prompt before match
#
# Lifecycle: a SessionStart hook (registered alongside this hook by the
# configurator's settings-patch) clears all three files. Markers are
# session-scoped, not persistent.
# configurator's settings-patch, matcher startup|clear) clears all three
# files on a fresh session or /clear. Markers are session-scoped but
# survive --resume and compaction, so a long session keeps its markers.

set -euo pipefail

Expand Down
1 change: 1 addition & 0 deletions examples/python-uv-fastapi/.claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
],
"SessionStart": [
{
"matcher": "startup|clear",
"hooks": [
{
"type": "command",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ Currently careful:

## Lifecycle

Careful patterns persist for the session, cleared on session start.
Careful patterns persist for the sessioncleared on a fresh session (`startup`) or `/clear`, but preserved across `--resume`/`--continue` and compaction.
7 changes: 4 additions & 3 deletions examples/python-uv-fastapi/.claude/skills/freeze/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ plan, or investigate without code changes happening.
## Notes

- Frozen state is per-project (the marker is in `.claude/`).
- Frozen state does **not** persist across Claude Code session
restarts: the `SessionStart` hook clears all microbit markers
(.frozen / .guarded / .careful).
- Frozen state is session-scoped: a fresh session (`startup`) or
`/clear` clears all microbit markers (.frozen / .guarded / .careful),
but it **survives** `--resume`/`--continue` and compaction — so a long
session keeps its freeze. Run `/unfreeze` to lift it deliberately.
- Read tools (Read, Grep, Glob, Bash with read-only commands) are
unaffected.
5 changes: 3 additions & 2 deletions examples/python-uv-fastapi/.claude/skills/guard/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ Currently guarded:

## Lifecycle

Guards persist for the session. The configurator clears `.guarded` on
session start (SessionStart hook). Removing a single guarded pattern
Guards persist for the session. A fresh session (`startup`) or `/clear`
clears `.guarded`; it survives `--resume`/`--continue` and compaction.
Removing a single guarded pattern
mid-session isn't supported in v2.1 — re-create the guard list by
clearing it (delete `.claude/.guarded`) and re-running `/guard` for
the patterns you still want.
2 changes: 1 addition & 1 deletion examples/python-uv-fastapi/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ over pasted code. Put path-scoped detail in .claude/rules/*.md instead.
The cc-configure scaffold makes assumptions about what to track vs. ignore. Honor them when first-committing a new project (`git init` + first `git add`) or when restructuring `.gitignore`:

- **Commit:** `CLAUDE.md`, `.claude/` (agents, hooks, skills, rules, settings.json, output-styles), `.mcp*.json`, `claude-ctx` if present, project source.
- **Gitignore by default:** `.claude/settings.local.json` (machine-local), `.claude/logs/`, `.claude/.frozen` / `.guarded` / `.careful` (transient state cleared by SessionStart), `.claude-config.json` (configurator working-state). cc-configure appends the `# --- Claude Code ---` block to `.gitignore` automatically — if you rewrite or replace `.gitignore`, preserve that block (or rerun `cc-configure --retrofit` to re-append it).
- **Gitignore by default:** `.claude/settings.local.json` (machine-local), `.claude/logs/`, `.claude/.frozen` / `.guarded` / `.careful` (transient session state), `.claude-config.json` (configurator working-state). cc-configure appends the `# --- Claude Code ---` block to `.gitignore` automatically — if you rewrite or replace `.gitignore`, preserve that block (or rerun `cc-configure --retrofit` to re-append it).
- **Nested upstream clones** (vendored deps, fork wrappers) should be gitignored and keep their own `.git/` — don't try to nest two repos in one tree. List the subdir in `.gitignore` *above* the Claude Code block.

## Design features
Expand Down
2 changes: 1 addition & 1 deletion templates/commands/careful/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ Currently careful:

## Lifecycle

Careful patterns persist for the session, cleared on session start.
Careful patterns persist for the sessioncleared on a fresh session (`startup`) or `/clear`, but preserved across `--resume`/`--continue` and compaction.
7 changes: 4 additions & 3 deletions templates/commands/freeze/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ plan, or investigate without code changes happening.
## Notes

- Frozen state is per-project (the marker is in `.claude/`).
- Frozen state does **not** persist across Claude Code session
restarts: the `SessionStart` hook clears all microbit markers
(.frozen / .guarded / .careful).
- Frozen state is session-scoped: a fresh session (`startup`) or
`/clear` clears all microbit markers (.frozen / .guarded / .careful),
but it **survives** `--resume`/`--continue` and compaction — so a long
session keeps its freeze. Run `/unfreeze` to lift it deliberately.
- Read tools (Read, Grep, Glob, Bash with read-only commands) are
unaffected.
Loading
Loading