|
| 1 | +commands: |
| 2 | + - name: fix |
| 3 | + description: | |
| 4 | + Autonomous patch-quality checks for pr-maintenance-loop, scoped to the current branch's |
| 5 | + diff vs a base ref (default origin/main): lint, failing tests, coverage gaps, and replying |
| 6 | + to/resolving GitHub PR review threads. |
| 7 | +
|
| 8 | + These commands gather data and apply purely mechanical steps (e.g. running the existing |
| 9 | + lint gate). The actual "fix this failing test" / "fix this coverage gap" / "fix this lint |
| 10 | + finding" reasoning is done by Claude Code agents that consume this output — see |
| 11 | + .claude/skills/lint/SKILL.md and .claude/skills/test-coverage/SKILL.md. |
| 12 | + flags: |
| 13 | + - name: all |
| 14 | + type: bool |
| 15 | + default: false |
| 16 | + description: Run sync, ci, threads, lint, then coverage in sequence (omits `comments`, which needs per-thread parameters) — the full pr-maintenance-loop cycle, runnable standalone outside the hourly loop |
| 17 | + steps: |
| 18 | + # `when:` conditions only see env/stack/step/status/ci (pkg/runner/runner.go |
| 19 | + # taskConditionContext) — flag values aren't available there, confirmed by |
| 20 | + # testing, so this guard has to stay a shell conditional rather than a |
| 21 | + # native `exit` step gated by `when`. |
| 22 | + - type: shell |
| 23 | + command: | |
| 24 | + set -eu |
| 25 | + if [ "{{ .Flags.all }}" != "true" ]; then |
| 26 | + echo "Select a fix mode, for example: atmos fix --all, or atmos fix lint / tests / coverage / comments" >&2 |
| 27 | + exit 1 |
| 28 | + fi |
| 29 | + - type: atmos |
| 30 | + command: fix sync |
| 31 | + - type: atmos |
| 32 | + command: fix ci |
| 33 | + - type: atmos |
| 34 | + command: fix threads |
| 35 | + - type: toast |
| 36 | + level: info |
| 37 | + content: "Running patch-scoped lint, then tests and coverage..." |
| 38 | + - type: atmos |
| 39 | + command: fix lint |
| 40 | + - type: atmos |
| 41 | + command: fix coverage |
| 42 | + - type: toast |
| 43 | + level: success |
| 44 | + content: "atmos fix --all complete." |
| 45 | + |
| 46 | + commands: |
| 47 | + - name: symlinks |
| 48 | + description: Verify that every Git-tracked symbolic link resolves |
| 49 | + working_directory: !repo-root . |
| 50 | + steps: |
| 51 | + - type: shell |
| 52 | + command: scripts/check-symlinks.sh |
| 53 | + |
| 54 | + - name: sync |
| 55 | + description: | |
| 56 | + Update the PR against origin/main if behind (GitHub-side, via `gh pr update-branch`), |
| 57 | + then sync this local checkout with the remote PR branch — `gh pr update-branch` only |
| 58 | + updates the remote side via GitHub's API, never the local checkout, so this second half |
| 59 | + is required every time to avoid stale local git state (false lint/coverage findings, |
| 60 | + rejected pushes). On a real conflict (gh pr update-branch fails), falls back to a local |
| 61 | + merge attempt and reports `STATUS: MERGE_CONFLICT` with the conflicted files' content |
| 62 | + for the `merge-conflict-resolve` agent (via the `fix-all` skill) to resolve when |
| 63 | + confident, or abort and report otherwise — this command itself never decides how to |
| 64 | + resolve content conflicts, only surfaces them. The final local-checkout sync is fail |
| 65 | + closed: if it isn't a clean fast-forward, this reports an error rather than forcing a |
| 66 | + merge or rewrite. |
| 67 | + steps: |
| 68 | + - type: toast |
| 69 | + level: info |
| 70 | + content: "Syncing with origin/main and the remote PR branch..." |
| 71 | + - type: shell |
| 72 | + command: .claude/skills/pr-maintenance-loop/scripts/sync-branch.sh |
| 73 | + - type: toast |
| 74 | + level: success |
| 75 | + content: "Sync complete." |
| 76 | + |
| 77 | + - name: lint |
| 78 | + description: | |
| 79 | + Patch-aware lint check. Delegates to the existing `atmos lint --changed`, which builds |
| 80 | + the custom golangci-lint binary (staleness-guarded — only rebuilds when missing or |
| 81 | + stale) and runs it scoped to lines changed vs origin/main. |
| 82 | + steps: |
| 83 | + # Without this, `--new-from-rev=origin/main` diffs against whatever the local |
| 84 | + # origin/main ref last pointed to — which can be stale in a long-running loop against |
| 85 | + # an actively-developed repo, wrongly flagging already-merged upstream commits as "new" |
| 86 | + # findings on this patch. Confirmed for real: a rebase a few cycles back left the local |
| 87 | + # ref behind, and 3 findings from an already-merged, unrelated PR (#2674) got reported |
| 88 | + # as if they belonged to this patch. |
| 89 | + - type: shell |
| 90 | + command: git fetch origin main --quiet |
| 91 | + - type: toast |
| 92 | + level: info |
| 93 | + content: "Running patch-scoped lint (atmos lint --changed)..." |
| 94 | + - type: atmos |
| 95 | + command: lint --changed |
| 96 | + - type: toast |
| 97 | + level: success |
| 98 | + content: "Patch-scoped lint complete." |
| 99 | + |
| 100 | + - name: coverage |
| 101 | + description: | |
| 102 | + Run tests scoped to the packages touched by the diff vs a base ref, and report raw |
| 103 | + pass/fail status, test output, coverage profile, and the diff itself — no line-level |
| 104 | + analysis is done here, that's left to the consuming agent. |
| 105 | + arguments: |
| 106 | + - name: base-ref |
| 107 | + description: Base ref to diff against |
| 108 | + default: origin/main |
| 109 | + # base-ref is passed via command-level env (not interpolated into a step's command |
| 110 | + # string) so shell metacharacters in the value can't be parsed by `sh -c` as command |
| 111 | + # syntax. Must be command-level, list-of-key/value form — step-level `env:` (either map |
| 112 | + # or list form) silently does not reach the shell subprocess on this atmos version, |
| 113 | + # confirmed by testing; command-level list form (matching .atmos.d/build.yaml's existing |
| 114 | + # convention) is the one that actually works. |
| 115 | + env: |
| 116 | + - key: ATMOS_BASE_REF |
| 117 | + value: '{{ index .Arguments "base-ref" }}' |
| 118 | + steps: |
| 119 | + # Same staleness fix as `atmos fix lint` — keep the local remote-tracking ref for the |
| 120 | + # base-ref current before diffing against it. |
| 121 | + - type: shell |
| 122 | + command: git fetch origin --quiet |
| 123 | + - type: toast |
| 124 | + level: info |
| 125 | + content: 'Running scoped tests + coverage vs {{ index .Arguments "base-ref" }}...' |
| 126 | + - type: shell |
| 127 | + command: .claude/skills/test-coverage/scripts/patch-test-coverage.sh "$ATMOS_BASE_REF" |
| 128 | + |
| 129 | + - name: tests |
| 130 | + description: | |
| 131 | + Alias for `atmos fix coverage` — the same scoped test run reports pass/fail status too, |
| 132 | + this is just the entry point to reach for when the question is "are my tests passing" |
| 133 | + rather than "is my patch covered." |
| 134 | + arguments: |
| 135 | + - name: base-ref |
| 136 | + description: Base ref to diff against |
| 137 | + default: origin/main |
| 138 | + steps: |
| 139 | + - type: atmos |
| 140 | + command: fix coverage {{ index .Arguments "base-ref" }} |
| 141 | + |
| 142 | + - name: comments |
| 143 | + description: | |
| 144 | + Reply to (and optionally resolve) a GitHub PR review thread. Hardcodes exactly two |
| 145 | + GraphQL mutations (addPullRequestReviewThreadReply, then resolveReviewThread only with |
| 146 | + --resolve) — never accepts arbitrary query text, so --body can't be repurposed into a |
| 147 | + different mutation even if fully attacker-controlled. |
| 148 | +
|
| 149 | + Run `atmos fix threads` first to find a thread's ID. |
| 150 | + flags: |
| 151 | + - name: thread-id |
| 152 | + required: true |
| 153 | + description: GraphQL node ID of the review thread (see `atmos fix threads`) |
| 154 | + - name: body |
| 155 | + required: true |
| 156 | + description: 'Reply text, e.g. `Fixed in COMMIT_SHA: one-line summary`' |
| 157 | + - name: resolve |
| 158 | + type: bool |
| 159 | + default: false |
| 160 | + description: Also mark the thread resolved — only pass this when a concrete fixing commit exists |
| 161 | + # thread-id and body are passed via command-level env (not interpolated into a step's |
| 162 | + # command string) so shell metacharacters in --body (quotes, backticks, $()) can't be |
| 163 | + # parsed by `sh -c` as command syntax. Must be command-level, list-of-key/value form — |
| 164 | + # see the identical note on `fix coverage` above for why. |
| 165 | + env: |
| 166 | + - key: ATMOS_THREAD_ID |
| 167 | + value: '{{ index .Flags "thread-id" }}' |
| 168 | + - key: ATMOS_BODY |
| 169 | + value: '{{ index .Flags "body" }}' |
| 170 | + steps: |
| 171 | + - type: shell |
| 172 | + command: | |
| 173 | + set -eu |
| 174 | + args=(--thread-id "$ATMOS_THREAD_ID" --body "$ATMOS_BODY") |
| 175 | + if [ "{{ .Flags.resolve }}" = "true" ]; then |
| 176 | + args+=(--resolve) |
| 177 | + fi |
| 178 | + .claude/skills/pr-maintenance-loop/scripts/gh-resolve-review-thread.sh "${args[@]}" |
| 179 | + - type: toast |
| 180 | + level: success |
| 181 | + content: 'Replied to review thread {{ index .Flags "thread-id" }}{{ if .Flags.resolve }} and marked it resolved{{ end }}.' |
| 182 | + |
| 183 | + # Named `threads`, deliberately NOT sharing the `comments` prefix: a multi-word name like |
| 184 | + # `comments list` still resolves through the same "comments" command node (same as a true |
| 185 | + # nested `commands:` under it) and inherits its required --thread-id/--body flags before |
| 186 | + # Cobra even dispatches — confirmed by testing, both ways failed identically on missing |
| 187 | + # required flags that belong to the unrelated parent. |
| 188 | + - name: threads |
| 189 | + description: | |
| 190 | + List unresolved, non-outdated CodeRabbit review threads on the current branch's PR — |
| 191 | + read-only, no mutation. Prints each thread's node ID (what `atmos fix comments |
| 192 | + --thread-id` needs), file path, comment URL, and a one-line preview. |
| 193 | + steps: |
| 194 | + - type: shell |
| 195 | + command: .claude/skills/pr-maintenance-loop/scripts/list-review-threads.sh |
| 196 | + |
| 197 | + - name: ci |
| 198 | + description: | |
| 199 | + List currently failing CI checks on the current branch's PR, with a failure-log excerpt |
| 200 | + where fetchable, plus the diff vs a base ref — read-only, no mutation. Does not judge |
| 201 | + whether a failure is caused by this patch or pre-existing; that's left to the consuming |
| 202 | + agent, same as `atmos fix coverage`. |
| 203 | + steps: |
| 204 | + # Same staleness fix as `atmos fix lint` — this script's diff-vs-origin/main section at |
| 205 | + # the end needs a current local ref. |
| 206 | + - type: shell |
| 207 | + command: git fetch origin main --quiet |
| 208 | + - type: toast |
| 209 | + level: info |
| 210 | + content: "Checking CI status..." |
| 211 | + - type: shell |
| 212 | + command: .claude/skills/pr-maintenance-loop/scripts/list-failing-checks.sh |
0 commit comments