misterpropre 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: {{INSTALL_NAME}}
3
+ description: "Triage open dependency-update PRs from Renovate and Dependabot on a single {{ORG}} GitHub repo. Converts drafts to ready, waits for fresh CI, auto-merges only low-risk green patch/minor updates after a changelog/usage breakage analysis, runs every major through a fix engine and hands it off, routes risky updates to a human via NOTIFY events, and fixes failing PRs until CI is green. Use when: purge renovate PRs, merge dependabot PRs, clean up dependency PRs. Invocation: /{{INSTALL_NAME}} <repo-name>"
4
+ user-invocable: true
5
+ ---
6
+
7
+ <!-- {{MANAGED_MARKER}} — generated from misterpropre's bundled template. Edits here are overwritten by `mrp skills install`. -->
8
+
9
+ # Dependabot Purge (misterpropre)
10
+
11
+ Triage every open Renovate/Dependabot dependency PR on **one** `{{ORG}}` repo: auto-merge the safe ones, hand the rest to a human via NOTIFY events, and fix failing ones where feasible.
12
+
13
+ ## Runtime contract (read first)
14
+
15
+ You are invoked **by the misterpropre orchestrator**:
16
+ - Your **current working directory is the repo clone**. Use it for `gh` operations. For fixing failing PRs, create **isolated git worktrees** (see `references/fix-matrix.md`) — **never** `git checkout`/`switch` the clone itself (it may be dirty / on a feature branch).
17
+ - **Notifications are events, not Telegram.** On every needs-human outcome, print a `MISTERPROPRE_NOTIFY_V1` line (format below); the CLI dispatches it to the configured channel. Do NOT call any telegram script.
18
+ - End with exactly one `MISTERPROPRE_RESULT_V1` summary line. The orchestrator reconciles the real outcome from GitHub; your line is a hint.
19
+
20
+ The skill's bundled scripts/references are alongside this file; call them by absolute path via `$SKILL_DIR` (this skill's directory).
21
+
22
+ ## Golden rules
23
+
24
+ - **Operate autonomously.** Never ask the user to approve a per-PR decision — the merge-policy table is authoritative; apply it and act.
25
+ - **Never disturb the clone's working tree.** Read-only steps use `gh … --repo`; the fix path uses an isolated worktree.
26
+ - **Auto-merge requires ALL of:** PR was already green (we didn't touch it) AND bump is patch/minor (a `0.x`→`0.y` bump counts as **major**) AND the breakage analysis is clean.
27
+ - **Never auto-merge** a major, a risky bump, or any PR the skill modified — those go to a human.
28
+ - **Every major is analyzed + run through the fix engine, then handed off** (never auto-merged); never punt a major with "go read the release notes".
29
+ - **Never count a draft's stale `skipped` checks as passing.** Wait for fresh post-conversion runs.
30
+ - Comment **only** when NOT merging. Auto-merges are silent.
31
+ - Skip any PR already carrying `purge:needs-human` (idempotent re-runs).
32
+
33
+ ## Step 0: Preconditions
34
+
35
+ Run in order; stop on first failure.
36
+ 1. **npm auth** — `npm whoami`. On `401`/error, **STOP**: private `{{PKG_SCOPE}}/*` packages can't install. Emit `MISTERPROPRE_RESULT_V1 {"v":1,"phase":"purge","kind":"FAIL","message":"npm auth"}` and report.
37
+ 2. **npm-only gate** — require a `package.json` in the clone. If none, STOP ("non-npm repo, out of scope"), emit a FAIL result.
38
+ 3. **Base branch** — `{{BASE_BRANCH}}` if it exists, else the repo default. `git fetch origin {base}`. Do **not** checkout the clone.
39
+ 4. **Classify repo type** — suffix `-back`=backend, `-front`=frontend, `-qa`=qa, else infer from scripts. Selects the matrix in `references/fix-matrix.md`.
40
+ 5. **Label** — ensure the label exists: `gh label create purge:needs-human --repo {{ORG}}/{repo} --color B60205 --description "needs human review" 2>/dev/null || true`.
41
+
42
+ ## Step 1: Discover PRs
43
+
44
+ ```bash
45
+ gh pr list --repo {{ORG}}/{repo} --author "app/renovate" --state open --json number,title,isDraft,headRefName,labels,createdAt
46
+ gh pr list --repo {{ORG}}/{repo} --author "app/dependabot" --state open --json number,title,isDraft,headRefName,labels,createdAt
47
+ ```
48
+ Skip any PR labeled `purge:needs-human`. Merge both lists, **sort oldest→newest**, process **sequentially**.
49
+
50
+ ## Step 2: Per-PR pipeline (oldest first)
51
+
52
+ **A0. Hard skip-gate.** Re-fetch the PR's current `state`+`labels` now (the Step 1 list goes stale within a run):
53
+ - `state` != `OPEN` → **skip** (merged/closed/superseded). Record `skipped-closed`, continue. (Closed PRs have UNKNOWN mergeable forever — any wait loop hangs.)
54
+ - labels include `purge:needs-human` → **skip** entirely. Record `skipped-labeled`, continue. The label is final.
55
+
56
+ **A. Make ready.** If draft: `gh pr ready {n} --repo {{ORG}}/{repo}` (triggers fresh CI).
57
+
58
+ **B. Wait for fresh checks.** `bash "$SKILL_DIR/scripts/wait_checks.sh" {{ORG}}/{repo} {n}`:
59
+ - `PASS` → D. `FAIL` → E (fix path).
60
+ - `NO_CHECKS`/`TIMEOUT` → **environmental**, not the PR's fault. Add ONE comment documenting it; do **NOT** label or NOTIFY. Record `no-CI-deferred`; the next run retries.
61
+
62
+ **C. Determine bump type** from the title/version delta: `patch` | `minor` | `major`. Any `0.x`→`0.y` is **major**.
63
+
64
+ **D. Checks GREEN:**
65
+ - **patch/minor** → run the **breakage analysis** (Step 3): clean → **auto-merge** (silent): `gh pr review {n} --repo {{ORG}}/{repo} --approve` then `gh pr merge {n} --repo {{ORG}}/{repo} --squash --delete-branch`. risky → comment + label + NOTIFY.
66
+ - **major** → **Major pipeline (Step 3M)**. Never auto-merge.
67
+
68
+ **E. Checks FAILING / conflicting:**
69
+ - **major** → **Major pipeline (Step 3M)**.
70
+ - **patch/minor** → **fix path** (`references/fix-matrix.md`): isolated worktree, reproduce, fix (mechanical + code adaptation), rebase onto `{base}`, ≤3 push/CI cycles. CI green → NOTIFY "fix ready" + label (no auto-merge). Can't fix → comment + label + NOTIFY "couldn't fix".
71
+
72
+ **F. After any merge:** `git fetch origin {base}`. Re-verify each remaining PR before deciding; route stale/conflicting ones through E.
73
+
74
+ ## Step 3: Breakage analysis (green patch/minor only)
75
+
76
+ Merge only if **both** are clean: (1) **Changelog** — WebFetch the package's release notes for the version range; scan for breaking-change language. (2) **Usage grep** — grep the repo for how the API is used. Any doubt → **risky** → hand off.
77
+
78
+ **Internal `{{PKG_SCOPE}}/*` packages are TRUSTED on patch/minor** (you own them; semver-respecting) — a missing public changelog is NOT a risk signal; let them flow to auto-merge. Internal **majors** still go through the Major pipeline + always hand off.
79
+
80
+ ## Step 3M: Major pipeline (analysis + verify + fix)
81
+
82
+ Runs for **every major**. Never auto-merged. Reuses the fix engine (`references/fix-matrix.md`).
83
+ 1. **Breakage analysis (always)** — WebFetch release notes across the **full** version range; list each breaking change; grep our code for whether it reaches us (npm → imports/APIs/config; GitHub Action → workflow steps; Docker → Dockerfile).
84
+ 2. **Worktree + local checks** — isolated worktree, `npm install`, run the matrix.
85
+ 3. **Decide:** local + CI green → **verified-good** (comment lists breakages checked + behavioral risks to watch) + label + NOTIFY "verified — review & merge". Red → **fix** (≤3 cycles): reached green → **fixed** (comment per-breakage) + label + NOTIFY; budget exhausted/needs decision → **couldn't-fix** + label + NOTIFY. **Only fix what a check actually catches** — behavioral risks go in the comment, not a speculative edit.
86
+
87
+ Comment shape (analysis first, never an ask): enumerate each breakage as ✓ no impact / 🔧 fixed / ⚠ watch during review / ⛔ blocking, then a recommendation. A comment that says "go read the release notes" is a bug.
88
+
89
+ ## Merge policy (decision table)
90
+
91
+ | PR state | Bump | Analysis | Action |
92
+ |---|---|---|---|
93
+ | Green, untouched | patch/minor | clean | **Auto-merge** (approve + squash), silent |
94
+ | Green, untouched, internal `{{PKG_SCOPE}}/*` | patch/minor | n/a | **Auto-merge**, silent — trusted |
95
+ | Green, untouched | patch/minor | risky | Comment + label + NOTIFY |
96
+ | major — local+CI green | analysis | **Verified**: comment + label + NOTIFY. Never merge |
97
+ | major — we fixed it, CI green | analysis + fix | **Fixed**: comment + label + NOTIFY. Never merge |
98
+ | major — can't fix | analysis + attempt | **Couldn't-fix**: comment + label + NOTIFY. Never merge |
99
+ | Fixed by us, CI green | any | — | Comment + label + NOTIFY "fix ready" |
100
+ | No CI / timeout | any | — | **Comment only** — no label, no NOTIFY (environmental) |
101
+ | Already labeled | — | — | **Skip** |
102
+ | `ERESOLVE` peer-dep, prerequisite bot PR exists | any | — | **Defer** `deferred-pending-#P` (fix-matrix.md) |
103
+
104
+ ## Step 4: NOTIFY events
105
+
106
+ Fire on **every needs-human outcome** (auto-merges stay silent): fix-ready, couldn't-fix, every major (verified/fixed/couldn't-fix), risky patch/minor. Do **NOT** fire for `no-CI-deferred`, `skipped-labeled`, `skipped-closed`. Print one line per event:
107
+
108
+ ```
109
+ MISTERPROPRE_NOTIFY_V1 {"v":1,"repo":"{{ORG}}/{repo}","pr":N,"url":"<pr-url>","outcome":"<outcome>","package":"<pkg>","bump":"<patch|minor|major>","message":"<one-line: what + action needed>"}
110
+ ```
111
+
112
+ ## Step 5: Iterate (catch newly-opened bot PRs)
113
+
114
+ A run can take hours; bots open PRs meanwhile. Bounded loop, **MAX_ITERATIONS = 3**: after Step 2, re-run Step 1 discovery; filter out non-OPEN / labeled / already-processed PRs; if empty → stable, go to Step 6; else process the new ones (Step 2) and loop (≤3). If the cap is hit with PRs still pending, NOTIFY once ("iteration cap reached, re-run") and proceed.
115
+
116
+ ## Step 6: Final report + result line
117
+
118
+ Print a per-PR outcome table (PR #, package, bump, iteration #, outcome ∈ {merged, fix-ready, major-verified, major-fixed, major-couldnt-fix, risky-handoff, no-CI-deferred, couldn't-fix, skipped-labeled, skipped-closed, deferred-pending-#P}) plus counts. Then emit **exactly one** final line:
119
+
120
+ ```
121
+ MISTERPROPRE_RESULT_V1 {"v":1,"phase":"purge","kind":"DONE","counts":{"merged":M,"handedOff":H,"stillOpen":S}}
122
+ ```
123
+ - `merged` = PRs auto-merged this run. `handedOff` = PRs labeled `purge:needs-human` this run. `stillOpen` = remaining open bot PRs not handed off (e.g. no-CI-deferred).
124
+ - `kind` = `FAIL` only if the run could not complete (precondition failure / fatal error); otherwise `DONE`.
125
+
126
+ ## Anti-patterns
127
+
128
+ - NEVER auto-merge a major / `0.x`-minor / risky bump, or any PR the skill modified.
129
+ - **NEVER punt a major** with "review the release notes" — enumerate breakages + verified/fixed/blocked per item.
130
+ - NEVER speculatively edit a green major to satisfy an untested changelog note — raise it as a "watch" in the comment.
131
+ - NEVER treat a draft's stale `skipped` runs as passing; NEVER merge while any check is failing/pending.
132
+ - NEVER comment on the auto-merge path. NEVER add `purge:needs-human` without a same-step comment explaining why.
133
+ - NEVER `git checkout`/`switch` the clone; fixes happen in an isolated worktree.
134
+ - NEVER use blanket workarounds (`legacy-peer-deps`, `--force`, `strict-ssl=false`) without explicit per-PR justification; prefer deferring to a prerequisite bot PR.
135
+ - NEVER label on an environmental CI failure (`NO_CHECKS`/`TIMEOUT`) — comment only, retry next run.
136
+ - NEVER `npm audit --fix`; NEVER hand-edit `package-lock.json`.
137
+ - ALWAYS end with exactly one `MISTERPROPRE_RESULT_V1` line.
@@ -0,0 +1,106 @@
1
+ # Fix path — validation matrix & dependency-fix decision tree
2
+
3
+ Load this when a PR's checks are FAILING or the PR conflicts with the base branch (Step 2-E).
4
+
5
+ ## Goal & budget
6
+
7
+ Make the PR's CI green, then **hand off to a human** (never auto-merge a PR we modified). Budget: iterate **locally** until the validation matrix passes (minimize CI churn), then push and allow **up to 3 push/CI cycles**. If still red after that, or the fix needs a human product/code decision, give up and hand off.
8
+
9
+ **Majors enter this engine proactively** (SKILL.md Step 3M) — driven by the breakage analysis, not only by a red CI. For a green major you still set up the worktree and run the matrix to **verify**; only a failing local check or CI run is a reproduced failure you then fix. **Never speculatively edit** to satisfy a changelog note that nothing tests — raise it as a "watch during review" risk in the handoff comment instead. Artifact-aware: a **GitHub Action** runs in GitHub CI (CI is its real test); a **Docker base image** verifies via `docker build`; an **npm package** verifies via the local matrix + CI.
10
+
11
+ ## Validation matrix (run in order, stop on first failure)
12
+
13
+ Check each script exists in `package.json` first; if missing, mark `SKIPPED` and continue.
14
+
15
+ | Step | Script | backend | frontend | qa |
16
+ |------|--------|:-------:|:--------:|:--:|
17
+ | 1 | `npm run lint` | yes | yes | yes |
18
+ | 2 | `npm run build` | yes | yes | yes |
19
+ | 3 | `npm run type-check` | yes | yes | yes |
20
+ | 4 | `npm test` | yes | yes | yes |
21
+ | 5 | `npm run test:integ` | yes | no | no |
22
+
23
+ **`test:integ` requires Docker.** Before step 5: `bash "$SKILL_DIR/scripts/ensure_docker.sh"`. Run `test:integ` **locally**; if Docker cannot start, mark it **BLOCKED** and hand off.
24
+
25
+ A locally-green matrix is the gate to push. The real CI is the final authority — `bash "$SKILL_DIR/scripts/wait_checks.sh" {{ORG}}/{repo} {n}` judges it after each push (`$SKILL_DIR` = this skill's base directory).
26
+
27
+ ## Isolated worktree (always)
28
+
29
+ Never `gh pr checkout` / switch branches in the clone — it may be dirty or on a feature branch. Work in a dedicated worktree (you start in the repo clone, your cwd):
30
+
31
+ ```bash
32
+ git fetch origin {pr-head-branch}
33
+ git worktree add -B {pr-head-branch} ~/.cache/misterpropre/purge-wt/{repo}-{n} origin/{pr-head-branch}
34
+ cd ~/.cache/misterpropre/purge-wt/{repo}-{n}
35
+ ```
36
+
37
+ (`{pr-head-branch}` = the PR's head ref, from `gh api repos/{{ORG}}/{repo}/pulls/{n} --jq .head.ref`.) Do all fix work here. **Clean up when done** (success, give-up, or error):
38
+
39
+ ```bash
40
+ cd - # back to the clone
41
+ git worktree remove ~/.cache/misterpropre/purge-wt/{repo}-{n} --force
42
+ ```
43
+
44
+ ## Cross-PR peer-dep prerequisite detection (defer, do not patch)
45
+
46
+ When `npm install`/`npm ci` fails with **`ERESOLVE`** naming a `peerDependency` conflict (e.g. *"{{PKG_SCOPE}}/design-system@13.25.0 requires i18next ^25.4.2 but i18next@26 is installed"*), **STOP** — do NOT reach for `--legacy-peer-deps`. Ask: is the missing version already on its way in another bot PR?
47
+
48
+ 1. **Identify the blocking package** — the one owning the unhappy `peerDependency` (read it from the npm error, e.g. `node_modules/{{PKG_SCOPE}}/design-system`).
49
+ 2. **Search the open bot PR list** for a PR upgrading that exact package to a compatible version:
50
+ ```bash
51
+ gh pr list --repo {{ORG}}/{repo} --state open \
52
+ --search '"{{PKG_SCOPE}}/design-system" in:title' \
53
+ --json number,title,author,headRefName,labels
54
+ ```
55
+ Filter to **bot PRs only** (`author.login` ∈ `{renovate[bot], dependabot[bot], app/renovate, app/dependabot}`).
56
+ 3. **If a bot prerequisite PR (`P`) EXISTS** → **defer the current PR** (`C`). Record `deferred-pending-#P`; do NOT comment/label/NOTIFY yet. Tear down `C`'s worktree, continue. Revisit `C` only after `P` is processed: if `P` merged and `C`'s CI now passes → proceed (D); else hand off `C` naming the chain (`#C blocked by #P`).
57
+ 4. **If NO bot prerequisite PR exists** → genuine block: comment naming the blocking peer + missing version, label `purge:needs-human`, NOTIFY "couldn't fix — peer dep conflict, no prerequisite PR". **Still do not push `legacy-peer-deps`.**
58
+
59
+ **Cycle detection.** Maintain `deferred[C] = P`; if the chain loops, abort participants with one NOTIFY ("circular peer-dep deferral") + label each.
60
+
61
+ ## Per-PR diagnosis is mandatory (no recipe sharing)
62
+
63
+ **Each PR is its own diagnosis.** Always: (1) clean isolated worktree off THAT PR's branch, (2) `npm install` + run the matrix to **reproduce THIS PR's failure**, (3) confirm the bump is the cause (`npm install {pkg}@{old} --no-save`, re-run), (4) only then design a fix. A fix that worked on an earlier PR is a **hypothesis**, never a recipe.
64
+
65
+ ### Banned blanket workarounds (require explicit per-PR justification)
66
+
67
+ Do NOT introduce as a default fix: `legacy-peer-deps` / `npm install --legacy-peer-deps`, `npm install --force`, `strict-ssl=false`, `ignore-scripts=true`, `--passWithNoTests` / `--bail=false`. If one is the ONLY way to make CI pass, treat it as **needs a human decision** — comment what failed and why this masks vs fixes, hand off. **Do not push the workaround.**
68
+
69
+ ## Reproduce → fix loop
70
+
71
+ 1. In the worktree: `npm install` — clean baseline.
72
+ 2. Run the matrix to reproduce. For a major, run `type-check` first. Confirm the bump is the cause before editing.
73
+ 3. Read CI logs for parity if the matrix and CI disagree (`gh run view --log-failed`).
74
+ 4. Apply the smallest fix (mechanical first, then code adaptation). Re-run. Repeat until green.
75
+ 5. **Give-up trigger:** if the fix needs a domain decision (e.g. changing shared `{{PKG_SCOPE}}/types`, altering runtime behavior) or sprawls beyond a safe mechanical change, **stop and hand off** — including any fix that would need a banned workaround.
76
+
77
+ ## Dependency-fix decision tree (mechanical)
78
+
79
+ Try in order, stop at first that works:
80
+ 1. **Lockfile-only:** `npm update {pkg}`; re-check `npm ls {pkg}`. Commit `package-lock.json` only.
81
+ 2. **Parent update:** bump the direct parent in `package.json` (`npm info {parent} version`), `npm install`, re-verify.
82
+ 3. **npm override (last resort):** add to `overrides` with a `>=` range (never pinned); scope for multi-major lines. Verify install; revert on peer conflict.
83
+
84
+ NEVER `npm audit --fix`. NEVER hand-edit `package-lock.json`. Verify `npm install` after each change. One logical change at a time.
85
+
86
+ ## Code adaptation (breaking APIs)
87
+
88
+ When a bump fails `build`/`type-check`/`test` from an API change, **edit the application code** to adapt (call sites, renamed imports, signatures, types, mocks, fixtures). Keep changes minimal + idiomatic. If adaptation balloons into a refactor or needs a product decision, stop and hand off.
89
+
90
+ ## Conflict resolution
91
+
92
+ If the PR conflicts with `{base}` (default `{{BASE_BRANCH}}`): `git fetch origin {base}` then `git rebase origin/{base}` on the bot branch; resolve (favor the bot's intended version, keep base's other changes); `npm install`, run the matrix.
93
+
94
+ ## Push & CI cycles
95
+
96
+ Once the matrix is locally green:
97
+ 1. `git add -A && git commit -m "fix(deps): adapt to {package} {version}"` (or `--amend` across cycles).
98
+ 2. `git push` (force-push only after a rebase: `git push --force-with-lease`).
99
+ 3. `bash "$SKILL_DIR/scripts/wait_checks.sh" {{ORG}}/{repo} {n}` → `PASS`=success; `FAIL`=fix more (max 3 cycles); `NO_CHECKS`/`TIMEOUT`=give-up endpoint.
100
+
101
+ Do not trigger a bot rebase after pushing your fix — it could clobber it.
102
+
103
+ ## Endpoints
104
+
105
+ - **Success (CI green):** emit a NOTIFY handoff line (`MISTERPROPRE_NOTIFY_V1`, "fix ready"), add the `purge:needs-human` label, leave the PR open. **Do not merge.**
106
+ - **Give up (still red after 3 cycles, or needs a human decision):** add `purge:needs-human`, comment a brief reason, emit a NOTIFY "couldn't fix". Leave the branch pushed (the human inherits progress).
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # Ensure the Docker daemon is running — `npm run test:integ` depends on it (local containers).
3
+ # Launches Docker Desktop (macOS) or the docker service (Linux) if needed, then waits.
4
+ # Exit 0 when Docker is ready; exit 1 if it cannot be started (caller: mark test:integ BLOCKED and hand off).
5
+ set -uo pipefail
6
+
7
+ if ! command -v docker >/dev/null 2>&1; then
8
+ echo "ERROR: docker CLI not found — cannot run integ tests." >&2
9
+ exit 1
10
+ fi
11
+
12
+ if docker info >/dev/null 2>&1; then
13
+ echo "Docker already running"
14
+ exit 0
15
+ fi
16
+
17
+ echo "Docker not running — launching..."
18
+ case "$(uname)" in
19
+ Darwin) open -a Docker 2>/dev/null || open -a "Docker Desktop" 2>/dev/null || true ;;
20
+ *) (sudo systemctl start docker 2>/dev/null || sudo service docker start 2>/dev/null) || true ;;
21
+ esac
22
+
23
+ # Wait up to ~150s for the daemon to accept connections.
24
+ for _ in $(seq 1 75); do
25
+ if docker info >/dev/null 2>&1; then
26
+ echo "Docker is ready"
27
+ exit 0
28
+ fi
29
+ sleep 2
30
+ done
31
+
32
+ echo "ERROR: Docker did not become ready within ~150s. Start it manually and re-run." >&2
33
+ exit 1
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ # Wait for a PR's fresh CI checks and print a single verdict.
3
+ # Usage: wait_checks.sh <owner/repo> <pr-number> [timeout-sec] [interval-sec]
4
+ # Verdict (last line): PASS | FAIL | NO_CHECKS | TIMEOUT
5
+ #
6
+ # Uses the REST API (gh api) so it is immune to the `gh pr checks` cwd quirk.
7
+ # Combines Check Runs (filter=latest) + the legacy combined Status.
8
+ # `skipped` check runs are ignored (a draft gates CI jobs on draft==false; we wait
9
+ # for the fresh post-ready runs). PASS requires >=1 real success and nothing
10
+ # failed/pending.
11
+ set -uo pipefail
12
+
13
+ REPO="${1:?owner/repo required}"
14
+ PR="${2:?pr number required}"
15
+ TIMEOUT="${3:-1200}" # 20 min
16
+ INTERVAL="${4:-30}"
17
+ GRACE="${GRACE:-25}" # let a freshly-triggered run register before first verdict
18
+
19
+ num() { local v; v="$(cat)"; [[ "$v" =~ ^[0-9]+$ ]] && echo "$v" || echo 0; }
20
+
21
+ SHA="$(gh api "repos/${REPO}/pulls/${PR}" --jq '.head.sha' 2>/dev/null)"
22
+ if [[ -z "${SHA:-}" ]]; then
23
+ echo "ERROR: could not resolve head sha for ${REPO}#${PR}" >&2
24
+ echo "NO_CHECKS"; exit 0
25
+ fi
26
+
27
+ echo "Waiting for checks on ${REPO}#${PR} (sha ${SHA:0:7}); timeout ${TIMEOUT}s..." >&2
28
+ sleep "$GRACE"
29
+
30
+ deadline=$(( $(date +%s) + TIMEOUT ))
31
+ last_pending=0
32
+
33
+ while :; do
34
+ cr="$(gh api "repos/${REPO}/commits/${SHA}/check-runs?filter=latest" 2>/dev/null)"
35
+ st="$(gh api "repos/${REPO}/commits/${SHA}/status" 2>/dev/null)"
36
+ cr="${cr:-{\"check_runs\":[]\}}"
37
+ st="${st:-{\"statuses\":[]\}}"
38
+
39
+ pend_cr=$(printf '%s' "$cr" | jq '[.check_runs[]? | select(.status!="completed")] | length' 2>/dev/null | num)
40
+ pend_st=$(printf '%s' "$st" | jq '[.statuses[]? | select(.state=="pending")] | length' 2>/dev/null | num)
41
+ fail_cr=$(printf '%s' "$cr" | jq '[.check_runs[]? | select(.conclusion as $c | ["failure","timed_out","cancelled","action_required","stale"] | index($c))] | length' 2>/dev/null | num)
42
+ fail_st=$(printf '%s' "$st" | jq '[.statuses[]? | select(.state=="failure" or .state=="error")] | length' 2>/dev/null | num)
43
+ ok_cr=$(printf '%s' "$cr" | jq '[.check_runs[]? | select(.conclusion=="success" or .conclusion=="neutral")] | length' 2>/dev/null | num)
44
+ ok_st=$(printf '%s' "$st" | jq '[.statuses[]? | select(.state=="success")] | length' 2>/dev/null | num)
45
+
46
+ P=$(( pend_cr + pend_st )); F=$(( fail_cr + fail_st )); S=$(( ok_cr + ok_st ))
47
+ last_pending=$P
48
+
49
+ if [ "$F" -gt 0 ]; then verdict=FAIL; break
50
+ elif [ "$P" -gt 0 ]; then : # still running -> keep waiting
51
+ elif [ "$S" -gt 0 ]; then verdict=PASS; break
52
+ fi # else: no checks yet / only skipped -> keep waiting
53
+
54
+ if [ "$(date +%s)" -ge "$deadline" ]; then
55
+ [ "$P" -gt 0 ] && verdict=TIMEOUT || verdict=NO_CHECKS
56
+ break
57
+ fi
58
+ sleep "$INTERVAL"
59
+ done
60
+
61
+ # Human-readable summary to stderr
62
+ printf '%s' "$cr" | jq -r '.check_runs[]? | " - \(.name): \(.status)/\(.conclusion // "—")"' 2>/dev/null >&2
63
+ printf '%s' "$st" | jq -r '.statuses[]? | " - \(.context): \(.state)"' 2>/dev/null >&2
64
+
65
+ echo "$verdict"
66
+ [ "$verdict" = PASS ] && exit 0 || exit 0
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: {{INSTALL_NAME}}
3
+ description: "Fix Dependabot security alerts of ALL severities (Critical, High, Medium, Low) on a GitHub repository under the {{ORG}} org. Runs inside an isolated git worktree provided by misterpropre, implements targeted fixes (version bumps, parent updates, npm overrides), verifies each fix, opens/updates a PR, and emits a machine-readable result line. Use when: fix dependabot alerts, fix security vulnerabilities, patch npm dependencies, resolve CVEs. Invocation: /{{INSTALL_NAME}} <repo-name>"
4
+ user-invocable: true
5
+ ---
6
+
7
+ <!-- {{MANAGED_MARKER}} — generated from misterpropre's bundled template. Edits here are overwritten by `mrp skills install`; change the template in the misterpropre package instead. -->
8
+
9
+ # Security Dependabot Fix (misterpropre)
10
+
11
+ Fix open Dependabot security alerts of **all severities** for a `{{ORG}}` org repository. Each fix is one commit; iterate highest-severity first so the most important fixes land earliest in history.
12
+
13
+ ## Runtime contract (read first — this differs from a standalone skill)
14
+
15
+ You are invoked **by the misterpropre orchestrator**, which has already prepared your environment:
16
+
17
+ - Your **current working directory is an isolated git worktree** for the target repo, already checked out on the security-fix branch (`fix/dependabot-security-<date>`, branched from `{{BASE_BRANCH}}`), or on an existing automation PR's branch (update mode).
18
+ - **Do NOT** `git clone`, `cd` elsewhere, `git fetch`/`checkout`/`switch`, or create/delete branches. Never touch `~/` checkouts. Operate **only** in the current directory on the current branch. The orchestrator owns branch lifecycle and worktree teardown.
19
+ - When done you MUST print exactly one **machine-readable result line** (see the last section). The orchestrator's authoritative outcome comes from GitHub + git state; your line is a validated hint.
20
+
21
+ ## Input
22
+
23
+ - **Argument**: `repo-name` — the GitHub repository name under the `{{ORG}}` org.
24
+ - The org is always `{{ORG}}`.
25
+
26
+ ---
27
+
28
+ ## Workflow
29
+
30
+ ### Step 0: Verify npm Authentication
31
+
32
+ The `{{ORG}}` org uses private `{{PKG_SCOPE}}/*` packages that require a valid npm token; without it `npm install` 404s and the run is blocked.
33
+
34
+ ```bash
35
+ npm whoami
36
+ ```
37
+
38
+ - Username printed → continue.
39
+ - `401`/error → **STOP**, emit `MISTERPROPRE_RESULT_V1 {"v":1,"phase":"fix","kind":"FAIL","message":"npm auth"}` and report that npm auth must be refreshed.
40
+
41
+ ### Step 1: Fetch Alerts
42
+
43
+ ```bash
44
+ gh api "repos/{{ORG}}/{repo}/dependabot/alerts?severity=critical,high,medium,low&state=open&per_page=100" --paginate \
45
+ --jq '[.[] | {number, pkg: .dependency.package.name, ecosystem: .dependency.package.ecosystem, manifest: .dependency.manifest_path, relationship: .dependency.relationship, severity: .security_advisory.severity, summary: .security_advisory.summary, cve: .security_advisory.cve_id, ghsa: .security_advisory.ghsa_id, patched: .security_vulnerability.first_patched_version.identifier, range: .security_vulnerability.vulnerable_version_range}]'
46
+ ```
47
+
48
+ Record the initial alert count broken down by severity. If zero alerts → report "No open security alerts found", emit the `NO_ALERTS` result line, and stop. Print a summary table before proceeding.
49
+
50
+ ### Step 2: Prepare (worktree-aware)
51
+
52
+ You are already in the worktree on the correct branch — **do not change branches**. Just establish a clean baseline:
53
+
54
+ ```bash
55
+ npm install
56
+ ```
57
+
58
+ If install fails on a private `{{PKG_SCOPE}}/*` 404 → that's an npm-auth problem (Step 0). Otherwise note the error for the report.
59
+
60
+ ### Step 2.5: Classify Repo Type
61
+
62
+ Select the validation matrix in Step 6:
63
+
64
+ - Suffix `-back` → **backend**; `-front` → **frontend**; `-qa` → **qa**.
65
+ - No matching suffix → classify by inspecting `package.json` scripts (presence of `test:integ`/`lint:cfn` ⇒ backend; `lint-style` ⇒ frontend); if still ambiguous treat as **backend** and note the assumption in the report.
66
+
67
+ ### Step 2.6: Capture the validation baseline (before any fixes)
68
+
69
+ Run the Step 6 validation matrix **now**, on the unmodified worktree, using the Step 6 execution rules (`CI=true`, `timeout`, jest `--forceExit`, Docker-ensure before `test:integ`). Record each check's PASS/FAIL and, for `type-check`, the **error count**. This is the **BASELINE**. Step 6 blocks only on checks that get **worse** than this baseline.
70
+
71
+ ### Step 3: Classify Alerts
72
+
73
+ Group by severity (critical → high → medium → low), then by package. Process top-down. **Deduplicate**: one fix per package; track which alert numbers each fix resolves.
74
+
75
+ **Outcome classes:** `FIXED`, `UNFIXABLE` (no patched version), `FAILED` (broke something in this repo's own control — blocks the gate), `BLOCKED_UPSTREAM` (broke an internal `{{PKG_SCOPE}}/*` lib this repo only consumes — does NOT block; needs an upstream release). Detect `BLOCKED_UPSTREAM` when the regression trace points under `node_modules/{{PKG_SCOPE}}/...`, an `ERESOLVE` peer conflict is declared by a `{{PKG_SCOPE}}/*` package, or the vulnerable transitive only arrives via a `{{PKG_SCOPE}}/*` parent still pinning the bad range.
76
+
77
+ ### Step 4: Fix Decision Tree
78
+
79
+ For each vulnerable package:
80
+
81
+ **A. No patched version (`patched` is null)** → mark **UNFIXABLE** (record pkg, severity, advisory, CVE/GHSA).
82
+
83
+ **B. Direct dependency (`relationship == "direct"`)**
84
+ 1. Edit the relevant `package.json` (use `manifest` path); preserve the `^`/`~`/pinned prefix. Flag major-version crossings in the report.
85
+ 2. `npm install`; verify with `npm ls {package}` (installed ≥ patched).
86
+ 3. If install fails, revert; mark **BLOCKED_UPSTREAM** if the blocker is `{{PKG_SCOPE}}/*`, else **FAILED**.
87
+
88
+ **C. Indirect/transitive** — try in order, stop at first success:
89
+ 1. **Lockfile-only (preferred):** `npm update {pkg}`; re-check `npm ls {pkg}` all ≥ patched → commit `package-lock.json` only. No override, no `package.json` change.
90
+ 2. **Parent update:** bump the parent in `package.json` (`npm info {parent} version`), `npm install`, re-verify.
91
+ 3. **Override (last resort):** add to `package.json` `overrides` (`">={patched}"`; use scoped/range overrides for multi-major lines). `npm install`, verify; on peer conflict revert and classify FAILED/BLOCKED_UPSTREAM.
92
+
93
+ Prefer lockfile-only updates: overrides add maintenance surface and can silently violate peer requirements.
94
+
95
+ ### Step 5: Apply and Commit Each Fix
96
+
97
+ Per successful fix: stage `package.json` + `package-lock.json`, commit `fix(security): update {package} to {version} ({CVE-ID})`. One commit per fix.
98
+
99
+ ### Step 6: Final Verification
100
+
101
+ 1. `npm install` (lockfile integrity).
102
+ 2. `npm audit --audit-level=low` (count remaining).
103
+ 3. **Validation matrix** by repo type; run in order, **stop on first failure**; missing script ⇒ `SKIPPED`.
104
+
105
+ | Step | Script | Backend | Frontend | QA |
106
+ |------|--------|:-------:|:--------:|:--:|
107
+ | 1 | `npm run lint` | ✅ | ✅ | ✅ |
108
+ | 2 | `npm run lint-style` | ❌ | ✅ | ❌ |
109
+ | 3 | `npm run lint:cfn` | ✅ | ❌ | ❌ |
110
+ | 4 | `npm run build` | ✅ | ✅ | ✅ |
111
+ | 5 | `npm run type-check` | ✅ | ✅ | ✅ |
112
+ | 6 | `npm test` | ✅ | ✅ | ✅ |
113
+ | 7 | `npm run test:integ` | ✅ | ❌ | ❌ |
114
+
115
+ Run each in the **foreground** with a hard timeout; never background-and-poll:
116
+ ```bash
117
+ CI=true timeout 1500 npm run <script> >/tmp/val.log 2>&1; ec=$?; tail -n 40 /tmp/val.log
118
+ ```
119
+ `CI=true` makes vite/jest/react-scripts run once. For jest `test`/`test:integ` append `--forceExit`. Interpret `ec`: `0`=PASS, `124`=timeout→FAIL, other non-zero=FAIL. Before `test:integ` (backend) ensure Docker is up; if unavailable, record FAIL ("Docker unavailable").
120
+
121
+ 4. **Compare each check to the Step 2.6 BASELINE — block only on regressions** (passed at baseline, now FAILs; or type-check error count increased). Pre-existing failures are `PRE-EXISTING`, do not block.
122
+ 5. **Per-fix regression isolation:** if a check regressed, bisect to the offending fix. If its root cause is under `node_modules/{{PKG_SCOPE}}/...` → revert ONLY that fix, mark its alert `BLOCKED_UPSTREAM`, re-run. If in this repo's own code → that fix is `FAILED` (blocks).
123
+ 6. Proceed if **no regressions vs baseline** and **no `FAILED`** (`BLOCKED_UPSTREAM`/`UNFIXABLE`/`SKIPPED`/pre-existing are fine).
124
+
125
+ ### Step 7: Generate Report
126
+
127
+ Print the standard report (Summary / Fixed / Unfixable / Failed / Blocked upstream / Verification), with repo `{{ORG}}/{repo}`. Count fixed alerts by severity for the result line.
128
+
129
+ ### Step 8: Push and Open/Update the PR
130
+
131
+ Gate — proceed if **either**: (A) pure success (no regressions, zero FAILED/BLOCKED_UPSTREAM), or (B) partial success — ≥1 fix is `FIXED` and the worktree is regression-free vs baseline (with FAILED fixes reverted). `FAILED`/`BLOCKED_UPSTREAM`/`UNFIXABLE` are documented but do NOT block. The ONLY blocker is an **unreverted regression** vs baseline.
132
+
133
+ **If the gate passes:**
134
+ ```bash
135
+ branch="$(git rev-parse --abbrev-ref HEAD)" # the worktree's current branch
136
+ git push -u origin "$branch"
137
+ existing="$(gh pr list --repo {{ORG}}/{repo} --head "$branch" --state open --json url --jq '.[0].url')"
138
+ if [ -z "$existing" ]; then
139
+ gh pr create --repo {{ORG}}/{repo} --base {{BASE_BRANCH}} --head "$branch" --title "fix: security" --body ""
140
+ fi
141
+ ```
142
+ Title exactly `fix: security`; empty body; ready for review (no `--draft`); no reviewers/assignees/labels. A brand-new PR ⇒ `NEW_PR`. Pushing commits onto a branch whose PR already existed ⇒ `UPDATED_PR`; if nothing new was needed ⇒ `PR_UP_TO_DATE`.
143
+
144
+ **If nothing is shippable** (every alert UNFIXABLE/BLOCKED_UPSTREAM, or all attempts reverted) and the tree is clean → push nothing ⇒ `NO_FIXABLE`.
145
+
146
+ **If an unreverted regression remains** (a clean-looking fix actually broke something) → do NOT push; leave the worktree as-is ⇒ `LEFT_LOCAL`.
147
+
148
+ ---
149
+
150
+ ## Machine-readable result (REQUIRED — last line of output)
151
+
152
+ Print **exactly one** line, as the final output, framed and versioned:
153
+
154
+ ```
155
+ MISTERPROPRE_RESULT_V1 {"v":1,"phase":"fix","kind":"<KIND>","prUrl":"<url>","counts":{"fixed":N,"critical":c,"high":h,"medium":m,"low":l}}
156
+ ```
157
+
158
+ - `kind` ∈ `NEW_PR | UPDATED_PR | PR_UP_TO_DATE | NO_ALERTS | NO_FIXABLE | LEFT_LOCAL | FAIL`.
159
+ - `prUrl` only for the PR kinds (omit otherwise). `counts` = alerts actually fixed this run by severity (omit for NO_ALERTS/FAIL).
160
+ - One line, single-line JSON, no trailing prose after it. The orchestrator reconciles this against GitHub + git; if you can't determine a field, omit it rather than guessing.
161
+
162
+ ## Anti-Patterns
163
+
164
+ - **NEVER** clone, `cd` out of the worktree, fetch, or create/switch/delete branches — the orchestrator owns all of that.
165
+ - **NEVER** push/open a PR with an **unreverted regression** vs the Step 2.6 baseline.
166
+ - **DO** ship the clean fixes even if some are `FAILED`/`BLOCKED_UPSTREAM`/`UNFIXABLE` (≥1 `FIXED`, tree regression-free).
167
+ - **NEVER** classify a regression rooted in `node_modules/{{PKG_SCOPE}}/...` as `FAILED` — that's `BLOCKED_UPSTREAM`.
168
+ - **NEVER** add a PR body/description; title-only `fix: security`. Never draft, never add reviewers/assignees/labels.
169
+ - **NEVER** `npm audit --fix`; **NEVER** hand-edit `package-lock.json`; **NEVER** dismiss alerts via the API.
170
+ - **NEVER** skip `npm install` verification after each fix. **ALWAYS** use `>=` for override versions.
171
+ - **ALWAYS** end with exactly one `MISTERPROPRE_RESULT_V1` line.