| name | project-starter-wizard |
| description | Use at the very start of a new customer project, on an unmodified (booted or not) clone of a Spryker b2b/b2b-marketplace demoshop — 'turn this demoshop into our project', 'start the project setup', 'run the project starter'. Also the trigger for 'give me the setup questionnaire' / 'here are my filled setup answers — run it autonomously', and the resume entry when a prior run left .ai-dev/project-setup.md. Collects the nine setup decisions either from a pre-filled questionnaire (references/questionnaire.md — no interview) or a live batched interview, then orchestrates the other project-start skills in autonomous or collaborative mode. |
Project Starter — Wizard
You turn an unmodified clone of a Spryker b2b / b2b-marketplace demoshop into the customer's project. The clone may already be booted — what matters is that nobody has customized it yet. You own the conversation and the flow; the transformation work is done by the other skills (which drive the spryker-import-tools tools with parameters — never hardcode file lists).
Work only from real files and real command output. Never assume Spryker specifics from memory — inspect the clone.
Communication (applies to EVERY message — the developer will not read walls of text)
Keep output short. Nobody reads long status dumps, so a long message is a failed message — anything important in it is lost. Four hard rules:
- A required human action is the FIRST thing in the message, alone, unmissable. Lead with a single
⚠ ACTION NEEDED: line and the exact command — before any status, table, or summary. A prerequisite buried under a validation table or a "✅ N of M complete" header reads as status, not a request, and the run stalls on an unread ask. Never phrase a prerequisite as "whenever" or "I'll tell you when I get there."
- Issues/blockers are HIGHLIGHTED, not narrated. Surface a problem as its own short, flagged line (
⚠/❌) — never mixed into a paragraph of prose. One glance should tell the developer: action needed? issue? or just progress?
- Default to terse. Report the outcome, not the play-by-play. A finished step = one line. Save detail for when it's asked, or for the improvement log (which is dev scaffolding, not something the developer reads for actions). Verbose reasoning belongs in your own thinking, not the output.
- Close-out contract — mode-dependent, and it never manufactures a stop:
autonomous: a turn that needs nothing from the developer does not hand control back at all — it continues in the same message (the step-completion protocol, §3). Never ask them to say "continue"; that is not autonomous. Control returns only for a genuinely blocking human action, and then the turn ends with ⚠ NEEDS YOU: followed by numbered concrete actions — each one an exact command or a yes/no, each naming what declining costs — e.g. "1. Run sudo … >> /etc/hosts — declining leaves the browser ACs BLOCKED; the server-side checks still run."
collaborative: a turn that returns control ends with EXACTLY ONE of that same ⚠ NEEDS YOU: line or ✅ NEEDS NOTHING — say "continue".
Banned as closing lines in both modes: "Ready when you are", "Shall I continue?", "Let me know how you'd like to proceed", and an unprompted options menu. Failure signature: the last line of a turn is a pleasantry, a recap, or a question the developer never asked for — that turn has handed back control without saying what it needs, and the run stalls.
This is not cosmetic — it is the difference between an unattended run that completes and one that silently waits on an unread ask.
0. Pre-flight (stop if any fails)
- Flavor: confirm this is b2b or b2b-marketplace (check
composer.json name/packages). Reject anything else.
- Still an unmodified demoshop — the ONE question that matters. This run deletes CI files, region dirs, catalog rows and (at boot) re-imports data. On an untouched demoshop that destroys nothing. On a project someone has already customized it destroys their work. So the gate is "has this been customized into a project?", NOT "has it been booted" and NOT "is the tree clean".
- Not blockers — say so and continue: a booted clone (a fresh demoshop someone ran
docker/sdk up on is still an unmodified demoshop); the AI Dev SDK onboarding delta (tracked composer.json / composer.lock / ConsoleDependencyProvider.php plus untracked .ai-dev/, CLAUDE.md, .claude/); untracked tooling (.cursor/, .windsurf*, .github/agents|skills/, .mcp.json, AGENTS.md, editor configs). Never require a commit (suggesting one is fine).
- Blockers — the clone is already a project. Any of: tracked modifications under
src/, data/import/, config/ beyond the SDK delta above; a custom namespace already registered (PROJECT_NAMESPACE ≠ Pyz in config/Shared/config_default.php); stores/locales already differing from the shipped set; or an untracked pile of data/CSV/patch files. Post-boot, also: real business data (orders/customers beyond the demo seed). Ask, don't guess when the shape is ambiguous — "is this still the demoshop, or your project?" is one question and it is always worth asking.
- Booted + unmodified → proceed, and say what it costs: the file transformations only reach the shop through a re-import, so the run will need
docker/sdk clean-data + a re-boot. Announce that blast radius (it wipes demo data only), then continue — during first setup this is normal, not a gated destructive action.
- If
.ai-dev/project-setup.md already exists, this is a prior run — do NOT stop; go straight to the Resume section (skip the interview, continue from the first not-done step).
If the clone is already a customized project (per the demoshop gate above) and there is no .ai-dev/project-setup.md, stop and hand over the "Return to fresh" recipe (§ Abort, change an answer, return to fresh) — do not half-apply, and do not leave the developer with a bare refusal. A booted-but-unmodified demoshop is NOT this case. (An existing state file → Resume, per above.)
1. Collect the answers — questionnaire fast-path, or interview
The nine decisions can arrive two ways, and the fast-path is the default preference:
- A filled questionnaire (
references/questionnaire.md) — no interview. It is the canonical
fillable question list (groups P/N/S/T/D/C/L/Q + run-config R, every question carrying its default
inline). Minimum viable set: P1 (project name), R1 (mode), R2 (hard-stop ack) — with those
three the run proceeds on shipped defaults for everything else. If the developer supplied it (a path,
pasted text, a yaml answer block, or the values inline in their request), skip the interview.
- A live interview — the batched AskUserQuestion path, when nothing was supplied.
Read references/interview.md BEFORE asking the first question. It carries the collection rules
that decide between those paths (Rule 0 — filled/partial/missing detection and the parsing rules;
Rule 1 — offer "fill it yourself vs interview me"), the AskUserQuestion mechanics (structured
options with the default first, pre-computed candidates for free-text values, batched sections — what
makes this a wizard, not a form), and the nine-section decision catalog: identity, namespace,
services/applications, stores + region, demo-data mode, catalog scope, localization, CI, and run mode.
Three rules bind both paths:
- A blank is a default, not a question. Every question except
P1/R1/R2 has a working default.
Under run_mode: autonomous a blank is resolved to that default — or, where there's a genuine fork,
decided and logged to .ai-dev/decision-log.md — never asked. Under collaborative the blanks
are asked. Coming back with questions on an autonomous run defeats the mode.
R1 is the one answer with no safe default. If a questionnaire arrives without it, ask that one
question and nothing else — it decides whether every other blank becomes a question or a decision.
- A questionnaire skips questions, never checks. Pre-flight (§0) runs in full either way, and its
developer-must-do findings are still surfaced as
⚠ ACTION NEEDED. Re-run the namespace/volume
collision check as soon as P1 is known.
Keep the two files in sync: questionnaire.md is the question bank, interview.md is how to collect
it. A decision added to one must be added to the other.
Two rules that bind EVERY edit of the run (interview through execution)
Surgical edits only — never tidy adjacent config. When editing a shipped file (deploy.dev.yml, config, YAML, recipes), change only the exact keys the current step owns; do not reorganise, reformat, "clean up", or move neighbouring blocks you were not asked to touch. Everything else stays byte-for-byte as shipped. (For example, an agent rewriting docker.testing.region also deleted the adjacent docker.mount.mutagen block and moved macOS to native mounts — crippling the Mac dev env. The region token was in scope; the mount block was not.)
Environment limits are NOT project defects. A failure caused by your run environment — no TTY for an interactive command, the 10-minute tool timeout, shell word-splitting (zsh vs bash), a detached/background process, /dev/null stdin — is a limit of how you are running, not a bug in the project. Never diagnose a sandbox limit as a project problem, and never edit project files to accommodate it. When a command needs a real terminal or exceeds the tool cap (notably docker/sdk up), hand it to the developer to run — do not fake a TTY, background it to dodge the cap, or "fix" the project to make it run headless. If unsure whether a failure is the project or the environment, assume the environment and surface it, rather than mutating the project.
2. Confirm & write state
Summarise every answer. Get explicit confirmation. Then write .ai-dev/project-setup.md exactly per the template at the end of references/interview.md — the frontmatter (project / namespace / services / stores / data / reduce_catalog / localize) plus the Steps table that §3 and Resume operate on.
In the same pass, create .ai-dev/run.log with the interview summary as its first line (see §3 → Run logging). Every step from here on appends to it.
3. Run the steps in order
Run mode — questionnaire R1 / interview §9 (run_mode: autonomous | collaborative in the state file; supervised is an accepted alias for collaborative). It governs only how control is handed back between decisions; it changes nothing about WHAT each step does, and it never relaxes the hard-stops below.
- Autonomous — run steps 1–9 as one continuous pass; after a non-gated step completes, proceed to the next in the same turn. At any reversible, in-project decision point — which value to pick, how to resolve a dangling reference, a strategy fork the questionnaire didn't pin down, or a questionnaire question left blank — choose the best option, record it in the decision log (below), and keep going. The default is continue. Asking a configuration question mid-run is a mode violation: the answers were collected up front precisely so this pass needs none.
- Step-completion protocol — three acts, ONE message, in this order: (1) update the step's row in
.ai-dev/project-setup.md; (2) append the | END line to .ai-dev/run.log; (3) invoke the next step's Skill call in that same message. Failure signature: a turn whose last tool call was the Edit of run.log — the protocol stopped at act 2 and the run is now stalled.
- Never end a message with "Continuing to
<step>" or any other forward-looking announcement. If you have written that sentence, the next skill's tool call belongs in the same message. The stall is not caused by the words but by the shape: a message shaped like a finished deliverable is what ends the turn.
- The step report is a log line, not a deliverable — at most 3 lines: outcome / gates hit / deferred, then the mandatory
Next step needs from you: line (below). Failure signature: a step report carrying a table, a "✅ N of M complete" header, or more than ~80 words.
Next step needs from you: — the last line of every step report, resolved for step N+1 specifically (the exact command, or the yes/no it will gate on), or the literal word nothing. Never "I'll tell you when I get there".
- A question whose options reduce to {continue, stop} is a mode violation, not a decision point. The default is continue; a real decision point offers substantive alternatives (which value, which file, which strategy). Failure signature: an
AskUserQuestion whose options are "keep going" / "pause and review" — doubly so after the developer pre-approved the run. This slips both other prohibitions (it is neither a step report nor a configuration question) while doing exactly the same damage.
- Collaborative — do silently stop, and do run straight through. At each , end the turn with a one-line result and an explicit , and wait. At any , surface it as a question with the options and your recommendation () and wait — never decide silently, never halt without a question. Record the answers in the decision log too, so a resume keeps the rationale.
Hard-stops — control returns to the developer in BOTH modes (autonomy never overrides these; collaborative asks anyway). These are what questionnaire R2 acknowledges — a filled questionnaire is never a promise of an uninterrupted run:
- (a) a
⚠ ACTION NEEDED prerequisite — something only the developer can do (start Docker/OrbStack, add the /etc/hosts line, supply a token). Surface it per the Communication rules and wait.
- (b) an IRRECOVERABLE action — gated in BOTH modes, EXCEPT during first project setup. Carve-out: while the project is still being set up (pre-go-live — un-booted, or iterating on the first boot),
reset / clean-data are normal setup operations and are not gated: there is no customer data to lose. Announce the blast radius in one line and proceed. The gate still stands for sudo, publishing outside the clone, and any destructive operation on a project that already carries real data. Otherwise the gate is recoverability, not the word "delete": if git checkout -- <paths> restores the pre-action state, the action is a recoverable edit and needs no gate. Gate whenever it does not — any DB/volume drop (reset/clean-data, docker volume rm), sudo, publishing outside the clone, deleting untracked files (git clean, or rm of anything the diff doesn't show as tracked), and any data deletion after the first boot (post-boot the DB/KV/search state lives outside git). In-place row/column removal (csv delete / filter --in-place / drop-columns --in-place) does NOT prompt at the OS level, so this rule — not a shell prompt — is what gates it. Present the concrete blast radius (the file list, the row/column count, or what the drop wipes) and get an explicit go-ahead. Autonomous performs and logs recoverable actions on its own (value transforms, added columns, file writes, and deletions that pass all three preconditions below) but never an irrecoverable one unattended.
- Preconditions for treating a deletion as recoverable — all three, or gate: (1) the clone is still an unmodified demoshop (booted is fine — its data is demo data, re-created by
clean-data + re-boot); (2) every path is git-tracked with no unstaged changes (git status --porcelain clean for those paths); (3) no data has been imported yet. If any one fails, gate — and that includes the routine happy-path deletions (the project-ci-generator CI/deploy/install wipe, ' non-canonical demo dirs + dangling manifests, 's strip passes) the moment the clone stops satisfying the triple.
Decision log (.ai-dev/decision-log.md) — the autonomous run's audit trail (and collaborative's answer record). Append one terse entry per decision: the step, what was decided, why (the evidence), the alternatives rejected, and how to reverse it (the git path, or the specific file/rows). Questionnaire blanks are accounted for in exactly one of two places — never both. A blank that resolved to its documented default (S2 → keep all dev services) is recorded only as an ID in the state file's answers_defaulted list; it does NOT get a decision-log entry, because a mechanical default carries no rationale worth a line and duplicating all ~15 of them here would drown the real decisions. A blank that required a genuine fork — no documented default, or the default doesn't fit the answers (e.g. a region token whose literal candidate collides with a shipped one) — gets a full entry here and is listed in the run.log's decided=[…]. If you can point at the questionnaire text that names the value, it's a default; if you had to reason, it's a decision. Like the improvement log it is dev scaffolding, not developer-facing status — its purpose is that a reversible-but-wrong autonomous call costs a review-and-revert, not a silent broken shop. The developer reads it to audit or override the run's choices after the fact.
Run logging — what, when, how, where
A run spans nine steps, several sub-skills, a boot, and can be interrupted and resumed hours later. The
three .ai-dev/ files already cover configuration (the state file), rationale (the decision log),
and maintainer feedback (the improvement log) — what is missing is the timeline: what actually
ran, in what order, and how each step ended. .ai-dev/run.log is that file. It records the steps
below; it changes nothing about what any step does, which gate fires, or when control returns.
Where. Alongside the other run artifacts, in the clone's own tree — .ai-dev/run.log, a
project-relative path like every other .ai-dev/ file. The run is self-contained: never write run
files outside this clone. It is created by the run and removed by the "Return to fresh" recipe, which
already deletes .ai-dev/project-setup.md (+ run logs).
The run's four files sit flat at .ai-dev/ because the state file's path is load-bearing: pre-flight
detects a prior run by it, Resume reads it, and "Return to fresh" deletes it.
When. Create it in §2 (Confirm & write state), in the same pass that writes
.ai-dev/project-setup.md — the interview answers are the first thing worth recording, and every step
from §3 onward appends to it. On Resume, do not start a new file: append a RESUME line and keep
going, so one run reads as one continuous timeline across interruptions.
How. Write it with the built-in Write / Edit / Read tools, not the shell. This is a direct
application of the Tooling discipline below: printf … >> file is a redirect, and redirects (like
pipes, &&, and subshells) prompt regardless of the allowlist. Appending a line with Edit costs no
prompt and keeps the run hands-off. Keep entries terse — one line per event:
[2026-08-10 14:02:11] INTERVIEW — SKIP (questionnaire pre-filled) · source=questionnaire · run_mode=autonomous · data.mode=adapt · defaulted=[S2,C1,L1]
[2026-08-10 14:02:40] STEP 1 project-ci-generator | START run_mode=autonomous
[2026-08-10 14:11:05] STEP 1 project-ci-generator | END done — 1 workflow kept, 14 files wiped (approved)
[2026-08-10 14:11:20] STEP 5 define-stores | SKIPPED (data.mode=leave)
[2026-08-10 14:48:02] STEP 8 boot-and-verify | ⚠ ACTION NEEDED /etc/hosts — waiting
[2026-08-10 15:03:55] RESUME — continuing from step 8 (in-progress: browser ACs pending)
[2026-08-10 15:12:31] STEP 8 boot-and-verify | END done (browser ACs BLOCKED — /etc/hosts declined)
What. One line per step boundary — | START run_mode=<mode> and | END <one-line outcome>. The
mode is re-asserted on every START on purpose: it is the one fact a long run drifts away from, and a
| START run_mode=autonomous line immediately above a "step done — continue?" turn is the greppable
proof of the drift. Plus every event a later
reader would need to explain the run:
- The collection summary: how the answers were collected (
source=questionnaire | interview | questionnaire+interview | defaults, and defaulted=[…] — the questions resolved without the
developer choosing), run_mode, and the answers that decide whether steps run (data.mode,
reduce_catalog, localize, the ci: plan). The source and the defaulted list are what let a
later reader tell a deliberate choice from a taken default.
- Each step's START/END with its terminal status, and each conditional skip with its reason
(
SKIPPED (data.mode=leave)) — a skip is a result, and it explains a "missing" step later.
- Every hard-stop as it happens: a
⚠ ACTION NEEDED prerequisite and how it resolved, each
destructive-op gate with the blast radius presented and the developer's answer, and any step
failure with the signature you matched in references/pitfalls.md.
- Each sub-skill handoff (which skill, for what) and the post-boot passes in step 8 — brand theming,
the codeception seed, the
cy:run smoke — with their individual outcomes.
- The
spryker-verifier verdict at the step-8 gate, per store.
- Every
RESUME, so an interrupted run's real elapsed shape stays visible.
Three rules:
- Mirror the one-liner, keep the detail in its own file. The decision log holds the why, the run
log holds the when — when you record a CRITICAL DECISION, add a one-line pointer here rather than
duplicating the rationale. Bulk output (boot logs, verifier transcripts) stays where it already lives;
reference it, don't paste it.
- Never log a step green that wasn't.
done (browser ACs BLOCKED — /etc/hosts declined) is the
honest terminal state and is exactly what belongs in the log — not done. A skipped, blocked, or
partially-completed step is recorded as precisely that. This is the same honesty rule the state
file's status column already carries.
- Write it as you go, never reconstruct it at the end. The log's whole value is surviving an
interruption; a timeline assembled from memory after the fact is the one thing it cannot be.
The closing summary points at .ai-dev/run.log so the developer can audit what ran without re-reading
the conversation.
Before step 1, confirm .ai-dev/preflight-baseline.json exists (captured in §0, on the pristine clone). If it is missing, say so and do NOT capture one now — the clone is no longer untouched, and a late "baseline" records this run's own damage as shipped state. Re-check the execution pre-flight (still fresh/un-booted — the SDK onboarding delta stays tolerated) before writing anything. Then run, updating each step's status in the state file as you go. Long steps record intra-step progress in the note column — set the step to in-progress and update the note after each major pass (e.g. in-progress: locales done, currencies pending, strip not started), so an interrupted run can resume precisely instead of blindly re-running (most passes are idempotent; the deletion passes are NOT — a blind re-run of those is the thing the note prevents).
- project-ci-generator — turn the repo's inherited product/vendor CI (
.github/workflows/ci.yml) into a single lean project pipeline.
- Runs first and pre-boot (pure CI-file work — no boot, namespace, or data needed), so it's unmissable rather than an end-of-run afterthought.
- The decisions were already collected in interview §8 (the
ci: block) — this step executes that plan against the discovered CI; it does NOT re-run the questionnaire. Outward-facing (it deletes CI files): the one thing still confirmed at execution time is the destructive wipe itself — present the concrete file list derived from the plan and get the go-ahead. The developer can decline and keep everything, but the wizard never silently skips the step. If there's no CI to transform, say so and move on.
- The
ci.keep_suites decision IS the robot/acceptance-fixture lane decision other steps read (define-stores' dangling-manifest sweep, later fixture adaptation) — derived, never asked, and never surfaced to the operator. There are exactly two states and no third "leave it alone": lane kept → adapting the b2b_robot fixtures to the project stores is a required follow-up (recorded in ## Required follow-ups); lane dropped → project-ci-generator removes the whole fixture tree. Failure signature: an operator-facing question about robot/acceptance fixtures, or a run that keeps the fixtures unadapted — that is the broken inert config define-stores forbids.
- Owns removing a dropped test-suite's whole footprint — not just the CI jobs but the
.github/deploy/*.yml, config/install/*.yml install-pipeline configs, and fixture dirs those jobs alone referenced. cypress-migration (step 7) no longer does this; it keeps only the Composer-package removal + vendoring. Keeping one owner avoids two steps re-editing the same CI/install files.
- Green CI on a non-
DE/AT/US store — the bare-runner store trap (verify after the trim; ci-generator owns these .github/workflows files, but it's vendored so the obligation is recorded here). The kept static-analysis / transfer:generate jobs run vendor/bin/console directly on the runner — no docker, no deploy file. Retargeting their store/region tokens to the project's (, ) is : on a DMS project whose stores aren't //, the first console call fatals — the legacy path is taken (because still ships, so DMS reads as OFF, and its fallback defines only //, while — which docker-sdk would supply from the deploy file — is absent on a bare runner). — short-circuits, is never consulted, the region resolves from (and it matches the already-DMS functional job). export the job's env and run its own commands — → expect exit 0 (pass ; the host default 128M dies inside . can't be verified this way — it spawns as a subprocess that doesn't inherit the flag). This is the sibling of 's codeception store-helper trap — both are "the shipped tests/CI assume store ."
Ordering gate — before starting step 8, every one of steps 1–7 must be done or explicitly skipped in the state-file Steps table. Read the table and confirm it; if any pre-boot step is still pending/in-progress, finish it first. Note cypress-migration (7) in particular: its proof (cy:run) is post-boot, but its file work (composer removal, vendoring, CI, companion skill) is pre-boot, so it must complete before the boot. This is what keeps the §3 continuous-run directive from turning "keep going" into "skip ahead".
- boot-and-verify — validate everything, first boot, per-store verification, and the post-boot passes (mandatory, on the checklist alongside the endpoint checks): brand-project's theming half (colours, logo wiring, BO/MP ymls,
configuration:sync, rendered colour/logo gate), the codeception seed's green codecept run (configure-codebase), and cypress-migration's cy:run smoke. The authoritative PASS/FAIL comes from the independent spryker-verifier sub-agent (shipped in this plugin's agents/, driven by the bundled spryker-qa-coverage/spryker-runtime skills), not the executor's self-assessment (boot-and-verify §4c). Its browser ACs need /etc/hosts, surfaced as a ⚠ ACTION NEEDED prerequisite before the gate; if declined, the server-side checks (curl --resolve) still stand and the step completes in a terminal state — done (browser ACs BLOCKED — /etc/hosts declined) — so the run finishes rather than parking forever (never a permanent in-progress). This is the one prerequisite whose decline is terminal, not a hard-stop wait — spell that out so an autonomous run doesn't stall on it.
- translate-content (optional — only if
localize.locales is non-empty) — translate the chosen locale(s) after a green boot; default is skip (English copies stand). Never blocks setup.
On any step failure: stop with guidance, leave the state file recording where it stopped, and log the failure line in .ai-dev/run.log (the step, the signature, the matched pitfalls.md entry if there is one) before you hand back — a run that stops is exactly the run whose timeline gets read. Do not proceed past a failed step.
Triage reference — the Known-traps catalog (references/pitfalls.md). It collects every known failure signature across the whole run (cross-cutting + per-step: volume/namespace collision, mutagen-down, boot aborts, "green but empty" post-boot, read-model recovery) as signature → cause → fix. On any failure or suspicious-but-green signal, match it there before diagnosing from scratch. Each step-skill also carries the one-line trigger inline for standalone use; this catalog is the shared depth and the single home for the cross-cutting traps.
When blocked on a human action, apply the Communication rules at the top: lead with the single ⚠ ACTION NEEDED: line and exact command, alone, above any status; never bury it, never say "whenever".
Resume
If invoked with an existing .ai-dev/project-setup.md, skip the interview and continue from the first step whose status is not done or skipped (covers the /etc/hosts and boot interruptions). Append a RESUME line to .ai-dev/run.log (never start a new log — one run, one timeline) recording which step you are resuming from and the progress note you resumed against. Re-read run_mode from the state file first — a resumed run honours the mode chosen at collection time (autonomous vs collaborative), and any ## Required follow-ups recorded there. Never re-collect answers on a resume: the state file's frontmatter is the answer set (whatever its answers_source), so a resumed autonomous run asks no configuration questions either. If that step is in-progress with a progress note, resume from the point the note records — verify the completed passes' outcomes are actually on disk (a quick columns/distinct spot-check), then continue with the pending passes; never blindly re-run a deletion pass the note says already ran. (Mark conditionally-skipped steps skipped with the reason at state-write time — e.g. define-stores: skipped (data.mode=leave) — so Resume never lands on a step that must not run.)
Abort, change an answer, return to fresh
The run's only verbs are not "continue" and "stop" — these three recovery paths exist, and a stop must always hand the applicable one over:
- Change an answer BEFORE boot (the common "I picked the wrong stores" case): everything pre-boot is file edits on a fresh clone, so git is the undo. Revert the affected files (
git checkout -- <paths> + delete created files/dirs the diff shows), update the answer in .ai-dev/project-setup.md, reset the affected steps' statuses to pending, and re-run from the earliest reverted step. Cheap for stores/data answers; identity/namespace answers touch many files — when in doubt, prefer the full return-to-fresh below over a partial revert you can't fully enumerate.
- Change an answer AFTER boot: file-level revert as above, plus the data/DB consequences follow
boot-and-verify §3b (a store/catalog answer change = deletions = a DB drop via reset). Announce the blast radius per the destructive-op gate first.
- Return to fresh (a booted-once clone, an abandoned run, a hopeless half-state): (1)
git checkout -- . + git clean of the run's created files — surface the git clean file list and get a yes first (it deletes untracked files; scope it to the paths the run created, never the developer's tooling); (2) delete .ai-dev/project-setup.md (+ run logs); (3) the developer stops/removes this project's containers and — their call — the <namespace>_* volumes (docker/sdk down; docker volume rm is theirs to run); (4) re-run pre-flight. A re-clone plus a new namespace is the equally valid lazy path — offer both.
Tooling discipline (applies to every step)
Work through a closed set of capabilities — never improvise in the shell:
- Data / CSV / validation → the
spryker-import-tools php tools (csv.php + validate.php). All CSV reading, inspection (distinct, columns --plain), transformation, and validation go through them.
- Reading / searching / editing files (configs, PHP, YAML, deploy files, import manifests) → Claude's built-in Read / Grep / Glob / Edit tools. Config and YAML edits are anchored, format-preserving Edit calls — never regenerate a file with
python/ruby/sed, and never pre-validate YAML with python/ruby (its validity is proven by docker/sdk bootstrap).
- State changes →
docker/sdk (developer approves; long boots run in the background per boot-and-verify).
- HTTP checks →
curl. Staging → git add. Filesystem moves (in-place dir renames) → mv/cp/mkdir/rm. Run git plainly from the project cwd — git status --porcelain, git add <relative-path>, git diff — never git -C <path> or an absolute path: the session cwd is already the project, and a leading -C <path> makes the command miss the git status/git add allowlist prefix (a needless prompt) and breaks the no-absolute-paths rule.
Read and write only inside this project's own tree — the run must be self-contained. Pre-boot, this clone has no vendor/ yet (composer installs it in-container during the boot), so a pre-boot step needing a vendor/core reference (a facade signature, a vendor *.configuration.yml, an importer's expected columns) has nothing to read locally. Do not reach into a sibling checkout on the same machine to get it — that clone may not exist on the developer's machine. Instead: defer the sub-task to post-boot when this clone's own vendor/ exists (most vendor-reference needs are verification, which is post-boot anyway); or take it from the plugin's bundled material if it ships one; or, if genuinely needed pre-boot, ask the developer. A neighbouring clone's vendor/ or src/ is never a dependency. For the same reason, never propose a git worktree (or a branch) for a run whose verification is post-boot: the Docker environment is bound to the checkout the boot ran in, so a linked worktree has no vendor/, no containers, and its edits are invisible to the mounted container — ../static-validation/SKILL.md states the mechanism (the container mounts the main checkout; worktree-only changes must be synced into it to be seen at all). Failure signature: a worktree offered as "Recommended" and scrapped minutes later on "no vendor/, no running containers".
One Bash call = one simple command. Compound one-liners (pipes, ;/&&, for/while loops, $( … ) subshells, > redirects, leading VAR=… assignments) prompt regardless of the allowlist, because prefix rules like Bash(grep:*) match a lone command, not a pipeline whose segments must each also match — so command shape, not tool choice, drives most avoidable prompts. Issue each command on its own and read output with the built-in Read/Grep tools; prefer Read/Grep/Glob over cat/find/ls pipelines. (rm and other destructive ops prompt by design.)
Reach for the already-available tools first — a general interpreter is a last resort, not a first move. Nothing here is prohibited (except sudo); python/ruby/perl/node/awk/sed/jq will simply prompt (they're not pre-allowed) — and that prompt is your cue to stop and ask: does an allowed capability already do this? Almost always yes:
- parse a tool's JSON by reading it (or
--plain/--quiet + exit code) — not … | python -c / jq;
- inspect/transform/count/translate CSVs with the php tools — not
awk/sed/a hand-written script;
- give the boot its TTY with
script (sanctioned) — not a python pty.spawn fake-TTY (that one actually hung on /dev/null stdin);
- read/search/edit files with the built-in Read/Grep/Edit.
Using a foreign interpreter instead of an available tool is the thing to avoid — it's slower, more error-prone, and prompts. If you genuinely need one and nothing available fits, go ahead (it'll prompt); just don't reach for it out of habit when the allowed path already does the job.
script is the sanctioned exception — the one allowed pty, used only for the docker/sdk up boot (mechanics owned by boot-and-verify). The pty.spawn prohibition above is about python/ruby fake-TTYs, not script.
Literal tool paths + one simple command per Bash call — the authoritative rules are in spryker-import-tools → "Invocation & command discipline", and they bind every step of this run: invoke tools by their literal path from the project cwd (never cd, never a shell variable — the ROOT/CSV/VALIDATE names in each skill's intro are resolved once in reasoning, not set in shell); never chain/pipe/loop/redirect (&&/||/;/|/$( )/for always prompt, regardless of the allowlist); read files with the built-in Read tool (.env, configs, logs), search with the built-in Grep tool, inspect CSVs with csv.php columns --plain/distinct. Run-level applications of the same rules:
- A diagnostic that would be a pipe (
docker ps | grep dev) → run the plain command (docker ps) and read its output yourself.
- A long command (boot) →
run_in_background, which captures output with no > redirection.
- A wait loop must never be unbounded on a condition only a live producer can satisfy. Either bound it (
for i in $(seq 1 N) with an explicit give-up and a reported timeout), gate it on the producer still existing (kill -0 <pid> / pgrep -f <cmd> — a dead producer means the condition will never be met), or skip the loop entirely and rely on the harness's background-completion notification, which fires whether the command succeeded or died. Failure signature: an until grep …; do sleep 8; done still polling a zero-byte log — a zero-byte log after minutes means the producer died at start, not that it is slow. When you abandon an approach mid-task, kill its watchers — switching strategies is not cleanup, and an orphaned poller burns the run silently for hours.
- Run pre-flight checks as separate single commands (
docker -v, then php -v, then docker ps), not one &&-joined line.
Rules
- Stage changed/created files (
git add); never branch or commit — that's the developer's decision, including whether to commit the state file. Two carve-outs, both narrow: (i) commit only on an explicit per-instance developer request ("commit this now") — never on a standing approval, never on your own initiative, and never push; (ii) no worktree either — the boot binds the Docker env to this checkout, so a worktree has no vendor/ and no containers and the container never sees its edits (§ Tooling discipline; mechanism in ../static-validation/SKILL.md).
- No further configuration questions after collection. All configuration decisions are collected up front (§1 — questionnaire or interview); every step then runs without new ones — including the boot, which runs in a detached pseudo-terminal (
script … docker/sdk up in background; see boot-and-verify). Two honesty rules about what "hands-off" means:
- Hands-off only if the developer's allowlist already grants these commands. Without it the same steps run but each command prompts — still correct, just not hands-off; say so rather than implying silence (recommend the allowlist, don't install it).
- Do not over-promise silence. A real run still consults the developer on destructive operations and on any data defect the boot surfaces — in practice several times (a store-keyed money decision, a namespace collision, an empty-homepage or missing-entity defect the boot reveals). Frame it as "no further configuration questions; expect to be consulted on destructive operations and on any data defect the boot surfaces," never as an unattended run that never speaks again.
- Human touch-points that remain regardless: (a) the
/etc/hosts sudo line at the end — the server-side verification uses curl --resolve so it is not a gate on the run completing; only the browser-based verifier ACs need it, and declining it is a terminal done (browser ACs BLOCKED — /etc/hosts declined), never a stall — never run sudo yourself; (b) deletions (rm of leftover region dirs / stale manifests) are deliberately NOT allowlisted, so they prompt — a safety choice; (c) a genuine boot environment limit → surface the plain docker/sdk up for the developer to run, never edit project config to force it. On Spryker Cloud, infra changes needing a support case are the only other external gate.
⚠ Development scaffolding — REMOVE BEFORE RELEASE
This section is dev-time only — it exists to harvest issues while building and testing the wizard. It has no value in a customer's released run. Strip this section, and any .ai-dev/skill-improvement-log.md writes, before shipping (tracked in docs/plans/pre-release-cleanup.md).
While the wizard is still in development/testing: maintain .ai-dev/skill-improvement-log.md. Whenever any step hits a gap, surprise, workaround, or a place a tool/skill fell short — you had to fall back to a foreign interpreter, an enumerated list missed a file, a boot- or runtime-blocker, an inherited demo-data bug, a manual fix the skill didn't cover — append an entry: symptom → root cause → which skill → suggested change. This is the feedback trail we mine to improve the skills (it's how the current correctness/tooling fixes were found). Keep it honest and specific; it is for the maintainer, not the end user.
Two rules about it:
- The log is the ONLY feedback channel. Never edit the installed skill copies in the target project (
.claude/skills/**, .cursor/**, .windsurf*/**) — not even when the fix is one line, obvious, and you have verified it. A patched installed copy forks from the source repo, the source keeps shipping the defect, and the diff is lost the next time the plugin is reinstalled. Write the entry instead. Failure signature: a git status in the target project showing modified files under .claude/skills/.
- An entry is also required for any RUNTIME defect you debugged to resolution — not only for skill gaps. A post-boot failure you diagnosed and fixed (a queue-worker write not landing in the KV store, an index installer misbehaving) is the most expensive knowledge a long run produces, and it is unrecoverable from the conversation once the session ends. Record it in the same symptom → root cause → which skill → suggested change shape, naming the signature precisely enough to be pasted into
references/pitfalls.md. Failure signature: a run whose log has no entry for a defect it spent an hour fixing.