diff --git a/plugins/spryker-ai-dev-sdk/.claude-plugin/plugin.json b/plugins/spryker-ai-dev-sdk/.claude-plugin/plugin.json index dcd0a5b..f25b567 100644 --- a/plugins/spryker-ai-dev-sdk/.claude-plugin/plugin.json +++ b/plugins/spryker-ai-dev-sdk/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spryker-ai-dev-sdk", - "version": "0.2.0", + "version": "0.3.0", "description": "Set of setup tools and skills for Spryker project development.", "author": { "name": "Spryker Systems GmbH", diff --git a/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/README.md b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/README.md new file mode 100644 index 0000000..a371f8d --- /dev/null +++ b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/README.md @@ -0,0 +1,134 @@ +# spryker-bugfix + +End-to-end bug-fixing workflow for Spryker projects: from a **ticket _or_ a plain description** to a +committed, validated, QA-accepted fix on a `bugfix/*` branch. In Autonomous mode it goes all the way to +a pushed **Draft PR** with a remote-CI watch loop. + +This skill is an **orchestrator** — it runs a fixed sequence of stages and delegates the heavy work of +each stage to an already-installed skill or Claude subagent. It writes no product code itself; it +coordinates the skills that do. + +## Files + +| File | Role | +|------|------| +| [`SKILL.md`](SKILL.md) | The workflow spine — Steps 0–12, one paragraph each. This is what Claude loads. | +| [`stages.md`](stages.md) | Full, authoritative per-step instructions + the stage→skill quick map. Read the relevant § before each step. | +| [`reference.md`](reference.md) | Run-lean context discipline (`$BUGFIX_DIR`, State Object), operating principles, decision log, red flags. Read once per run. | + +## Two ways in — the ticket is optional + +The bug context can come from **either or both** of: + +- a **ticket** from any tracker (JIRA key, GitHub issue URL/number, or any other service), **or** +- a **free-text description** of the symptom with technical hints. + +A ticket is **never required**. When a ticket exists and its integration is reachable (Atlassian MCP, +`gh`, …) the skill pulls it for extra context; otherwise it works entirely from the description. The +branch name, PR title, and commit message all fall back to `no-ticket` / `NO-TICKET`. + +## Design constraints + +- **Claude-only.** Every stage delegates to a bundled/installed `Skill(...)` or a Claude subagent. No + third-party MCP is required for the core flow — the **only** external touchpoint is the optional + Step 0 ticket pull. Every spawned subagent runs on a Claude model. +- **Run lean.** Bulk output (Chrome, XDebug, codecept, phpcs/phpstan, review, verification, CI logs) + runs inside subagents that write raw logs to `$BUGFIX_DIR` + (`$CLAUDE_PROJECT_DIR/.claude/.cache/spryker-bugfix//`) and return only compact verdicts. +- **Plan is a task list.** Step 0 arranges the 12 stages as a `TaskCreate` task list and drives it with + `TaskUpdate` (one task `in_progress` at a time; a fresh attempt task per loopback). `TaskList` is the + live position — it survives compaction and scheduled wake-ups, so a resumed run or the watch loop + re-orients from it. It complements the `run.log` timeline and the `decisions.md` rationale. +- **The bug is the contract.** A fix is "done" only when the user-visible symptom no longer reproduces + with evidence **and** every gate is green. +- **PR channel is capability-gated.** Step 0 probes how the run can reach a remote and records + `PR_CHANNEL` — `gh` (push + Draft PR + CI watch), `git-only` (push a branch, no PR API), or `none` + (local-only). `gh` is **never assumed**: if it's missing or unauthenticated, Step 11 degrades to a + clean local terminal (commit, and — where possible — push + a handoff `gh pr create` line) instead of + failing. Every GitHub step checks the channel first and is skipped-with-a-note when unavailable. + +## Modes + +- **Collaborative** — the agent stops at the important decision points and **before push** for review. +- **Autonomous** — after the single Step 0 intake, the agent runs unattended to a pushed Draft PR; + every fork is a logged CRITICAL DECISION, not a question. The only hard stops are a stale/dirty base + (Step 2) and the attempt budget (`attempt > 3`). + +## Flow + +```mermaid +flowchart TD + Start([Trigger: 'fix this bug' / ticket / description]) --> S0 + + S0["Step 0 — Mode + context
(ticket OPTIONAL, or description)
run dir + logger, env-reset
probe PR_CHANNEL: gh / git-only / none"] + S0 --> S1["Step 1 — Intake & framing"] + S1 --> S2{"Step 2 — Branch
SAFETY GATE
clean tree & fresh base?"} + + S2 -- "dirty / stale" --> Abort["Ask (Collaborative)
or abort (Autonomous)"] + Abort --> Report + S2 -- "ok → bugfix/<key|no-ticket>/<name>" --> S3 + + S3["Step 3 — Reproduce
spryker-runtime + spryker-docs-research"] + S3 --> S4 + + S4["Step 4 — Root cause
LOOP RE-ENTRY · attempt++
ai-runtime-debugging + spryker-runtime"] + S4 --> S5["Step 5 — Fix + re-run repro"] + S5 --> S6["Step 6 — Functional tests
codecept-functional"] + S6 --> S7["Step 7 — Static validation
static-validation"] + S7 --> S8{"Step 8 — Code review GATE
code-review"} + + S8 -- "blocker/major" --> Budget + S8 -- "clean" --> S9{"Step 9 — QA GATE
spryker-qa-coverage (E2E)"} + + S9 -- "rejected" --> Budget + S9 -- "accepted" --> S10{"Step 10 — Final verification
tests + spryker-verifier / spryker-runtime
symptom gone in running app?"} + + S10 -- "FAIL" --> Budget + S10 -- "PASS" --> S11{"Step 11 — commit always
MODE + PR_CHANNEL gate"} + + Budget{"attempt > 3?"} + Budget -- "no → attempt++" --> S4 + Budget -- "yes" --> Report + + S11 -- "Collaborative / pre-push review" --> Confirm["Commit → STOP
present for user confirmation"] + S11 -- "Autonomous · PR_CHANNEL=none" --> Local["Commit LOCALLY
skip push/PR/watch
report publish commands"] + S11 -- "Autonomous · PR_CHANNEL=git-only" --> PushOnly["Commit → push branch
skip PR + watch
hand over gh pr create line"] + S11 -- "Autonomous · PR_CHANNEL=gh/mcp" --> Ship["Commit → push →
Draft PR (no labels)"] + Confirm --> Report + Local --> Report + PushOnly --> Report + Ship --> Watch{"Watch loop
gh pr checks ~15m"} + + Watch -- "all green" --> Report + Watch -- "red (your code)" --> Budget + Watch -- "flaky/infra" --> Watch + + Report(["Step 12 — Final report
ALWAYS LAST, every terminal state
outcome · root cause · decisions · gates · log path"]) +``` + +### The verification loop + +Steps 8 (review), 9 (QA), 10 (final verification), and 11 (remote CI) are gates. A failure in any of +them draws from **one shared `attempt` counter** and loops back to **Step 4** to re-investigate, then +forward through the full chain again. The hard stop is `attempt > 3`: the run STOPs and reports — +nothing is pushed and no PR is marked ready with a known-broken state. + +## Stage → skill map + +| Step | Delegates to | +|------|--------------| +| 0 | `AskUserQuestion`; **optional** ticket pull (Atlassian MCP / `gh issue view` / paste) | +| 3 | `Skill(spryker-runtime)` + `Skill(spryker-docs-research)` | +| 4 | `Skill(ai-runtime-debugging)` + `Skill(spryker-runtime)` | +| 6 | `Skill(codecept-functional)` | +| 7 | `Skill(static-validation)` | +| 8 | `Skill(code-review)` | +| 9 | `Skill(spryker-qa-coverage)` | +| 10 | `Skill(codecept-functional)` re-run + `spryker-verifier` agent / `Skill(spryker-runtime)` | +| 11 | `git` / `gh pr create --draft` / `ScheduleWakeup` or `CronCreate` watch loop | + +## Packaging note + +This skill ships in the `spryker-ai-dev-sdk` plugin under `vendor/spryker-sdk/ai-dev/…`, which is +Composer-managed — `composer update spryker-sdk/ai-dev` may overwrite it. The durable home for edits is +the plugin's own repository (`github.com/spryker-sdk/ai-dev`). diff --git a/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/SKILL.md b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/SKILL.md new file mode 100644 index 0000000..d74c823 --- /dev/null +++ b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/SKILL.md @@ -0,0 +1,173 @@ +--- +name: spryker-bugfix +description: > + End-to-end bug-fixing workflow: from an optional tracker ticket (JIRA, GitHub Issues, or any + service) or a plain bug description to a committed, + validated, QA-accepted fix on a bugfix branch (autonomous mode adds a pushed Draft PR + + CI watch). Orchestrates reproduce → root-cause → fix → functional tests → static checks → + review → QA → final verification via the project's stage skills. Trigger — "fix this bug", + "fix ticket XY-1122", "broken, reproduce and fix it": any bug symptom PLUS the expectation of a + DELIVERED fix. NOT for a single isolated step (one test, "just run CI"), new features, + refactors without a symptom, or investigation-only requests ("just find where it goes wrong, + don't fix"). +--- + +# Spryker Bug-Fixing Workflow + +This skill drives a bug from intake to a delivered, validated fix. It is an **orchestrator**: each +stage delegates to an existing installed skill. Your job is to run the stages in order, carry context +between them, enforce the verification loop, and respect the operating mode the user picked. + +> **Note — the ticket is optional.** The bug can come from a tracker ticket (JIRA, GitHub Issues, or +> any other service) **or** from a plain free-text description with no ticket at all. A ticket is never +> required — it is just one possible source of the bug context. When a ticket exists and the matching +> MCP/CLI is available, the skill pulls it for extra context; otherwise it works entirely from the +> description. Everything downstream (branch name, PR title, commit message) has a `no-ticket` fallback. + +> **Note — PR delivery is chosen and capability-gated.** Step 0 asks **whether to open a PR at all and +> via which channel**, then **probes** what's actually available and records `PR_CHANNEL`: `gh` (native +> GitHub CLI — push + Draft PR + CI watch), `mcp` (a connected **forge MCP server** for the remote host +> — GitHub, GitLab, Bitbucket, … — same flow via MCP tools), `git-only` (push a branch but no PR API — +> commit + push, then hand the create-PR command to the user), or `none` (no reachable remote — +> **local-only**: commit and stop). `gh` is **never assumed** — a missing/unauthenticated `gh` is fine +> when an MCP or plain `git` is available. Every push/PR action checks the preference + `PR_CHANNEL` +> first; anything unavailable (or a "no PR" choice) is **skipped with a clear note in the report**, never +> attempted-and-failed. This never aborts the run — it only changes how far Step 11 goes. + +> **Note — Claude-only orchestration.** This skill is built to run on **Claude and the skills/agents +> already installed in the project** — nothing else. Every stage delegates to a bundled/installed +> `Skill(...)` or Claude subagent; no third-party MCP server is required for the core flow. The only +> external touchpoint is the **optional** Step 0 ticket pull (e.g. JIRA via the Atlassian MCP); if the +> ticket text is pasted or a free-text description is given, the whole workflow runs without any external +> MCP. Keep every subagent this skill spawns on a Claude model. + +The core lessons this workflow enforces: a bug usually has *multiple* candidate paths, the visible +symptom is often downstream of the real cause, and a fix is not "done" until the running app proves +the symptom is gone **and** every gate is green. Build that rigor in regardless of which module, +feature, or layer the bug lives in. + +**This file is the workflow spine only.** The full, authoritative instructions live verbatim in two +companion files in this directory: + +- [stages.md](stages.md) — complete per-step instructions (Steps 0–12) + the stage→skill quick map. + **Read the relevant § before executing each step below.** +- [reference.md](reference.md) — the run-lean context discipline (`$BUGFIX_DIR`, State Object), + operating principles, the decision & question log, and the red-flags list. **Read it once at the + start of every run, before Step 0.** + +## Context discipline (summary) + +Heavy work (Chrome, XDebug, codecept, phpcs/phpstan, review, final verification, CI logs) runs in +**subagents** that write raw output to files under `$BUGFIX_DIR` +(`$CLAUDE_PROJECT_DIR/.claude/.cache/spryker-bugfix//`) and return only compact verdicts. +The orchestrator retains only the **State Object**: mode/base/branch/attempt, extra expectations, repro +summary, root-cause `file:line`, diff stat, per-gate verdicts (≤5 actionable items each), and log-file +pointers. Details: [reference.md](reference.md) § Run lean / § The run directory / § The State Object. + +## Plan tracking (summary) + +At the end of Step 0, **arrange the plan as a task list** with `TaskCreate` — one task per stage +(Steps 1–12) — and drive it with `TaskUpdate` as the run proceeds: mark a stage `in_progress` when you +enter it and `completed` when its gate is green; on a loopback, reopen the affected gate task(s) and add +a fresh attempt task. `TaskList` is the live, at-a-glance checklist a human (or a resumed run) can read +to see where the run stands — it **survives compaction and scheduled wake-ups**, complementing the +append-only `run.log` timeline (the log is the history; the task list is the current state). This is the +same pattern the PRD workflow uses. Details: [stages.md](stages.md) § Step 0 (plan task list) and +[reference.md](reference.md) § Plan task list. + +## Principles (summary) + +The bug is the contract — the fix is proven only when the user-visible symptom no longer reproduces, +with evidence. Carry context forward between stages; confirm the real executing path before fixing; +never report a stage green that wasn't; honor `.claude/rules/*`. **Autonomous mode never asks after +Step 0** — decide, proceed, and log a CRITICAL DECISION in `$BUGFIX_DIR/decisions.md`; the only +Autonomous stops are the stale/dirty base (Step 2) and `attempt > 3`. Maintain the append-only +decision & question log from Step 0 onward. Details: [reference.md](reference.md) § Operating +principles and § The decision & question log. + +## Workflow spine + +**Step 0 — Choose mode and gather context (ALWAYS FIRST).** One multi-tab `AskUserQuestion`: mode +(Autonomous vs Collaborative), the bug context (an **optional** ticket from any tracker **and/or** a +free-text description), base branch, env freshness, pre-push personal review, **PR delivery** (create a +PR or not, and via which channel — auto / `gh` / a named forge MCP / push-only), and extra expectations +beyond the default scope. Then create `$BUGFIX_DIR` + `run.log` step logger, **probe `PR_CHANNEL`**, +handle the env-reset decision, and — only if a ticket was given and its tracker is reachable — pull the +ticket for extra context. Read [stages.md](stages.md) § Step 0 before executing this stage. + +**Step 1 — Intake & framing.** Turn the context into a crisp problem statement: verbatim symptom, +affected actor/surface, environment, provisional module/layer scope. Keep it short — it aligns +later stages and subagents. Read [stages.md](stages.md) § Step 1 before executing this stage. + +**Step 2 — Create the bugfix branch. ← SAFETY GATE.** Verify a clean tree and an up-to-date base +first; if dirty/stale, ask (Collaborative) or abort with a report (Autonomous). Branch +`bugfix//` (lowercase; `no-ticket` fallback), then finalize +`$BUGFIX_DIR`. Read [stages.md](stages.md) § Step 2 before executing this stage. + +**Step 3 — Reproduce & understand the bug.** Ensure the env is actually running, then delegate +reproduction to a subagent using `Skill(spryker-runtime)` (writes `$BUGFIX_DIR/repro-notes.md`, +returns a summary) and, in parallel, `Skill(spryker-docs-research)` for expected behavior. A bug you +couldn't reproduce is a hypothesis. Read [stages.md](stages.md) § Step 3 before executing this stage. + +**Step 4 — Root-cause investigation. ← LOOP RE-ENTRY POINT.** The shared `attempt` counter is +defined here (starts at 1; +1 on every loopback from Steps 8/9/11; **hard stop when `attempt > 3`**). +Delegate runtime tracing to a subagent (`Skill(ai-runtime-debugging)` + `Skill(spryker-runtime)`) +that returns the confirmed executing path `file:line` + decisive values. When several candidates +survive in Autonomous mode, pick the evidence-backed one and log a CRITICAL DECISION. +Read [stages.md](stages.md) § Step 4 before executing this stage. + +**Step 5 — Implement the fix & verify it resolves the bug.** Smallest correct change at the root +cause; parallel subagents for independent multi-file edits. **Mandatory:** re-run the Step 3 repro +and confirm the symptom is gone with the same evidence. Read [stages.md](stages.md) § Step 5 before +executing this stage. + +**Step 6 — Functional test coverage (subagent).** One subagent with `Skill(codecept-functional)`: +enter testing mode first, add/update a regression test (ideally fails without the fix, passes with +it), write the run log to `$BUGFIX_DIR`, return only the pass/fail verdict + test paths. Confirm +green before moving on. Read [stages.md](stages.md) § Step 6 before executing this stage. + +**Step 7 — Static validation (subagent).** Subagent runs `Skill(static-validation)` +(phpcbf/phpcs/phpstan on the diff), fixes and re-runs until clean, returns `clean` or the +remaining violations. Read [stages.md](stages.md) § Step 7 before executing this stage. + +**Step 8 — Code review. ← GATE (loops back to Step 4).** Run `Skill(code-review)` (keep +only ≤5 blocker/major items); fix findings, then re-run static checks (and tests if behavior was +touched). Only blocker/major findings gate — **nits never loop**. Clean → Step 9; failing → +`attempt`+1 and return to Step 4 through 5→6→7→8; `attempt > 3` → **STOP and report** (no push, no +PR). Read [stages.md](stages.md) § Step 8 before executing this stage. + +**Step 9 — QA acceptance (independent). ← GATE.** `Skill(spryker-qa-coverage)` in an isolated +subagent, handed the full session context (changed files, repro scenarios, env gotchas, regen +commands already run, ticket/description). QA must confirm the symptom is gone **E2E**. Accepted → continue; +rejected → treat as a Step 8 failure (loop to Step 4 within the budget). +Read [stages.md](stages.md) § Step 9 before executing this stage. + +**Step 10 — Final verification before commit.** Re-run the affected functional tests (subagent), +then perform an **end-to-end final verification of the fix in the running app** (subagent — the +`spryker-verifier` agent, or `Skill(spryker-runtime)`) as the last gate before commit/push: confirm +the user-visible symptom is gone with fresh evidence. Returns a PASS/FAIL verdict + evidence; a FAIL +is treated as a Step 8 failure (loop to Step 4 within the budget). +Read [stages.md](stages.md) § Step 10 before executing this stage. + +**Step 11 — Commit, push, Draft PR, watch loop. ← MODE GATE + PR-CHANNEL GATE.** Always commit on the +branch first. Then act by mode **and** by the `PR_CHANNEL` probed at Step 0. Collaborative (or pre-push +review requested): commit, then **STOP and present** for user confirmation — never push without it. +Autonomous: push and open a **Draft PR** *only if `PR_CHANNEL=gh`* (title `: `, +**no labels**), write `$BUGFIX_DIR/watch-state.md`, and arm a ~15-min watch loop +that re-hydrates from that file and polls `gh pr checks` — red check → subagent pulls the failed logs, +`attempt`+1, return to Step 4 through the **full** gate chain before re-pushing; `attempt > 3` → STOP, +cancel the loop, leave the PR in Draft, comment and report. If `PR_CHANNEL=git-only`, push the branch +and **skip** the PR + watch loop, handing the user a ready-to-run `gh pr create` line and the branch +name. If `PR_CHANNEL=none`, **skip push, PR, and watch entirely** — leave the committed branch local and +tell the user how to push it themselves. Read [stages.md](stages.md) § Step 11 before executing this stage. + +**Step 12 — Final report (ALWAYS LAST, every terminal state).** Outcome, bug + root cause, +CRITICAL DECISIONS (headline section), OPEN QUESTIONS/RISKS, per-gate status (honest), extra +expectations handled, and — always the last line — the absolute path to the step log and decision +log. Read [stages.md](stages.md) § Step 12 before executing this stage. + +## Quick map & red flags + +The stage → skill quick-map table lives in [stages.md](stages.md) § Stage → skill quick map. +Before deviating from any gate, shortcut, or budget above, read +[reference.md](reference.md) § Red flags — stop and reconsider. diff --git a/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/reference.md b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/reference.md new file mode 100644 index 0000000..1ab6ecb --- /dev/null +++ b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/reference.md @@ -0,0 +1,165 @@ +# Spryker Bugfix — Reference + +Context discipline, operating principles, the decision & question log, and red flags for the +workflow spine in [SKILL.md](SKILL.md). Read this file once at the start of every run. + +## Run lean — the orchestrator is a coordinator, not a worker + +This is critical for **autonomous** runs, which can span hours and many loop iterations: the main +(orchestrator) context must stay small, because everything in it is re-read on every loopback and +every scheduled wake-up. A bloated orchestrator is the #1 failure mode here — it crowds out the +working room the later gates need and eventually destabilizes the run. + +**The rule: heavy work happens in subagents; the orchestrator holds only compact state.** A "heavy" +stage is anything that produces bulk output — driving Chrome (screenshots, page text), runtime/XDebug +dumps, `codecept run` output, phpcs/phpstan reports, code-review findings, final-verification runs, and +`gh run view --log-failed`. Run each of those **inside a subagent** (via the `Agent` tool, pointing it +at the relevant `Skill(...)` or agent `subagent_type`), and require the subagent to: + +- **write its raw output to a file** under the run directory `$BUGFIX_DIR` (e.g. + `$BUGFIX_DIR/-attempt.log`), and +- **return only a compact, structured verdict** as its final message — never the raw logs. + +The orchestrator then keeps just that verdict. The **State Object** below is the *entire* working +memory the main loop needs; if something isn't in it, it lives in a file you can re-open on demand. + +### The run directory (`$BUGFIX_DIR`) + +Every file this skill produces — logs, decision log, repro notes, per-gate output, the watch handoff — +lives together in **one per-bugfix folder** so a run is self-contained and easy to find or clean up. +Anchor it to the project root Claude Code loaded (`$CLAUDE_PROJECT_DIR`, with a `$(pwd)` fallback) so +it is stable regardless of the current working directory: + +``` +${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/.cache/spryker-bugfix// +``` + +`` is the ticket key (e.g. a JIRA key `CC-39232` or a GitHub issue number) when there is +one, else the sanitized branch suffix (e.g. `no-ticket-`). Set `BUGFIX_DIR` once at Step 0 and reference it everywhere; keep +**all** run files inside it — never scatter them elsewhere. The folder is the run's scratch space and +survives across loopbacks and wake-ups, which is exactly why the watch loop can re-hydrate from it. + +### The State Object (the only thing the orchestrator must retain) + +Keep this small block current in your head and mirror it into the step log. Everything else is on disk. + +- `mode`, `base`, `branch`, `attempt` (current value), env-freshness choice. +- **PR preference** — the Step 0 answer for how the run should end: create a PR (auto / a forced channel) + or commit-only. Combines with `PR_CHANNEL` at Step 11. +- **`PR_CHANNEL`** — how the run *can* interact with a PR, probed once at Step 0: `gh` (native GitHub CLI + → push + Draft PR + `gh pr checks` watch), `mcp` (a connected forge MCP for the remote host — GitHub / + GitLab / Bitbucket / … → push + PR/MR + checks via MCP tools), `git-only` (push a branch, no PR API), + or `none` (local-only). For `mcp`, also retain the resolved create-PR / list-checks tool names. Step 11 + and the optional ticket pull check this before any push/PR action; anything the channel can't do is + skipped, not attempted. +- **Extra expectations** — any Step 0 delta from the standard scope (e.g. "also update JIRA", "fix + related bugs too", "single module only"), so every later step honors it without re-asking. +- **Repro:** 1–3 line scenario summary + path to the full repro notes file. +- **Root cause:** the `file:line` references + one-sentence defect explanation. +- **Diff:** `git diff --stat` summary (files + ±lines), not the diff body. +- **Per-gate verdict:** for tests / static / review / QA / final-verification / remote-CI — `pass|fail`, + a one-line reason, and the path to that gate's output file. For a failing gate, keep **only the ≤5 + actionable items** you must act on (e.g. blocker/major review findings, the failing test name), not + the surrounding report. +- Pointers to the **decision log** and **step log** files. + +If you ever need detail you dropped (a full stack trace, the complete review report), `Read` the file +the subagent wrote — pull the specific lines you need, don't reload the whole thing. Pulling 20 lines +on demand beats carrying 2,000 lines for the whole run. + +### Plan task list + +Alongside the State Object, the run keeps a **plan task list** (created at the end of Step 0 — see +[stages.md](stages.md) § Arrange the plan as a task list) with one task per stage. It is the run's +**live, at-a-glance position**: exactly one task `in_progress` (the current stage), completed tasks +behind it, pending stages ahead, and a fresh attempt task per loopback. Three surfaces, three roles — +keep them distinct: + +- **`run.log`** — the append-only *timeline* (what happened, when). +- **`decisions.md`** — the *rationale* (why each fork was resolved a given way). +- **Task list (`TaskCreate`/`TaskUpdate`/`TaskList`)** — the *current state* (where the run is now). + +The task list is the cheapest thing to read to re-orient — a resumed run or the watch loop should check +`TaskList` first, then `$BUGFIX_DIR/watch-state.md`, before touching the transcript. Because it lives +outside the conversation, it survives compaction and scheduled wake-ups. Drive it faithfully: never +mark a stage completed whose gate wasn't actually green (the same honesty rule as the step log). + +## Operating principles + +- **The bug is the contract.** Everything is judged against "does the original reported symptom no + longer reproduce, with evidence". A green test suite is necessary but not sufficient — reproduce + the *user-visible* symptom and confirm it's gone (see `spryker-qa-coverage`'s E2E-over-workaround rule). +- **Carry context forward.** Each stage hands the next one what it learned: the repro steps, the + root-cause file:line references, the diff, the failing/added tests. Don't re-derive. +- **Find the real cause, not the first plausible one.** Resist fixing the first suspicious line. + Confirm the path actually executes (runtime debugging / a failing test) before editing. +- **Never report a stage green that wasn't.** If a step was skipped, blocked, or only partially + done, say so plainly. Faithful status beats optimistic status. +- **The ticket is optional.** The bug context may be an **optional** tracker ticket (JIRA, GitHub + Issues, any service) **or** a plain free-text description — a ticket is never required. Branch name, + PR title, and commit message all have a `no-ticket` / `NO-TICKET` fallback. +- **Claude-only orchestration.** Every stage delegates to a bundled/installed `Skill(...)` or a Claude + subagent — no third-party MCP server is required for the core flow. The single **optional** external + touchpoint is the Step 0 ticket pull (e.g. JIRA via the Atlassian MCP, or `gh issue view`); if the + ticket text is provided directly, or there's no ticket at all, the run needs no external MCP. Keep + every subagent on a Claude model. +- **Project rules still apply.** Honor the repo's `.claude/rules/*` (php-code-style, no-redundant-docblocks, + facade-method-signatures, BC policy, etc.) and `CLAUDE.local.md` file-reference format throughout. +- **Autonomous means autonomous (from Step 3 onward).** After the single Step 0 intake, Autonomous + mode runs to completion **without any further `AskUserQuestion` or permission stop** — no scope + clarifications, no "which fix should I pick", no "is this in scope" pauses. When you hit a fork + (multiple candidate root causes, ambiguous scope, a second bug discovered, a risky trade-off), + **decide it yourself using the most reasonable interpretation, proceed, and record it as a + CRITICAL DECISION in the running report** (see "The decision & question log" below). The *only* + permitted Autonomous stops are the two hard safety gates already defined: a stale/dirty base in + Step 2, and the `attempt > 3` budget exhaustion. Everything else is a logged decision, not a question. + +## The decision & question log (maintained across every step) + +From Step 0 onward, keep a running, append-only log in `$BUGFIX_DIR/decisions.md` (see the run +directory below) with two sections you update as you go: + +- **CRITICAL DECISIONS** — every fork you resolved on your own: the choice, the alternatives you + rejected, and the one-line reason. (e.g. "Picked path A in `` over path B in + `` — B is downstream of A; confirmed by runtime log at file:line.") +- **OPEN QUESTIONS / RISKS / FURTHER BUGS** — anything you could not fully resolve and chose to + proceed past: out-of-scope smells, additional suspected bugs, BC risks, data assumptions, things a + human should confirm later. + +This log is the source for the Step 12 final report. Update it at each loopback (note the `attempt` +value) and whenever you make a non-obvious choice — do not wait until the end. + +## Red flags — stop and reconsider + +- "Tests pass, so the bug is fixed" → No. Reproduce the *user-visible* symptom and confirm it's gone. + The gate is the **functional (Codeception) regression test from Step 6** — one that failed before + the fix and passes after — plus the **final verification in the running app (Step 10)**. A test that + never went red, or that merely executes the changed code without asserting the fixed behavior, proves + nothing about the fix. +- "The first suspicious line is the cause" → No. Confirm the path actually executes; look for all + paths that produce the symptom. +- "Review failed again, I'll just push it anyway" → No. After 3 attempts, STOP and report — never + push broken code. +- "I'll branch even though the tree is dirty / master is stale" → No. Surface it; ask or abort. +- "Autonomous means I can also change permissions / delete data / merge the PR" → No. Autonomous + authorizes the *bugfix-to-Draft-PR* path only; prohibited actions stay prohibited. +- "Skip QA, the gates are green" → No. Independent QA acceptance is a required stage. +- "Skip the final verification, tests and QA already passed" → No. Step 10 drives the running app one + last time before commit and is a required gate; a FAIL loops back to Step 4 within the budget. +- "In Autonomous mode I should ask the user which fix / whether this is in scope" → No. After Step 0, + Autonomous mode never asks — decide it, proceed, and log it as a CRITICAL DECISION. The only + Autonomous stops are the stale/dirty base (Step 2) and `attempt > 3` (Steps 8/11). +- "I'll write the final report only on success" → No. Step 12 runs on every terminal state, and its + last line is always the log file path. +- "I'll just run `codecept` / the verifier / `gh run view --log-failed` here and read the output" → No. + Bulk output belongs in a subagent that writes it to a file and returns a compact verdict. The + orchestrator carries the verdict, not the logs — otherwise an autonomous run drowns its own context + (re-read on every loopback and every wake-up). +- "The watch loop can just re-run the whole skill from the top each wake" → No. It re-hydrates from + `$BUGFIX_DIR/watch-state.md`, polls with `gh pr checks`, and only fans out to a subagent if a + check is red. +- "Just run `gh pr create` / `git push` — it'll work" → No. **Never assume `gh` or a remote exists.** + Use the `PR_CHANNEL` probed at Step 0: `gh` → full push + PR + watch; `git-only` → push + hand over a + `gh pr create` line; `none` → commit locally and stop. A missing/unauthenticated `gh` **downgrades** + Step 11 to a clean local terminal state — it never aborts the run, and a skipped GitHub step is + reported plainly, never attempted-and-failed. diff --git a/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/stages.md b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/stages.md new file mode 100644 index 0000000..51ea11a --- /dev/null +++ b/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/stages.md @@ -0,0 +1,571 @@ +# Spryker Bugfix — Stage Details + +Full, authoritative per-step instructions for the workflow spine in [SKILL.md](SKILL.md). +Read the relevant § before executing each step. + +## Step 0 — Choose mode and gather context (ALWAYS FIRST) + +This is the **one and only** place the skill collects choices up front. In **Autonomous** mode, +everything you need to run unattended must be settled here — after this step, Autonomous mode asks +nothing more (see the Operating principle above). + +Begin with **one `AskUserQuestion` call carrying multiple questions** (multi-tab). Ask: + +1. **Mode** — `Autonomous` (agent decides everything, no human in the loop, goes all the way to + pushed Draft PR + watch loop) vs `Collaborative` (agent asks the user at the important decision + points and **stops before push** for the user to review). +2. **Context** — the bug context. A tracker ticket is **optional**; accept **either or both** of: + - a **ticket** from any tracker (JIRA key like `XY-1122`, a GitHub issue URL/number, or any other + service reference), and/or + - a **free-text description** of the symptom plus technical hints and any advice. + (Open-ended; the user can paste a ticket reference, a paragraph, or both. If neither the ticket nor a + usable description is given, ask once for at least a description — the workflow needs a symptom to + reproduce, but it does **not** need a ticket.) +3. **Target branch** — base branch to cut from (default `master`). +4. **Environment freshness** — is the local Docker environment freshly reset to the recent target + branch? Offer three answers: `Fresh — already reset` / `Not reset — reset it for me` / + `Not reset — I'll keep the current env` (drives Step 2's safety check + the reset decision below). +5. **Personal review before push** — does the user want to personally review and accept the fix + before it is pushed, in addition to the automated gates? (yes / no.) +6. **PR delivery — create a PR, and via what?** How should the run end at Step 11? Offer: + - **Create a PR — auto (recommended)** — open a Draft PR using the **best channel the Step 0 probe + finds** (native `gh` for GitHub, else a connected forge MCP for GitHub/GitLab/Bitbucket/…, else + push-only handoff). This is the default. + - **Create a PR — specific channel** — force it: `gh` CLI, or a **named forge MCP server** (e.g. + GitHub/GitLab/Bitbucket MCP), or `git push only` (push the branch, hand over a create-PR command). + If the forced channel turns out unavailable at Step 11, the run reports the mismatch rather than + silently switching (Autonomous: downgrades with a logged CRITICAL DECISION). + - **No PR — commit only** — commit on the branch and stop (push only if a remote exists and the user + allows it); never open a PR or arm the watch loop. + + Record the answer as the **PR preference**; it combines with the probed `PR_CHANNEL` (below) at + Step 11. Because Autonomous mode won't ask again, this is the moment to capture "don't open a PR" or + "use the GitLab MCP, not gh". +7. **Extra expectations beyond the standard workflow** — is there anything you want from this run + *on top of* the default scope and steps below? This is **open-ended and optional** — the default + answer is "no, just the standard workflow". Surface it because Autonomous mode won't ask again, so + anything non-standard must be captured now. Examples the user might raise: also update the tracker + ticket / post a status comment, fix related bugs you find along the way (vs. only-the-reported one), + add a changelog entry, target a specific reviewer, apply specific PR labels (the skill sets none by + default), skip the E2E/QA stage, produce a written RCA doc, keep the fix to a single module, or a + hard time/scope cap. + + > **The default scope** (so the user knows what's already covered and need not restate it): reproduce + > → root-cause → minimal fix → functional test → static validation → code review → independent QA → + > final verification → (autonomous) commit + push + Draft PR + remote-CI watch. Capture any **delta** + > from this, record it as a project constraint in `$BUGFIX_DIR/decisions.md`, and honor it throughout + > the run. + +After the answers, restate the chosen mode, the context, and any extra expectations in one line, then +proceed. In **Autonomous** mode, do not ask further questions — make reasoned decisions and record them +in your running decision log. In **Collaborative** mode, pause for confirmation at the marked decision points. + +**Set up the run directory and step logger (do this immediately after the answers).** Create the +per-bugfix folder and a single run log file inside it, and write every step's start/end and key +outcome there as you go — this is the file whose path you show at the very end (Step 12). Anchor the +run directory to `$CLAUDE_PROJECT_DIR` (the project root Claude Code loaded) so it is stable no matter +the current working directory; fall back to `$(pwd)` if the variable is unset. + +```bash +# : JIRA key if known, else "no-ticket-". Finalize after Step 2 if unknown now. +PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" +BUGFIX_DIR="$PROJECT_DIR/.claude/.cache/spryker-bugfix/" +mkdir -p "$BUGFIX_DIR" +BUGFIX_LOG="$BUGFIX_DIR/run.log" +printf '[%s] STEP 0 — mode=%s base=%s env=%s | START\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$MODE" "$BASE" "$ENV_FRESHNESS" >> "$BUGFIX_LOG" +``` + +- All run files live under `$BUGFIX_DIR`: `run.log` (step log), `decisions.md` (decision log), + `repro-notes.md`, the per-gate `-attempt.log`, and `watch-state.md`. Do **not** write run + files anywhere else. +- Log **one line per step boundary**: `[] STEP | START` and `| END `. +- Also log every loopback (`attempt=`), every CRITICAL DECISION (mirror the one-liner into the log), + and any gate verdict (review/QA/verification pass-fail). +- Keep the human-readable decision log (`decisions.md`) separate from this terse step log; the step log + is the timeline, the decision log is the rationale. + +### Probe the PR channel (do this in Step 0, before you rely on it) + +Step 11 (and the optional ticket pull) needs a way to talk to the remote forge. **Never assume `gh` +exists or is authenticated** — and `gh` is not the only way. A connected **forge MCP server** (GitHub, +GitLab, Bitbucket, Gitea, …) can create and watch a PR/MR just as well; the `git` CLI alone can still +push a branch. Probe once at Step 0 and record the result as `PR_CHANNEL`. This is a capability check, +not an action — it makes no network changes. + +Resolve the channel in this order (first match wins), deciding from the **remote** first (a run's +channel is a property of its repo, not of a globally-authenticated tool): + +1. **`none`** — no `origin` remote at all → local-only. +2. **`gh`** — the origin is a GitHub remote **and** `gh` is installed + `gh auth status` succeeds. Native + CLI: push + Draft PR + `gh pr checks` watch loop. +3. **`mcp`** — a **forge MCP server matching the remote host is connected** (e.g. a GitHub MCP for a + `github.com` remote, a GitLab MCP for a `gitlab.*` remote, a Bitbucket MCP for `bitbucket.org`). Use + the MCP's create-PR / list-checks tools for the same push + PR + watch flow. Discover these via + `ToolSearch` (e.g. query the forge name + "pull request"/"merge request"); if a create-PR tool + resolves for the remote's host, the channel is `mcp`. **Prefer `gh` over `mcp` only for GitHub when + both exist** (native `gh pr checks` is the cheapest poll); for non-GitHub remotes `mcp` is the *only* + PR-capable channel. +4. **`git-only`** — a remote exists but neither `gh` nor a matching forge MCP is usable. Push a branch, + but no PR API. + +```bash +# CLI/remote part of the probe (the MCP check is a ToolSearch, done in-agent — see below). +REMOTE_URL="$(git remote get-url origin 2>/dev/null || true)" +if [ -z "$REMOTE_URL" ]; then + PR_CHANNEL="none" # no origin remote → local-only +else + PR_CHANNEL="git-only" # a remote exists → at least push a branch + case "$REMOTE_URL" in + *github.com*) # GitHub remote → native gh is possible… + if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then + PR_CHANNEL="gh" # …and gh is installed + authenticated → full native + fi + ;; + esac +fi +# Then, if PR_CHANNEL is still "git-only", check for a forge MCP matching the remote host +# (ToolSearch for the host's create-PR/MR tool). If one resolves, set PR_CHANNEL="mcp" +# and remember the tool names. This step is done by the agent, not in bash. +printf '[%s] STEP 0 — PR_CHANNEL=%s remote=%s | probe\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$PR_CHANNEL" "${REMOTE_URL:-none}" >> "$BUGFIX_LOG" +``` + +- **`gh`** — GitHub remote + usable `gh`. Step 11 pushes, opens a Draft PR, and runs the `gh pr checks` + watch loop. GitHub-issue ticket pull available via `gh`. +- **`mcp`** — a forge MCP server for the remote host is connected. Step 11 pushes (git), then creates + the Draft PR/MR and polls checks **through the MCP tools** instead of `gh`. Works for GitHub, GitLab, + Bitbucket, etc. Ticket pull can use the same MCP if it exposes an issue-fetch tool. +- **`git-only`** — remote exists, but no PR-capable tool. Step 11 commits and pushes the branch, then + **skips** the PR + watch loop and hands the user a ready-to-run `gh pr create` (or MR) line. Ticket + pull falls back to "paste the ticket text". +- **`none`** — no `origin` remote (or nothing usable). Step 11 commits **locally only** and **skips + push, PR, and watch**; the report tells the user how to add a remote and push. The rest of the + workflow (reproduce → … → final verification) is unaffected — it never needed the remote. + +Record `PR_CHANNEL` (and, for `mcp`, the resolved create-PR / list-checks tool names) in the **State +Object**. If it is `git-only` or `none`, note it as an OPEN RISK in `decisions.md` ("no PR channel — +Step 11 will stop at a local commit / branch push") so the downgrade is visible up front rather than a +surprise at Step 11. In **Autonomous** mode this is a logged fact, not a question — do **not** stop to +ask the user to install `gh` or connect an MCP. + +### Arrange the plan as a task list (do this right after the logger) + +Immediately after setting up the run directory, **create the plan as a task list** so the run's +progress is visible and survives compaction or a scheduled wake-up. Use `TaskCreate` once per stage +— one task for each of Steps 1–12 — with the mode/`` in each subject for context: + +``` +TaskCreate "S1 Intake & framing" +TaskCreate "S2 Create bugfix branch (safety gate)" +TaskCreate "S3 Reproduce & understand" +TaskCreate "S4 Root-cause investigation (loop re-entry)" +TaskCreate "S5 Implement fix & verify repro" +TaskCreate "S6 Functional test coverage" +TaskCreate "S7 Static validation" +TaskCreate "S8 Code review (gate)" +TaskCreate "S9 QA acceptance (gate)" +TaskCreate "S10 Final verification before commit" +TaskCreate "S11 Commit / push / Draft PR / watch (mode gate)" +TaskCreate "S12 Final report" +``` + +Then drive it as you go: + +- **Entering a stage** → `TaskUpdate status=in_progress`. Keep **exactly one** task `in_progress` + at a time (the stage you're actually running) so the list reads as the true current position. +- **Stage green** → `TaskUpdate status=completed`, then move to the next. +- **On a loopback** (Step 8 review fail / Step 9 QA reject / Step 10 verify fail / Step 11 red CI) → + reopen the affected gate task (`status=in_progress` again) and `TaskCreate` a short attempt task + (e.g. `"S4 re-investigate — attempt "`) so each pass through the loop is auditable. Mirror the + same `attempt=` note into `run.log`. +- **At any terminal state** (success, stop-before-push, or `attempt > 3`) → mark the reached task + completed and leave the rest as-is; Step 12's report reflects the final task states. + +`TaskList` is the live checklist — read it (not the full transcript) to see where the run stands; the +watch loop and any resumed run should re-orient from it plus `$BUGFIX_DIR/watch-state.md`. This +**complements** the `run.log` timeline and the `decisions.md` rationale — it does not replace either. + +### Environment reset decision (item from Step 0 answer 4) + +- **`Fresh — already reset`** → proceed; Step 2 still verifies the base independently (the answer is a + claim, not proof). +- **`Not reset — reset it for me`** → reset the environment to the target branch **before Step 3**: + check out/pull ``, then run `script -q /dev/null docker/sdk reset` (this removes all + containers and volumes — destructive but intended here), then bring it up with + `script -q /dev/null docker/sdk up`. In **Collaborative** mode, this confirmation was already given + by the answer — do not ask again. In **Autonomous** mode, the Step 0 answer is the authorization; + run the reset, and **log it as a CRITICAL DECISION** ("reset env to per user request"). +- **`Not reset — I'll keep the current env`** → do **not** reset. Proceed with the current env, but + log an OPEN RISK that the env may be stale and reproduction/QA results carry that caveat. + +### Optional ticket pull (only when a ticket was given) + +The ticket is **optional** and may come from any tracker. Resolve it only if the user gave a ticket +reference **and** the matching integration is available — otherwise skip this entirely and work from +the pasted description: + +- **JIRA key** (e.g. `XY-1122`) with the Atlassian MCP available → `jira_get_issue`; if its text comes + back thin, fall back to `jira_get_issue_images` to read the bug visually. +- **GitHub issue** (URL or `#number`) with `PR_CHANNEL=gh` → `gh issue view `. If `gh` isn't + usable (`PR_CHANNEL` is `git-only`/`none`), fall back to paste — do not try to call `gh`. +- **Any other tracker / no integration available** → do **not** block. Ask the user to paste the + relevant ticket text, or proceed from the free-text description alone. + +Whatever the source, capture the reported steps, expected vs actual, and any environment/version notes +— they seed Step 3. A ticket pull is **never** required for the run to proceed: if the ticket text is +pasted or only a free-text description is given, skip the pull and work from that. This is the **only** +external touchpoint in the whole workflow. + +## Step 1 — Intake & framing + +Turn the context into a crisp problem statement before touching code: +- **What is reported** (verbatim symptom), **affected actor/surface**, **environment**. +- **Provisional scope** — which module(s)/layer(s) likely involved (a guess to focus Step 3, not a + commitment). + +Keep this short. It exists so the later stages — and any subagents — share the same framing. + +## Step 2 — Create the bugfix branch (with safety check) + +**Verify the base before branching** (the user's "environment is recent" answer is a claim, not proof): + +```bash +git status --porcelain # working tree must be clean +# Fetch only when a remote exists (PR_CHANNEL != none); otherwise compare against the local base. +if [ "$PR_CHANNEL" != "none" ]; then + git fetch origin # default: master + git rev-list --left-right --count origin/...HEAD # how far behind/ahead vs remote +else + git rev-list --left-right --count ...HEAD # local base only — no remote to compare +fi +``` + +- If the working tree is **dirty** or HEAD is **behind** the base: surface exactly what you found and + **ask before proceeding** (Collaborative) or **abort with a clear report** (Autonomous — do not + silently branch off a polluted/stale base). Branching off the wrong base produces a fix that can't be + reviewed cleanly. +- When `PR_CHANNEL=none` there is no remote to compare against — verify the working tree is clean and + branch off the **local** base, and note in the report that base-freshness could not be checked against + a remote. Do not treat "no remote" as a stale-base abort. +- When clean and current, create the branch: + +```bash +git checkout +[ "$PR_CHANNEL" != "none" ] && git pull origin # pull only when a remote exists +git checkout -b bugfix// +``` + +Branch name pattern: `bugfix/{ticket-key}/brief-name` (e.g. `bugfix/ab-1234/short-symptom-name`), always lowercase. +The ticket key is whatever tracker key exists (JIRA key, GitHub issue number, etc.). **If there is no +ticket, use `bugfix/no-ticket/brief-name`** and note it — the ticket is optional. + +**Finalize the run directory** now that the branch exists: if `` wasn't known at Step 0 +(no JIRA key), rename `$BUGFIX_DIR` to `$PROJECT_DIR/.claude/.cache/spryker-bugfix/no-ticket-/`, +update `BUGFIX_DIR`/`BUGFIX_LOG`, and append the Step 2 boundary line. From here on, every run file stays +under this stable folder. + +## Step 3 — Reproduce & understand the bug + +Goal: a written, **reproducible** scenario — there may be several to cover the issue fully. + +- **Ensure the environment is actually running first** — Step 0's "freshness" answer is a claim, not + a live env. If you ran a reset in Step 0, the env is already up; otherwise bring it up before + reproducing: `script -q /dev/null docker/sdk run` (or `up` for a cold build). A repro that fails + because the stack is down wastes the investigation. +- **Delegate the reproduction to a subagent** so the Chrome/console bulk (screenshots, page text, + command output) stays out of the orchestrator. Spawn an `Agent` that uses `Skill(spryker-runtime)` + to reproduce the reported steps end-to-end as the right actor (resolve hosts from `deploy.dev.yml`; + lightest mode that shows the symptom, but drive Chrome for rendered-UI/JS symptoms). Tell it to + **write the full repro notes + evidence to `$BUGFIX_DIR/repro-notes.md`** and return only: + reproduced yes/no, a 1–3 line scenario summary per scenario, and the key evidence values (status + code, the wrong vs expected rendered value). The orchestrator keeps that summary in the State Object. +- In parallel, spawn **`Skill(spryker-docs-research)`** as a subagent to ground the affected + functionality in official docs — how it's *supposed* to behave. It is docs-only and starts **blind**, + so pass it the **module/feature name**, **reported symptom**, **actor/surface**, and the specific + **expected-vs-actual question**. Ask it to return a short "expected behavior" summary, not a doc dump. +- If the repro is blocked (broken control, missing data, env issue), the subagent says so and you + resolve it before claiming you reproduced it. A bug you couldn't reproduce is a hypothesis. + +The per-scenario notes (**step-by-step repro**, **current behavior**, **expected behavior**, concrete +evidence) live in the `$BUGFIX_DIR/repro-notes.md` file the subagent wrote — the orchestrator +references it by path and carries only the summary forward. + +## Step 4 — Root-cause investigation ← LOOP RE-ENTRY POINT + +> **The attempt counter (define once, here).** There is **one shared counter**, `attempt`, starting +> at `1` on the first entry to this step. **Increment it by 1 every time *any* downstream gate sends +> control back to Step 4** — that is: a Step 8 code-review failure, a Step 9 QA rejection, a Step 10 +> final-verification failure, OR a Step 11 remote-CI failure all draw from the same budget. Passing a +> gate does **not** reset the counter. +> **Hard stop when `attempt > 3`**: do not loop again — go to the stop-and-report terminal for +> whichever gate you were in (see Steps 8 and 11). State the current `attempt` value in your running +> report at each loopback so it's auditable. + +Identify *why* it happens, with **code references and an explanation**, not a guess. + +- **Delegate the runtime tracing to a subagent.** XDebug variable dumps and `[AI-DEBUG]` log streams + are bulky — run them in an `Agent` that uses **`Skill(ai-runtime-debugging)`** + **`Skill(spryker-runtime)`** + to confirm which path actually executes and the values at the failure point, writes the raw trace to + `$BUGFIX_DIR/rootcause-attempt.log`, and returns only: the confirmed executing path + (`file:line`), the decisive values observed, and which candidate paths it ruled out. Keep that, not + the trace. +- Remember the core lesson: **the displayed symptom may be produced by a different path than the + one you first suspect.** Confirm the candidate path is the one that runs before committing to it. + Look for *all* paths that could produce the symptom. +- **When several candidate causes survive (Autonomous mode):** do not stop to ask which to fix. Pick + the one the runtime evidence most strongly supports (the path you confirmed executes), fix that, + and **log a CRITICAL DECISION** naming the rejected candidates and why. If fixing it later proves + wrong, the loop (Step 8/9/10/11 → Step 4) catches it within the attempt budget. +- Output: root-cause statement + `file:line` references + a short explanation of the defect. + +> **This step is the re-entry point of the verification loop.** If Step 8 (code review) fails and a +> retry is warranted, you come back *here* — because a review failure means the understanding or the +> fix was wrong, not just the formatting. + +## Step 5 — Implement the fix & verify it resolves the bug + +- Apply the smallest correct change that fixes the root cause. +- **Use subagents in parallel** for independent edits when the fix spans multiple files/modules, to + speed things up. Keep each subagent's task scoped and hand it the root-cause context. +- **Verify against the bug**, not just the unit: re-run the Step 3 repro and confirm the symptom is + gone with the same evidence you captured before. This is mandatory before moving on. + +## Step 6 — Functional test coverage (run in a subagent) + +**Delegate this whole stage to one subagent** that uses **`Skill(codecept-functional)`** — test +authoring, the `codecept build`/`codecept run` cycle, and any fix iterations all produce bulk output +(build chatter, stack traces) that must not land in the orchestrator. Spawn an `Agent` and hand it the +root-cause `file:line`, the changed files, and the repro scenarios; instruct it to: + +- Decide whether a test must be **updated or added** to lock in the fix and prevent regression, and + write/edit it under the affected module's `tests/` tree. +- **Enter testing mode before running anything**: `script -q /dev/null docker/sdk testing "exit"`, and + `docker/sdk testing codecept build -c ` after creating a suite/helper or changing + `codeception.yml` — otherwise the first `codecept run` fails because the testing container/bootstrap + isn't ready and an attempt is burned for an avoidable reason. Build the suite first if the module had + none (`module-test-infrastructure` rule). +- Prefer a regression test that **fails without the fix and passes with it** (prove it against the + pre-fix state when feasible — the strongest evidence a fix actually addresses the bug). +- Follow the skill's conventions (entry-point focus, AAA, helpers, correct suite name, `-c `, + single-colon method filter). +- **Write the full run to `$BUGFIX_DIR/tests-attempt.log`** and return only: `pass|fail`, counts, + the path(s) of any test file it created/edited, and — if failing — the failing test names + the + one-line assertion message each. + +The orchestrator keeps just that verdict (and the new test paths, for the diff); it `Read`s the log +only for a failure it must act on. Confirm green before moving on. + +## Step 7 — Static validation (run in a subagent) + +**Delegate to a subagent** that runs **`Skill(static-validation)`** (phpcbf/phpcs/phpstan on the diff) +— the raw sniffer output is verbose. The subagent applies the auto-fixes, addresses what the sniffer +cannot, re-runs until clean, writes the run to `$BUGFIX_DIR/static-attempt.log`, and returns only +`clean` or the list of remaining violations (`file:line` + rule). Keep just that verdict. (For a quick +interim check `sh .claude/bash-local/validation.sh` is fine; the skill is the authoritative gate.) + +## Step 8 — Code review ← GATE (loops back to Step 4) + +1. Run **`Skill(code-review)`** — it fans the diff out to `spryker-code-reviewer` subagents. Have the + review write its full findings to `$BUGFIX_DIR/review-attempt.md` and surface back to the + orchestrator only the **blocker/major** items (≤5, each: `file:line` + one-line issue). Nits stay in + the file. The orchestrator keeps only that short blocker/major list in the State Object. +2. **Fix the findings that relate to the code** (blockers/majors first; apply nits where cheap — open + the review file to read a nit only when you're about to fix it). +3. After fixing, **re-run `Skill(static-validation)`** and **re-run the functional tests if the review + fixes touched tested behavior**. + +**The verification loop:** +- Only **blocker/major** findings gate the loop. **Nits never cause a return to Step 4** — apply them + where cheap and move on; a reviewer re-raising the same nit must not cause oscillation. +- If review is clean (no blocker/major remaining) → continue to Step 9. +- If review still finds **code-related** blockers/majors that indicate the fix is wrong or incomplete + → increment `attempt` (the shared counter from Step 4) and **return to Step 4** (re-investigate), + then forward through 5→6→7→8 again. +- **Hard stop when `attempt > 3`.** If the budget is exhausted and Step 8 still fails: **STOP and + report.** Do not push, do not open a PR, do not commit broken code. Produce a clear report: what was + tried each attempt, the remaining review findings, the current diff, and a recommendation. Hand back + to the user. (This applies in both modes — in Autonomous mode, stopping with a report is the correct + terminal state here.) + +## Step 9 — QA acceptance (independent) + +Spawn **`Skill(spryker-qa-coverage)` as a subagent in an isolated context** and have it return a QA +report that explicitly **accepts or rejects** the fix. Because the subagent starts blind, you MUST +pass it the session context per that skill's rule: what changed (files), the bug + repro scenarios, +environment gotchas (worker running, accounts/passwords, queue state, feature installed), **which +regen/cache commands were already run in Step 5** (so the QA env matches — e.g. transfer regen, schema +install, twig path-cache reset), the ticket key/description (whichever the run has), and ask it to +return the full report. + +- QA must verify the **user-visible symptom is gone E2E** (not just a server-layer workaround). +- **Accepted** → continue. **Rejected** (a real failure found) → treat like a Step 8 failure: return + to Step 4 within the 3-attempt budget; if exhausted, STOP and report. + +## Step 10 — Final verification before commit + +The last gate before commit/push — prove the fix holds in the **running application** with fresh +evidence, not just green tests. + +- Re-run the affected functional tests once more for a clean final signal (subagent, as in Step 6). +- **Perform an end-to-end final verification in the running app (subagent).** Spawn the + `spryker-verifier` agent (via the `Agent` tool with `subagent_type="spryker-verifier"`), or a + subagent using **`Skill(spryker-runtime)`** if you prefer a raw runtime drive. Hand it the changed + files, the repro scenarios, the acceptance expectation (the exact user-visible symptom that must be + gone), and any env gotchas. It drives the affected surface (Yves / Back Office / Glue as relevant), + writes its evidence to `$BUGFIX_DIR/final-verify-attempt.log`, and returns only a **PASS / FAIL / + BLOCKED** verdict with the decisive evidence (status code, the now-correct rendered value, DB/queue + state as applicable). +- **PASS** → proceed to Step 11. **FAIL** → treat like a Step 8 failure: increment `attempt` and + return to Step 4 within the 3-attempt budget; if exhausted, STOP and report. **BLOCKED** (env/data + issue, not the fix) → resolve the blocker and re-verify without consuming an attempt; if it can't be + resolved, report it honestly rather than claiming a pass. + +## Step 11 — Commit, push, Draft PR, watch loop + +**Gated by mode, the Step 0 PR preference, _and_ `PR_CHANNEL`.** Always **commit on the branch first** — +the commit happens in every mode and every channel. What happens *after* the commit depends on (a) +whether the user asked for a PR at all (Step 0 answer 7) and (b) which channel is available. + +**First honor the Step 0 PR preference:** +- **"No PR — commit only"** → commit on the branch and stop (optionally push if a remote exists and the + user allowed pushing). Skip PR creation and the watch loop regardless of channel. Report the branch. +- **"Create a PR"** with a **forced channel** (the user named `gh` / a specific forge MCP / `git push + only`) → use that channel if the probe confirms it's usable; if the forced channel isn't available, + do **not** silently fall back — report the mismatch (Autonomous: log it and downgrade to the best + available channel with a CRITICAL DECISION). +- **"Create a PR — auto"** (default) → use the best channel the probe found, per the table below. + +| `PR_CHANNEL` | Collaborative | Autonomous | +|---|---|---| +| `gh` / `mcp` | commit → STOP for review; push/PR after user OK | commit → push → Draft PR → checks-watch loop | +| `git-only` | commit → STOP for review; on OK, push branch + hand over a `gh pr create`/MR line | commit → push branch → **skip PR + watch**; hand over the create-PR line | +| `none` | commit → STOP; report the local branch + how to push | commit **locally** → **skip push, PR, watch**; report the local branch + how to add a remote and push | + +The commit message references the ticket if there is one (e.g. `fix(): ()`); +with no ticket, omit the trailing key. + +- **Collaborative mode (or if the user asked for personal review before push):** commit on the branch, + then **STOP and present** the result — summary, root cause, diff, test/QA/verification status, and the + `PR_CHANNEL` — and ask the user to review and confirm before any push/PR. Do not push without that + confirmation. After the user confirms, do the push/PR that the preference + channel allow (Draft PR on + `gh`/`mcp`, branch push + handover on `git-only`, nothing to push on `none`). + +- **`PR_CHANNEL=none` (any mode) — local-only terminal.** Commit on the branch and **stop there**: do + **not** attempt `git push`, PR creation, or the watch loop (there is no reachable remote). Report the + committed branch name, the diff summary, and a copy-paste snippet for the user to publish it themselves + (`git remote add origin ` if needed, then `git push -u origin ` and `gh pr create + --draft …` / the forge's MR command). This is a **successful** terminal state, not a failure — mark + Step 11's task completed and go to Step 12. + +- **`PR_CHANNEL=git-only` (Autonomous) — push, then hand off the PR.** Commit and `git push -u origin + `, then **skip** PR creation and the checks-watch loop (no PR API available). Report the + pushed branch and a ready-to-run create-PR line (`gh pr create --draft --title + ": " --body "…"`, no labels — or the equivalent MR command for the + forge) so the user (or a machine with the tool) can open the PR in one step. Mark Step 11 completed and + go to Step 12. + +- **`PR_CHANNEL=mcp` (Autonomous) — same flow as `gh`, through the MCP.** `git push -u origin `, + then create the Draft PR/MR via the forge MCP's create-PR tool (title `: + `, **no labels**, body as below), and run the watch loop polling checks via the MCP's + list-checks/status tool instead of `gh pr checks`. Everything else (red-check loopback, budget, + handoff file) is identical to the `gh` path. + +- **Autonomous mode with `PR_CHANNEL=gh` (and user did not request pre-push review):** go all the way + (`mcp` follows the identical shape, swapping `gh` calls for the forge MCP's create-PR / list-checks tools): + 1. Commit with a message referencing the ticket if there is one (e.g. + `fix(): ()`); with no ticket, omit the trailing key. + 2. `git push -u origin `. + 3. Open a **Draft PR** via `gh pr create --draft` (or the MCP create-PR tool for `mcp`). Requirements: + - **`--draft`** (always a draft). + - **Title starts with the ticket number in UPPER CASE**, e.g. `AB-1234: `. If + there is no ticket, prefix with `NO-TICKET:`. + - **Do not set any labels.** Labeling is left to the repo's own automation / reviewers; the skill + never passes `--label` (and never creates labels). *(Exception: only if the user explicitly asked + for specific labels as a Step 0 extra expectation — then add exactly those, nothing more.)* + - **Body** summarizing: bug, root cause (file:line), fix, tests added, QA verdict, and the ticket + link if there is one. + - Record the PR URL. + + ```bash + gh pr create --draft \ + --title ": " \ + --body "" + ``` + 4. **Arm a watch loop** that polls the PR's **remote checks** roughly every 15 minutes. + *(Steps 3–4 run only when `PR_CHANNEL` is `gh` or `mcp`; for `git-only` you already handed the + create-PR line to the user and there is no PR to watch.)* + + **Write a one-page handoff file first** — `$BUGFIX_DIR/watch-state.md` containing the PR + URL, branch, current `attempt`, the State Object, and the paths to the decision/step logs. The + watch loop should re-hydrate from *this file*, not from the full bugfix transcript. This matters + because the run may already be long: re-reading the whole conversation on every 15-min wake is + exactly what pushes context to the danger zone. The handoff file is the loop's working memory. + + Pick the mechanism by how the run is driven: + - Interactive session: `ScheduleWakeup` with `delaySeconds: 900`. Re-pass a **minimal** `/loop` + input that says "resume the bugfix watch loop from `$BUGFIX_DIR/watch-state.md`" — + not the original full bug context. + - Unattended/headless: a **Cron** (`CronCreate`) — load its schema via `ToolSearch` first; point + it at the same handoff file. + - **Fallback if no scheduler is available:** do NOT claim a loop is running. Report the PR URL + and explicitly hand monitoring back to the user. + On each wake, **poll with the cheapest call** — `gh pr checks ` (a compact status table), or for + `mcp` the forge's list-checks/status tool — **not** `gh run view`. The poll itself must add almost + nothing to context. + - **All green** → report success and **remove the loop** (cancel the Cron / stop rescheduling). + - **Any red** in the changed code → **do not pull the raw CI logs into the orchestrator.** Spawn a + subagent that runs `gh run view --log-failed`, writes it to `$BUGFIX_DIR/ci-remote-attempt.log`, + and returns only the failing job(s) + the one-line root error each. Increment `attempt`, update + the State Object, and **return to Step 4**. The fix must traverse the **full gate chain again** + (4→5→6→7→8→9→10) before you re-push — do not jump 4→push and skip the gates. Then push and keep + watching. If it's flaky/infra (not your code), note it and re-poll without consuming an attempt. + - **Budget exhausted (`attempt > 3`) on a red check:** STOP. Cancel the scheduled wakeup/Cron, + **leave the PR in Draft**, post a PR comment summarizing the remaining failures and the diff + state, and report to the user. Never force-push a known-broken state and never mark the PR + ready. + - 15 min fits remote CI that takes minutes; don't poll faster (wasted cache). + +> Pushing and opening a PR are outward-facing actions. Autonomous mode was explicitly authorized by +> the user's mode choice in Step 0; without that authorization, default to the Collaborative stop. + +## Step 12 — Final report (ALWAYS LAST, both modes, every terminal state) + +This step runs at the very end **no matter how the run terminated** — full success, a Collaborative +stop-before-push, or a hard stop (`attempt > 3`). Append a `STEP 12 | END` line to the step log, then +present a concise report to the user containing, in this order: + +1. **Outcome** — one line, reflecting the `PR_CHANNEL` that was available: shipped (PR URL) / pushed — + PR handoff line provided (`git-only`) / committed locally — not pushed, no remote (`none`) / awaiting + your review (committed, not pushed) / stopped after N attempts (why). When the channel downgraded the + ending (`git-only` or `none`), say so explicitly and include the exact command(s) the user needs to + finish publishing. +2. **The bug & root cause** — symptom + root-cause `file:line` (clickable absolute-path format). +3. **CRITICAL DECISIONS** — bullet list pulled from the decision log: each fork you resolved on your + own, the choice, and the one-line reason. This is the headline section — surface it prominently so + the user can sanity-check the calls you made without being asked mid-run. +4. **OPEN QUESTIONS / RISKS / FURTHER BUGS** — scope concerns, additional suspected bugs, BC/data + risks, stale-env caveats, anything a human should confirm. Be honest; an empty list is fine only if + genuinely empty. +5. **Gate status** — tests / static / review / QA / final verification / remote CI, each green/red with + the real result (never report green what wasn't). +6. **Extra expectations** — if the user set any Step 0 delta from the standard scope, confirm how each + was handled (done / partially / not done + why). Omit this line only if there were none. +7. **Log file path** — print the absolute path to the step log (`$BUGFIX_LOG`) and the decision log so + the user can open them. This MUST be the last line of the report. + +Keep it skimmable — it is the artifact the user reads instead of having been interrupted during the run. + +## Stage → skill quick map + +| Step | Skill / tool | +|------|--------------| +| 0 Mode, context, logger, PR-channel probe, env-reset decision | `AskUserQuestion`, **optional** ticket pull (JIRA via Atlassian MCP `jira_get_issue`/`jira_get_issue_images`, or `gh issue view`, or paste) — skipped when there's no ticket, `PR_CHANNEL` probe (`command -v gh` + `gh auth status` + remote check), `docker/sdk reset` (if chosen) | +| 3 Reproduce | `Skill(spryker-runtime)` + `Skill(spryker-docs-research)` — **subagents**, return summaries to `repro-notes.md` | +| 4 Root cause | `Skill(ai-runtime-debugging)` + `Skill(spryker-runtime)` — **subagent**, returns path+values | +| 5 Fix | edits (+ parallel subagents), regen/cache commands | +| 6 Tests | `Skill(codecept-functional)` — **subagent**, returns pass/fail + failing names | +| 7 Static | `Skill(static-validation)` — **subagent**, returns clean/violations | +| 8 Review (gate) | `Skill(code-review)` — returns ≤5 blocker/major; loop to 4 (max 3) | +| 9 QA | `Skill(spryker-qa-coverage)` (isolated subagent) | +| 10 Final verification (gate) | `Skill(codecept-functional)` re-run + `spryker-verifier` agent / `Skill(spryker-runtime)` — **subagent**, returns PASS/FAIL + evidence | +| 11 Ship + remote CI watch (PR-channel gated) | always `git commit`; then by `PR_CHANNEL`: `gh` → `git push` + `gh pr create --draft` (no labels) + `ScheduleWakeup`/`CronCreate` watch loop · `git-only` → `git push` + handover `gh pr create` line · `none` → local commit only, report publish commands | +| 12 Final report | decision log + step log → user-facing report ending with the log file path |