Confirms the framework is wired in correctly so the rest of the framework's skills resolve from the right paths, and surfaces any drift between the committed lock (project pin) and the local lock (per-machine fetch). Read-only by default — surfaces gaps and remediation commands.
--auto-fix-symlinks — exception to read-only. If the snapshot is present but symlinks are missing or dangling in any active target dir (agents.md — .agents/skills/, .claude/skills/, .github/skills/, plus any present holdout), recreate them across all of them. Used by the post-checkout hook (adopt.md Step 10) on a fresh worktree where the gitignored symlinks didn't follow the checkout.git rev-parse --show-toplevel — must succeed.adopt.md Step 0): skills/setup/SKILL.md exists at the repo root with name: magpie-setup and skills/list-skills/ is present. If so and .apache-magpie.lock records method: local, the repo is self-adopted — run the Local self-adoption checks instead of the snapshot checks below, then stop. A framework checkout with no method: local lock is simply not adopted yet — point at /magpie-setup.<repo-root>/.apache-magpie.lock is missing, the repo is not adopted. Surface and stop with a pointer at /magpie-setup adopt.Run these (and only these) when pre-flight detected a framework checkout self-adopted with method: local. There is no snapshot, no remote lock, and no per-machine lock to drift against — the committed symlinks into the in-repo skills/ source are the adoption state.
.apache-magpie.lock parses and records method: local. ✓ when present; ✗ with a pointer at /magpie-setup otherwise..agents/skills/, every magpie-<n> is a symlink whose target (../../skills/<n>/) resolves to a directory containing SKILL.md. In every relay target dir (agents.md — .claude/skills/, .github/skills/, plus any present holdout), every magpie-<n> is a symlink whose target (../../.agents/skills/magpie-<n>) resolves through the canonical entry to the same SKILL.md. ✗ list any dangling or non-symlink entry — or any relay that points straight at the snapshot/source instead of at .agents/skills/ — naming the target dir; remediation: re-run /magpie-setup (idempotent).skills/<n>/ with a SKILL.md has a canonical magpie-<n> symlink in .agents/skills/ and a matching relay in every other active target dir (unless a skill-families: filter was deliberately applied). ⚠ list any source skill with no link, per target; remediation: /magpie-setup..gitignore. Each active target dir's <dir>/* is ignored, with !/<dir>/magpie-* un-ignoring the committed symlinks — .agents/skills/, .claude/skills/, .github/skills/, and any present holdout. ✗ if any un-ignore line is missing (those symlinks would not be tracked)..apache-magpie/ snapshot dir and no .apache-magpie.local.lock — local self-adoption uses neither. ⚠ surface either if found (a stale remote adoption was not cleaned up).Run all checks even on early failure (a missing snapshot at check 1 doesn't tell us anything about the override directory or doc updates — surface every check).
<snapshot-dir>/ exists (as a directory or a symlink that resolves to one) and contains the expected top-level files (README.md, AGENTS.md, .claude/skills/, tools/).
git rev-parse --git-dir equals git rev-parse --git-common-dir) → run /magpie-setup upgrade (it gracefully handles the recover-snapshot case when the committed lock exists but the snapshot does not)./magpie-setup worktree-init to symlink <snapshot-dir> to the main checkout's. Do not propose upgrade — that creates a per-worktree snapshot, which is the bug worktree-init is designed to prevent./magpie-setup worktree-init (with the move-aside flow) to convert into a symlink to the main's snapshot. Verify continues — the per-worktree snapshot is still functional, just wasteful.<snapshot-dir> is a symlink that resolves outside the same repo's main checkout — the operator pointed it at a different framework checkout deliberately. Surface the resolved target and continue; do not auto-remediate.<committed-lock> (.apache-magpie.lock) and <local-lock> (.apache-magpie.local.lock) both parse.
<committed-lock> is missing → not adopted; redirect (already caught in pre-flight).<local-lock> is missing or unparsable → first install on this machine has not run, or the file was truncated. Suggest /magpie-setup upgrade to re-create the snapshot + write the local lock.This is the core drift check. The same logic every framework skill runs at the top of its invocation.
Compare:
<committed-lock>.method vs <local-lock>.source_method<committed-lock>.url vs <local-lock>.source_url<committed-lock>.ref vs <local-lock>.source_refgit-branch: also compare upstream tip (the actual current HEAD of the branch on the remote) against <local-lock>.fetched_commit.| Result | Severity |
|---|---|
All match (and for git-branch, local is at upstream tip) | ✓ |
| Method or URL differ | ✗ — full re-install needed; remediation: /magpie-setup upgrade |
Ref differs (e.g. project bumped tag, or git-branch local is behind upstream) | ⚠ — sync needed; remediation: /magpie-setup upgrade |
svn-zip SHA-512 differs from the verification anchor in <committed-lock> | ✗ — security-flagged; the released zip changed content; investigate before upgrading |
.gitignore correctly excludes the snapshot + local lock + symlinks + project-local settingsCheck that the entries from adopt.md Step 7 are present in <repo-root>/.gitignore. Required:
/.apache-magpie/ (snapshot path)/.apache-magpie.local.lock (per-machine state)/.claude/settings.local.json (per-machine project-scope settings — written to by sandbox-add-project-root.sh as the per-worktree sandbox-allowlist defense for issue #197; must never be committed since the content is machine-specific absolute paths)__pycache__/ and *.pyc (byte-compiled artefacts emitted when framework skill scripts run from the adopter checkout; non-anchored so they match at any depth)Recommended (a uniform magpie-* glob block per active target dir — agents.md — with no per-layout variation):
Canonical target (.agents/skills/) — always present: /.agents/skills/magpie-* with !/.agents/skills/magpie-setup.
Relay targets (.claude/skills/, .github/skills/) — the same two-line block keyed on each dir (/.claude/skills/magpie-* with !/.claude/skills/magpie-setup, and likewise for .github/skills/).
Any present holdout (.windsurf/skills/, .goose/skills/, …) — the same two-line block keyed on its own dir.
✗ if /.apache-magpie/ is not gitignored — the snapshot is at risk of being accidentally committed.
✗ if /.apache-magpie.local.lock is not gitignored — per-machine state would leak into the repo.
✗ if /.claude/settings.local.json is not gitignored — per-machine absolute paths would leak into the repo; the sandbox-allowlist helper refuses to write to a non-ignored target as defense in depth, but verify surfaces the underlying .gitignore gap so the operator fixes the root cause.
⚠ if __pycache__/ or *.pyc is not gitignored — byte-compiled artefacts from skill scripts could be accidentally committed.
⚠ if symlink patterns are not gitignored.
Run this check across every active target dir (agents.md — .agents/skills/, .claude/skills/, .github/skills/, plus any present holdout), not just the .claude//.github/ pair.
For each magpie-* symlink under any active target dir — canonical ones resolving (via .agents/skills/) into .apache-magpie/skills/<name>/, relays resolving through ../../.agents/skills/magpie-<name> to the same:
.agents/skills/ entry, naming the target dir. Remediation: /magpie-setup adopt (idempotent re-run) or this same skill with --auto-fix-symlinks.For each framework skill in the snapshot not symlinked in a given active target dir, classify it (a skill missing from .agents/skills/ is as much a gap as one missing from .claude/skills/):
family: setup skill except setup itself, and every family: utilities skill — read the family: frontmatter key, per SKILL.md Golden rule 8) → surface as ✗. These families are not opt-in; missing symlinks here indicate a broken install or a skipped upgrade pass. Remediation: /magpie-setup verify --auto-fix-symlinks (cheap), or /magpie-setup upgrade (covers the family-wide pass).<committed-lock> / <local-lock>) → surface as ✗. The project declared the family but the install is missing a skill from it. Remediation as above.The --auto-fix-symlinks path repairs the first two classes in place — in every active target dir — without prompting; the ⚠ class needs an explicit /magpie-setup adopt re-run with the family added to the pick.
.apache-magpie-overrides/ exists + has the README<repo-root>/.apache-magpie-overrides/ is a directory with the README.md scaffold from adopt.md Step 9.
/magpie-setup adopt (idempotently re-creates).README.md is missing — the directory may have been hand-created. Suggest re-running /magpie-setup adopt.setup skill itself is up to dateCompare the canonical committed setup skill (at .agents/skills/magpie-setup/) against the snapshot's .apache-magpie/skills/setup/.
✓ if same content.
⚠ if different — the adopter's committed copy has drifted from the snapshot. The remediation depends on which way the drift goes:
/magpie-setup upgrade). Run /magpie-setup upgrade — its Step 4b auto-overwrites the committed copy with the snapshot's version, reloads the skill in-flight so the rest of the upgrade run executes against the new bootstrap content (per SKILL.md Golden rule 9), surfaces local modifications first if any exist, and lands the change in git status for the user to commit.apache/magpie; the local fix is to revert the modifications and use .apache-magpie-overrides/ instead.Two sub-checks on <repo-root>/.git/hooks/post-checkout:
Presence + executable. File exists, is executable, and carries the current hook body — the sandbox-allowlist helper chain and the agent-guard seeding block (see adopt.md Step 10). It must not contain the long-removed /magpie-setup verify --auto-fix-symlinks line (a slash command is not shell-callable; it printed a spurious error on every checkout).
git worktree add (they fall back to /magpie-setup worktree-init). Print the install recipe.Content drift vs the framework's expected. Diff the installed hook against the framework's expected hook content (the canonical source is shipped under the snapshot — locate it during the check). Same logic applies for any other adopter-installed local hook or config file the framework grows in future.
/magpie-setup (adopt or upgrade), whose hook+config-sync pass re-installs from the snapshot after asking about hand-edits.Three sub-checks for the deterministic guard (tools/agent-guard):
<repo-root>/.claude/hooks/agent-guard.py exists and its content matches the snapshot's tools/agent-guard/src/agent_guard/__init__.py./magpie-setup (adopt or upgrade), whose sync pass re-installs it.guards.d populated. <repo-root>/.claude/hooks/guards.d/ exists and contains every guard the snapshot ships — the engine‘s bundled guards.d/*.py and each skill-owned skills/*/guards/*.py (e.g. mention, mark_ready, security_language). Flag a missing expected guard or a stale copy; extra locally-added *.py are fine. A missing skill guard means that skill’s deterministic protection is silently inactive — remediation is /magpie-setup (adopt/upgrade), which re-collects.<repo-root>/.claude/settings.json has a hooks.PreToolUse entry (matcher Bash) whose command runs agent-guard.py.adopt.md Step 12) for the maintainer to apply (settings.json is agent-edit-denied).The script + guards.d are gitignored framework code (adopt.md Step 7), synced from the snapshot rather than committed — so a missing script is the expected state of a fresh checkout, not a defect, and the fix is always a re-sync (never git add). When this check runs inside a worktree, the script + guards.d are per-worktree files (the settings.json wiring resolves $CLAUDE_PROJECT_DIR/.claude/hooks/agent-guard.py against the worktree root). The remediation for a missing script in a worktree is not the main-checkout sync but worktree-init.md Step 1d (or the post-checkout hook on the next git worktree add), which seeds it from the main checkout's already-synced copy.
Defensive cross-check for issue #197: sandbox.filesystem.allowRead: ["."] does not in practice cover CWD under the harness, so /magpie-setup (adopt, upgrade, worktree-init) chains into ~/.claude/scripts/sandbox-add-project-root.sh to add explicit absolute paths to each worktree's own project-local settings. This check verifies that chain landed for the current worktree.
For the current worktree (resolved via git rev-parse --show-toplevel):
<worktree>/.claude/settings.local.json's sandbox.filesystem.allowRead and sandbox.filesystem.allowWrite.~/.claude/scripts/sandbox-add-project-root.sh is installed — remediation: ~/.claude/scripts/sandbox-add-project-root.sh (no --all-worktrees needed — just this worktree), or re-run /magpie-setup (adopt/upgrade) which chains into the helper as part of its Step 12 / Step 6c sandbox-allowlist pass./magpie-setup-isolated-setup-install yet. Suggest that skill. Not ✗ because secure-agent isolation is independent of framework adoption, and an adopter who runs without the sandbox enabled has nothing to lose by the missing entry.<worktree>/.claude/settings.local.json is absent entirely — same remediation (re-run the helper or /magpie-setup-isolated-setup-install). The file is auto-created by the helper on first run.<worktree>/.claude/settings.local.json exists AND is not gitignored (cross-check via git check-ignore). Per the security rationale in docs/setup/secure-agent-setup.md → Security rationale — why project-local is safe to write to, the per-machine settings.local.json must never be committed. Remediation: add /.claude/settings.local.json to the adopter's .gitignore (also surfaced by check 4 above).The check scopes to the current worktree only, not the full git worktree list, because each worktree carries its own project-local settings file — /magpie-setup verify running in worktree A has no business asserting on worktree B‘s file (which it cannot even reliably read without crossing into another working tree’s path).
This check is read-only on the framework state. The defence is layered: /magpie-setup writes during adopt/upgrade, setup-isolated-setup-verify adds a live read+write probe (check 8 there), and this check is the cheap static cross-check to surface drift between the two skill families.
.claude/worktrees/Detect worktrees the agent (or a prior session) created under <repo-root>/.claude/worktrees/ that have been left lying around beyond their useful life. Main-checkout only — worktrees can only be inspected from the checkout that owns them, and the git worktree list output is the same across the family anyway.
Stale agent-worktrees are a real friction source: they hold branches (typically main, since EnterWorktree defaults to branching from main), so a subsequent git checkout main from the main checkout fails with “main is already used by worktree at …” — silently, in the middle of a longer command pipeline, producing confusing downstream failures. A session that ended without explicit ExitWorktree(action: "remove") leaves the worktree on disk; the next session has no way to know it is abandoned.
The check:
Run git worktree list --porcelain and filter to entries whose worktree path is under <repo-root>/.claude/worktrees/.
For each, compute the age — the maximum of:
mtime (file-system signal — how long since anything inside changed); andgit -C <worktree> log -1 --format=%cI HEAD‘s commit time (git-state signal — how recent the latest commit on the worktree’s branch is).The max-of-two avoids two failure modes: a worktree whose commits are old but whose files were touched recently (still active) and a worktree whose files are old but whose branch was recently rebased (still in use). Both look fresh to one of the signals alone.
Bucket the result against a threshold (default: 7 days; adopter override via worktree_stale_days in <project-config>/magpie-setup.md — if absent, default stands):
git -C <worktree> status --porcelain is empty) — surface the path, age, branch name, and propose git worktree remove <path> as the cleanup.git worktree remove --force <path> (or EnterWorktree(path) to enter it interactively and decide).The check is read-only: it never auto-removes a worktree, never force-anything. The proposal lands in the verify-report and the operator chooses to act.
Threshold rationale. Agent-worktrees are designed for per-task isolation: open, work, close. A worktree older than 7 days is overwhelmingly a session that ended without explicit cleanup. Lower thresholds (3 days, 1 day) hit false-positive on multi-day tasks that legitimately stretch across sessions; higher thresholds (14, 30 days) let the bug class persist long enough to actually break a git checkout main weeks later.
Why this check exists separately from worktree-init. worktree-init wires up a newly-created worktree. There is no symmetric step for end-of-life: EnterWorktree(action: "remove") from inside a session removes it cleanly, but sessions that crash, get interrupted, or end via context- window-exhaustion leak. This check is the periodic cleanup sweep that catches the leakage.
Audit the adopter‘s per-machine permission allow-list for patterns that grant arbitrary code execution, and surface the recommended read-only patterns the framework’s skills use heavily. Local-state only — the framework never mutates .claude/settings*.json; this check produces proposals the operator confirms before any write.
Two files to read:
<repo-root>/.claude/settings.json (committed, project-wide).<repo-root>/.claude/settings.local.json (gitignored, per-machine — same security model as .apache-magpie.local.lock).For each, parse the JSON, walk permissions.allow[], and bucket each entry against two canonical lists.
Forbidden — propose removal (✗ per entry hit): broad wildcards over interpreters, shells, and package runners. Treat any of the following allow-list strings as an arbitrary-code-execution hole, regardless of how the adopter justified adding them:
Bash(python *), Bash(python3 *), Bash(node *), Bash(bun *), Bash(deno *), Bash(ruby *), Bash(perl *), Bash(php *), Bash(lua *)Bash(bash *), Bash(sh *), Bash(zsh *), Bash(fish *), Bash(eval *), Bash(exec *), Bash(ssh *)Bash(npx *), Bash(bunx *), Bash(uvx *), Bash(uv run *)Bash(npm run *), Bash(yarn run *), Bash(pnpm run *), Bash(bun run *), Bash(make *), Bash(just *), Bash(cargo run *), Bash(go run *)Bash(gh api *), Bash(docker run *), Bash(docker exec *), Bash(kubectl exec *), Bash(sudo *)The list mirrors the “Never allowlist a pattern that grants arbitrary code execution” rule from Claude Code‘s user-level /fewer-permission-prompts slash command — the framework’s copy lives here so adoption itself is not silently contingent on a sibling skill being present. It is not exhaustive: an allow-list entry that fits the same category (anything that can spawn an arbitrary process or shell out via a flag) is a ✗ even if its exact token does not appear above.
Recommended — propose addition (⚠ per entry missing): narrow read-only patterns the framework's skills invoke often. An adopter who picks up the security family will hit these constantly; pre-allowing them removes the repetitive confirmation prompts without weakening the boundary. Tailor the recommendation to the families the adopter opted into via <committed-lock> → skill-families:
security family —
mcp__claude_ai_Gmail__get_threadmcp__claude_ai_Gmail__search_threadsmcp__claude_ai_Gmail__list_draftsmcp__claude_ai_Gmail__list_labelsmcp__gmail-plaintext__create_draftmcp__ponymail__search_listmcp__ponymail__auth_statusmcp__ponymail__get_threadmcp__ponymail__get_emailmcp__ponymail__list_restrictionsmcp__apache-projects__project_statsmcp__apache-projects__get_committeemcp__apache-projects__get_group_membersmcp__apache-projects__get_personmcp__apache-projects__search_peopleBash(vulnogram-api-record-fetch *)(The mcp__apache-projects__* read tools back the roster / affiliation lookups — also used by contributor-nomination, the maintainer-side /committer assessment skill. Both MCP servers are installed from the latest main of apache/comdev; see tools/apache-projects/tool.md and tools/ponymail/tool.md.)
Any family that ships docs / markdown (effectively every adopter, since the framework itself ships docs) —
Bash(lychee *) — read-only link-checker invoked by the “run lychee before pushing a PR” hygiene gate documented in AGENTS.md.The recommended list is deliberately narrow — every entry is read-only, scoped to a specific tool, and verified against Claude Code's auto-allowed harness exclusions (READONLY_COMMANDS, GIT_READ_ONLY_COMMANDS, GH_READ_ONLY_COMMANDS, etc.) so the framework does not redundantly propose entries that never prompt anyway.
Implementation. The classification logic and the atomic edit path are factored out into the tools/permission-audit CLI; the canonical forbidden + recommended-by-family lists live in tools/permission-audit/src/permission_audit/audit.py. The skill invokes the CLI once per settings file:
uv run --project <framework>/tools/permission-audit \ permission-audit audit <repo>/.claude/settings.local.json \ --families <comma-joined families from the lock>
The CLI emits structured JSON the skill folds into the verify report. Exit code 1 from the CLI maps to ✗ on this check.
Reporting shape: group findings by file, then by bucket. For each forbidden entry, print the exact JSON-pointer-style path (.permissions.allow[<index>]) the CLI returned so the operator can locate it instantly; for each recommended entry missing, print the suggested string verbatim ready for paste. Do not auto-write the files — the per-machine settings.local.json is the operator's; surface the proposal and let /magpie-setup verify --apply-permission-audit (interactive) or a hand-edit close the gap. The apply path calls
uv run --project <framework>/tools/permission-audit \ permission-audit apply <repo>/.claude/settings.local.json \ --add '<entry>' --remove '<entry>' ...
which holds a POSIX fcntl.flock advisory exclusive lock on the target file, re-parses under the lock, mutates .permissions.allow[] in place, writes to a sibling temp file, and os.replaces into place — so concurrent /magpie-setup-isolated-setup-install (which also writes to the same file‘s sandbox.filesystem.* arrays) does not silently clobber the diff. When the target file lives at a path the agent’s sandbox marks as denyWithinAllow (the per-machine settings files typically are), the apply path requires the operator to authorise the sandbox bypass for that single write — it does not silently skip the file. ⚠ if either file is absent (most adopters will have at least settings.local.json after the first /magpie-setup-isolated-setup-install pass; absence is a soft signal not a hard fault).
Why we propose, never auto-apply. The allow-list is the operator‘s capability surface for the agent in this checkout. Even an objectively-safer edit (drop a known-dangerous wildcard) is a capability change the operator must own, both to know it happened and to keep the audit trail human-readable. The framework’s job is to surface the gap — the operator's job is to close it.
Run this check only for ASF projects — detect ASF the same way as adopt.md Step 9c: <project-config>/project.md declares project_metadata.mandatory: true or Mail sources ponymail mandatory: yes. Skip otherwise (the two MCP servers are optional for non-ASF adopters).
For ASF projects, both the PonyMail and Apache Projects MCP servers are mandatory pre-flight prerequisites, installed from the latest main of apache/comdev (tracked, not pinned). Confirm:
mcp__ponymail__* and mcp__apache-projects__* appear in the session tool list. ✗ on either missing — the mandatory pre-flight gates in security-issue-import / security-issue-sync (PonyMail) and contributor-nomination (Apache Projects) will hard-stop. Remediation: adopt.md Step 9c.mcp__ponymail__auth_status() should report an authenticated session. ⚠ if registered but unauthenticated (remediation: mcp__ponymail__login()).main, current. Resolve each server's checkout root from its mcpServers args path and confirm origin is apache/comdev, the branch is main, and it is not behind the last-fetched origin/main. This is the read-only, offline form of the freshness assertion; the authoritative live fetch belongs to /magpie-setup upgrade Step 6e and setup-isolated-setup-update. ✗ off-main or non-apache/comdev remote; ⚠ behind origin/main.Verify that the auto-sourced stable configuration fields in .apache-magpie-overrides/project.md are in sync with the repository's live metadata:
upstream_repo, upstream_default_branch, product_family_url, labels — from gh repo view.organization: ASF: the mailing lists, against the current .asf.yaml. Skip the .asf.yaml comparison for a non-ASF organization (an independent project has no .asf.yaml to drift against — not a finding).master to main, or — for ASF projects — mailing-list routing in .asf.yaml was updated). Recommend running /magpie-setup upgrade to re-derive and align the committed configuration with the new metadata.Two files to check (per adopt.md Step 11):
<repo-root>/README.md — should have a contributor-facing section (typically ## Agent-assisted contribution (apache-magpie)) that mentions the snapshot mechanism, the /magpie-setup invocation for fresh clones, the .apache-magpie.lock pin, and .apache-magpie-overrides/. Grep for apache-magpie and /magpie-setup together as a proxy. ⚠ if either token is absent.<repo-root>/AGENTS.md — if the file exists, it should have an ## apache-magpie framework section that cross-references the README section. Grep for apache-magpie and a link to the README anchor. ⚠ if the file exists but lacks the section; not applicable if the file does not exist (do not create one just to satisfy the check).Cheap to skip if both are absent on a minimal repo — surface as ⚠ overall only, never ✗. CONTRIBUTING.md counts as a fallback for README.md if the adopter declared it so during adoption.
Only when <project-config>/skill-sources.md (the trust list) lists at least one source — otherwise skip this check silently (the adopter runs in-tree skills only). For each trusted source (skill-sources.md):
.apache-magpie.sources.lock (method/url/ref + anchor). Missing ⇒ ✗: the trust list vouches for a source that was never pinned — run /magpie-setup skill-sources..apache-magpie-sources/<id>/ exists on disk with the source's skills_root. Missing ⇒ ✗ with the remediation /magpie-setup skill-sources (the fetch is gitignored, so a fresh clone has none — expected, same as the framework snapshot)..apache-magpie.sources.local.lock block — a mismatch ⇒ ⚠ and proposes /magpie-setup upgrade, exactly like framework drift (check 3).magpie-<name> the source provides resolves through the canonical .agents/skills/magpie-<name> → ../../.apache-magpie-sources/<id>/skills/<name>/ and its relays (same rule as check 5). Dangling / misdirected ⇒ ✗ with /magpie-setup verify --auto-fix-symlinks.magpie-<name> provided by a source shadows a framework skill or another source's skill. Collision ⇒ ✗ (surface, do not auto-resolve).If every check is ✓ (or ⚠ on items the adopter has intentionally opted out of), say so explicitly and stop.
If anything is ✗, end the report with a concrete next-step list, ordered most → least urgent:
/magpie-setup upgrade (re-fetches per the committed lock)./magpie-setup upgrade./magpie-setup verify --auto-fix-symlinks (cheap; no-op when symlinks already correct)./magpie-setup adopt (idempotent re-create).git worktree remove <path> per the per-worktree proposal in the report. Idempotent; safe to batch across all flagged worktrees in one pass.EnterWorktree(path) (or cd <path> outside the harness) to inspect, then either commit / push or stash, then git worktree remove --force <path>. Never propose --force without first surfacing the diff.permissions.allow[] array. Print the JSON- pointer path so the operator can locate it. Per-machine settings.local.json writes go via /magpie-setup verify --apply-permission-audit (interactive, atomic JSON edit, sandbox-bypass requires per-write authorisation). Committed settings.json writes are a regular file edit + commit; flag them loudly because they bind every developer on the project.--apply-permission-audit flag, or paste manually. The recommendation is family-scoped, so an adopter who skipped the security family will not see the Gmail / PonyMail entries surfaced as gaps.main) → /magpie-setup adopt Step 9c to (re-)install from latest apache/comdev main. ⚠ on check 8e (PonyMail unauthenticated, or checkout behind origin/main) → mcp__ponymail__login() and/or /magpie-setup upgrade Step 6e (live fetch + git pull --ff-only).