[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6H46RQQVQ6ZDH2RVQSCW9M4":3,"skill-same:01M6H46RQQVQ6ZDH2RVQSCW9M4":214},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":56,"repositoryHtmlUrl":57,"repositoryHomepageUrl":6,"ownerHtmlUrl":58,"ownerBlogUrl":59,"ownerDescription":60,"updatedAt":61,"install":62,"resources":75},"01M6H46RQQVQ6ZDH2RVQSCW9M4","dx-simple",null,"Apply a small AEM change (a11y label, color, spacing, copy, CSS class, icon, focus trap) via authoring (JCR) or code (PR) paths, read from the ADO story. Use for 'simple change', 'small tweak', '@kai-simple'.","easingthemes\u002Fdx-aem-flow","easingthemes","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F2750284?v=4","01M6H3XYGJY8QCX9ES36P2WF4H","Dragan Filipović","01M6H3XYMN1XVSGKN7N79SW3RH",7,"MIT","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"dx","simple","dx simple","authoring","paths","spacing","edit","write","read","jcr","mvn","focus","change","work","code","Bash","URL","hooks","search","css","2026-11-10 10:12:30",false,"---\nname: dx-simple\ndescription: \"Apply a small AEM change (a11y label, color, spacing, copy, CSS class, icon, focus trap) via authoring (JCR) or code (PR) paths, read from the ADO story. Use for 'simple change', 'small tweak', '@kai-simple'.\"\nargument-hint: \"\u003CADO Work Item ID or full URL>\"\nallowed-tools: [\"read\", \"edit\", \"search\", \"write\", \"agent\"]\nmodel: sonnet\nhooks:\n  PreToolUse:\n    - matcher: Bash\n      command: ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fhooks\u002Fblock-mvn-deploy.sh\n      timeout: 5\n---\n\nYou are the coordinator for `\u002Fdx-simple` — a tight, pipeline-grade skill that applies a small AEM change (authoring or code) under a strict confidence model. You do NOT implement anything in main context except:\n\n1. Parse the ADO story's `simple` block (Phase 1).\n2. Dispatch parallel research subagents (Phase 2).\n3. Apply authoring writes via AEM MCP (Phase 3a) — this is in main context because audit log + rollback record require it.\n4. Read the work-plan to decide control flow.\n\nAll other work — codebase reading, visual verify, compile, diff review — runs in `context: fork` subagents so the main context stays under ~30 turns.\n\n## Argument\n\n`$ARGUMENTS` is `\u003Cid-or-url> [free text]` — the ADO work item ID (numeric, e.g.\n`9999999`, or a full URL) **optionally followed by a free-text instruction**, just\nlike `\u002Fdx-simple 9999999 make the heading blue` typed in a local session. Extract the\nnumeric `TICKET_ID` (leading digits, or from the URL); anything after it is\n`INLINE_INPUT`. Always pass `TICKET_ID` (not the raw `$ARGUMENTS`) to the scripts and\nMCP calls below. If no id, ask once; if still none in pipeline mode, exit non-zero.\n\n## Pre-flight (HARD GATE — runs before anything else)\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fpreflight.sh\n```\n\nIf it exits non-zero, **STOP**. Post the stderr to ADO as a comment:\n\n```\nmcp__ado__wit_work_item_comment_write with action=\"add\", text=\u003Cthe preflight error text>\n```\n\nExit non-zero. **No other action.**\n\n## Recovery config (read once, after pre-flight)\n\nResumable recovery is config-driven — never hardcode the token or the\nattempt cap. Read both from `.ai\u002Fconfig.yaml` (defaults applied if unset):\n\n```bash\n# Trigger keyword (default @kai-simple). The bot NEVER emits the literal token —\n# it writes the keyword bare and instructs the human to prefix it with @.\nTRIGGER_TOKEN=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.recovery.trigger-token'); TRIGGER_TOKEN=${TRIGGER_TOKEN:-@kai-simple}\nKEYWORD=${TRIGGER_TOKEN#@}                 # bare form used in bot-emitted text\nMAX_ATTEMPTS=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.recovery.max-attempts'); MAX_ATTEMPTS=${MAX_ATTEMPTS:-3}\n```\n\n> The documented ADO Service Hook filter string MUST match `trigger-token` — this\n> is the single source of truth (see the pipeline + website docs). The comment\n> hook fires only on comments containing the token; the bot's own comments never\n> contain the literal token, so they cannot self-trigger.\n\n## User input (the text after the trigger) — like local CLI args\n\nThe free text a human writes after the trigger is the **primary instruction**,\nexactly as if they ran `\u002Fdx-simple \u003Cid> \u003Cinstruction>` locally. The token only fires\nthe pipeline; the words after it are the prompt. Resolve `USER_INPUT` once, here:\n\n1. **Local session** — if `INLINE_INPUT` (text after the id in `$ARGUMENTS`) is\n   non-empty → `USER_INPUT=\"$INLINE_INPUT\"`, `TRIGGER_COMMENT_ID=\"\"`.\n2. **Pipeline run** (fired by an ADO comment) — else fetch comments and select the\n   triggering one:\n   ```\n   mcp__ado__wit_work_item with action=\"list_comments\", workItemId=$TICKET_ID\n   ```\n   Pick the comment that is (a) authored by a **non-bot** identity, (b) contains\n   `$TRIGGER_TOKEN`, (c) has the **highest** ADO comment id (ids are monotonic).\n   Strip the token → `USER_INPUT`; record its id as `TRIGGER_COMMENT_ID`.\n3. `USER_INPUT` may be **empty** (a bare `@\u003Ckeyword>` with no words) — then only the\n   story body drives the change (today's behavior).\n\n`USER_INPUT` is **authoritative**: where it conflicts with the story body, it wins.\nPhase 1 folds it into the change request on a fresh run (step 2d); Phase 0 uses it\n(with `TRIGGER_COMMENT_ID` vs `COMMENT_CURSOR`) to reopen a completed ticket.\n\n## Spec directory + branch (established by Phase 0)\n\n`SPEC_DIR` and the per-ticket `BRANCH` are NOT computed here — **Phase 0**\n(`resume-check.sh`) establishes them, because a fresh pipeline container can't\nrecompute run 1's slug and must discover the ticket's existing branch + committed\nstate. Phase 0 emits `SPEC_DIR=…` \u002F `BRANCH=…`; capture those values.\n\n**Fresh-run state init (only when Phase 0 dispatch is `fresh`):**\n\nProgress follows `.ai\u002Frules\u002Ftask-progress.md` — `simple-progress.md` is the source of truth, written at every phase\u002Fgate transition. Task tools, if this session has them, are an optional mirror; never block a transition on one.\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \"$SPEC_DIR\" \"Preflight\" \"done\"\necho '{\"gates\":{}}' > \"$SPEC_DIR\u002Fconfidence.json\"\n# resume-state.json — the durable status the next run dispatches on.\nsed \"s\u002F{TICKET}\u002F$TICKET_ID\u002F\" \\\n    \"${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fresume-state.json.tmpl\" \\\n    > \"$SPEC_DIR\u002Fresume-state.json\"\n# authoring-diff.json is created lazily by Phase 3a when the first write happens\n```\nOn a **resume** dispatch, these files already exist on the branch (committed by a\nprior run) — do NOT re-initialize them; read them.\n\nTouch the orchestration flag (in case a future orchestrator wraps this skill):\n```bash\nmkdir -p .ai\u002Frun-context\ntouch .ai\u002Frun-context\u002Forchestrating.flag\n```\n\n**State files written during the run** (the skill creates these as it goes):\n\n| File | Created in | Purpose |\n|---|---|---|\n| `resume-state.json` | Phase 0 (fresh) \u002F read on resume | Durable recovery status — `status`, `last-completed-phase`, `blocked-at-phase`, `blocker{}`, `answer-attempts`, `comment-cursor`, `run-history[]`. Phase 0 dispatches on it. Committed every checkpoint. |\n| `raw-story.md` | Phase 1 | ADO story dump |\n| `simple-block.yaml` | Phase 1 (after parse) | Parsed DoR block |\n| `before.png` | Phase 1 (after G1) | Pre-change Chrome screenshot |\n| `locator-bbox.json` | Phase 1 (after G1) | `{x, y, w, h}` of locator's bounding box |\n| `confidence.json` | initialized in pre-flight; appended per gate | `{\"gates\":{\"G1\":{\"score\":1,\"status\":\"pass\"}, ...}}` |\n| `dialog-map.json` | Phase 2 (from dialog-inspector subagent) | Field name → type + values |\n| `file-list.json` | Phase 2 (from file-resolver subagent) | Source file paths |\n| `work-plan.json` | Phase 2 (classify-work.sh) | Authoring + code arrays |\n| `authoring-diff.json` | Phase 3a (per write) | Before\u002Fafter for rollback |\n| `compile.log` | Phase 4 (per retry, overwritten) | Build output, tail in report |\n| `after.png` | Phase 5 | Post-change Chrome screenshot |\n| `visual-diff.json` | Phase 5 (visual-diff.sh) | Overall + region pixel scores |\n| `diff-review.md` | Phase 5.5 (from dx-pr-reviewer) | Reviewer findings |\n| `simple-progress.md` | initialized in pre-flight; updated per phase | Phase status table |\n| `followup.md` | Reopen path (done + fresh comment) | Verbatim post-trigger `USER_INPUT` that reopened a completed ticket; checkpointed for audit\u002Freport |\n| `report.md` | Phase 7 (rendered from template) | Final human-readable report |\n\n## Confidence model (gates G1, G3–G9)\n\nThis skill enforces 8 confidence gates. **Any gate failure → ABORT path: classify the blocker, conditionally roll back authoring (terminal only — C3), persist state, ADO comment, verdict: fail (pipeline exits non-zero).** Track scores in `$SPEC_DIR\u002Fconfidence.json`; the final report quotes them. A blocker on a **resumed** run feeds the re-ask loop (see ABORT path) instead of a flat abort.\n\n| Gate | Phase | Threshold |\n|---|---|---|\n| G1 Locator match | 1 | exactly 1 DOM match for element |\n| G3 Classification | 2 | high or medium |\n| G4 Per-file edit confidence | 3b | ≥ 0.85 |\n| G5 Visual non-target identical | 5 | ≥ 99% |\n| G6 Visual target changed | 5 | ≥ 5% |\n| G7 Review blockers @ ≥80% conf | 5.5 | 0 |\n| G8 Cost | global | ≤ $2 (configurable) |\n| G9 Time | global | ≤ 12 min |\n\n> **Note:** G2 (resource-type allowlist) was removed — the skill now runs on\n> any component. The G* numbering is preserved for backwards compatibility\n> with existing reports.\n\n## Flow\n\n```dot\ndigraph dx_simple {\n    \"Preflight\" [shape=box];\n    \"Phase 0: Resume (branch + dispatch)\" [shape=box];\n    \"Read resume comment + apply answer\" [shape=box];\n    \"Replay code edits from work-plan.json\" [shape=box];\n    \"Phase 1: Fetch + extract change details\" [shape=box];\n    \"Phase 0.5: Repo identity guard\" [shape=box];\n    \"G1: locator match exactly 1?\" [shape=diamond];\n    \"Phase 2: Classify (parallel subagents)\" [shape=box];\n    \"G3: classification high or medium?\" [shape=diamond];\n    \"Phase 3a: Apply authoring writes\" [shape=box];\n    \"Phase 3b: Apply code edits\" [shape=box];\n    \"G4: per-file edit confidence ≥85%?\" [shape=diamond];\n    \"Scope-check ok?\" [shape=diamond];\n    \"Phase 4: Compile (≤3 retries)\" [shape=box];\n    \"Compile passed?\" [shape=diamond];\n    \"Phase 5: Visual verify\" [shape=box];\n    \"G5+G6: visual gates pass?\" [shape=diamond];\n    \"One re-edit retry left?\" [shape=diamond];\n    \"Re-edit with screenshot context\" [shape=box];\n    \"Phase 5.5: Diff review (dx-pr-reviewer)\" [shape=box];\n    \"G7: zero blockers ≥80% conf?\" [shape=diamond];\n    \"Phase 6: Activate + Commit + PR\" [shape=box];\n    \"Phase 7: Write report + ADO comment\" [shape=doublecircle];\n\n    \u002F\u002F --- ABORT, expanded (classify → conditional rollback → persist → comment) ---\n    \"ABORT: classify blocker\" [shape=box];\n    \"Terminal (hard\u002Ffinal)?\" [shape=diamond];\n    \"Rollback authoring (terminal only)\" [shape=box];\n    \"Keep authoring (recoverable)\" [shape=box];\n    \"Write report.md (Recovery section)\" [shape=box];\n    \"save-state.sh: commit + push state\" [shape=box];\n    \"git checkout -- . (discard source edits)\" [shape=box];\n    \"Post classified ADO comment + exit\" [shape=doublecircle];\n\n    \u002F\u002F --- re-ask loop on a failed RESUMED gate ---\n    \"Resumed gate still fails?\" [shape=diamond];\n    \"answer-attempts \u003C max?\" [shape=diamond];\n    \"Post sharper question (blocked-needs-input)\" [shape=box];\n    \"Downgrade to blocked-hard → DevAgent\" [shape=box];\n\n    \u002F\u002F --- terminal resume exits ---\n    \"Re-post hard note + exit\" [shape=doublecircle];\n    \"No-op: already done (PR exists)\" [shape=doublecircle];\n\n    \u002F\u002F --- reopen completed work on a fresh follow-up comment ---\n    \"Fresh follow-up trigger comment?\" [shape=diamond];\n    \"Reopen: apply follow-up delta (reuse artifacts)\" [shape=box];\n\n    \"Preflight\" -> \"Phase 0: Resume (branch + dispatch)\";\n\n    \u002F\u002F Phase 0 dispatch out-edges\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Phase 1: Fetch + extract change details\" [label=\"fresh\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Replay code edits from work-plan.json\" [label=\"in-progress (past 3b)\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Phase 1: Fetch + extract change details\" [label=\"in-progress (at\u002Fbefore 3b) → last+1\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Read resume comment + apply answer\" [label=\"blocked-needs-input\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Re-post hard note + exit\" [label=\"blocked-hard\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Fresh follow-up trigger comment?\" [label=\"done\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Read resume comment + apply answer\" [label=\"ambiguous-branch (asks which branch)\"];\n\n    \u002F\u002F done is a no-op UNLESS a new follow-up comment (id > comment-cursor) reopens it\n    \"Fresh follow-up trigger comment?\" -> \"Reopen: apply follow-up delta (reuse artifacts)\" [label=\"yes (non-bot, id > cursor)\"];\n    \"Fresh follow-up trigger comment?\" -> \"No-op: already done (PR exists)\" [label=\"no\"];\n    \u002F\u002F reuse committed story\u002Fplan\u002Fwork-plan; re-enter the edit phase (resume-forward, last=Phase 2)\n    \"Reopen: apply follow-up delta (reuse artifacts)\" -> \"Phase 3a: Apply authoring writes\" [label=\"reuse artifacts, edit-phase delta\"];\n    \"Reopen: apply follow-up delta (reuse artifacts)\" -> \"Phase 1: Fetch + extract change details\" [label=\"escape: different element\u002Fpage\"];\n\n    \"Read resume comment + apply answer\" -> \"Replay code edits from work-plan.json\" [label=\"re-enter past 3b\"];\n    \"Read resume comment + apply answer\" -> \"Phase 1: Fetch + extract change details\" [label=\"re-enter ≤ 3b\"];\n    \"Replay code edits from work-plan.json\" -> \"Phase 4: Compile (≤3 retries)\" [label=\"re-enter target phase\"];\n\n    \"Phase 1: Fetch + extract change details\" -> \"Phase 0.5: Repo identity guard\";\n    \"Phase 0.5: Repo identity guard\" -> \"ABORT: classify blocker\" [label=\"wrong target\"];\n    \"Phase 0.5: Repo identity guard\" -> \"G1: locator match exactly 1?\" [label=\"proceed\"];\n    \"G1: locator match exactly 1?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G1: locator match exactly 1?\" -> \"Phase 2: Classify (parallel subagents)\" [label=\"yes\"];\n    \"Phase 2: Classify (parallel subagents)\" -> \"G3: classification high or medium?\";\n    \"G3: classification high or medium?\" -> \"ABORT: classify blocker\" [label=\"no — abort\"];\n    \"G3: classification high or medium?\" -> \"Phase 3a: Apply authoring writes\" [label=\"yes — if authoring items\"];\n    \"G3: classification high or medium?\" -> \"Phase 3b: Apply code edits\" [label=\"yes — if code items\"];\n    \"Phase 3a: Apply authoring writes\" -> \"Phase 5: Visual verify\" [label=\"if no code items\"];\n    \"Phase 3a: Apply authoring writes\" -> \"G4: per-file edit confidence ≥85%?\" [label=\"if code items also queued\"];\n    \"Phase 3b: Apply code edits\" -> \"G4: per-file edit confidence ≥85%?\";\n    \"G4: per-file edit confidence ≥85%?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G4: per-file edit confidence ≥85%?\" -> \"Scope-check ok?\" [label=\"yes\"];\n    \"Scope-check ok?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"Scope-check ok?\" -> \"Phase 4: Compile (≤3 retries)\" [label=\"yes\"];\n    \"Phase 4: Compile (≤3 retries)\" -> \"Compile passed?\";\n    \"Compile passed?\" -> \"Phase 5: Visual verify\" [label=\"yes\"];\n    \"Compile passed?\" -> \"ABORT: classify blocker\" [label=\"no (hard — after 3 retries)\"];\n    \"Phase 5: Visual verify\" -> \"G5+G6: visual gates pass?\";\n    \"G5+G6: visual gates pass?\" -> \"Phase 5.5: Diff review (dx-pr-reviewer)\" [label=\"yes\"];\n    \"G5+G6: visual gates pass?\" -> \"One re-edit retry left?\" [label=\"no\"];\n    \"One re-edit retry left?\" -> \"Re-edit with screenshot context\" [label=\"yes\"];\n    \"One re-edit retry left?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"Re-edit with screenshot context\" -> \"Phase 4: Compile (≤3 retries)\";\n    \"Phase 5.5: Diff review (dx-pr-reviewer)\" -> \"G7: zero blockers ≥80% conf?\";\n    \"G7: zero blockers ≥80% conf?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G7: zero blockers ≥80% conf?\" -> \"Phase 6: Activate + Commit + PR\" [label=\"yes\"];\n    \"Phase 6: Activate + Commit + PR\" -> \"Phase 7: Write report + ADO comment\";\n\n    \u002F\u002F A gate that fails on a RESUMED run feeds the re-ask loop instead of a flat abort.\n    \"ABORT: classify blocker\" -> \"Resumed gate still fails?\";\n    \"Resumed gate still fails?\" -> \"answer-attempts \u003C max?\" [label=\"yes (this was a needs-input resume)\"];\n    \"Resumed gate still fails?\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"no (first failure \u002F non-input)\"];\n    \"answer-attempts \u003C max?\" -> \"Post sharper question (blocked-needs-input)\" [label=\"yes\"];\n    \"answer-attempts \u003C max?\" -> \"Downgrade to blocked-hard → DevAgent\" [label=\"no (cap reached)\"];\n    \"Post sharper question (blocked-needs-input)\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"recoverable\"];\n    \"Downgrade to blocked-hard → DevAgent\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"terminal\"];\n\n    \"Terminal (hard\u002Ffinal)?\" -> \"Rollback authoring (terminal only)\" [label=\"yes\"];\n    \"Terminal (hard\u002Ffinal)?\" -> \"Keep authoring (recoverable)\" [label=\"no\"];\n    \"Rollback authoring (terminal only)\" -> \"Write report.md (Recovery section)\";\n    \"Keep authoring (recoverable)\" -> \"Write report.md (Recovery section)\";\n    \"Write report.md (Recovery section)\" -> \"save-state.sh: commit + push state\";\n    \"save-state.sh: commit + push state\" -> \"git checkout -- . (discard source edits)\";\n    \"git checkout -- . (discard source edits)\" -> \"Post classified ADO comment + exit\";\n}\n```\n\n## Node Details\n\n### Phase 0: Resume (branch + dispatch)\n\nRuns immediately after pre-flight, **before Phase 1**. It establishes the\nper-ticket branch (created early — decision #5), reverse-maps it to the committed\nspec dir, reads `resume-state.json`, and decides how to proceed. The per-ticket\nbranch is the durable state store, so a crashed\u002Fblocked prior run is recoverable.\n\n```bash\n# Capture the key=value lines, then read each value (values like \"Phase 2\" contain\n# spaces, so DON'T `eval` the block — read fields explicitly).\nRC=$(bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fresume-check.sh \"$TICKET_ID\")\nval() { sed -n \"s\u002F^$1=\u002F\u002Fp\" \u003C\u003C\u003C\"$RC\" | head -1; }\nDISPATCH=$(val DISPATCH); BRANCH=$(val BRANCH); SPEC_DIR=$(val SPEC_DIR)\nSTATUS=$(val STATUS); RE_ENTER_PHASE=$(val RE_ENTER_PHASE)\nREPLAY_CODE_EDITS=$(val REPLAY_CODE_EDITS); ANSWER_ATTEMPTS=$(val ANSWER_ATTEMPTS)\nLAST_COMPLETED_PHASE=$(val LAST_COMPLETED_PHASE); BLOCKED_AT_PHASE=$(val BLOCKED_AT_PHASE)\n```\n\n`resume-check.sh` owns anchored discovery (`feature\u002F\u003Cid>-*` \u002F `bugfix\u002F\u003Cid>-*` —\nnever a bare `*\u003Cid>*` substring, so `123` can't match `1234`), the 0 \u002F 1 \u002F >1\nbranch decision, and the reverse branch→spec-dir mapping. It delegates **only the\ncreate-fresh path** to `..\u002F..\u002Fshared\u002Fensure-feature-branch.sh` (M2 — the shared helper's\ncontract is unchanged for its other consumers). It is **read-only** w.r.t.\n`resume-state.json` (never bumps `answer-attempts` — a crash must not burn the\nneeds-input cap, M4).\n\nDispatch on `DISPATCH`:\n\n| DISPATCH | meaning | action |\n|---|---|---|\n| `fresh` | no branch \u002F no committed state | run fresh-run state init (above), proceed to **Phase 1**. |\n| `resume-forward` | prior run **crashed** (`in-progress`) | if `REPLAY_CODE_EDITS=true`, run **Replay code edits** first, then re-enter `RE_ENTER_PHASE`. Reuse committed discovery artifacts. Does NOT consume an answer-attempt. |\n| `resume-blocked-input` | recoverable block awaiting a human answer | run **Read resume comment + apply answer**, then (replay if past 3b) re-enter `RE_ENTER_PHASE`. |\n| `resume-blocked-hard` | unrecoverable, or `answer-attempts` cap reached | **Re-post hard note + exit** — re-post the \"needs manual fix \u002F re-tag DevAgent\" note; exit non-zero. |\n| `done` | Phase 7 already succeeded | Run **Reopen completed work**: if a fresh follow-up trigger comment exists (non-bot, contains the token, id > `COMMENT_CURSOR`) treat it as a continuation on the same branch. **Otherwise no-op** — comment \"already completed in PR; reply `@\u003Ckeyword> \u003Cchange>` to request a follow-up, or open a new ticket\"; exit 0. |\n| `ambiguous-branch` | >1 `feature\u002F\u003Cid>-*` match | post a `needs-user-input` comment asking **which branch** to resume (loop-safe format); set `blocked-needs-input`; exit. Do NOT pick one. |\n\n> **git-rules.md exception.** `git-rules.md` says \"never reuse old feature\u002Fbugfix\n> branches for new work.\" `\u002Fdx-simple` resume is a deliberate, scoped exception —\n> it reuses the branch for the **same ticket, same work, continued**, only when the\n> anchored match is **unique** (ambiguous → blocker, not reuse).\n\n### Reopen completed work (follow-up comment)\n\nOnly on `DISPATCH=done`. A completed run is normally a no-op, but a reviewer often\nwants a small refinement (\"reuse the existing query-param util\", \"tighten the\nspacing\") rather than a whole new ticket. Treat the **fresh** trigger comment as a\nnew prompt **in a new session that already has all the prior artifacts** — reuse\nthem and do only the delta (decision: skip the steps that are already done — story\nfetch, locate, classify — exactly as a developer reopening the spec dir locally\nwould). Refuse to reopen on a stale or already-consumed comment.\n\n1. **Is there a fresh follow-up?** Reopen only if `USER_INPUT` is non-empty **and**\n   `TRIGGER_COMMENT_ID > COMMENT_CURSOR` (numeric; treat an empty cursor as `0`).\n   - Otherwise → genuine **no-op** (stray re-trigger \u002F service-hook replay \u002F bare\n     token \u002F already-consumed comment). Post once: \"Already completed in PR. Reply\n     `@\u003Ckeyword> \u003Cchange>` to request a follow-up, or open a new ticket.\" Exit 0.\n2. **Reuse the committed artifacts** on the branch — `raw-story.md`,\n   `simple-block.yaml`, `dialog-map.json`, `file-list.json`, `work-plan.json`,\n   `confidence.json`. Do **not** re-fetch the story, re-run G1 locate, or re-classify\n   (Phase 2): they are already committed. This is the \"new local session on an\n   existing spec dir\" behavior the user expects.\n3. **Apply the follow-up as a delta.** Read `work-plan.json` + `USER_INPUT` and update\n   the work-plan to satisfy the follow-up (e.g. change a `replacement` to call the\n   existing util; add or adjust a `code[]` item). Append `USER_INPUT` to\n   `simple-block.yaml`'s `what` (report context) and record it verbatim in\n   `$SPEC_DIR\u002Ffollowup.md`.\n   - **Escape hatch:** if the follow-up targets a *different element or page* than the\n     committed locator, the reused discovery no longer holds — re-enter **Phase 1**\n     instead (full re-run with `USER_INPUT` as the primary request).\n4. **Seed resume-forward + advance the cursor atomically**, then checkpoint.\n   Advancing the cursor **before** re-entry is the loop guard (L2): once this run\n   completes to `done`, the same comment is at\u002Fbelow the cursor and cannot reopen\n   again — a further follow-up needs a brand-new comment.\n   ```bash\n   jq --arg c \"$TRIGGER_COMMENT_ID\" \\\n      '.status=\"in-progress\" | .[\"comment-cursor\"]=$c\n       | .[\"last-completed-phase\"]=\"Phase 2\"\n       | .[\"run-history\"] += [{\"event\":\"reopen\",\"comment\":$c}]' \\\n      \"$SPEC_DIR\u002Fresume-state.json\" > \"$SPEC_DIR\u002F.rs.tmp\" \\\n      && mv \"$SPEC_DIR\u002F.rs.tmp\" \"$SPEC_DIR\u002Fresume-state.json\"\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 2\"\n   ```\n   (Exit 3 = `BRANCH-ADVANCED` → a concurrent resume is in flight; stop.)\n5. **Re-enter the edit phase** exactly as a `resume-forward` would: with\n   `last-completed-phase = Phase 2`, the next actionable phase is **Phase 3a**\n   (authoring) \u002F **Phase 3b** (code). Apply the updated work-plan, then flow forward —\n   compile → visual verify → diff review → **Phase 6 (update-mode amends the existing\n   PR, M5)** → Phase 7. Discovery and classification are skipped; only the delta is\n   implemented and verified. This path does **not** touch `answer-attempts` (it is a\n   new request, not a re-ask of a blocked gate).\n\n### Read resume comment + apply answer\n\nOnly on `resume-blocked-input` (or `ambiguous-branch` resolution). Select the\nhuman's answer and apply it before re-entering the blocked phase.\n\n1. Fetch comments (`mcp__ado__wit_work_item`, `action: \"list_comments\"`). Select the comment\n   that is: (a) authored by a **non-bot** identity, (b) contains the trigger token\n   (`$TRIGGER_TOKEN`), (c) has the **highest ADO comment id** (ids are monotonic —\n   order by id, not timestamp), and (d) id > the stored `comment-cursor`. If none\n   qualifies → cheap exit (stray re-trigger; nothing to do).\n2. Strip the token; the remainder is `continue-input`. Apply it to\n   `blocked-at-phase` (e.g. set `simple-block.yaml` `element` for a G1\n   ambiguity; add a file\u002Fline\u002Fanchor hint for G4; pick the branch for\n   `ambiguous-branch`).\n3. **Commit the updated `comment-cursor` atomically with \u002F before re-entering the\n   phase** (L2) — via `jq` into `resume-state.json` then `save-state.sh` —\n   otherwise a crash after applying the answer reprocesses the same comment.\n4. Re-enter `RE_ENTER_PHASE` (replaying code edits first if past 3b). **If the gate\n   still fails:** `answer-attempts++`, post a *sharper* question. **When quoting the\n   prior answer, never echo the literal token** — render it as `@\u003Ckeyword>` (H4).\n   Set `blocked-needs-input` again; update `comment-cursor`. After the\n   `$MAX_ATTEMPTS`-th failed cycle, downgrade to `blocked-hard` and recommend\n   DevAgent (M4 — prevents infinite human↔bot ping-pong). Crashes\u002Ftransient\n   resumes never count against this cap.\n\n### Replay code edits from work-plan.json\n\nCode edits are **never committed** (decision #3), so on any resume the working tree\nhas none of them. Before re-entering **any phase after Phase 3b** (C2), deterministically\nre-apply them from `work-plan.json` onto the clean committed base:\n\nFor each item in `work-plan.code[]` (which carries filled `match-context` \u002F\n`replacement` from run 1): `Edit(file=\u003Cpath>, old_string=\u003Cmatch-context>,\nnew_string=\u003Creplacement>)`. Same base + unique `match-context` → identical diff.\n\n**Authoring is the opposite** — JCR writes persist in AEM, so resume **skips**\nPhase 3a and reconciles via the committed `authoring-diff.json` (the Phase 3a\nbefore-check treats `actual == target-after` as already-applied → skip). The two\nhalves of the resume contract are asymmetric: **code replays, authoring reconciles.**\n\nRe-entering Phase 3b itself does NOT need a separate replay — 3b re-fills and\nre-applies the work-plan as part of its normal flow.\n\n### Phase 1: Fetch + extract change details\n\n1. Fetch the work item:\n   ```\n   mcp__ado__wit_work_item with action=\"get\", id=$TICKET_ID\n   ```\n   Write the description + comments to `$SPEC_DIR\u002Fraw-story.md` with provenance\n   frontmatter. Record the resolved `USER_INPUT` (the post-trigger instruction, see\n   *User input* above) at the top of `raw-story.md` under a `## User input` heading so\n   the change request and the report both preserve exactly what the human asked for.\n\n2. Parse the `simple` block (recommended but not required):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fparse-simple-block.sh \\\n        \"$SPEC_DIR\u002Fraw-story.md\" \"$SPEC_DIR\u002Fsimple-block.yaml\"\n   ```\n   - Exit 0 (parsed) → continue to step 2a (fill in inferred fields if any are missing).\n   - Exit 2 (no block) → fall through to step 2b (LLM extraction from story prose).\n   - Exit 3 (malformed: missing `page-url`, duplicate field, or unclosed fence) → post the specific error from stderr as an ADO comment. STOP.\n\n2a. **Fill in missing fields** (block was present but partial):\n    Read `simple-block.yaml`. For each missing optional field, infer it\n    from the surrounding story text and overwrite `simple-block.yaml`:\n    - `element` missing → infer from the story's element references.\n      Examples: \"Language Selector button\" → `dialog-title=\"Language Selector\"`;\n      \"Get started heading\" → `heading-text=\"Get started\"`. If the story\n      gives a JCR path, use `jcr-path=...`.\n    - `what` missing → use the story's natural-language description\n      of the change in plain English (this is the input the model uses to\n      decide whether the change is content, code, or both).\n    The classifier (Phase 2) uses these as hints; nothing here gates execution.\n\n2b. **LLM extraction** (no block found): read `raw-story.md` and extract\n    the same fields directly from the story description, acceptance\n    criteria, and comments:\n    - `page-url` (REQUIRED): find the QA author URL in the story. If\n      multiple, prefer the one nearest the change description; if still\n      ambiguous, post the template at\n      `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fsimple-block.md.tmpl`\n      as an ADO comment and STOP.\n    - `element`: derive from the story's element references\n      (visible text, dialog title, JCR path).\n    - `what`: take the most specific change description in the story.\n      Keep it as natural language — the model decides downstream whether each\n      part is a content edit or a code edit.\n    Write the inferred values to `$SPEC_DIR\u002Fsimple-block.yaml` with a\n    `# inferred: true` comment at the top so downstream phases can flag\n    lower confidence in the report.\n\n2d. **Fold in `USER_INPUT`** (the text the human typed after the trigger — see\n    *User input* above). If non-empty, it is the **primary** change request: merge it\n    into `what` in `simple-block.yaml`, overriding the story body on conflict (a human\n    typing `@\u003Ckeyword> make it blue` means *make it blue*). The classifier (Phase 2)\n    and edit phases then act on `USER_INPUT` plus the story context. If `USER_INPUT`\n    is empty (bare trigger), only the story body drives the change (today's behavior).\n\n3. Semantic validation (Safeguard #1):\n   - Read `simple-block.yaml`; extract `page-url`, `element`, `what`.\n   - **QA Basic Auth (first navigation only):** follow the `qa-basic-auth` rule. Resolve credentials in priority order:\n     1. Env vars: `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS`\n     2. `.ai\u002Fconfig.yaml` keys `aem.qa-basic-auth.username` \u002F `aem.qa-basic-auth.password`\n     If credentials are found AND `page-url` is not a localhost URL, embed them in the URL for this navigation: `https:\u002F\u002F\u003Cuser>:\u003Cpass>@\u003Chost>\u002Fpath`. Subsequent navigations (Phase 5) use the clean URL — the session cookie persists. Never log or echo the URL with embedded credentials.\n     If no credentials are found and the URL is non-localhost: post ADO comment explaining that `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS` env vars are not set (see `qa-basic-auth` rule) and exit non-zero.\n   - Navigate Chrome to the page URL (credential-embedded for first QA visit):\n     ```\n     mcp__plugin_dx-aem_playwright__browser_navigate with url=\u003Cpage-url>\n     ```\n   - Take a snapshot:\n     ```\n     mcp__plugin_dx-aem_playwright__browser_snapshot\n     ```\n   - Match the locator. The locator is one of:\n     - `heading-text=\"...\"` \u002F `button-text=\"...\"` \u002F `link-text=\"...\"` → look for elements with that exact visible text\n     - `jcr-path=...` → use AEM MCP `getNodeContent` to confirm node exists; locator match count = 1 if node exists, 0 if not\n     - `dialog-title=\"...\"` → ambiguous on its own; require AEM MCP `scanPageComponents` to resolve to a unique component instance\n     - free-form description (from LLM extraction) → use the Chrome snapshot + scanPageComponents to find the single best match; if more than one element matches, ABORT G1 with the ambiguous-locator message\n\n4. **Take BEFORE screenshot** (Safeguard #6): capture, then move into the spec dir as `before.png`:\n   ```\n   mcp__plugin_dx-aem_playwright__browser_take_screenshot with filename=before.png\n   cp .ai\u002Fplaywright\u002Fscreenshots\u002Fbefore.png $SPEC_DIR\u002Fbefore.png\n   ```\n   (`browser_take_screenshot` takes a `filename` basename and saves under the MCP `--output-dir`, `.ai\u002Fplaywright\u002Fscreenshots\u002F` — it does not honor an arbitrary path.)\n   Also record the locator's bounding box (from snapshot) to `$SPEC_DIR\u002Flocator-bbox.json` for the visual-diff step.\n\n5. **G1 — Locator match (HARD GATE):**\n   - If 0 matches: post ADO comment \"Component not found on page. Verified by: \u003Csnapshot-id>. Check page-url + the element you wanted to change.\" Exit non-zero. NO file reads beyond this point.\n   - If >1 matches: post \"Ambiguous locator: \u003CN> matches. Add a `jcr-path=...` to the `simple` block, or describe the element more specifically.\" Exit non-zero.\n   - If exactly 1: continue. Record `G1 = 1 match` in `confidence.json`.\n\n6. Update progress + **checkpoint** (commit + push recovery state to the branch):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \\\n        \"$SPEC_DIR\" \"Phase 1: Extract + locate\" \"done\" \"G1 passed\"\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 1\"\n   ```\n   If `save-state.sh` exits 3 (`BRANCH-ADVANCED`), STOP — a concurrent resume is\n   in flight (H2); do not force-push.\n\n### Phase 0.5: Repo identity guard\n\nRuns once, right after `parse-simple-block.sh` writes `simple-block.yaml`, before G1.\nConfirms this repo is a legitimate target for the ticket and records authoring ownership.\n\n```bash\nGUARD_OUT=$(bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frepo-guard.sh \\\n  \"$SPEC_DIR\u002Fsimple-block.yaml\" \".ai\u002Fconfig.yaml\") || GUARD_RC=$?\neval \"$GUARD_OUT\"   # sets DECISION, REASON?, AUTHORING_OWNER?\n```\n\n- If `DECISION=abort` (exit 3): set blocker class **`wrong-target`** and follow the\n  **ABORT path** — this is a **hard\u002Fterminal** blocker (no re-ask loop: the human must\n  re-trigger in the correct repo). Post `REASON` verbatim as the classified ADO comment.\n  No JCR writes, no code edits. Verdict: fail.\n- If `DECISION=proceed`: continue to G1. Persist `AUTHORING_OWNER` into\n  `resume-state.json` (`authoring-owner` key) — Phase 3a reads it.\n- Single-repo \u002F unconfigured projects: `project.platform` is unset and the block has no\n  `platform`, so the guard proceeds with `AUTHORING_OWNER=true` (today's behavior).\n\n### Phase 2: Classify (parallel subagents)\n\nDispatch THREE subagents in a single message (parallel):\n\n1. **Page resolver** (reuse `aem-page-finder`, model: haiku):\n   ```\n   Agent(subagent_type: aem-page-finder, prompt: \"Resolve page-url \u003Cpage-url> to its JCR content path. Confirm the page exists on QA. Return JSON: {\\\"jcr-path\\\": \\\"\u002Fcontent\u002F...\\\", \\\"language-master\\\": \\\"\u003Cpath or null>\\\"}.\")\n   ```\n\n2. **Dialog inspector** (reuse `aem-inspector`, model: sonnet):\n   ```\n   Agent(subagent_type: aem-inspector, prompt: \"For component at JCR path \u003Cjcr-path>, read sling:resourceType, then fetch \u002Fapps\u002F\u003Cresource-type>\u002Fcq:dialog. Walk the dialog fields and return JSON:\n   {\n     \\\"jcr-path\\\": \\\"\u003Cjcr>\\\",\n     \\\"resource-type\\\": \\\"\u003Crtype>\\\",\n     \\\"fields\\\": { \\\"\u003Cname>\\\": \\\"\u003Ctype>\\\", ... },\n     \\\"values\\\": { \\\"\u003Cname>\\\": \\\"\u003Ccurrent value>\\\", ... }\n   }\n   Return only the JSON, no prose.\")\n   ```\n\n3. **File resolver** (reuse `aem-file-resolver`, model: haiku):\n   ```\n   Agent(subagent_type: aem-file-resolver, prompt: \"Resolve source files for resource type \u003Cresource-type>. Return JSON: {\\\"files\\\": [{\\\"path\\\": \\\"...\\\"}, ...]}.\")\n   ```\n\nSynthesize the three returns into `$SPEC_DIR\u002Fdialog-map.json` and `$SPEC_DIR\u002Ffile-list.json`. Then build a **baseline** work-plan with the deterministic heuristic:\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fclassify-work.sh \\\n     \"$SPEC_DIR\u002Fsimple-block.yaml\" \\\n     \"$SPEC_DIR\u002Fdialog-map.json\" \\\n     \"$SPEC_DIR\u002Ffile-list.json\" \\\n     \"$SPEC_DIR\u002Fwork-plan.json\"\n```\n\n`classify-work.sh` only suggests obvious matches based on keywords in\n`what` against dialog field names. **You — the orchestrator — are\nresponsible for the final classification.** Read `work-plan.json`, read\n`what` and `raw-story.md`, and decide what to do:\n\n- The change is a content edit that matches a dialog field → keep \u002F add\n  an item in `.authoring[]`.\n- The change involves hardcoded strings, JS behavior (focus traps,\n  keyboard handlers, click handlers), HTL templates, or CSS classes →\n  keep \u002F add items in `.code[]`.\n- The change requires **both** (e.g. \"rename the heading **and** trap\n  focus in the modal\") → populate **both** arrays. Phase 3a and Phase 3b\n  will both run.\n- The locator pointed at a component whose dialog has no matching field,\n  but the what clearly describes a content change → that string\n  is hardcoded; route to `.code[]` and let the file-resolver candidates\n  in there carry it.\n\nIf the deterministic baseline missed items, append them to `work-plan.json`\nvia `Write`. If the baseline included items the model rejects on inspection,\nremove them. Once `work-plan.json` reflects the model's final plan, set\n`confidence.\"G3-classification\"` to one of:\n\n- `high` — at least one path (authoring or code) has an unambiguous target\n- `medium` — at least one path has a target, but with ambiguity worth noting\n  in the report\n- `low` — neither path has a workable target → abort with G3 fail\n\nUpdate progress + **checkpoint**:\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \\\n     \"$SPEC_DIR\" \"Phase 2: Classify\" \"done\" \"G3=\u003Clevel>, authoring=\u003Cn>, code=\u003Cn>\"\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 2\"\n```\n\n### Phase 3a: Apply authoring writes\n\n**Runs whenever `work-plan.authoring[]` is non-empty.** If `work-plan.code[]`\nis also non-empty, Phase 3b runs immediately after Phase 3a (sequential, not\nparallel — Phase 3a must record the JCR before-state in main context for\nrollback before Phase 3b touches anything).\n\n**Authoring-owner gate (multi-repo).** First read `authoring-owner` from\n`resume-state.json` (set by Phase 0.5; defaults to `true`). If it is `false`,\n**skip all authoring items** — another repo owns authoring for this ticket on this\nAEM instance. Record in `report.md`:\n`Authoring skipped — owned by the AEM-author-capable repo for this platform.` Then:\n- if `work-plan.code[]` is also non-empty → fall through to Phase 3b (the edge\n  `Phase 3a -> G4 [if code items also queued]`);\n- if there are no code items → this repo has nothing to apply → go to Phase 5 via the\n  existing `[if no code items]` edge. With nothing written, visual verify\u002Freport simply\n  confirms no change in this repo → write report, verdict: **success (no-op)**.\n\n**Read-before-write idempotency (always — even when this repo IS the owner).** Before\neach JCR write, `getNodeContent` the target property; if its current value already\nequals the target (`item.after`), **skip the write** and log `unchanged` in\n`authoring-diff.json`. This keeps recovery re-runs and any parallel\nsame-instance run side-effect-free. (The per-item drift check below applies the same\nread; the gate here is the general rule it implements.)\n\nFor each item in `work-plan.authoring[]`:\n\n1. Read current value:\n   ```\n   mcp__plugin_dx-aem_AEM__getNodeContent with path=\u003Cjcr-path>\n   ```\n   Confirm `item.before` matches the actual current value. **Idempotent re-entry\n   (H1) \u002F read-before-write:** if `actual == item.after`, the target value is\n   already in place — either a prior run that crashed mid-Phase-3a applied it, or a\n   parallel same-instance run did → **skip the write**, log `unchanged` in\n   `authoring-diff.json`, and record it as applied. Only abort\n   `\"value drifted: expected \u003Cbefore>, got \u003Cactual>\"` when `actual` is neither\n   `before` nor `after`.\n\n2. Apply the write:\n   ```\n   mcp__plugin_dx-aem_AEM__updateComponent with path=\u003Cjcr-path>, properties={\u003Cproperty>: \u003Cafter>}\n   ```\n\n3. Append to `$SPEC_DIR\u002Fauthoring-diff.json`:\n   ```json\n   { \"writes\": [{ \"jcr-path\": \"...\", \"property\": \"...\", \"before\": \"...\", \"after\": \"...\", \"applied\": true, \"applied-at\": \"\u003CISO>\" }] }\n   ```\n\n4. Log to audit:\n   ```bash\n   AUDIT_LOG_PREFIX=simple source .ai\u002Flib\u002Faudit.sh\n   _audit_append '{\"ts\":\"\u003CISO>\",\"action\":\"updateComponent\",\"path\":\"\u003Cjcr>\",\"property\":\"\u003Cprop>\",\"ticket\":\"\u003Cid>\"}'\n   ```\n\n5. **Checkpoint per JCR write (H1).** AEM writes are external side-effects not\n   transactional with the git commit. Commit `authoring-diff.json` **after each\n   write**, not once at end of phase — otherwise a crash after write 2 of 3 leaves\n   the writes live on AEM but the diff uncommitted, and resume's before-check\n   (now idempotent, step 1) would have no record of them:\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 3a\"\n   ```\n\nIf ANY write fails partway through:\n- Mark all subsequent items as `applied: false`\n- Classify the blocker and follow the **ABORT path** (authoring is kept on a\n  recoverable block, rolled back only on terminal failure — C3)\n- Post ADO comment, exit non-zero\n\nUpdate progress.\n\n### Phase 3b: Apply code edits\n\n**Runs whenever `work-plan.code[]` is non-empty** — independently of whether\nPhase 3a ran. The code path now covers any change that touches source\nfiles: hardcoded strings, HTL templates, CSS classes, focus traps and\nother JS behavior, click\u002Fkeyboard handlers, etc. The model chose this\npath in Phase 2 based on the what text and dialog map.\n\n`classify-work.sh` populates `.code[]` with placeholder items (confidence=0, empty contexts) for each candidate file. The agent MUST fill them in (or remove them) and rewrite `work-plan.json` to disk BEFORE the G4 gate runs. For changes that add new code (focus traps, new event listeners) rather than replace an existing line, set `match-context` to the anchor line you're inserting **after**, and `replacement` to the anchor line followed by the new code.\n\nFor each item in `work-plan.code[]`:\n\n1. Read the file:\n   ```\n   Read(file=\u003Cpath>)\n   ```\n\n2. Identify the exact match line. Fill in `match-line`, `match-context`, `replacement`, `rationale`, and `confidence` (LLM self-rated, 0.0–1.0):\n   - High confidence (≥0.85): one unambiguous match in the file for the locator's context. Rationale example: \"only `color: red` occurrence inside .hero-cta selector\".\n   - Below 0.85: multiple matches with unclear precedence, or no clear anchor. Do NOT guess.\n\n3. **Persist the updated work-plan to disk** (the deterministic G4 check reads this file):\n   ```\n   Write(file=$SPEC_DIR\u002Fwork-plan.json, content=\u003Cwork-plan JSON with all .code[] items populated>)\n   ```\n\n4. **G4 — Per-file edit confidence (HARD GATE):**\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fcheck-g4.sh \"$SPEC_DIR\u002Fwork-plan.json\" 0.85\n   ```\n   Exit 4 → rollback authoring + exit non-zero. Record `G4` status in `confidence.json`.\n\n5. Apply the Edits (only after G4 passes):\n   ```\n   Edit(file=\u003Cpath>, old_string=\u003Cmatch-context>, new_string=\u003Creplacement>)\n   ```\n\n6. After all code items applied: run scope-check:\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fscope-check.sh \"$SPEC_DIR\u002Fwork-plan.json\"\n   ```\n   Exit 4 → ABORT (scope exceeded is `hard`).\n\nUpdate progress + **checkpoint** (commits the filled `work-plan.json` so a later\nresume can replay the code edits — C2; the edits themselves stay uncommitted):\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 3b\"\n```\n\n### Phase 4: Compile (≤3 retries)\n\n**Only if Phase 3b ran (code edits were applied).** Skip if authoring-only.\n\nRead build command from `.ai\u002Fconfig.yaml`:\n- Prefer `dx-simple.build-compile-fast` if set\n- Else `build.compile-fast`\n- Else `build.compile`\n- Else abort: \"no compile command configured\"\n\n```bash\nCOMPILE=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.build-compile-fast' || \\\n          bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'build.compile-fast' || \\\n          bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'build.compile')\n```\n\nRun the compile, capturing output to `$SPEC_DIR\u002Fcompile.log`. Loop up to 3 attempts:\n\n```bash\nfor ATTEMPT in 1 2 3; do\n  $COMPILE > \"$SPEC_DIR\u002Fcompile.log\" 2>&1 && break\n  if [[ \"$ATTEMPT\" -eq 3 ]]; then\n    # Rollback + exit\n    bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frollback-authoring.sh \"$SPEC_DIR\u002Fauthoring-diff.json\"\n    exit 1\n  fi\n  # Read the last 50 lines of compile.log, identify the error, edit the file, retry.\ndone\n```\n\nThe agent's job between attempts: read `compile.log` tail, identify which file\u002Fline caused the error, Edit it, then re-loop.\n\nUpdate progress with attempt count.\n\n### Phase 5: Visual verify\n\nThe classifier produces EITHER authoring OR code items, never both — `classify-work.sh` routes by G3 confidence. Visual-verify rules differ by path:\n\n**Authoring path (Phase 3a ran):** mandatory if `activate: true` in the simple block (writes are visible on author immediately). Skip only when `activate: false` AND no rendered preview is expected.\n\n**Code path (Phase 3b ran):** **visual verify is skipped in pipeline mode** because `mvn compile` (the only build allowed by `block-mvn-deploy.sh`) does NOT deploy to AEM. The QA author serves the old bundle, so `before.png` and `after.png` would be identical and G6 (target changed ≥5%) would always fail. The change is verified post-merge by the normal CI deploy + QA cycle.\n\n  - To request visual verify for a code-path run anyway, add `force-visual-verify: true` to the simple block. The pipeline will navigate and screenshot, but G5\u002FG6 are downgraded to WARN (recorded in report.md, do not abort). Useful only when an external job pre-deploys the branch to QA before SimpleAgent runs.\n  - Local dev (non-pipeline) is free to run visual verify when the developer has deployed locally — the same `force-visual-verify: true` flag opts in.\n\n**When skipped:** code-only non-visual changes (aria-label edits, copy in HTL, etc.) — recorded as \"verify-deferred-to-qa\" in the report.\n\nIf running:\n\n1. Navigate Chrome to the page (note: same QA author URL, NOT publish, so authoring writes are visible):\n   ```\n   mcp__plugin_dx-aem_playwright__browser_navigate with url=\u003Cpage-url>\n   ```\n\n2. Take AFTER screenshot:\n   ```\n   mcp__plugin_dx-aem_playwright__browser_take_screenshot with filename=after.png\n   cp .ai\u002Fplaywright\u002Fscreenshots\u002Fafter.png $SPEC_DIR\u002Fafter.png\n   ```\n\n3. Run visual-diff:\n   ```bash\n   BBOX=$(cat $SPEC_DIR\u002Flocator-bbox.json | jq -r '\"\\(.x),\\(.y),\\(.w),\\(.h)\"')\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fvisual-diff.sh \\\n        \"$SPEC_DIR\u002Fbefore.png\" \"$SPEC_DIR\u002Fafter.png\" \"$BBOX\" \\\n        > \"$SPEC_DIR\u002Fvisual-diff.json\"\n   ```\n\n4. **G5 — Visual non-target identical:** `non-target-identical ≥ 99%`\n5. **G6 — Visual target changed:** `region-diff ≥ 5%`\n\nEither gate fail:\n- Authoring path → rollback authoring + exit non-zero.\n- Code path with `force-visual-verify: true` AND re-edit budget remaining → invoke ONE re-edit cycle: open the staged file, show the agent the screenshot diff, ask \"this change didn't render as expected — re-edit.\" Then loop back to Phase 4 (recompile).\n- Code path WITHOUT `force-visual-verify` → unreachable (Phase 5 skipped — see selection rules above).\n\n### Phase 5.5: Diff review (only if code path)\n\nIf Phase 3b ran (code edits applied), invoke `dx-pr-reviewer` for a single-pass review on the working tree:\n\n```\nAgent(subagent_type: dx-pr-reviewer, prompt: \"Review the staged diff (git diff HEAD) for the following changes. Context: this is a small ≤50-line tweak via \u002Fdx-simple on component \u003Cresource-type> — change described as \u003Cwhat>. Focus on:\n- Does the change actually accomplish what the requirement says?\n- Any obvious bug (wrong variable, missing semicolon, off-by-one)?\n- Any accessibility regression (e.g., removing existing aria text)?\nReturn findings as JSON: [{ \\\"severity\\\": \\\"blocker|suggestion\\\", \\\"confidence\\\": 0.0-1.0, \\\"file\\\": \\\"...\\\", \\\"line\\\": N, \\\"comment\\\": \\\"...\\\" }]\n\")\n```\n\nWrite the review to `$SPEC_DIR\u002Fdiff-review.md`.\n\n**G7 — Zero blockers at ≥80% confidence:**\n- Any `severity: blocker AND confidence ≥ 0.8` → rollback authoring + exit non-zero, no PR.\n- Suggestions are recorded in PR description as \"Reviewer notes\" but do not block.\n\n### Phase 6: Activate + Commit + PR\n\n1. **Activate authoring** (only if work-plan has authoring items AND `simple-block.yaml` `activate: true` AND all gates passed):\n   For each unique JCR path in `authoring-diff.json`:\n   ```\n   mcp__plugin_dx-aem_AEM__activatePage with path=\u003Cjcr-path>\n   ```\n   Log each activation to audit. Update `authoring-diff.json` to set `activated: true` per item.\n\n2. **Commit + PR** (only if Phase 3b ran):\n\n   **Idempotent (M5).** PR creation is irreversible, so:\n   - **Checkpoint `last-completed = Phase 6` BEFORE the create** so a crash during\n     create resumes back into Phase 6 (not past it):\n     ```bash\n     bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 6\"\n     ```\n   - **Detect an existing open PR** for this branch first\n     (`mcp__ado__repo_pull_request` with `action: \"list\"`,\n     `sourceRefName: refs\u002Fheads\u002F\u003Cbranch>`, `status: Active`). If one exists, switch\n     `\u002Fdx-pr-commit` to **update-mode** (push new commits + update the description)\n     instead of creating a second PR — a crash between PR-create and the post-create\n     checkpoint must not duplicate the PR.\n\n   Delegate to `\u002Fdx-pr-commit`:\n   ```\n   Skill(\u002Fdx-pr-commit)\n   ```\n   The conventional commit message: `feat(\u003Cscope>): \u003Cwhat summary>`. Include in the PR body:\n   - Link to `report.md`\n   - Reviewer notes from Phase 5.5 (if any)\n   - Authoring changes summary (if any, with \"activated: yes\" flag)\n   - Before\u002Fafter screenshots\n\n### Phase 7: Write report + ADO comment\n\nRender `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Freport.md.tmpl` into `$SPEC_DIR\u002Freport.md`, substituting all `{PLACEHOLDER}` tokens from `confidence.json`, `work-plan.json`, `authoring-diff.json`. Also substitute `{PLUGIN_ROOT}` with the literal value of `${CLAUDE_PLUGIN_ROOT}` (so the printed revert command is copy-pasteable) and `{SPEC_DIR}` with the absolute spec directory.\n\nPost a truncated version to ADO:\n```\nmcp__ado__wit_work_item_comment_write with action=\"add\", workItemId=\u003Cticket>, text=\u003Ctruncated report>\n```\n\nAfter the comment posts successfully, record it so the pipeline's fallback step\ndoes not post a duplicate failure note:\n```bash\nmkdir -p .ai\u002Frun-context && touch .ai\u002Frun-context\u002Fado-comment-posted.flag\n```\n\nUpdate final progress row to `done`, mark recovery state terminal, and checkpoint\nso a stray re-trigger short-circuits to the `done` no-op:\n```bash\njq '.status = \"done\"' \"$SPEC_DIR\u002Fresume-state.json\" > \"$SPEC_DIR\u002F.rs.tmp\" && mv \"$SPEC_DIR\u002F.rs.tmp\" \"$SPEC_DIR\u002Fresume-state.json\"\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 7\"\n```\n\nClean up (leave `ado-comment-posted.flag` in place — the pipeline inspects it):\n```bash\nrm -f .ai\u002Frun-context\u002Forchestrating.flag\n```\n\n## Return contract\n\nWhen this skill is invoked from an orchestrator (future composition), emit at the end:\n\n```markdown\n## Return\nverdict: pass | warn | fail\nsummary: \u003Cone sentence>\nartifacts:\n  - $SPEC_DIR\u002Freport.md\n  - $SPEC_DIR\u002Fwork-plan.json\n  - $SPEC_DIR\u002Fauthoring-diff.json\nnext_action: \u003Chuman-readable next step or \"none\">\n```\n\nIf running standalone (no `orchestrating.flag`), also print a human summary above the Return block.\n\n## ABORT path (any gate failure)\n\n**Order matters.** Classify first, then roll back authoring *only if terminal*\n(C3), then persist state **before** discarding the source edits (today's\n`git checkout -- .` wipes the uncommitted report + state — that is the bug this\nfixes).\n\n1. **Classify the blocker** → set `blocker.class` (`needs-user-input` | `transient`\n   | `hard`), `blocker.recoverable`, `blocker.reason`, `blocker.needs`,\n   `blocked-at-phase`, and `status` in `resume-state.json` (via `jq`). Use the\n   **Blocker taxonomy** below.\n\n   **Re-ask loop (if this is a resumed `needs-input` run whose gate failed again):**\n   if `answer-attempts \u003C $MAX_ATTEMPTS`, bump `answer-attempts`, set\n   `status=blocked-needs-input`, and post a *sharper* question (loop-safe — see\n   comment format; never echo the literal token). Otherwise downgrade to\n   `status=blocked-hard` and recommend DevAgent (M4).\n\n2. **Conditional authoring rollback (C3).** Roll back JCR writes **only when the\n   blocker is terminal** (`hard`, or final fail):\n   ```bash\n   # terminal only:\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frollback-authoring.sh \"$SPEC_DIR\u002Fauthoring-diff.json\"\n   ```\n   On a **recoverable** block (`needs-input` \u002F `transient`), **leave the authoring\n   writes in place** — they are recorded in the committed `authoring-diff.json`, and\n   the resume run skips Phase 3a. (Rolling back here would lose the authoring half of\n   a combined authoring+code change, since resume re-enters at Phase 3b, past 3a.)\n\n3. **Write the failure `report.md`** (same template; `verified: false`, failing\n   gate row marked `fail`, **Recovery section populated** — never the literal token,\n   use `@\u003Ckeyword>`).\n\n4. **Persist state — commit + push** (the report + resume-state + authoring-diff):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"\u003Cblocked-phase>\"\n   ```\n   (Exit 3 = `BRANCH-ADVANCED` → a concurrent resume is in flight; stop.)\n\n5. **Then** discard the now-irrelevant *source* edits — spec files are committed, so\n   they survive; code edits are replayed from `work-plan.json` on resume (C2):\n   ```bash\n   git checkout -- .\n   ```\n\n6. **Post the classified ADO comment** (loop-safe format below) + record the flag so\n   the pipeline fallback does not double-post:\n   ```bash\n   mkdir -p .ai\u002Frun-context && touch .ai\u002Frun-context\u002Fado-comment-posted.flag\n   ```\n\n7. Clean up orchestrating flag (leave `ado-comment-posted.flag` for the pipeline).\n\n8. Exit non-zero.\n\n### Blocker taxonomy\n\n| class | examples | recovery |\n|-------|----------|----------|\n| `needs-user-input` | G1 ambiguous\u002Fno match, missing `page-url`, missing QA Basic Auth credentials, G3 low classification, authoring value drifted, `ambiguous-branch` (M1) | human replies `@\u003Ckeyword> \u003Canswer>` → resume at blocked phase |\n| `transient` | MAX_TURNS \u002F step timeout \u002F MCP unreachable \u002F network blip — **infra only** | re-trigger (any `@\u003Ckeyword>` reply, or re-run) → resume forward from `last-completed-phase` (replaying code edits if past 3b) |\n| `hard` | scope-check exceeded (>5 files \u002F >50 lines \u002F >10 writes), G7 real review blocker, **any compile failure surviving Phase 4's in-run 3× retry (M3)**, `answer-attempts` cap reached | not recoverable here → recommend re-tag `KAI-DEV-AUTOMATION` (DevAgent) |\n| `wrong-target` | Phase 0.5 repo identity guard aborted — ticket's platform\u002Fbrand\u002Fscope does not match this repo's identity | terminal — not recoverable here → human must re-trigger in the correct repo |\n\n> **Compile is `hard`, not `transient` (M3).** Phase 4 already retries compile 3×\n> against a *deterministic* edit; a cross-run re-trigger replays the identical edit,\n> so it won't compile any better. `transient` is reserved for infra (MCP\u002Fnetwork)\n> and MAX_TURNS, which a genuinely fresh run can fix.\n\n### Resume jump table\n\n| blocked at | needs from human | re-enters at | reuses \u002F notes |\n|------------|------------------|--------------|--------|\n| G1 (0 or >1 locator matches) | jcr-path \u002F exact visible text | Phase 1 (re-locate) | — |\n| `ambiguous-branch` (M1) | which branch to resume | Phase 0 discovery | — |\n| G3 (classification low) | content vs code | Phase 2 | persisted `dialog-map.json`, `file-list.json` |\n| G4 (edit confidence \u003C0.85) | file \u002F line \u002F anchor | Phase 3b (re-fill + re-apply) | `work-plan.json`. **If 3a authoring was applied: NOT rolled back (C3); reconcile via committed `authoring-diff.json`.** |\n| compile fails (survives 3× retry) | `hard` → DevAgent (M3) | — | — |\n| G7 (review blocker) | guidance, or `hard` | Phase 3b re-edit (**replay code edits first — C2**) | `work-plan.json` |\n| crashed after 3b (`in-progress`) | nothing (transient) | `last-completed-phase` + 1 | committed artifacts **+ replay code edits (C2)** |\n| crashed at\u002Fbefore 3b (`in-progress`) | nothing (transient) | `last-completed-phase` + 1 | committed artifacts (no code edits exist yet) |\n| crashed after Phase 6 PR-create (`in-progress`) | nothing (transient) | Phase 6 | **update-mode, not a second create (M5)** |\n\n### Blocker comment format (human + machine, loop-safe)\n\nThe literal trigger token **never appears** in the bot's own text (loop safety) —\nthe bot says the keyword **bare** and instructs the user to prefix it with `@`.\nThis holds **even when quoting the human's prior answer** in a re-ask (H4).\n\n```\n⚠️ \u002Fdx-simple paused on #\u003Cid> — needs your input\n\n**What failed:** G1 locator — \"Language Selector\" matched 3 elements on the page.\n**Recoverable:** yes (answer-attempt 1 of \u003Cmax-attempts>)\n**What I need:** reply on this ticket, beginning your comment with the kai-simple\n   keyword (prefixed with @ so it re-triggers me), followed by a precise target, e.g.\n   @\u003Ckeyword> jcr-path=\u002Fcontent\u002Fsite\u002F...\u002Flanguagenavigation\n\n\u003C!-- dx-simple:blocked phase=G1 reason=ambiguous-locator attempt=1 comment-cursor=\u003Cid> -->\n```\n\nThe HTML marker lets the next run parse `phase` \u002F `attempt` \u002F `comment-cursor`\ndeterministically without re-reading its own prose. Render the keyword in examples\nas `@\u003Ckeyword>` (placeholder) so the example line cannot self-trigger the hook.\n\n## Examples\n\n1. `\u002Fdx-simple 9999999` — runs the full pipeline against ticket 9999999. Pauses only on errors.\n\n2. `\u002Fdx-simple https:\u002F\u002Fdev.azure.com\u002Forg\u002Fproj\u002F_workitems\u002Fedit\u002F9999999` — same, but parses the ID from the URL.\n\n## Troubleshooting\n\n- **\"Component not found on page\"** — Locator did not match anything in Chrome snapshot. Check `page-url` (loads on QA?), `element` (matches visible element?), QA content sync (component exists on QA?). If the QA site requires HTTP Basic Auth, ensure `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS` env vars are set (see `qa-basic-auth` rule) — the skill embeds them in the first navigation URL automatically.\n\n- **\"Ambiguous locator\"** — Multiple DOM matches. Use `jcr-path=...` form for unambiguous targeting, or describe the element more specifically (e.g. add the surrounding section name).\n\n- **\"Edit confidence too low (G4)\"** — Agent couldn't identify which file\u002Fline to edit unambiguously. Either the source isn't deterministic from the locator, or the change is too ambiguous for \u002Fdx-simple. Re-tag as `KAI-DEV-AUTOMATION` to use full DevAgent.\n\n- **\"Visual verify failed (G5 or G6)\"** — Either the change caused side-effects (G5 fail: page-wide change) or no visible change rendered (G6 fail: edit was a no-op). Check the visual-diff.json + the before\u002Fafter screenshots in spec dir.\n\n## Rules\n\n- **Coordinator only** — read state, dispatch subagents, never directly read codebase or write code outside the well-defined phases.\n- **Strict gate enforcement** — never proceed past a failing gate; never \"best effort\" guess.\n- **Audit every AEM write** — `audit.sh` with `AUDIT_LOG_PREFIX=simple`.\n- **Don't echo file content** — reference paths instead.\n","You are the coordinator for `\u002Fdx-simple` — a tight, pipeline-grade skill that applies a small AEM change (authoring or code) under a strict confidence model. You do NOT implement anything in main context except:\n\n1. Parse the ADO story's `simple` block (Phase 1).\n2. Dispatch parallel research subagents (Phase 2).\n3. Apply authoring writes via AEM MCP (Phase 3a) — this is in main context because audit log + rollback record require it.\n4. Read the work-plan to decide control flow.\n\nAll other work — codebase reading, visual verify, compile, diff review — runs in `context: fork` subagents so the main context stays under ~30 turns.\n\n## Argument\n\n`$ARGUMENTS` is `\u003Cid-or-url> [free text]` — the ADO work item ID (numeric, e.g.\n`9999999`, or a full URL) **optionally followed by a free-text instruction**, just\nlike `\u002Fdx-simple 9999999 make the heading blue` typed in a local session. Extract the\nnumeric `TICKET_ID` (leading digits, or from the URL); anything after it is\n`INLINE_INPUT`. Always pass `TICKET_ID` (not the raw `$ARGUMENTS`) to the scripts and\nMCP calls below. If no id, ask once; if still none in pipeline mode, exit non-zero.\n\n## Pre-flight (HARD GATE — runs before anything else)\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fpreflight.sh\n```\n\nIf it exits non-zero, **STOP**. Post the stderr to ADO as a comment:\n\n```\nmcp__ado__wit_work_item_comment_write with action=\"add\", text=\u003Cthe preflight error text>\n```\n\nExit non-zero. **No other action.**\n\n## Recovery config (read once, after pre-flight)\n\nResumable recovery is config-driven — never hardcode the token or the\nattempt cap. Read both from `.ai\u002Fconfig.yaml` (defaults applied if unset):\n\n```bash\n# Trigger keyword (default @kai-simple). The bot NEVER emits the literal token —\n# it writes the keyword bare and instructs the human to prefix it with @.\nTRIGGER_TOKEN=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.recovery.trigger-token'); TRIGGER_TOKEN=${TRIGGER_TOKEN:-@kai-simple}\nKEYWORD=${TRIGGER_TOKEN#@}                 # bare form used in bot-emitted text\nMAX_ATTEMPTS=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.recovery.max-attempts'); MAX_ATTEMPTS=${MAX_ATTEMPTS:-3}\n```\n\n> The documented ADO Service Hook filter string MUST match `trigger-token` — this\n> is the single source of truth (see the pipeline + website docs). The comment\n> hook fires only on comments containing the token; the bot's own comments never\n> contain the literal token, so they cannot self-trigger.\n\n## User input (the text after the trigger) — like local CLI args\n\nThe free text a human writes after the trigger is the **primary instruction**,\nexactly as if they ran `\u002Fdx-simple \u003Cid> \u003Cinstruction>` locally. The token only fires\nthe pipeline; the words after it are the prompt. Resolve `USER_INPUT` once, here:\n\n1. **Local session** — if `INLINE_INPUT` (text after the id in `$ARGUMENTS`) is\n   non-empty → `USER_INPUT=\"$INLINE_INPUT\"`, `TRIGGER_COMMENT_ID=\"\"`.\n2. **Pipeline run** (fired by an ADO comment) — else fetch comments and select the\n   triggering one:\n   ```\n   mcp__ado__wit_work_item with action=\"list_comments\", workItemId=$TICKET_ID\n   ```\n   Pick the comment that is (a) authored by a **non-bot** identity, (b) contains\n   `$TRIGGER_TOKEN`, (c) has the **highest** ADO comment id (ids are monotonic).\n   Strip the token → `USER_INPUT`; record its id as `TRIGGER_COMMENT_ID`.\n3. `USER_INPUT` may be **empty** (a bare `@\u003Ckeyword>` with no words) — then only the\n   story body drives the change (today's behavior).\n\n`USER_INPUT` is **authoritative**: where it conflicts with the story body, it wins.\nPhase 1 folds it into the change request on a fresh run (step 2d); Phase 0 uses it\n(with `TRIGGER_COMMENT_ID` vs `COMMENT_CURSOR`) to reopen a completed ticket.\n\n## Spec directory + branch (established by Phase 0)\n\n`SPEC_DIR` and the per-ticket `BRANCH` are NOT computed here — **Phase 0**\n(`resume-check.sh`) establishes them, because a fresh pipeline container can't\nrecompute run 1's slug and must discover the ticket's existing branch + committed\nstate. Phase 0 emits `SPEC_DIR=…` \u002F `BRANCH=…`; capture those values.\n\n**Fresh-run state init (only when Phase 0 dispatch is `fresh`):**\n\nProgress follows `.ai\u002Frules\u002Ftask-progress.md` — `simple-progress.md` is the source of truth, written at every phase\u002Fgate transition. Task tools, if this session has them, are an optional mirror; never block a transition on one.\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \"$SPEC_DIR\" \"Preflight\" \"done\"\necho '{\"gates\":{}}' > \"$SPEC_DIR\u002Fconfidence.json\"\n# resume-state.json — the durable status the next run dispatches on.\nsed \"s\u002F{TICKET}\u002F$TICKET_ID\u002F\" \\\n    \"${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fresume-state.json.tmpl\" \\\n    > \"$SPEC_DIR\u002Fresume-state.json\"\n# authoring-diff.json is created lazily by Phase 3a when the first write happens\n```\nOn a **resume** dispatch, these files already exist on the branch (committed by a\nprior run) — do NOT re-initialize them; read them.\n\nTouch the orchestration flag (in case a future orchestrator wraps this skill):\n```bash\nmkdir -p .ai\u002Frun-context\ntouch .ai\u002Frun-context\u002Forchestrating.flag\n```\n\n**State files written during the run** (the skill creates these as it goes):\n\n| File | Created in | Purpose |\n|---|---|---|\n| `resume-state.json` | Phase 0 (fresh) \u002F read on resume | Durable recovery status — `status`, `last-completed-phase`, `blocked-at-phase`, `blocker{}`, `answer-attempts`, `comment-cursor`, `run-history[]`. Phase 0 dispatches on it. Committed every checkpoint. |\n| `raw-story.md` | Phase 1 | ADO story dump |\n| `simple-block.yaml` | Phase 1 (after parse) | Parsed DoR block |\n| `before.png` | Phase 1 (after G1) | Pre-change Chrome screenshot |\n| `locator-bbox.json` | Phase 1 (after G1) | `{x, y, w, h}` of locator's bounding box |\n| `confidence.json` | initialized in pre-flight; appended per gate | `{\"gates\":{\"G1\":{\"score\":1,\"status\":\"pass\"}, ...}}` |\n| `dialog-map.json` | Phase 2 (from dialog-inspector subagent) | Field name → type + values |\n| `file-list.json` | Phase 2 (from file-resolver subagent) | Source file paths |\n| `work-plan.json` | Phase 2 (classify-work.sh) | Authoring + code arrays |\n| `authoring-diff.json` | Phase 3a (per write) | Before\u002Fafter for rollback |\n| `compile.log` | Phase 4 (per retry, overwritten) | Build output, tail in report |\n| `after.png` | Phase 5 | Post-change Chrome screenshot |\n| `visual-diff.json` | Phase 5 (visual-diff.sh) | Overall + region pixel scores |\n| `diff-review.md` | Phase 5.5 (from dx-pr-reviewer) | Reviewer findings |\n| `simple-progress.md` | initialized in pre-flight; updated per phase | Phase status table |\n| `followup.md` | Reopen path (done + fresh comment) | Verbatim post-trigger `USER_INPUT` that reopened a completed ticket; checkpointed for audit\u002Freport |\n| `report.md` | Phase 7 (rendered from template) | Final human-readable report |\n\n## Confidence model (gates G1, G3–G9)\n\nThis skill enforces 8 confidence gates. **Any gate failure → ABORT path: classify the blocker, conditionally roll back authoring (terminal only — C3), persist state, ADO comment, verdict: fail (pipeline exits non-zero).** Track scores in `$SPEC_DIR\u002Fconfidence.json`; the final report quotes them. A blocker on a **resumed** run feeds the re-ask loop (see ABORT path) instead of a flat abort.\n\n| Gate | Phase | Threshold |\n|---|---|---|\n| G1 Locator match | 1 | exactly 1 DOM match for element |\n| G3 Classification | 2 | high or medium |\n| G4 Per-file edit confidence | 3b | ≥ 0.85 |\n| G5 Visual non-target identical | 5 | ≥ 99% |\n| G6 Visual target changed | 5 | ≥ 5% |\n| G7 Review blockers @ ≥80% conf | 5.5 | 0 |\n| G8 Cost | global | ≤ $2 (configurable) |\n| G9 Time | global | ≤ 12 min |\n\n> **Note:** G2 (resource-type allowlist) was removed — the skill now runs on\n> any component. The G* numbering is preserved for backwards compatibility\n> with existing reports.\n\n## Flow\n\n```dot\ndigraph dx_simple {\n    \"Preflight\" [shape=box];\n    \"Phase 0: Resume (branch + dispatch)\" [shape=box];\n    \"Read resume comment + apply answer\" [shape=box];\n    \"Replay code edits from work-plan.json\" [shape=box];\n    \"Phase 1: Fetch + extract change details\" [shape=box];\n    \"Phase 0.5: Repo identity guard\" [shape=box];\n    \"G1: locator match exactly 1?\" [shape=diamond];\n    \"Phase 2: Classify (parallel subagents)\" [shape=box];\n    \"G3: classification high or medium?\" [shape=diamond];\n    \"Phase 3a: Apply authoring writes\" [shape=box];\n    \"Phase 3b: Apply code edits\" [shape=box];\n    \"G4: per-file edit confidence ≥85%?\" [shape=diamond];\n    \"Scope-check ok?\" [shape=diamond];\n    \"Phase 4: Compile (≤3 retries)\" [shape=box];\n    \"Compile passed?\" [shape=diamond];\n    \"Phase 5: Visual verify\" [shape=box];\n    \"G5+G6: visual gates pass?\" [shape=diamond];\n    \"One re-edit retry left?\" [shape=diamond];\n    \"Re-edit with screenshot context\" [shape=box];\n    \"Phase 5.5: Diff review (dx-pr-reviewer)\" [shape=box];\n    \"G7: zero blockers ≥80% conf?\" [shape=diamond];\n    \"Phase 6: Activate + Commit + PR\" [shape=box];\n    \"Phase 7: Write report + ADO comment\" [shape=doublecircle];\n\n    \u002F\u002F --- ABORT, expanded (classify → conditional rollback → persist → comment) ---\n    \"ABORT: classify blocker\" [shape=box];\n    \"Terminal (hard\u002Ffinal)?\" [shape=diamond];\n    \"Rollback authoring (terminal only)\" [shape=box];\n    \"Keep authoring (recoverable)\" [shape=box];\n    \"Write report.md (Recovery section)\" [shape=box];\n    \"save-state.sh: commit + push state\" [shape=box];\n    \"git checkout -- . (discard source edits)\" [shape=box];\n    \"Post classified ADO comment + exit\" [shape=doublecircle];\n\n    \u002F\u002F --- re-ask loop on a failed RESUMED gate ---\n    \"Resumed gate still fails?\" [shape=diamond];\n    \"answer-attempts \u003C max?\" [shape=diamond];\n    \"Post sharper question (blocked-needs-input)\" [shape=box];\n    \"Downgrade to blocked-hard → DevAgent\" [shape=box];\n\n    \u002F\u002F --- terminal resume exits ---\n    \"Re-post hard note + exit\" [shape=doublecircle];\n    \"No-op: already done (PR exists)\" [shape=doublecircle];\n\n    \u002F\u002F --- reopen completed work on a fresh follow-up comment ---\n    \"Fresh follow-up trigger comment?\" [shape=diamond];\n    \"Reopen: apply follow-up delta (reuse artifacts)\" [shape=box];\n\n    \"Preflight\" -> \"Phase 0: Resume (branch + dispatch)\";\n\n    \u002F\u002F Phase 0 dispatch out-edges\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Phase 1: Fetch + extract change details\" [label=\"fresh\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Replay code edits from work-plan.json\" [label=\"in-progress (past 3b)\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Phase 1: Fetch + extract change details\" [label=\"in-progress (at\u002Fbefore 3b) → last+1\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Read resume comment + apply answer\" [label=\"blocked-needs-input\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Re-post hard note + exit\" [label=\"blocked-hard\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Fresh follow-up trigger comment?\" [label=\"done\"];\n    \"Phase 0: Resume (branch + dispatch)\" -> \"Read resume comment + apply answer\" [label=\"ambiguous-branch (asks which branch)\"];\n\n    \u002F\u002F done is a no-op UNLESS a new follow-up comment (id > comment-cursor) reopens it\n    \"Fresh follow-up trigger comment?\" -> \"Reopen: apply follow-up delta (reuse artifacts)\" [label=\"yes (non-bot, id > cursor)\"];\n    \"Fresh follow-up trigger comment?\" -> \"No-op: already done (PR exists)\" [label=\"no\"];\n    \u002F\u002F reuse committed story\u002Fplan\u002Fwork-plan; re-enter the edit phase (resume-forward, last=Phase 2)\n    \"Reopen: apply follow-up delta (reuse artifacts)\" -> \"Phase 3a: Apply authoring writes\" [label=\"reuse artifacts, edit-phase delta\"];\n    \"Reopen: apply follow-up delta (reuse artifacts)\" -> \"Phase 1: Fetch + extract change details\" [label=\"escape: different element\u002Fpage\"];\n\n    \"Read resume comment + apply answer\" -> \"Replay code edits from work-plan.json\" [label=\"re-enter past 3b\"];\n    \"Read resume comment + apply answer\" -> \"Phase 1: Fetch + extract change details\" [label=\"re-enter ≤ 3b\"];\n    \"Replay code edits from work-plan.json\" -> \"Phase 4: Compile (≤3 retries)\" [label=\"re-enter target phase\"];\n\n    \"Phase 1: Fetch + extract change details\" -> \"Phase 0.5: Repo identity guard\";\n    \"Phase 0.5: Repo identity guard\" -> \"ABORT: classify blocker\" [label=\"wrong target\"];\n    \"Phase 0.5: Repo identity guard\" -> \"G1: locator match exactly 1?\" [label=\"proceed\"];\n    \"G1: locator match exactly 1?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G1: locator match exactly 1?\" -> \"Phase 2: Classify (parallel subagents)\" [label=\"yes\"];\n    \"Phase 2: Classify (parallel subagents)\" -> \"G3: classification high or medium?\";\n    \"G3: classification high or medium?\" -> \"ABORT: classify blocker\" [label=\"no — abort\"];\n    \"G3: classification high or medium?\" -> \"Phase 3a: Apply authoring writes\" [label=\"yes — if authoring items\"];\n    \"G3: classification high or medium?\" -> \"Phase 3b: Apply code edits\" [label=\"yes — if code items\"];\n    \"Phase 3a: Apply authoring writes\" -> \"Phase 5: Visual verify\" [label=\"if no code items\"];\n    \"Phase 3a: Apply authoring writes\" -> \"G4: per-file edit confidence ≥85%?\" [label=\"if code items also queued\"];\n    \"Phase 3b: Apply code edits\" -> \"G4: per-file edit confidence ≥85%?\";\n    \"G4: per-file edit confidence ≥85%?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G4: per-file edit confidence ≥85%?\" -> \"Scope-check ok?\" [label=\"yes\"];\n    \"Scope-check ok?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"Scope-check ok?\" -> \"Phase 4: Compile (≤3 retries)\" [label=\"yes\"];\n    \"Phase 4: Compile (≤3 retries)\" -> \"Compile passed?\";\n    \"Compile passed?\" -> \"Phase 5: Visual verify\" [label=\"yes\"];\n    \"Compile passed?\" -> \"ABORT: classify blocker\" [label=\"no (hard — after 3 retries)\"];\n    \"Phase 5: Visual verify\" -> \"G5+G6: visual gates pass?\";\n    \"G5+G6: visual gates pass?\" -> \"Phase 5.5: Diff review (dx-pr-reviewer)\" [label=\"yes\"];\n    \"G5+G6: visual gates pass?\" -> \"One re-edit retry left?\" [label=\"no\"];\n    \"One re-edit retry left?\" -> \"Re-edit with screenshot context\" [label=\"yes\"];\n    \"One re-edit retry left?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"Re-edit with screenshot context\" -> \"Phase 4: Compile (≤3 retries)\";\n    \"Phase 5.5: Diff review (dx-pr-reviewer)\" -> \"G7: zero blockers ≥80% conf?\";\n    \"G7: zero blockers ≥80% conf?\" -> \"ABORT: classify blocker\" [label=\"no\"];\n    \"G7: zero blockers ≥80% conf?\" -> \"Phase 6: Activate + Commit + PR\" [label=\"yes\"];\n    \"Phase 6: Activate + Commit + PR\" -> \"Phase 7: Write report + ADO comment\";\n\n    \u002F\u002F A gate that fails on a RESUMED run feeds the re-ask loop instead of a flat abort.\n    \"ABORT: classify blocker\" -> \"Resumed gate still fails?\";\n    \"Resumed gate still fails?\" -> \"answer-attempts \u003C max?\" [label=\"yes (this was a needs-input resume)\"];\n    \"Resumed gate still fails?\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"no (first failure \u002F non-input)\"];\n    \"answer-attempts \u003C max?\" -> \"Post sharper question (blocked-needs-input)\" [label=\"yes\"];\n    \"answer-attempts \u003C max?\" -> \"Downgrade to blocked-hard → DevAgent\" [label=\"no (cap reached)\"];\n    \"Post sharper question (blocked-needs-input)\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"recoverable\"];\n    \"Downgrade to blocked-hard → DevAgent\" -> \"Terminal (hard\u002Ffinal)?\" [label=\"terminal\"];\n\n    \"Terminal (hard\u002Ffinal)?\" -> \"Rollback authoring (terminal only)\" [label=\"yes\"];\n    \"Terminal (hard\u002Ffinal)?\" -> \"Keep authoring (recoverable)\" [label=\"no\"];\n    \"Rollback authoring (terminal only)\" -> \"Write report.md (Recovery section)\";\n    \"Keep authoring (recoverable)\" -> \"Write report.md (Recovery section)\";\n    \"Write report.md (Recovery section)\" -> \"save-state.sh: commit + push state\";\n    \"save-state.sh: commit + push state\" -> \"git checkout -- . (discard source edits)\";\n    \"git checkout -- . (discard source edits)\" -> \"Post classified ADO comment + exit\";\n}\n```\n\n## Node Details\n\n### Phase 0: Resume (branch + dispatch)\n\nRuns immediately after pre-flight, **before Phase 1**. It establishes the\nper-ticket branch (created early — decision #5), reverse-maps it to the committed\nspec dir, reads `resume-state.json`, and decides how to proceed. The per-ticket\nbranch is the durable state store, so a crashed\u002Fblocked prior run is recoverable.\n\n```bash\n# Capture the key=value lines, then read each value (values like \"Phase 2\" contain\n# spaces, so DON'T `eval` the block — read fields explicitly).\nRC=$(bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fresume-check.sh \"$TICKET_ID\")\nval() { sed -n \"s\u002F^$1=\u002F\u002Fp\" \u003C\u003C\u003C\"$RC\" | head -1; }\nDISPATCH=$(val DISPATCH); BRANCH=$(val BRANCH); SPEC_DIR=$(val SPEC_DIR)\nSTATUS=$(val STATUS); RE_ENTER_PHASE=$(val RE_ENTER_PHASE)\nREPLAY_CODE_EDITS=$(val REPLAY_CODE_EDITS); ANSWER_ATTEMPTS=$(val ANSWER_ATTEMPTS)\nLAST_COMPLETED_PHASE=$(val LAST_COMPLETED_PHASE); BLOCKED_AT_PHASE=$(val BLOCKED_AT_PHASE)\n```\n\n`resume-check.sh` owns anchored discovery (`feature\u002F\u003Cid>-*` \u002F `bugfix\u002F\u003Cid>-*` —\nnever a bare `*\u003Cid>*` substring, so `123` can't match `1234`), the 0 \u002F 1 \u002F >1\nbranch decision, and the reverse branch→spec-dir mapping. It delegates **only the\ncreate-fresh path** to `..\u002F..\u002Fshared\u002Fensure-feature-branch.sh` (M2 — the shared helper's\ncontract is unchanged for its other consumers). It is **read-only** w.r.t.\n`resume-state.json` (never bumps `answer-attempts` — a crash must not burn the\nneeds-input cap, M4).\n\nDispatch on `DISPATCH`:\n\n| DISPATCH | meaning | action |\n|---|---|---|\n| `fresh` | no branch \u002F no committed state | run fresh-run state init (above), proceed to **Phase 1**. |\n| `resume-forward` | prior run **crashed** (`in-progress`) | if `REPLAY_CODE_EDITS=true`, run **Replay code edits** first, then re-enter `RE_ENTER_PHASE`. Reuse committed discovery artifacts. Does NOT consume an answer-attempt. |\n| `resume-blocked-input` | recoverable block awaiting a human answer | run **Read resume comment + apply answer**, then (replay if past 3b) re-enter `RE_ENTER_PHASE`. |\n| `resume-blocked-hard` | unrecoverable, or `answer-attempts` cap reached | **Re-post hard note + exit** — re-post the \"needs manual fix \u002F re-tag DevAgent\" note; exit non-zero. |\n| `done` | Phase 7 already succeeded | Run **Reopen completed work**: if a fresh follow-up trigger comment exists (non-bot, contains the token, id > `COMMENT_CURSOR`) treat it as a continuation on the same branch. **Otherwise no-op** — comment \"already completed in PR; reply `@\u003Ckeyword> \u003Cchange>` to request a follow-up, or open a new ticket\"; exit 0. |\n| `ambiguous-branch` | >1 `feature\u002F\u003Cid>-*` match | post a `needs-user-input` comment asking **which branch** to resume (loop-safe format); set `blocked-needs-input`; exit. Do NOT pick one. |\n\n> **git-rules.md exception.** `git-rules.md` says \"never reuse old feature\u002Fbugfix\n> branches for new work.\" `\u002Fdx-simple` resume is a deliberate, scoped exception —\n> it reuses the branch for the **same ticket, same work, continued**, only when the\n> anchored match is **unique** (ambiguous → blocker, not reuse).\n\n### Reopen completed work (follow-up comment)\n\nOnly on `DISPATCH=done`. A completed run is normally a no-op, but a reviewer often\nwants a small refinement (\"reuse the existing query-param util\", \"tighten the\nspacing\") rather than a whole new ticket. Treat the **fresh** trigger comment as a\nnew prompt **in a new session that already has all the prior artifacts** — reuse\nthem and do only the delta (decision: skip the steps that are already done — story\nfetch, locate, classify — exactly as a developer reopening the spec dir locally\nwould). Refuse to reopen on a stale or already-consumed comment.\n\n1. **Is there a fresh follow-up?** Reopen only if `USER_INPUT` is non-empty **and**\n   `TRIGGER_COMMENT_ID > COMMENT_CURSOR` (numeric; treat an empty cursor as `0`).\n   - Otherwise → genuine **no-op** (stray re-trigger \u002F service-hook replay \u002F bare\n     token \u002F already-consumed comment). Post once: \"Already completed in PR. Reply\n     `@\u003Ckeyword> \u003Cchange>` to request a follow-up, or open a new ticket.\" Exit 0.\n2. **Reuse the committed artifacts** on the branch — `raw-story.md`,\n   `simple-block.yaml`, `dialog-map.json`, `file-list.json`, `work-plan.json`,\n   `confidence.json`. Do **not** re-fetch the story, re-run G1 locate, or re-classify\n   (Phase 2): they are already committed. This is the \"new local session on an\n   existing spec dir\" behavior the user expects.\n3. **Apply the follow-up as a delta.** Read `work-plan.json` + `USER_INPUT` and update\n   the work-plan to satisfy the follow-up (e.g. change a `replacement` to call the\n   existing util; add or adjust a `code[]` item). Append `USER_INPUT` to\n   `simple-block.yaml`'s `what` (report context) and record it verbatim in\n   `$SPEC_DIR\u002Ffollowup.md`.\n   - **Escape hatch:** if the follow-up targets a *different element or page* than the\n     committed locator, the reused discovery no longer holds — re-enter **Phase 1**\n     instead (full re-run with `USER_INPUT` as the primary request).\n4. **Seed resume-forward + advance the cursor atomically**, then checkpoint.\n   Advancing the cursor **before** re-entry is the loop guard (L2): once this run\n   completes to `done`, the same comment is at\u002Fbelow the cursor and cannot reopen\n   again — a further follow-up needs a brand-new comment.\n   ```bash\n   jq --arg c \"$TRIGGER_COMMENT_ID\" \\\n      '.status=\"in-progress\" | .[\"comment-cursor\"]=$c\n       | .[\"last-completed-phase\"]=\"Phase 2\"\n       | .[\"run-history\"] += [{\"event\":\"reopen\",\"comment\":$c}]' \\\n      \"$SPEC_DIR\u002Fresume-state.json\" > \"$SPEC_DIR\u002F.rs.tmp\" \\\n      && mv \"$SPEC_DIR\u002F.rs.tmp\" \"$SPEC_DIR\u002Fresume-state.json\"\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 2\"\n   ```\n   (Exit 3 = `BRANCH-ADVANCED` → a concurrent resume is in flight; stop.)\n5. **Re-enter the edit phase** exactly as a `resume-forward` would: with\n   `last-completed-phase = Phase 2`, the next actionable phase is **Phase 3a**\n   (authoring) \u002F **Phase 3b** (code). Apply the updated work-plan, then flow forward —\n   compile → visual verify → diff review → **Phase 6 (update-mode amends the existing\n   PR, M5)** → Phase 7. Discovery and classification are skipped; only the delta is\n   implemented and verified. This path does **not** touch `answer-attempts` (it is a\n   new request, not a re-ask of a blocked gate).\n\n### Read resume comment + apply answer\n\nOnly on `resume-blocked-input` (or `ambiguous-branch` resolution). Select the\nhuman's answer and apply it before re-entering the blocked phase.\n\n1. Fetch comments (`mcp__ado__wit_work_item`, `action: \"list_comments\"`). Select the comment\n   that is: (a) authored by a **non-bot** identity, (b) contains the trigger token\n   (`$TRIGGER_TOKEN`), (c) has the **highest ADO comment id** (ids are monotonic —\n   order by id, not timestamp), and (d) id > the stored `comment-cursor`. If none\n   qualifies → cheap exit (stray re-trigger; nothing to do).\n2. Strip the token; the remainder is `continue-input`. Apply it to\n   `blocked-at-phase` (e.g. set `simple-block.yaml` `element` for a G1\n   ambiguity; add a file\u002Fline\u002Fanchor hint for G4; pick the branch for\n   `ambiguous-branch`).\n3. **Commit the updated `comment-cursor` atomically with \u002F before re-entering the\n   phase** (L2) — via `jq` into `resume-state.json` then `save-state.sh` —\n   otherwise a crash after applying the answer reprocesses the same comment.\n4. Re-enter `RE_ENTER_PHASE` (replaying code edits first if past 3b). **If the gate\n   still fails:** `answer-attempts++`, post a *sharper* question. **When quoting the\n   prior answer, never echo the literal token** — render it as `@\u003Ckeyword>` (H4).\n   Set `blocked-needs-input` again; update `comment-cursor`. After the\n   `$MAX_ATTEMPTS`-th failed cycle, downgrade to `blocked-hard` and recommend\n   DevAgent (M4 — prevents infinite human↔bot ping-pong). Crashes\u002Ftransient\n   resumes never count against this cap.\n\n### Replay code edits from work-plan.json\n\nCode edits are **never committed** (decision #3), so on any resume the working tree\nhas none of them. Before re-entering **any phase after Phase 3b** (C2), deterministically\nre-apply them from `work-plan.json` onto the clean committed base:\n\nFor each item in `work-plan.code[]` (which carries filled `match-context` \u002F\n`replacement` from run 1): `Edit(file=\u003Cpath>, old_string=\u003Cmatch-context>,\nnew_string=\u003Creplacement>)`. Same base + unique `match-context` → identical diff.\n\n**Authoring is the opposite** — JCR writes persist in AEM, so resume **skips**\nPhase 3a and reconciles via the committed `authoring-diff.json` (the Phase 3a\nbefore-check treats `actual == target-after` as already-applied → skip). The two\nhalves of the resume contract are asymmetric: **code replays, authoring reconciles.**\n\nRe-entering Phase 3b itself does NOT need a separate replay — 3b re-fills and\nre-applies the work-plan as part of its normal flow.\n\n### Phase 1: Fetch + extract change details\n\n1. Fetch the work item:\n   ```\n   mcp__ado__wit_work_item with action=\"get\", id=$TICKET_ID\n   ```\n   Write the description + comments to `$SPEC_DIR\u002Fraw-story.md` with provenance\n   frontmatter. Record the resolved `USER_INPUT` (the post-trigger instruction, see\n   *User input* above) at the top of `raw-story.md` under a `## User input` heading so\n   the change request and the report both preserve exactly what the human asked for.\n\n2. Parse the `simple` block (recommended but not required):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fparse-simple-block.sh \\\n        \"$SPEC_DIR\u002Fraw-story.md\" \"$SPEC_DIR\u002Fsimple-block.yaml\"\n   ```\n   - Exit 0 (parsed) → continue to step 2a (fill in inferred fields if any are missing).\n   - Exit 2 (no block) → fall through to step 2b (LLM extraction from story prose).\n   - Exit 3 (malformed: missing `page-url`, duplicate field, or unclosed fence) → post the specific error from stderr as an ADO comment. STOP.\n\n2a. **Fill in missing fields** (block was present but partial):\n    Read `simple-block.yaml`. For each missing optional field, infer it\n    from the surrounding story text and overwrite `simple-block.yaml`:\n    - `element` missing → infer from the story's element references.\n      Examples: \"Language Selector button\" → `dialog-title=\"Language Selector\"`;\n      \"Get started heading\" → `heading-text=\"Get started\"`. If the story\n      gives a JCR path, use `jcr-path=...`.\n    - `what` missing → use the story's natural-language description\n      of the change in plain English (this is the input the model uses to\n      decide whether the change is content, code, or both).\n    The classifier (Phase 2) uses these as hints; nothing here gates execution.\n\n2b. **LLM extraction** (no block found): read `raw-story.md` and extract\n    the same fields directly from the story description, acceptance\n    criteria, and comments:\n    - `page-url` (REQUIRED): find the QA author URL in the story. If\n      multiple, prefer the one nearest the change description; if still\n      ambiguous, post the template at\n      `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fsimple-block.md.tmpl`\n      as an ADO comment and STOP.\n    - `element`: derive from the story's element references\n      (visible text, dialog title, JCR path).\n    - `what`: take the most specific change description in the story.\n      Keep it as natural language — the model decides downstream whether each\n      part is a content edit or a code edit.\n    Write the inferred values to `$SPEC_DIR\u002Fsimple-block.yaml` with a\n    `# inferred: true` comment at the top so downstream phases can flag\n    lower confidence in the report.\n\n2d. **Fold in `USER_INPUT`** (the text the human typed after the trigger — see\n    *User input* above). If non-empty, it is the **primary** change request: merge it\n    into `what` in `simple-block.yaml`, overriding the story body on conflict (a human\n    typing `@\u003Ckeyword> make it blue` means *make it blue*). The classifier (Phase 2)\n    and edit phases then act on `USER_INPUT` plus the story context. If `USER_INPUT`\n    is empty (bare trigger), only the story body drives the change (today's behavior).\n\n3. Semantic validation (Safeguard #1):\n   - Read `simple-block.yaml`; extract `page-url`, `element`, `what`.\n   - **QA Basic Auth (first navigation only):** follow the `qa-basic-auth` rule. Resolve credentials in priority order:\n     1. Env vars: `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS`\n     2. `.ai\u002Fconfig.yaml` keys `aem.qa-basic-auth.username` \u002F `aem.qa-basic-auth.password`\n     If credentials are found AND `page-url` is not a localhost URL, embed them in the URL for this navigation: `https:\u002F\u002F\u003Cuser>:\u003Cpass>@\u003Chost>\u002Fpath`. Subsequent navigations (Phase 5) use the clean URL — the session cookie persists. Never log or echo the URL with embedded credentials.\n     If no credentials are found and the URL is non-localhost: post ADO comment explaining that `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS` env vars are not set (see `qa-basic-auth` rule) and exit non-zero.\n   - Navigate Chrome to the page URL (credential-embedded for first QA visit):\n     ```\n     mcp__plugin_dx-aem_playwright__browser_navigate with url=\u003Cpage-url>\n     ```\n   - Take a snapshot:\n     ```\n     mcp__plugin_dx-aem_playwright__browser_snapshot\n     ```\n   - Match the locator. The locator is one of:\n     - `heading-text=\"...\"` \u002F `button-text=\"...\"` \u002F `link-text=\"...\"` → look for elements with that exact visible text\n     - `jcr-path=...` → use AEM MCP `getNodeContent` to confirm node exists; locator match count = 1 if node exists, 0 if not\n     - `dialog-title=\"...\"` → ambiguous on its own; require AEM MCP `scanPageComponents` to resolve to a unique component instance\n     - free-form description (from LLM extraction) → use the Chrome snapshot + scanPageComponents to find the single best match; if more than one element matches, ABORT G1 with the ambiguous-locator message\n\n4. **Take BEFORE screenshot** (Safeguard #6): capture, then move into the spec dir as `before.png`:\n   ```\n   mcp__plugin_dx-aem_playwright__browser_take_screenshot with filename=before.png\n   cp .ai\u002Fplaywright\u002Fscreenshots\u002Fbefore.png $SPEC_DIR\u002Fbefore.png\n   ```\n   (`browser_take_screenshot` takes a `filename` basename and saves under the MCP `--output-dir`, `.ai\u002Fplaywright\u002Fscreenshots\u002F` — it does not honor an arbitrary path.)\n   Also record the locator's bounding box (from snapshot) to `$SPEC_DIR\u002Flocator-bbox.json` for the visual-diff step.\n\n5. **G1 — Locator match (HARD GATE):**\n   - If 0 matches: post ADO comment \"Component not found on page. Verified by: \u003Csnapshot-id>. Check page-url + the element you wanted to change.\" Exit non-zero. NO file reads beyond this point.\n   - If >1 matches: post \"Ambiguous locator: \u003CN> matches. Add a `jcr-path=...` to the `simple` block, or describe the element more specifically.\" Exit non-zero.\n   - If exactly 1: continue. Record `G1 = 1 match` in `confidence.json`.\n\n6. Update progress + **checkpoint** (commit + push recovery state to the branch):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \\\n        \"$SPEC_DIR\" \"Phase 1: Extract + locate\" \"done\" \"G1 passed\"\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 1\"\n   ```\n   If `save-state.sh` exits 3 (`BRANCH-ADVANCED`), STOP — a concurrent resume is\n   in flight (H2); do not force-push.\n\n### Phase 0.5: Repo identity guard\n\nRuns once, right after `parse-simple-block.sh` writes `simple-block.yaml`, before G1.\nConfirms this repo is a legitimate target for the ticket and records authoring ownership.\n\n```bash\nGUARD_OUT=$(bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frepo-guard.sh \\\n  \"$SPEC_DIR\u002Fsimple-block.yaml\" \".ai\u002Fconfig.yaml\") || GUARD_RC=$?\neval \"$GUARD_OUT\"   # sets DECISION, REASON?, AUTHORING_OWNER?\n```\n\n- If `DECISION=abort` (exit 3): set blocker class **`wrong-target`** and follow the\n  **ABORT path** — this is a **hard\u002Fterminal** blocker (no re-ask loop: the human must\n  re-trigger in the correct repo). Post `REASON` verbatim as the classified ADO comment.\n  No JCR writes, no code edits. Verdict: fail.\n- If `DECISION=proceed`: continue to G1. Persist `AUTHORING_OWNER` into\n  `resume-state.json` (`authoring-owner` key) — Phase 3a reads it.\n- Single-repo \u002F unconfigured projects: `project.platform` is unset and the block has no\n  `platform`, so the guard proceeds with `AUTHORING_OWNER=true` (today's behavior).\n\n### Phase 2: Classify (parallel subagents)\n\nDispatch THREE subagents in a single message (parallel):\n\n1. **Page resolver** (reuse `aem-page-finder`, model: haiku):\n   ```\n   Agent(subagent_type: aem-page-finder, prompt: \"Resolve page-url \u003Cpage-url> to its JCR content path. Confirm the page exists on QA. Return JSON: {\\\"jcr-path\\\": \\\"\u002Fcontent\u002F...\\\", \\\"language-master\\\": \\\"\u003Cpath or null>\\\"}.\")\n   ```\n\n2. **Dialog inspector** (reuse `aem-inspector`, model: sonnet):\n   ```\n   Agent(subagent_type: aem-inspector, prompt: \"For component at JCR path \u003Cjcr-path>, read sling:resourceType, then fetch \u002Fapps\u002F\u003Cresource-type>\u002Fcq:dialog. Walk the dialog fields and return JSON:\n   {\n     \\\"jcr-path\\\": \\\"\u003Cjcr>\\\",\n     \\\"resource-type\\\": \\\"\u003Crtype>\\\",\n     \\\"fields\\\": { \\\"\u003Cname>\\\": \\\"\u003Ctype>\\\", ... },\n     \\\"values\\\": { \\\"\u003Cname>\\\": \\\"\u003Ccurrent value>\\\", ... }\n   }\n   Return only the JSON, no prose.\")\n   ```\n\n3. **File resolver** (reuse `aem-file-resolver`, model: haiku):\n   ```\n   Agent(subagent_type: aem-file-resolver, prompt: \"Resolve source files for resource type \u003Cresource-type>. Return JSON: {\\\"files\\\": [{\\\"path\\\": \\\"...\\\"}, ...]}.\")\n   ```\n\nSynthesize the three returns into `$SPEC_DIR\u002Fdialog-map.json` and `$SPEC_DIR\u002Ffile-list.json`. Then build a **baseline** work-plan with the deterministic heuristic:\n\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fclassify-work.sh \\\n     \"$SPEC_DIR\u002Fsimple-block.yaml\" \\\n     \"$SPEC_DIR\u002Fdialog-map.json\" \\\n     \"$SPEC_DIR\u002Ffile-list.json\" \\\n     \"$SPEC_DIR\u002Fwork-plan.json\"\n```\n\n`classify-work.sh` only suggests obvious matches based on keywords in\n`what` against dialog field names. **You — the orchestrator — are\nresponsible for the final classification.** Read `work-plan.json`, read\n`what` and `raw-story.md`, and decide what to do:\n\n- The change is a content edit that matches a dialog field → keep \u002F add\n  an item in `.authoring[]`.\n- The change involves hardcoded strings, JS behavior (focus traps,\n  keyboard handlers, click handlers), HTL templates, or CSS classes →\n  keep \u002F add items in `.code[]`.\n- The change requires **both** (e.g. \"rename the heading **and** trap\n  focus in the modal\") → populate **both** arrays. Phase 3a and Phase 3b\n  will both run.\n- The locator pointed at a component whose dialog has no matching field,\n  but the what clearly describes a content change → that string\n  is hardcoded; route to `.code[]` and let the file-resolver candidates\n  in there carry it.\n\nIf the deterministic baseline missed items, append them to `work-plan.json`\nvia `Write`. If the baseline included items the model rejects on inspection,\nremove them. Once `work-plan.json` reflects the model's final plan, set\n`confidence.\"G3-classification\"` to one of:\n\n- `high` — at least one path (authoring or code) has an unambiguous target\n- `medium` — at least one path has a target, but with ambiguity worth noting\n  in the report\n- `low` — neither path has a workable target → abort with G3 fail\n\nUpdate progress + **checkpoint**:\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh \\\n     \"$SPEC_DIR\" \"Phase 2: Classify\" \"done\" \"G3=\u003Clevel>, authoring=\u003Cn>, code=\u003Cn>\"\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 2\"\n```\n\n### Phase 3a: Apply authoring writes\n\n**Runs whenever `work-plan.authoring[]` is non-empty.** If `work-plan.code[]`\nis also non-empty, Phase 3b runs immediately after Phase 3a (sequential, not\nparallel — Phase 3a must record the JCR before-state in main context for\nrollback before Phase 3b touches anything).\n\n**Authoring-owner gate (multi-repo).** First read `authoring-owner` from\n`resume-state.json` (set by Phase 0.5; defaults to `true`). If it is `false`,\n**skip all authoring items** — another repo owns authoring for this ticket on this\nAEM instance. Record in `report.md`:\n`Authoring skipped — owned by the AEM-author-capable repo for this platform.` Then:\n- if `work-plan.code[]` is also non-empty → fall through to Phase 3b (the edge\n  `Phase 3a -> G4 [if code items also queued]`);\n- if there are no code items → this repo has nothing to apply → go to Phase 5 via the\n  existing `[if no code items]` edge. With nothing written, visual verify\u002Freport simply\n  confirms no change in this repo → write report, verdict: **success (no-op)**.\n\n**Read-before-write idempotency (always — even when this repo IS the owner).** Before\neach JCR write, `getNodeContent` the target property; if its current value already\nequals the target (`item.after`), **skip the write** and log `unchanged` in\n`authoring-diff.json`. This keeps recovery re-runs and any parallel\nsame-instance run side-effect-free. (The per-item drift check below applies the same\nread; the gate here is the general rule it implements.)\n\nFor each item in `work-plan.authoring[]`:\n\n1. Read current value:\n   ```\n   mcp__plugin_dx-aem_AEM__getNodeContent with path=\u003Cjcr-path>\n   ```\n   Confirm `item.before` matches the actual current value. **Idempotent re-entry\n   (H1) \u002F read-before-write:** if `actual == item.after`, the target value is\n   already in place — either a prior run that crashed mid-Phase-3a applied it, or a\n   parallel same-instance run did → **skip the write**, log `unchanged` in\n   `authoring-diff.json`, and record it as applied. Only abort\n   `\"value drifted: expected \u003Cbefore>, got \u003Cactual>\"` when `actual` is neither\n   `before` nor `after`.\n\n2. Apply the write:\n   ```\n   mcp__plugin_dx-aem_AEM__updateComponent with path=\u003Cjcr-path>, properties={\u003Cproperty>: \u003Cafter>}\n   ```\n\n3. Append to `$SPEC_DIR\u002Fauthoring-diff.json`:\n   ```json\n   { \"writes\": [{ \"jcr-path\": \"...\", \"property\": \"...\", \"before\": \"...\", \"after\": \"...\", \"applied\": true, \"applied-at\": \"\u003CISO>\" }] }\n   ```\n\n4. Log to audit:\n   ```bash\n   AUDIT_LOG_PREFIX=simple source .ai\u002Flib\u002Faudit.sh\n   _audit_append '{\"ts\":\"\u003CISO>\",\"action\":\"updateComponent\",\"path\":\"\u003Cjcr>\",\"property\":\"\u003Cprop>\",\"ticket\":\"\u003Cid>\"}'\n   ```\n\n5. **Checkpoint per JCR write (H1).** AEM writes are external side-effects not\n   transactional with the git commit. Commit `authoring-diff.json` **after each\n   write**, not once at end of phase — otherwise a crash after write 2 of 3 leaves\n   the writes live on AEM but the diff uncommitted, and resume's before-check\n   (now idempotent, step 1) would have no record of them:\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 3a\"\n   ```\n\nIf ANY write fails partway through:\n- Mark all subsequent items as `applied: false`\n- Classify the blocker and follow the **ABORT path** (authoring is kept on a\n  recoverable block, rolled back only on terminal failure — C3)\n- Post ADO comment, exit non-zero\n\nUpdate progress.\n\n### Phase 3b: Apply code edits\n\n**Runs whenever `work-plan.code[]` is non-empty** — independently of whether\nPhase 3a ran. The code path now covers any change that touches source\nfiles: hardcoded strings, HTL templates, CSS classes, focus traps and\nother JS behavior, click\u002Fkeyboard handlers, etc. The model chose this\npath in Phase 2 based on the what text and dialog map.\n\n`classify-work.sh` populates `.code[]` with placeholder items (confidence=0, empty contexts) for each candidate file. The agent MUST fill them in (or remove them) and rewrite `work-plan.json` to disk BEFORE the G4 gate runs. For changes that add new code (focus traps, new event listeners) rather than replace an existing line, set `match-context` to the anchor line you're inserting **after**, and `replacement` to the anchor line followed by the new code.\n\nFor each item in `work-plan.code[]`:\n\n1. Read the file:\n   ```\n   Read(file=\u003Cpath>)\n   ```\n\n2. Identify the exact match line. Fill in `match-line`, `match-context`, `replacement`, `rationale`, and `confidence` (LLM self-rated, 0.0–1.0):\n   - High confidence (≥0.85): one unambiguous match in the file for the locator's context. Rationale example: \"only `color: red` occurrence inside .hero-cta selector\".\n   - Below 0.85: multiple matches with unclear precedence, or no clear anchor. Do NOT guess.\n\n3. **Persist the updated work-plan to disk** (the deterministic G4 check reads this file):\n   ```\n   Write(file=$SPEC_DIR\u002Fwork-plan.json, content=\u003Cwork-plan JSON with all .code[] items populated>)\n   ```\n\n4. **G4 — Per-file edit confidence (HARD GATE):**\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fcheck-g4.sh \"$SPEC_DIR\u002Fwork-plan.json\" 0.85\n   ```\n   Exit 4 → rollback authoring + exit non-zero. Record `G4` status in `confidence.json`.\n\n5. Apply the Edits (only after G4 passes):\n   ```\n   Edit(file=\u003Cpath>, old_string=\u003Cmatch-context>, new_string=\u003Creplacement>)\n   ```\n\n6. After all code items applied: run scope-check:\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fscope-check.sh \"$SPEC_DIR\u002Fwork-plan.json\"\n   ```\n   Exit 4 → ABORT (scope exceeded is `hard`).\n\nUpdate progress + **checkpoint** (commits the filled `work-plan.json` so a later\nresume can replay the code edits — C2; the edits themselves stay uncommitted):\n```bash\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 3b\"\n```\n\n### Phase 4: Compile (≤3 retries)\n\n**Only if Phase 3b ran (code edits were applied).** Skip if authoring-only.\n\nRead build command from `.ai\u002Fconfig.yaml`:\n- Prefer `dx-simple.build-compile-fast` if set\n- Else `build.compile-fast`\n- Else `build.compile`\n- Else abort: \"no compile command configured\"\n\n```bash\nCOMPILE=$(bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'dx-simple.build-compile-fast' || \\\n          bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'build.compile-fast' || \\\n          bash .ai\u002Flib\u002Fdx-common.sh yaml-val 'build.compile')\n```\n\nRun the compile, capturing output to `$SPEC_DIR\u002Fcompile.log`. Loop up to 3 attempts:\n\n```bash\nfor ATTEMPT in 1 2 3; do\n  $COMPILE > \"$SPEC_DIR\u002Fcompile.log\" 2>&1 && break\n  if [[ \"$ATTEMPT\" -eq 3 ]]; then\n    # Rollback + exit\n    bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frollback-authoring.sh \"$SPEC_DIR\u002Fauthoring-diff.json\"\n    exit 1\n  fi\n  # Read the last 50 lines of compile.log, identify the error, edit the file, retry.\ndone\n```\n\nThe agent's job between attempts: read `compile.log` tail, identify which file\u002Fline caused the error, Edit it, then re-loop.\n\nUpdate progress with attempt count.\n\n### Phase 5: Visual verify\n\nThe classifier produces EITHER authoring OR code items, never both — `classify-work.sh` routes by G3 confidence. Visual-verify rules differ by path:\n\n**Authoring path (Phase 3a ran):** mandatory if `activate: true` in the simple block (writes are visible on author immediately). Skip only when `activate: false` AND no rendered preview is expected.\n\n**Code path (Phase 3b ran):** **visual verify is skipped in pipeline mode** because `mvn compile` (the only build allowed by `block-mvn-deploy.sh`) does NOT deploy to AEM. The QA author serves the old bundle, so `before.png` and `after.png` would be identical and G6 (target changed ≥5%) would always fail. The change is verified post-merge by the normal CI deploy + QA cycle.\n\n  - To request visual verify for a code-path run anyway, add `force-visual-verify: true` to the simple block. The pipeline will navigate and screenshot, but G5\u002FG6 are downgraded to WARN (recorded in report.md, do not abort). Useful only when an external job pre-deploys the branch to QA before SimpleAgent runs.\n  - Local dev (non-pipeline) is free to run visual verify when the developer has deployed locally — the same `force-visual-verify: true` flag opts in.\n\n**When skipped:** code-only non-visual changes (aria-label edits, copy in HTL, etc.) — recorded as \"verify-deferred-to-qa\" in the report.\n\nIf running:\n\n1. Navigate Chrome to the page (note: same QA author URL, NOT publish, so authoring writes are visible):\n   ```\n   mcp__plugin_dx-aem_playwright__browser_navigate with url=\u003Cpage-url>\n   ```\n\n2. Take AFTER screenshot:\n   ```\n   mcp__plugin_dx-aem_playwright__browser_take_screenshot with filename=after.png\n   cp .ai\u002Fplaywright\u002Fscreenshots\u002Fafter.png $SPEC_DIR\u002Fafter.png\n   ```\n\n3. Run visual-diff:\n   ```bash\n   BBOX=$(cat $SPEC_DIR\u002Flocator-bbox.json | jq -r '\"\\(.x),\\(.y),\\(.w),\\(.h)\"')\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fvisual-diff.sh \\\n        \"$SPEC_DIR\u002Fbefore.png\" \"$SPEC_DIR\u002Fafter.png\" \"$BBOX\" \\\n        > \"$SPEC_DIR\u002Fvisual-diff.json\"\n   ```\n\n4. **G5 — Visual non-target identical:** `non-target-identical ≥ 99%`\n5. **G6 — Visual target changed:** `region-diff ≥ 5%`\n\nEither gate fail:\n- Authoring path → rollback authoring + exit non-zero.\n- Code path with `force-visual-verify: true` AND re-edit budget remaining → invoke ONE re-edit cycle: open the staged file, show the agent the screenshot diff, ask \"this change didn't render as expected — re-edit.\" Then loop back to Phase 4 (recompile).\n- Code path WITHOUT `force-visual-verify` → unreachable (Phase 5 skipped — see selection rules above).\n\n### Phase 5.5: Diff review (only if code path)\n\nIf Phase 3b ran (code edits applied), invoke `dx-pr-reviewer` for a single-pass review on the working tree:\n\n```\nAgent(subagent_type: dx-pr-reviewer, prompt: \"Review the staged diff (git diff HEAD) for the following changes. Context: this is a small ≤50-line tweak via \u002Fdx-simple on component \u003Cresource-type> — change described as \u003Cwhat>. Focus on:\n- Does the change actually accomplish what the requirement says?\n- Any obvious bug (wrong variable, missing semicolon, off-by-one)?\n- Any accessibility regression (e.g., removing existing aria text)?\nReturn findings as JSON: [{ \\\"severity\\\": \\\"blocker|suggestion\\\", \\\"confidence\\\": 0.0-1.0, \\\"file\\\": \\\"...\\\", \\\"line\\\": N, \\\"comment\\\": \\\"...\\\" }]\n\")\n```\n\nWrite the review to `$SPEC_DIR\u002Fdiff-review.md`.\n\n**G7 — Zero blockers at ≥80% confidence:**\n- Any `severity: blocker AND confidence ≥ 0.8` → rollback authoring + exit non-zero, no PR.\n- Suggestions are recorded in PR description as \"Reviewer notes\" but do not block.\n\n### Phase 6: Activate + Commit + PR\n\n1. **Activate authoring** (only if work-plan has authoring items AND `simple-block.yaml` `activate: true` AND all gates passed):\n   For each unique JCR path in `authoring-diff.json`:\n   ```\n   mcp__plugin_dx-aem_AEM__activatePage with path=\u003Cjcr-path>\n   ```\n   Log each activation to audit. Update `authoring-diff.json` to set `activated: true` per item.\n\n2. **Commit + PR** (only if Phase 3b ran):\n\n   **Idempotent (M5).** PR creation is irreversible, so:\n   - **Checkpoint `last-completed = Phase 6` BEFORE the create** so a crash during\n     create resumes back into Phase 6 (not past it):\n     ```bash\n     bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 6\"\n     ```\n   - **Detect an existing open PR** for this branch first\n     (`mcp__ado__repo_pull_request` with `action: \"list\"`,\n     `sourceRefName: refs\u002Fheads\u002F\u003Cbranch>`, `status: Active`). If one exists, switch\n     `\u002Fdx-pr-commit` to **update-mode** (push new commits + update the description)\n     instead of creating a second PR — a crash between PR-create and the post-create\n     checkpoint must not duplicate the PR.\n\n   Delegate to `\u002Fdx-pr-commit`:\n   ```\n   Skill(\u002Fdx-pr-commit)\n   ```\n   The conventional commit message: `feat(\u003Cscope>): \u003Cwhat summary>`. Include in the PR body:\n   - Link to `report.md`\n   - Reviewer notes from Phase 5.5 (if any)\n   - Authoring changes summary (if any, with \"activated: yes\" flag)\n   - Before\u002Fafter screenshots\n\n### Phase 7: Write report + ADO comment\n\nRender `${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Freport.md.tmpl` into `$SPEC_DIR\u002Freport.md`, substituting all `{PLACEHOLDER}` tokens from `confidence.json`, `work-plan.json`, `authoring-diff.json`. Also substitute `{PLUGIN_ROOT}` with the literal value of `${CLAUDE_PLUGIN_ROOT}` (so the printed revert command is copy-pasteable) and `{SPEC_DIR}` with the absolute spec directory.\n\nPost a truncated version to ADO:\n```\nmcp__ado__wit_work_item_comment_write with action=\"add\", workItemId=\u003Cticket>, text=\u003Ctruncated report>\n```\n\nAfter the comment posts successfully, record it so the pipeline's fallback step\ndoes not post a duplicate failure note:\n```bash\nmkdir -p .ai\u002Frun-context && touch .ai\u002Frun-context\u002Fado-comment-posted.flag\n```\n\nUpdate final progress row to `done`, mark recovery state terminal, and checkpoint\nso a stray re-trigger short-circuits to the `done` no-op:\n```bash\njq '.status = \"done\"' \"$SPEC_DIR\u002Fresume-state.json\" > \"$SPEC_DIR\u002F.rs.tmp\" && mv \"$SPEC_DIR\u002F.rs.tmp\" \"$SPEC_DIR\u002Fresume-state.json\"\nbash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"Phase 7\"\n```\n\nClean up (leave `ado-comment-posted.flag` in place — the pipeline inspects it):\n```bash\nrm -f .ai\u002Frun-context\u002Forchestrating.flag\n```\n\n## Return contract\n\nWhen this skill is invoked from an orchestrator (future composition), emit at the end:\n\n```markdown\n## Return\nverdict: pass | warn | fail\nsummary: \u003Cone sentence>\nartifacts:\n  - $SPEC_DIR\u002Freport.md\n  - $SPEC_DIR\u002Fwork-plan.json\n  - $SPEC_DIR\u002Fauthoring-diff.json\nnext_action: \u003Chuman-readable next step or \"none\">\n```\n\nIf running standalone (no `orchestrating.flag`), also print a human summary above the Return block.\n\n## ABORT path (any gate failure)\n\n**Order matters.** Classify first, then roll back authoring *only if terminal*\n(C3), then persist state **before** discarding the source edits (today's\n`git checkout -- .` wipes the uncommitted report + state — that is the bug this\nfixes).\n\n1. **Classify the blocker** → set `blocker.class` (`needs-user-input` | `transient`\n   | `hard`), `blocker.recoverable`, `blocker.reason`, `blocker.needs`,\n   `blocked-at-phase`, and `status` in `resume-state.json` (via `jq`). Use the\n   **Blocker taxonomy** below.\n\n   **Re-ask loop (if this is a resumed `needs-input` run whose gate failed again):**\n   if `answer-attempts \u003C $MAX_ATTEMPTS`, bump `answer-attempts`, set\n   `status=blocked-needs-input`, and post a *sharper* question (loop-safe — see\n   comment format; never echo the literal token). Otherwise downgrade to\n   `status=blocked-hard` and recommend DevAgent (M4).\n\n2. **Conditional authoring rollback (C3).** Roll back JCR writes **only when the\n   blocker is terminal** (`hard`, or final fail):\n   ```bash\n   # terminal only:\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frollback-authoring.sh \"$SPEC_DIR\u002Fauthoring-diff.json\"\n   ```\n   On a **recoverable** block (`needs-input` \u002F `transient`), **leave the authoring\n   writes in place** — they are recorded in the committed `authoring-diff.json`, and\n   the resume run skips Phase 3a. (Rolling back here would lose the authoring half of\n   a combined authoring+code change, since resume re-enters at Phase 3b, past 3a.)\n\n3. **Write the failure `report.md`** (same template; `verified: false`, failing\n   gate row marked `fail`, **Recovery section populated** — never the literal token,\n   use `@\u003Ckeyword>`).\n\n4. **Persist state — commit + push** (the report + resume-state + authoring-diff):\n   ```bash\n   bash ${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh \"$SPEC_DIR\" \"\u003Cblocked-phase>\"\n   ```\n   (Exit 3 = `BRANCH-ADVANCED` → a concurrent resume is in flight; stop.)\n\n5. **Then** discard the now-irrelevant *source* edits — spec files are committed, so\n   they survive; code edits are replayed from `work-plan.json` on resume (C2):\n   ```bash\n   git checkout -- .\n   ```\n\n6. **Post the classified ADO comment** (loop-safe format below) + record the flag so\n   the pipeline fallback does not double-post:\n   ```bash\n   mkdir -p .ai\u002Frun-context && touch .ai\u002Frun-context\u002Fado-comment-posted.flag\n   ```\n\n7. Clean up orchestrating flag (leave `ado-comment-posted.flag` for the pipeline).\n\n8. Exit non-zero.\n\n### Blocker taxonomy\n\n| class | examples | recovery |\n|-------|----------|----------|\n| `needs-user-input` | G1 ambiguous\u002Fno match, missing `page-url`, missing QA Basic Auth credentials, G3 low classification, authoring value drifted, `ambiguous-branch` (M1) | human replies `@\u003Ckeyword> \u003Canswer>` → resume at blocked phase |\n| `transient` | MAX_TURNS \u002F step timeout \u002F MCP unreachable \u002F network blip — **infra only** | re-trigger (any `@\u003Ckeyword>` reply, or re-run) → resume forward from `last-completed-phase` (replaying code edits if past 3b) |\n| `hard` | scope-check exceeded (>5 files \u002F >50 lines \u002F >10 writes), G7 real review blocker, **any compile failure surviving Phase 4's in-run 3× retry (M3)**, `answer-attempts` cap reached | not recoverable here → recommend re-tag `KAI-DEV-AUTOMATION` (DevAgent) |\n| `wrong-target` | Phase 0.5 repo identity guard aborted — ticket's platform\u002Fbrand\u002Fscope does not match this repo's identity | terminal — not recoverable here → human must re-trigger in the correct repo |\n\n> **Compile is `hard`, not `transient` (M3).** Phase 4 already retries compile 3×\n> against a *deterministic* edit; a cross-run re-trigger replays the identical edit,\n> so it won't compile any better. `transient` is reserved for infra (MCP\u002Fnetwork)\n> and MAX_TURNS, which a genuinely fresh run can fix.\n\n### Resume jump table\n\n| blocked at | needs from human | re-enters at | reuses \u002F notes |\n|------------|------------------|--------------|--------|\n| G1 (0 or >1 locator matches) | jcr-path \u002F exact visible text | Phase 1 (re-locate) | — |\n| `ambiguous-branch` (M1) | which branch to resume | Phase 0 discovery | — |\n| G3 (classification low) | content vs code | Phase 2 | persisted `dialog-map.json`, `file-list.json` |\n| G4 (edit confidence \u003C0.85) | file \u002F line \u002F anchor | Phase 3b (re-fill + re-apply) | `work-plan.json`. **If 3a authoring was applied: NOT rolled back (C3); reconcile via committed `authoring-diff.json`.** |\n| compile fails (survives 3× retry) | `hard` → DevAgent (M3) | — | — |\n| G7 (review blocker) | guidance, or `hard` | Phase 3b re-edit (**replay code edits first — C2**) | `work-plan.json` |\n| crashed after 3b (`in-progress`) | nothing (transient) | `last-completed-phase` + 1 | committed artifacts **+ replay code edits (C2)** |\n| crashed at\u002Fbefore 3b (`in-progress`) | nothing (transient) | `last-completed-phase` + 1 | committed artifacts (no code edits exist yet) |\n| crashed after Phase 6 PR-create (`in-progress`) | nothing (transient) | Phase 6 | **update-mode, not a second create (M5)** |\n\n### Blocker comment format (human + machine, loop-safe)\n\nThe literal trigger token **never appears** in the bot's own text (loop safety) —\nthe bot says the keyword **bare** and instructs the user to prefix it with `@`.\nThis holds **even when quoting the human's prior answer** in a re-ask (H4).\n\n```\n⚠️ \u002Fdx-simple paused on #\u003Cid> — needs your input\n\n**What failed:** G1 locator — \"Language Selector\" matched 3 elements on the page.\n**Recoverable:** yes (answer-attempt 1 of \u003Cmax-attempts>)\n**What I need:** reply on this ticket, beginning your comment with the kai-simple\n   keyword (prefixed with @ so it re-triggers me), followed by a precise target, e.g.\n   @\u003Ckeyword> jcr-path=\u002Fcontent\u002Fsite\u002F...\u002Flanguagenavigation\n\n\u003C!-- dx-simple:blocked phase=G1 reason=ambiguous-locator attempt=1 comment-cursor=\u003Cid> -->\n```\n\nThe HTML marker lets the next run parse `phase` \u002F `attempt` \u002F `comment-cursor`\ndeterministically without re-reading its own prose. Render the keyword in examples\nas `@\u003Ckeyword>` (placeholder) so the example line cannot self-trigger the hook.\n\n## Examples\n\n1. `\u002Fdx-simple 9999999` — runs the full pipeline against ticket 9999999. Pauses only on errors.\n\n2. `\u002Fdx-simple https:\u002F\u002Fdev.azure.com\u002Forg\u002Fproj\u002F_workitems\u002Fedit\u002F9999999` — same, but parses the ID from the URL.\n\n## Troubleshooting\n\n- **\"Component not found on page\"** — Locator did not match anything in Chrome snapshot. Check `page-url` (loads on QA?), `element` (matches visible element?), QA content sync (component exists on QA?). If the QA site requires HTTP Basic Auth, ensure `QA_BASIC_AUTH_USER` \u002F `QA_BASIC_AUTH_PASS` env vars are set (see `qa-basic-auth` rule) — the skill embeds them in the first navigation URL automatically.\n\n- **\"Ambiguous locator\"** — Multiple DOM matches. Use `jcr-path=...` form for unambiguous targeting, or describe the element more specifically (e.g. add the surrounding section name).\n\n- **\"Edit confidence too low (G4)\"** — Agent couldn't identify which file\u002Fline to edit unambiguously. Either the source isn't deterministic from the locator, or the change is too ambiguous for \u002Fdx-simple. Re-tag as `KAI-DEV-AUTOMATION` to use full DevAgent.\n\n- **\"Visual verify failed (G5 or G6)\"** — Either the change caused side-effects (G5 fail: page-wide change) or no visible change rendered (G6 fail: edit was a no-op). Check the visual-diff.json + the before\u002Fafter screenshots in spec dir.\n\n## Rules\n\n- **Coordinator only** — read state, dispatch subagents, never directly read codebase or write code outside the well-defined phases.\n- **Strict gate enforcement** — never proceed past a failing gate; never \"best effort\" guess.\n- **Audit every AEM write** — `audit.sh` with `AUDIT_LOG_PREFIX=simple`.\n- **Don't echo file content** — reference paths instead.\n",{"name":5,"hooks":44,"model":49,"description":7,"allowed-tools":50,"argument-hint":52},{"PreToolUse":45},[46],{"command":47,"matcher":34,"timeout":48},"${CLAUDE_PLUGIN_ROOT}\u002Fskills\u002Fdx-simple\u002Fhooks\u002Fblock-mvn-deploy.sh",5,"sonnet",[27,25,37,26,51],"agent","\u003CADO Work Item ID or full URL>","main","5c642842a92100dc7561ae0682a152a74846e186","plugins\u002Fdx-core\u002Fskills\u002Fdx-simple",3,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow","https:\u002F\u002Fgithub.com\u002Feasingthemes","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdraganfilipovic\u002F","Agentic SDLC platform in production. I build agent-ready codebases. AEM MCP Server author.","2026-11-11 09:59:48",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":74},[64,67,70],{"tool":65,"command":66},"npx","npx skills add easingthemes\u002Fdx-aem-flow --skill dx-simple",{"tool":68,"command":69},"bunx","bunx skills add easingthemes\u002Fdx-aem-flow --skill dx-simple",{"tool":71,"command":72},"pnpm","pnpm dlx skills add easingthemes\u002Fdx-aem-flow --skill dx-simple","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002FSKILL.md",true,[76,82,86,90,94,98,102,106,110,114,118,122,126,130,136,140,144,148,152,158,164,167,171,175,179,183,187,191,195,199,203,206,210],{"path":77,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":80,"sourceHtmlUrl":81},"hooks\u002Fblock-mvn-deploy.sh","SCRIPT","脚本",1310,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fhooks\u002Fblock-mvn-deploy.sh",{"path":83,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":84,"sourceHtmlUrl":85},"scripts\u002F__tests__\u002Fresume-recovery.test.sh",8826,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002F__tests__\u002Fresume-recovery.test.sh",{"path":87,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":88,"sourceHtmlUrl":89},"scripts\u002Fcheck-g4.sh",1938,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fcheck-g4.sh",{"path":91,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":92,"sourceHtmlUrl":93},"scripts\u002Fclassify-work.sh",7018,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fclassify-work.sh",{"path":95,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":96,"sourceHtmlUrl":97},"scripts\u002Fparse-simple-block.sh",3767,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fparse-simple-block.sh",{"path":99,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":100,"sourceHtmlUrl":101},"scripts\u002Fpreflight.sh",2595,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fpreflight.sh",{"path":103,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":104,"sourceHtmlUrl":105},"scripts\u002Frepo-guard.sh",3218,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frepo-guard.sh",{"path":107,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":108,"sourceHtmlUrl":109},"scripts\u002Fresume-check.sh",9202,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fresume-check.sh",{"path":111,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":112,"sourceHtmlUrl":113},"scripts\u002Frollback-authoring.sh",943,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Frollback-authoring.sh",{"path":115,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":116,"sourceHtmlUrl":117},"scripts\u002Fsave-state.sh",5440,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fsave-state.sh",{"path":119,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":120,"sourceHtmlUrl":121},"scripts\u002Fscope-check.sh",2187,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fscope-check.sh",{"path":123,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":124,"sourceHtmlUrl":125},"scripts\u002Fupdate-progress.sh",597,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fupdate-progress.sh",{"path":127,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":128,"sourceHtmlUrl":129},"scripts\u002Fvisual-diff.sh",3879,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Fscripts\u002Fvisual-diff.sh",{"path":131,"resourceType":132,"resourceTypeDesc":133,"sizeBytes":134,"sourceHtmlUrl":135},"templates\u002Freport.md.tmpl","OTHER","其他",1954,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Freport.md.tmpl",{"path":137,"resourceType":132,"resourceTypeDesc":133,"sizeBytes":138,"sourceHtmlUrl":139},"templates\u002Fresume-state.json.tmpl",283,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fresume-state.json.tmpl",{"path":141,"resourceType":132,"resourceTypeDesc":133,"sizeBytes":142,"sourceHtmlUrl":143},"templates\u002Fsimple-block.md.tmpl",2312,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fsimple-block.md.tmpl",{"path":145,"resourceType":132,"resourceTypeDesc":133,"sizeBytes":146,"sourceHtmlUrl":147},"templates\u002Fwork-plan.json.tmpl",225,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftemplates\u002Fwork-plan.json.tmpl",{"path":149,"resourceType":132,"resourceTypeDesc":133,"sizeBytes":150,"sourceHtmlUrl":151},"tests\u002Ffixtures\u002F.gitattributes",177,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002F.gitattributes",{"path":153,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":156,"sourceHtmlUrl":157},"tests\u002Ffixtures\u002Fauthoring-diff-sample.json","REFERENCE","参考文档",544,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fauthoring-diff-sample.json",{"path":159,"resourceType":160,"resourceTypeDesc":161,"sizeBytes":162,"sourceHtmlUrl":163},"tests\u002Ffixtures\u002Fbefore-blue.png","ASSET","资产文件",168,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fbefore-blue.png",{"path":165,"resourceType":160,"resourceTypeDesc":161,"sizeBytes":162,"sourceHtmlUrl":166},"tests\u002Ffixtures\u002Fbefore-red.png","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fbefore-red.png",{"path":168,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":169,"sourceHtmlUrl":170},"tests\u002Ffixtures\u002Fconfig-multi-platform.yaml",486,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fconfig-multi-platform.yaml",{"path":172,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":173,"sourceHtmlUrl":174},"tests\u002Ffixtures\u002Fconfig-single-platform.yaml",98,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fconfig-single-platform.yaml",{"path":176,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":177,"sourceHtmlUrl":178},"tests\u002Ffixtures\u002Fdialog-map.json",325,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fdialog-map.json",{"path":180,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":181,"sourceHtmlUrl":182},"tests\u002Ffixtures\u002Ffile-list.json",213,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Ffile-list.json",{"path":184,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":185,"sourceHtmlUrl":186},"tests\u002Ffixtures\u002Fraw-story-crlf.md",501,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fraw-story-crlf.md",{"path":188,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":189,"sourceHtmlUrl":190},"tests\u002Ffixtures\u002Fraw-story-hex-color.md",464,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fraw-story-hex-color.md",{"path":192,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":193,"sourceHtmlUrl":194},"tests\u002Ffixtures\u002Fraw-story-missing-block.md",126,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fraw-story-missing-block.md",{"path":196,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":197,"sourceHtmlUrl":198},"tests\u002Ffixtures\u002Fraw-story-platform.md",356,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fraw-story-platform.md",{"path":200,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":201,"sourceHtmlUrl":202},"tests\u002Ffixtures\u002Fraw-story-valid.md",516,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fraw-story-valid.md",{"path":204,"resourceType":160,"resourceTypeDesc":161,"sizeBytes":173,"sourceHtmlUrl":205},"tests\u002Ffixtures\u002Fsub-filtered.png","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fsub-filtered.png",{"path":207,"resourceType":154,"resourceTypeDesc":155,"sizeBytes":208,"sourceHtmlUrl":209},"tests\u002Ffixtures\u002Fwork-plan-mixed.json",931,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Ffixtures\u002Fwork-plan-mixed.json",{"path":211,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":212,"sourceHtmlUrl":213},"tests\u002Frun-tests.sh",30928,"https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-simple\u002Ftests\u002Frun-tests.sh",[215,237,258,279,295,308],{"skillNo":216,"name":217,"nameZh":6,"description":218,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":219,"categories":220,"keywords":221,"publishedAt":39},"01M6H48W19F46A7B7TSSQAMYRW","dx-hub-status","Show hub dispatch status — which repos are running, done, blocked or failed. Use for 'hub status', 'what's running'.","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-hub\u002Fskills\u002Fdx-hub-status\u002FSKILL.md",[],[19,222,223,224,225,226,227,228,229,230,231,232,34,233,234,27,26,235,236],"dx hub status","hub","status","hub status","which","show","for","ticket","clean","use","done","id","dispatch","repos","grep",{"skillNo":238,"name":239,"nameZh":6,"description":240,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":241,"categories":242,"keywords":243,"publishedAt":39},"01M6H48R6ZH7YMVPMVWMMWT3RA","dx-hub-init","Initialize a hub directory for multi-repo orchestration. Discovers sibling repos, merges configs, validates CLI compatibility. Use when setting up hub mode for the first time. Trigger on \"init hub\", \"set up hub\", \"multi-repo setup\".","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-hub\u002Fskills\u002Fdx-hub-init\u002FSKILL.md",[],[244,245,19,246,223,247,248,249,250,251,252,253,254,255,34,256,257,27,26,25],"init","hub init","dx hub init","SET","time","mode","default","up","repo setup","orchestration","configure","multi-repo","cli","directory",{"skillNo":259,"name":260,"nameZh":6,"description":261,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":262,"categories":263,"keywords":269,"publishedAt":39},"01M6H48K267CWEF8YNN3Y49BVT","dx-hub-dispatch","Dispatch a ticket to several repos — open an independent Claude session per repo in VS Code terminals. Use for 'hub dispatch', 'work on ticket across repos'.","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-hub\u002Fskills\u002Fdx-hub-dispatch\u002FSKILL.md",[264],{"categoryNo":265,"categoryName":266,"taxonomyType":267,"taxonomyTypeDesc":268},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[19,270,234,223,271,228,272,273,274,229,275,276,231,35,34,277,33,278,233,32],"dx hub dispatch","ado","repo","work on","session","open","MCP","vscode","across",{"skillNo":280,"name":281,"nameZh":6,"description":282,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":283,"categories":284,"keywords":285,"publishedAt":39},"01M6H48FAWKTT6AAWT3H80ZKE0","dx-hub-config","View and edit hub configuration — add repos, adjust terminal delay. Use to manage hub settings. Trigger on \"hub config\", \"add repo to hub\", \"terminal delay\".","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-hub\u002Fskills\u002Fdx-hub-config\u002FSKILL.md",[],[19,286,223,287,227,288,272,289,290,291,292,231,254,34,293,27,294,26,25,235],"dx hub config","config","view","manage","trigger","true","adjust","terminal","settings",{"skillNo":296,"name":297,"nameZh":6,"description":298,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":299,"categories":300,"keywords":301,"publishedAt":39},"01M6H48BNMYEJY6CCWPYHNQDAT","dx-discover-repos","Resolve which repos a work item touches (explicit directive → simple block → cross-repo table → LLM). Used by the KAI-HUB router to fan an agent out across repos; 'which repos'.","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-hub\u002Fskills\u002Fdx-discover-repos\u002FSKILL.md",[],[302,19,303,235,226,272,278,304,233,305,32,20,223,306,49,307],"discover","dx discover repos","router","directive","block","table",{"skillNo":309,"name":310,"nameZh":6,"description":311,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":312,"categories":313,"keywords":315,"publishedAt":39},"01M6H487QA1FDSBT1HTZGW4HAQ","dx-upgrade","Upgrade a consumer project to the installed plugin versions — run dx-doctor, fix stale and missing files, list manual actions. Use after upgrading plugins.","https:\u002F\u002Fgithub.com\u002Feasingthemes\u002Fdx-aem-flow\u002Fblob\u002Fmain\u002Fplugins\u002Fdx-core\u002Fskills\u002Fdx-upgrade\u002FSKILL.md",[314],{"categoryNo":265,"categoryName":266,"taxonomyType":267,"taxonomyTypeDesc":268},[316,19,317,318,319,37,320,321,322,323,27,26,25,324,325,326,327,231,328,329],"upgrade","dx upgrade","project","actions","fix","files","auto","run","list","doctor","manual","missing","plugin","plugins"]