confirm — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited confirm (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The single home for every interactive user gate. The run controller never prompts - it BLOCKS with status=blocked + blocked_reason and hands the user here. confirm reads blocked_reason, runs exactly the matching AskUserQuestion round, persists each answer into state.decisions (sticky - never re-asked on resume), clears the block, advances current_step, and re-invokes run via the Skill tool. It owns: the filter-gate round (G-FILTER, G-NAMING, G-ONBOARD, G-MEMORIES) + GROUPED-vs-STANDALONE assignment + G-COST; and the browser-accelerator round (G-AUTO-REOFFER, G-LOGIN/G-BROWSER).
Read state.status and state.blocked_reason. If status != blocked, print "Nothing to confirm - run /claude-migrate:run <run-name> to advance, or /claude-migrate:progress <run-name> to see where you are." and exit without asking anything. Decisions already present in state.decisions are STICKY - never re-ask them.
/claude-migrate:confirm <run-name>
<run-name> is required. RUN_PATH=".planning/claude-migrate/<run-name>". Abort if state.json is missing.
STATUS=$(bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh get "$RUN_PATH" .status)
REASON=$(bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh get "$RUN_PATH" .blocked_reason)
OUT_MODE=$(bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh get "$RUN_PATH" .output.mode)Dispatch by REASON:
filter-gate → Step 2 (the filter-gate round + G-COST).auto-reoffer → Step 3 (G-AUTO-REOFFER, then login/browser).login or browser-lost → Step 4 (G-LOGIN/G-BROWSER only).cost → Step 2f (G-COST only, then re-evaluate)./claude-migrate:health <run-name>." and exit.Run these in order. Each is one AskUserQuestion call; each answer is persisted via state.sh set .decisions.<key>.
First read the preflight scores to build the summary: count KEEP + REFERENCE vs DROP from value/*.json, and surface any duplicate clusters (representative = lowest idx). Ask ONE summary question. Header: "Confirm what migrates", body: "Migrate N (KEEP/REFERENCE), skip M (DROP: empty / starter project / tool-only / near-duplicate)?"
looks_duplicate_of) BEFORE its AskUserQuestion call (not batched). Duplicate clusters are SURFACED for an explicit keep/drop pick - NEVER auto-dropped. An empty-body-but-meaningful-name chat is a low-confidence KEEP candidate (M-6).Apply the decision: move DROP units to units/dropped/ (never delete the source); KEEP/REFERENCE units stay in the queue. Update counters.kept / counters.dropped via state.sh inc/dec so the §3.3 invariants hold.
For each KEEP/REFERENCE unit, ask whether it joins a destination project (GROUPED) or stays a standalone chat (DEFAULT = STANDALONE). Present the available PNN__slug projects (from project/). Persist the user-confirmed map:
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" '.decisions.project_assignment."UNNN"' '"PNN__slug"' # GROUPED
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" '.decisions.project_assignment."UNNN"' null # STANDALONEThis map is the SOLE authority for grouping - preflight-value may not emit GROUPED, and resume never re-derives it.
Header: "How should migrated chats be named?"
Name DD.MM tag (UX M-4). Store the scheme string in config.yaml and set:bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .decisions.naming_convention '"keep"' # or "custom:<scheme>"Header: "Use the OK-protocol onboarding (reply `OK` to each brief, then auto-remove that line after seeding)?"
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .decisions.onboarding_ok_protocol '"ok-then-strip"' # | strip-myself | noneSkip this question entirely unless $RUN_PATH/source/memories.json exists. If it does, header: "Migrate account memory? (may contain stale or third-party PII)"
references/pii-policy.md).bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .decisions.memories '"skip"' # | paste-to-memory | fold-into-projectThe estimate is driven by the DETERMINISTIC est_tokens already in value/*.json (computed by the parser; never by a model). Compute cost_estimate (a pure function of the parsed units + active profile) and read usd_high, chats_total, and the max per-chat est_tokens. Show the gate ONLY if usd_high >= 10 OR chats_total > 75 OR any chat est_tokens > 80000; below all thresholds, silently proceed (set cost_acknowledged=true).
When shown: "Estimated $X (in-tokens Y) to distill N chats. Proceed?"
> $25 → HARD-STOP requiring an explicit Proceed.> 75 chats → note "long run -> resumable batches".> 80k-token chat → note it will be summarized at distill.Browser seeding itself is $0 API (it consumes the destination message cap - noted later at pre-apply). On Proceed:
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .decisions.cost_acknowledged trueMark the filter gate confirmed, advance, clear the block, hand off:
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" '.gates.filter.user_confirmed' true
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" '.gates.filter.verdict' '"PASS"'
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .current_step distill
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .blocked_reason null
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .status running
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh checkpoint "$RUN_PATH"Then invoke skill claude-migrate:run with argument <run-name> via the Skill tool. run advances distill -> synthesize -> build-page -> verify-gate -> ready.
Only reached when output.mode == "auto" and the run is at ready.
Show the out/index.html path and 1-2 sample brief titles from briefs/*.name.txt. Ask for an explicit acknowledgement. Header: "The copy page is verified. Proceed with browser seeding?"
ready (the copy page is already a complete success); exit without advancing.bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .decisions.auto_reoffer_ack trueProbe the browser transport + auth marker:
bash ${CLAUDE_PLUGIN_ROOT}/bin/browser-probe.sh "$RUN_PATH"Read state.output.browser.authed and .transport. Follow ${CLAUDE_PLUGIN_ROOT}/references/login-policy.md verbatim - NEVER script credentials, 2FA, or captcha.
user_chose_auto == false (Enter-on-default AUTO) AND no browser → DEMOTE to copy-page and SAY SO: set output.mode=copy-page, print the macOS CDP launch command from the probe + "the copy page at out/index.html is your complete deliverable; to enable automation later, launch the browser and run /claude-migrate:resume <run-name>." Set status=passed, leave current_step=ready, exit.user_chose_auto == true (explicitly chose AUTO) AND no browser → do NOT demote: status=blocked + blocked_reason=login, print the CDP launch command + "log into the NEW account, then /claude-migrate:resume <run-name>." Exit.bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .current_step pre-apply-gate
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .blocked_reason null
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh set "$RUN_PATH" .status running
bash ${CLAUDE_PLUGIN_ROOT}/bin/state.sh checkpoint "$RUN_PATH"Invoke skill claude-migrate:run with argument <run-name> via the Skill tool. run runs GATE 3 (incl. dest_hash != source_hash) then apply -> finalize -> done.
Re-probe (Step 3b). If now authed, clear blocked_reason, set status=running, and hand to run (it resumes from pre-apply-gate or apply). If still not authed, re-print the login-policy halt + CDP launch command and exit (do NOT script login).
decisions.project_assignment and is NEVER re-derived on resume.est_tokens only - never by a model score. > $25 is a HARD-STOP needing explicit Proceed.auto and copy-page.references/login-policy.md).state.decisions and is never re-asked on resume.blocked_reason and set status=running before handing to run; never advance the pipeline yourself beyond the one step the gate unlocks.state.json outside bin/state.sh. Never reference any specific domain; labels come from config.yaml.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.