@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.3
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.
- package/README.md +13 -5
- package/bin/m3l-groundwork.mjs +36 -2
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +14 -0
- package/dist/baseline-stage.d.ts +173 -0
- package/dist/baseline-stage.js +215 -0
- package/dist/caps.d.ts +4 -1
- package/dist/caps.js +17 -3
- package/dist/conflicts.d.ts +23 -2
- package/dist/conflicts.js +89 -11
- package/dist/customize-paths.d.ts +92 -0
- package/dist/customize-paths.js +115 -0
- package/dist/emit.d.ts +50 -1
- package/dist/emit.js +121 -13
- package/dist/fatal.d.ts +70 -0
- package/dist/fatal.js +132 -0
- package/dist/format-error.d.ts +113 -0
- package/dist/format-error.js +553 -0
- package/dist/fs-guard.d.ts +142 -0
- package/dist/fs-guard.js +222 -0
- package/dist/git.js +10 -1
- package/dist/harness/conformance.js +2 -0
- package/dist/harness/frontmatter.js +2 -13
- package/dist/harness/grade.js +37 -8
- package/dist/harness/rules.d.ts +20 -3
- package/dist/harness/rules.js +108 -6
- package/dist/harness/types.d.ts +13 -0
- package/dist/harness/types.js +3 -0
- package/dist/inventory.d.ts +77 -3
- package/dist/inventory.js +103 -12
- package/dist/jsonc.d.ts +49 -2
- package/dist/jsonc.js +140 -7
- package/dist/main.d.ts +62 -3
- package/dist/main.js +538 -67
- package/dist/merge-json.d.ts +47 -4
- package/dist/merge-json.js +120 -8
- package/dist/mode.js +12 -2
- package/dist/pack-stage.d.ts +210 -0
- package/dist/pack-stage.js +287 -0
- package/dist/packs.d.ts +19 -13
- package/dist/packs.js +231 -28
- package/dist/palette.d.ts +23 -0
- package/dist/palette.js +22 -0
- package/dist/plugin.d.ts +122 -6
- package/dist/plugin.js +687 -47
- package/dist/report.d.ts +21 -2
- package/dist/report.js +93 -5
- package/dist/staging.d.ts +176 -0
- package/dist/staging.js +375 -0
- package/dist/survey/fs-walk.d.ts +34 -2
- package/dist/survey/fs-walk.js +70 -5
- package/dist/survey/internal/blocked-path.d.ts +27 -0
- package/dist/survey/internal/blocked-path.js +129 -0
- package/dist/survey/internal/package-json.d.ts +13 -0
- package/dist/survey/internal/package-json.js +37 -0
- package/dist/survey/internal/read-guard.d.ts +178 -0
- package/dist/survey/internal/read-guard.js +276 -0
- package/dist/survey/survey-docs.d.ts +17 -2
- package/dist/survey/survey-docs.js +61 -21
- package/dist/survey/survey-harness.d.ts +20 -2
- package/dist/survey/survey-harness.js +87 -46
- package/dist/survey/survey-shape.d.ts +17 -2
- package/dist/survey/survey-shape.js +60 -46
- package/dist/survey/survey-toolchain.d.ts +16 -1
- package/dist/survey/survey-toolchain.js +69 -66
- package/dist/survey/survey.js +6 -4
- package/dist/survey/types.d.ts +79 -0
- package/dist/survey/types.js +2 -6
- package/dist/term.d.ts +80 -0
- package/dist/term.js +145 -0
- package/dist/tokens.js +2 -0
- package/dist/toolchain/conformance.js +2 -0
- package/dist/toolchain/grade.js +26 -8
- package/dist/toolchain/rules.d.ts +13 -4
- package/dist/toolchain/rules.js +16 -0
- package/dist/toolchain/tsconfig-chain.d.ts +2 -0
- package/dist/toolchain/tsconfig-chain.js +32 -8
- package/dist/toolchain/types.d.ts +16 -4
- package/dist/toolchain/types.js +3 -0
- package/package.json +4 -3
- package/plugin/skills/customize/SKILL.md +292 -18
- package/plugin/src/domain-map.ts +39 -14
- package/plugin/src/index.ts +5 -1
- package/plugin/src/kind-facet-map.ts +25 -8
- package/plugin/src/pack-map.ts +147 -25
- package/plugin/src/plugin-map.ts +236 -0
- package/templates/core/.claude/agents/Explore.md +0 -1
- package/templates/core/.claude/agents/code-implementer.md +4 -4
- package/templates/core/.claude/agents/code-reviewer.md +4 -4
- package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
- package/templates/core/.claude/agents/test-author.md +7 -5
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
- package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
- package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
- package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
- package/templates/core/.claude/rules/agent-dispatch.md +7 -0
- package/templates/core/.claude/rules/tests.md +2 -2
- package/templates/core/.claude/settings.json +5 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
- package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
- package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
- package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
- package/templates/core/.github/dependabot.yml +18 -0
- package/templates/core/.github/workflows/ci.yml +15 -15
- package/templates/core/.github/workflows/dependency-review.yml +2 -2
- package/templates/core/.github/workflows/security-audit.yml +10 -4
- package/templates/core/.prettierignore +4 -0
- package/templates/core/CLAUDE.md +53 -3
- package/templates/core/README.md +30 -10
- package/templates/core/_gitignore +17 -0
- package/templates/core/bin/check-exports.mjs +11 -5
- package/templates/core/bin/lib/agent-roster.mjs +1 -1
- package/templates/core/bin/lib/frontmatter.mjs +4 -2
- package/templates/core/bin/lib/harness-rules.mjs +169 -20
- package/templates/core/bin/lib/protected-paths.mjs +93 -5
- package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
- package/templates/core/bin/lib/verify-steps.mjs +29 -11
- package/templates/core/bin/verify.mjs +12 -0
- package/templates/core/eslint.config.js +5 -0
- package/templates/core/package.json +7 -7
- package/templates/core/vitest.config.ts +8 -1
- package/templates/packs/README.md +35 -24
- package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
- package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
- package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
- package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
- package/templates/packs/github/pack.json +19 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
- package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
- package/templates/packs/harness-extras/pack.json +18 -13
- package/templates/packs/publishing/files/.changeset/README.md +25 -0
- package/templates/packs/publishing/files/.changeset/config.json +7 -0
- package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
- package/templates/packs/publishing/files/.github/workflows/release.yml +293 -0
- package/templates/packs/publishing/files/REUSE.toml +28 -0
- package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
- package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
- package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
- package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
- package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
- package/templates/packs/publishing/pack.json +35 -0
- package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
- package/templates/packs/quality/pack.json +29 -0
- package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
- package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
- package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
- package/templates/packs/supply-chain/pack.json +19 -0
- package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
- package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
- package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
- package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
- package/templates/packs/worktrees/files/.worktreeinclude +11 -0
- package/templates/packs/worktrees/pack.json +64 -0
- package/templates/packs/statusline/pack.json +0 -31
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
- /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
- /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewing-dependabot-prs
|
|
3
|
+
description: >-
|
|
4
|
+
Reviews every open Dependabot pull request, classifies each by semver bump
|
|
5
|
+
level and CI check status, and proposes merge/hold/close per PR -- then
|
|
6
|
+
acts only on the batch the user confirms, never a PR the user didn't
|
|
7
|
+
approve. Use for /reviewing-dependabot-prs, "review the dependabot PRs",
|
|
8
|
+
"triage dependency updates", or when several Dependabot PRs have
|
|
9
|
+
accumulated. GitHub stance: gh CLI.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Triage every open Dependabot pull request in this repository: classify each
|
|
13
|
+
by how risky its bump is and whether its checks are green, propose an
|
|
14
|
+
action per PR, then act only on the batch the user actually confirms. This
|
|
15
|
+
skill never merges or closes a PR the user hasn't approved, and never
|
|
16
|
+
guesses at a version bump's safety from the checks alone -- a major bump is
|
|
17
|
+
always held for a human look, checks or no checks.
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
### 1 — List every open Dependabot PR
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gh pr list --search "author:app/dependabot" --state open --limit 100 --json \
|
|
25
|
+
number,title,headRefName,isDraft,mergeable,statusCheckRollup
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`--limit 100` avoids `gh pr list`'s 30-result default silently truncating a
|
|
29
|
+
large backlog. If this returns nothing, report that there is nothing to
|
|
30
|
+
triage and stop.
|
|
31
|
+
|
|
32
|
+
### 2 — Classify each PR's bump level
|
|
33
|
+
|
|
34
|
+
Dependabot's own titles follow `Bump <package> from <old> to <new>` (or
|
|
35
|
+
`Bump <package> in <dir> from <old> to <new>` for a monorepo/directory-scoped
|
|
36
|
+
config) -- but a project with a `commit-message.prefix` in its
|
|
37
|
+
`dependabot.yml` gets a prefixed, often-lowercased title instead, e.g.
|
|
38
|
+
`chore(deps): bump <package> from <old> to <new>`. Match case-insensitively
|
|
39
|
+
and allow an optional prefix before `bump`, then parse `<old>`/`<new>` and
|
|
40
|
+
compare them as semver:
|
|
41
|
+
|
|
42
|
+
- **major**: the leftmost _non-zero_ version segment differs (not simply
|
|
43
|
+
the first segment -- under semver a `0.x` package treats its second
|
|
44
|
+
segment as breaking, so `0.3.0 -> 0.4.0` is major, not minor). A major
|
|
45
|
+
bump is a breaking-change risk by definition, regardless of check status.
|
|
46
|
+
- **minor**/**patch**: lower risk, but still gated on checks (next step).
|
|
47
|
+
- **unparseable** (a non-semver ecosystem, a grouped-update PR covering
|
|
48
|
+
several packages, or a title that doesn't match the pattern even with the
|
|
49
|
+
prefix/case allowance above): mark it **unknown** and never guess a risk
|
|
50
|
+
level for it -- always propose _hold_ for a human look, and say why
|
|
51
|
+
parsing failed.
|
|
52
|
+
|
|
53
|
+
### 3 — Classify each PR's check status and mergeability
|
|
54
|
+
|
|
55
|
+
`statusCheckRollup` mixes two entry shapes: a GitHub Actions **check run**
|
|
56
|
+
(carries `conclusion`) and a third-party **commit status** (carries `state`
|
|
57
|
+
instead -- no `conclusion` field at all). Read whichever field the entry
|
|
58
|
+
actually has, and reduce the whole set to one of:
|
|
59
|
+
|
|
60
|
+
- **green**: every entry's `conclusion` is `SUCCESS`, `NEUTRAL`, or
|
|
61
|
+
`SKIPPED` (not a failure), or its `state` is `SUCCESS`.
|
|
62
|
+
- **red**: at least one entry's `conclusion` is `FAILURE`, `CANCELLED`,
|
|
63
|
+
`TIMED_OUT`, `ACTION_REQUIRED`, `STARTUP_FAILURE`, or `STALE`, or its
|
|
64
|
+
`state` is `FAILURE`/`ERROR`.
|
|
65
|
+
- **pending**: at least one entry is still queued/in-progress (`conclusion`
|
|
66
|
+
absent, or `state: PENDING`) and none are red.
|
|
67
|
+
- **no checks configured**: `statusCheckRollup` is empty -- this is _not_
|
|
68
|
+
the same as green; propose **hold** and say the repository has no CI
|
|
69
|
+
configured for this PR, rather than treating silence as success.
|
|
70
|
+
|
|
71
|
+
Also read `mergeable`: a PR reported as `CONFLICTING` is never proposed for
|
|
72
|
+
merge regardless of bump level or check status. A draft PR (`isDraft:
|
|
73
|
+
true`) is never proposed for merge either.
|
|
74
|
+
|
|
75
|
+
### 4 — Propose an action per PR
|
|
76
|
+
|
|
77
|
+
| Bump level | Checks | Mergeable | Draft | Proposal |
|
|
78
|
+
| ------------------- | --------- | ---------------- | ----- | ----------------------------------------------------- |
|
|
79
|
+
| patch/minor | green | yes | no | **merge** |
|
|
80
|
+
| patch/minor | red | any | no | **hold** -- name the failing check |
|
|
81
|
+
| patch/minor | pending | any | no | **hold** -- checks still running, re-check later |
|
|
82
|
+
| patch/minor | no checks | any | no | **hold** -- no CI configured, needs a human look |
|
|
83
|
+
| any | any | no (conflicting) | any | **hold** -- merge conflict, needs a rebase/human look |
|
|
84
|
+
| major | any | any | no | **hold** -- breaking-change risk, needs a human read |
|
|
85
|
+
| unknown/unparseable | any | any | any | **hold** -- couldn't classify the bump safely |
|
|
86
|
+
| any | any | any | yes | **skip** -- draft PR |
|
|
87
|
+
|
|
88
|
+
For a **held** PR whose changelog or release notes look clearly abandoned,
|
|
89
|
+
irrelevant, or superseded by a newer PR for the same package, note **close**
|
|
90
|
+
as an alternative to raise with the user in the next step -- this skill
|
|
91
|
+
never closes a PR on its own initiative, only on explicit confirmation.
|
|
92
|
+
|
|
93
|
+
### 5 — Present the report and confirm the batch
|
|
94
|
+
|
|
95
|
+
Show one table: PR number, title, bump level, check status, mergeable,
|
|
96
|
+
proposed action. Then ask the user which of the **merge**-proposed PRs to
|
|
97
|
+
actually merge, and separately whether any **held** PR should instead be
|
|
98
|
+
**closed** -- offer "all of them", a subset by number, or none for each.
|
|
99
|
+
Never treat silence or a general "looks good" as consent to act; get an
|
|
100
|
+
explicit list or an explicit "yes, all of them" for merges, and a separate
|
|
101
|
+
explicit confirmation for any close.
|
|
102
|
+
|
|
103
|
+
### 6 — Act only on the confirmed batch
|
|
104
|
+
|
|
105
|
+
For each confirmed merge:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
gh pr merge <number> --squash --auto
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Prefer `--squash` (the common convention for a Dependabot PR), but if `gh`
|
|
112
|
+
rejects it (squash merges disabled by the repository, or "Allow auto-merge"
|
|
113
|
+
itself is off -- GitHub's own default), fall back to whichever merge method
|
|
114
|
+
the repository actually allows (`--merge`/`--rebase`, or drop `--auto` and
|
|
115
|
+
report that the PR is ready but must be merged by hand) rather than treating
|
|
116
|
+
the rejection as this skill's own failure. `--auto` arms auto-merge rather
|
|
117
|
+
than forcing an immediate merge -- if the repository's branch protection
|
|
118
|
+
requires checks or reviews, the merge still waits for those; this skill does
|
|
119
|
+
not bypass a repository's own merge rules. If `gh pr merge` reports the PR
|
|
120
|
+
isn't mergeable (a conflict, a newly-failed check since step 1), report that
|
|
121
|
+
PR's failure and continue with the rest of the batch rather than aborting it.
|
|
122
|
+
|
|
123
|
+
For each confirmed close:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
gh pr close <number> --comment "<one-line reason from step 4>"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 7 — Report outcomes
|
|
130
|
+
|
|
131
|
+
Summarize what was merged, what's queued behind `--auto`, what was closed,
|
|
132
|
+
and what was held or skipped, with a one-line reason for every held/skipped/
|
|
133
|
+
closed PR.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triaging-scan-alerts
|
|
3
|
+
description: >-
|
|
4
|
+
Fetches this repository's open code-scanning alerts via gh api, groups
|
|
5
|
+
them by tool and severity, and maps each to its file:line -- the output
|
|
6
|
+
is a triage report and options only, it never edits code. Use for
|
|
7
|
+
/triaging-scan-alerts, "review the scan alerts", "what does CodeQL flag",
|
|
8
|
+
"triage the security findings", or after a code-scanning workflow run.
|
|
9
|
+
GitHub stance: gh CLI.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Fetch every open code-scanning alert for this repository, group it by
|
|
13
|
+
which tool raised it and how severe it is, and map each to the exact
|
|
14
|
+
file and line it points at. This skill produces a report and a set of
|
|
15
|
+
options per alert -- it never edits code, dismisses an alert, or opens a
|
|
16
|
+
PR on its own.
|
|
17
|
+
|
|
18
|
+
## Steps
|
|
19
|
+
|
|
20
|
+
### 1 — Fetch open alerts
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gh api 'repos/{owner}/{repo}/code-scanning/alerts?state=open' \
|
|
24
|
+
--paginate \
|
|
25
|
+
-q '.[] | {number, tool: .tool.name, severity: .rule.security_severity_level // .rule.severity, rule: .rule.id, path: .most_recent_instance.location.path, line: .most_recent_instance.location.start_line, description: .rule.description}'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
(`state=open` is in the query string, not a `-f` field -- `gh api` switches
|
|
29
|
+
to `POST` the moment any `-f`/`-F` field is passed without an explicit
|
|
30
|
+
`--method GET`, which would break this list call. The whole path is single-
|
|
31
|
+
quoted because `?` is a glob character in zsh -- macOS's default shell --
|
|
32
|
+
and an unquoted one there fails with "no matches found" before `gh` runs.)
|
|
33
|
+
|
|
34
|
+
Use the current repository (`gh` infers `{owner}/{repo}` from the working
|
|
35
|
+
directory's remote when the placeholders are left as-is). If the endpoint
|
|
36
|
+
returns a 404 or a permissions error, code scanning may not be enabled on
|
|
37
|
+
this repository -- report that plainly rather than treating it as "no
|
|
38
|
+
alerts".
|
|
39
|
+
|
|
40
|
+
### 2 — Group by tool and severity
|
|
41
|
+
|
|
42
|
+
Group the results by `tool` (CodeQL, a third-party SARIF uploader, etc.)
|
|
43
|
+
first, then by `severity` within each tool (`critical`/`high`/`medium`/
|
|
44
|
+
`low`, or the tool's own scale if it doesn't use those exact words --
|
|
45
|
+
report the tool's own label rather than forcing it into one of the four).
|
|
46
|
+
|
|
47
|
+
### 3 — Map each alert to file:line
|
|
48
|
+
|
|
49
|
+
Every alert in the fetched output already carries `path`/`line` from its
|
|
50
|
+
most recent instance -- present these verbatim (`path:line`) so the user
|
|
51
|
+
can jump straight to the code. If `path`/`line` is absent (some
|
|
52
|
+
alert-generating tools don't localize to a line), say so rather than
|
|
53
|
+
inventing a location.
|
|
54
|
+
|
|
55
|
+
### 4 — Present the report
|
|
56
|
+
|
|
57
|
+
One table per tool, ordered severity-first: alert number, rule id, one-line
|
|
58
|
+
description, `path:line`, severity. Note the total open-alert count at the
|
|
59
|
+
top.
|
|
60
|
+
|
|
61
|
+
### 5 — Offer options per alert -- never act automatically
|
|
62
|
+
|
|
63
|
+
For each alert, describe (not perform) the realistic options:
|
|
64
|
+
|
|
65
|
+
- **fix now**: a one-line description of what the fix would look like, if
|
|
66
|
+
obvious from the rule/description -- this skill still does not make the
|
|
67
|
+
edit itself; the option is to hand it to a code-writing task.
|
|
68
|
+
- **dismiss as false positive** / **won't fix** / **used in tests**: the
|
|
69
|
+
three reasons GitHub's own dismissal UI accepts, named but not run:
|
|
70
|
+
```bash
|
|
71
|
+
gh api -X PATCH repos/{owner}/{repo}/code-scanning/alerts/{number} \
|
|
72
|
+
-f state=dismissed -f "dismissed_reason=false positive"
|
|
73
|
+
```
|
|
74
|
+
(swap the quoted value for `won't fix` or `used in tests` -- each contains
|
|
75
|
+
a space or apostrophe, so it must stay quoted as one shell argument).
|
|
76
|
+
- **needs a human decision**: for anything ambiguous (a finding in
|
|
77
|
+
generated code, a suppressed pattern that might be intentional).
|
|
78
|
+
|
|
79
|
+
Ask the user which option applies before running any dismissal command --
|
|
80
|
+
this skill's own default is to report, not to close anything out.
|
|
81
|
+
|
|
82
|
+
### 6 — Note the adjacent alert surfaces this skill doesn't cover
|
|
83
|
+
|
|
84
|
+
Dependabot alerts (`gh api repos/{owner}/{repo}/dependabot/alerts`) and
|
|
85
|
+
secret-scanning alerts (`gh api repos/{owner}/{repo}/secret-scanning/alerts`)
|
|
86
|
+
are separate GitHub endpoints from code-scanning -- if the user asks about
|
|
87
|
+
either, say so and fetch that endpoint too rather than assuming this
|
|
88
|
+
skill's code-scanning report already covers it.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: watching-pr-checks
|
|
3
|
+
description: >-
|
|
4
|
+
Checks a pull request's CI status via gh pr checks: reports ready-to-merge
|
|
5
|
+
and asks before merging on green, or reads the real failing job log and
|
|
6
|
+
hands off to /triaging-ci on red -- never guesses at a failure from the
|
|
7
|
+
check name alone. Use for /watching-pr-checks, "check this PR's status",
|
|
8
|
+
"is this PR ready to merge", "did CI pass on my PR". GitHub stance: gh
|
|
9
|
+
CLI.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Check one pull request's CI status and act on what the checks actually
|
|
13
|
+
say, rather than assuming green or guessing at a failure. This is a
|
|
14
|
+
single check, not a polling loop -- if checks are still running, report
|
|
15
|
+
that plainly and let the user decide whether to ask again later or wait.
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1 — Resolve the PR
|
|
20
|
+
|
|
21
|
+
If the user gave an explicit PR number or URL, use it. Otherwise, resolve
|
|
22
|
+
the PR for the current branch:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gh pr view --json number,headRefName,state
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If there is no PR for the current branch, or its `state` isn't `OPEN` (a
|
|
29
|
+
merged or closed PR still resolves by branch name), say so and stop --
|
|
30
|
+
there is nothing to watch.
|
|
31
|
+
|
|
32
|
+
### 2 — Check status
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gh pr checks <number> --json name,bucket,link,workflow
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`bucket` (not `conclusion` -- `gh pr checks --json` has no `conclusion`
|
|
39
|
+
field) is the ready-made summary each check reduces to: `pass`, `fail`,
|
|
40
|
+
`pending`, `skipping`, or `cancel`. Reduce the whole set to one of:
|
|
41
|
+
|
|
42
|
+
- **all green**: every check's `bucket` is `pass` or `skipping`.
|
|
43
|
+
- **still running**: at least one `pending` and none `fail`/`cancel`.
|
|
44
|
+
- **failed**: at least one `fail` or `cancel`.
|
|
45
|
+
- **no checks configured**: the list is empty -- report this plainly rather
|
|
46
|
+
than treating it as either green or failed; there is nothing to watch
|
|
47
|
+
until the repository has CI wired up for this PR.
|
|
48
|
+
|
|
49
|
+
### 3a — All green
|
|
50
|
+
|
|
51
|
+
Report the PR is ready to merge, name every check that passed, and ask the
|
|
52
|
+
user before merging -- never merge without that confirmation, even when
|
|
53
|
+
every check is green:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gh pr merge <number> --squash --auto
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If `gh` rejects `--squash --auto` (squash merges disabled, or "Allow
|
|
60
|
+
auto-merge" itself is off -- GitHub's own default), fall back to whichever
|
|
61
|
+
merge method the repository actually allows, or drop `--auto` and report
|
|
62
|
+
that the PR is ready but must be merged by hand -- don't report the
|
|
63
|
+
rejection as this skill's own failure. (`--auto` respects any
|
|
64
|
+
branch-protection requirement still pending -- see
|
|
65
|
+
`reviewing-dependabot-prs`'s step 6 for the same reasoning if that skill is
|
|
66
|
+
also installed.)
|
|
67
|
+
|
|
68
|
+
### 3b — Still running
|
|
69
|
+
|
|
70
|
+
Report which checks are still pending and which have already passed. Do
|
|
71
|
+
not guess at the eventual outcome. Suggest the user re-invoke this skill
|
|
72
|
+
in a few minutes, or -- if the calling session supports scheduling a
|
|
73
|
+
recurring check -- offer to check again automatically rather than blocking
|
|
74
|
+
on a manual re-ask.
|
|
75
|
+
|
|
76
|
+
### 3c — Failed
|
|
77
|
+
|
|
78
|
+
Do not report the failure from the check's name alone -- pull the actual
|
|
79
|
+
job log before describing what went wrong. The failing check's `workflow`
|
|
80
|
+
field distinguishes a GitHub Actions run (has a log `gh run view` can read)
|
|
81
|
+
from a third-party check (a status-only integration with no run to fetch):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
gh run view <run-id> --log-failed
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
(`<run-id>` comes from the failing check's `link`, a
|
|
88
|
+
`.../actions/runs/<run-id>/job/...` URL -- a check with no such `link`, or
|
|
89
|
+
whose `workflow` is empty, is from an external app; report its `link` and
|
|
90
|
+
that no log is available to read rather than guessing at why it failed.)
|
|
91
|
+
Once the real failure is in hand, hand off to `/triaging-ci` (if the
|
|
92
|
+
project has it installed) to map the failure to its root cause and present
|
|
93
|
+
fix options -- this skill's own job ends at "here's what actually failed
|
|
94
|
+
and why," not at proposing or applying a fix.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
name: Claude PR Review
|
|
2
|
+
|
|
3
|
+
# What this does: reviews every pull request when it's opened, pushed to,
|
|
4
|
+
# marked ready or reopened, checking the same things a human reviewer would
|
|
5
|
+
# (see the prompt below), then posts the result as one sticky summary comment
|
|
6
|
+
# plus inline comments. It never approves, blocks, or pushes code, and it
|
|
7
|
+
# skips bot-authored and fork pull requests.
|
|
8
|
+
#
|
|
9
|
+
# Two jobs, because Claude does not post the review itself. `review` runs
|
|
10
|
+
# Claude read-only and gets its findings back as schema-validated structured
|
|
11
|
+
# output (`--json-schema`); `post` is plain `gh` + `jq` and is the only job
|
|
12
|
+
# holding `pull-requests: write`. Letting the model post through `gh pr
|
|
13
|
+
# comment` or the inline-comment MCP tool fails silently and green: runs
|
|
14
|
+
# complete successfully with real spend and permission denials and post
|
|
15
|
+
# nothing, so a new push looks unreviewed until the PR is closed and
|
|
16
|
+
# reopened. The trigger is not the problem -- `synchronize` and `reopened`
|
|
17
|
+
# both start runs. Likely cause: the action loads the repository's own
|
|
18
|
+
# `.claude/` and CLAUDE.md, and a project that documents subagent workflows
|
|
19
|
+
# makes the model dispatch review subagents that have no MCP tool and no
|
|
20
|
+
# `gh api`; background subagents also end the action's run at the first
|
|
21
|
+
# result message. Upstream reports the same shape: claude-code-action#1523
|
|
22
|
+
# (success, nothing posted), #1823 (a `--edit-last` re-review silently
|
|
23
|
+
# skipped), #1852/#1499/#1646 (background subagents), #1679 (the buffered
|
|
24
|
+
# inline-comment step exits 0 after posting nothing). There is no input that
|
|
25
|
+
# fails a run which posted nothing, and `use_sticky_comment` does nothing in
|
|
26
|
+
# this mode, so posting moved out of the model: without `structured_output`
|
|
27
|
+
# the action itself fails the run (docs/usage.md), and the `post` job fails
|
|
28
|
+
# on any API error other than an inline comment GitHub can't anchor
|
|
29
|
+
# (HTTP 422), which is folded into the summary instead.
|
|
30
|
+
#
|
|
31
|
+
# One run is deliberately skipped: claude-code-action will not run on a PR
|
|
32
|
+
# that edits this very file (its copy must match the default branch's, a
|
|
33
|
+
# guard against a PR rewriting its own reviewer). It logs a warning and exits
|
|
34
|
+
# success with no outputs, so the `gate` step tells that case apart from a
|
|
35
|
+
# real empty result -- no `conclusion` AND this file differs from the base --
|
|
36
|
+
# and `post` is skipped with a notice. Any other empty result fails `review`.
|
|
37
|
+
# A PR touching this file therefore gets no review until it merges.
|
|
38
|
+
#
|
|
39
|
+
# `--disallowedTools` keeps subagents and writes off in `review`, and the job
|
|
40
|
+
# has no write token at all (contents/pull-requests read), so a prompt
|
|
41
|
+
# injected through a PR's contents can at worst return a wrong review, not
|
|
42
|
+
# post, push or approve. A review is never a formal GitHub review either
|
|
43
|
+
# (Anthropic's own docs/capabilities-and-limitations.md), so it cannot
|
|
44
|
+
# satisfy or bypass any branch-protection required check or approval count.
|
|
45
|
+
# Comments appear as github-actions[bot] rather than claude[bot]. Excludes
|
|
46
|
+
# bot-authored and fork PRs explicitly below, rather than relying on
|
|
47
|
+
# anthropics/claude-code-action's own internal bot/permission checks
|
|
48
|
+
# (docs/security.md) -- that way a Dependabot or fork PR never starts a run
|
|
49
|
+
# that would just fail on missing secrets. Runs but fails cleanly on an auth
|
|
50
|
+
# error until the one-time setup described in claude.yml's comments is done
|
|
51
|
+
# by hand.
|
|
52
|
+
on:
|
|
53
|
+
pull_request:
|
|
54
|
+
types: [opened, synchronize, ready_for_review, reopened]
|
|
55
|
+
|
|
56
|
+
# Cancel a stale run when the PR gets a new push, same as this project's own
|
|
57
|
+
# CI -- a `synchronize` fires on every push, and a paid review run per push
|
|
58
|
+
# queues up otherwise. This also cancels a `post` job still running for the
|
|
59
|
+
# old head.
|
|
60
|
+
concurrency:
|
|
61
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
62
|
+
cancel-in-progress: true
|
|
63
|
+
|
|
64
|
+
# Reset, then grant per job.
|
|
65
|
+
permissions: {}
|
|
66
|
+
|
|
67
|
+
jobs:
|
|
68
|
+
review:
|
|
69
|
+
if: |
|
|
70
|
+
github.event.pull_request.user.type != 'Bot' &&
|
|
71
|
+
github.event.pull_request.head.repo.full_name == github.repository
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
timeout-minutes: 30
|
|
74
|
+
permissions:
|
|
75
|
+
contents: read
|
|
76
|
+
pull-requests: read
|
|
77
|
+
id-token: write # see claude.yml for why this is needed regardless of auth method
|
|
78
|
+
outputs:
|
|
79
|
+
review: ${{ steps.claude.outputs.structured_output }}
|
|
80
|
+
skipped: ${{ steps.gate.outputs.skipped }}
|
|
81
|
+
steps:
|
|
82
|
+
# Full history, not depth 1: the prompt tells Claude to `git diff` the
|
|
83
|
+
# base against the head, and a shallow clone has no base to diff.
|
|
84
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
85
|
+
with:
|
|
86
|
+
persist-credentials: false
|
|
87
|
+
fetch-depth: 0
|
|
88
|
+
|
|
89
|
+
- id: claude
|
|
90
|
+
uses: anthropics/claude-code-action@ed670b4cf9de2a5a570d130d2f6197b9e543cd64 # v1.0.240
|
|
91
|
+
with:
|
|
92
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
93
|
+
# Swap in CLAUDE_CODE_OAUTH_TOKEN or the Workload Identity
|
|
94
|
+
# Federation inputs instead -- see claude.yml's comments for all
|
|
95
|
+
# three auth options; this workflow needs the same one configured.
|
|
96
|
+
|
|
97
|
+
# Pinned rather than left on the action's default: there is no
|
|
98
|
+
# `model:` input (anthropics/claude-code-action's action.yml has
|
|
99
|
+
# none), the action's own docs defer the default's definition to
|
|
100
|
+
# a separate page rather than naming it, and no official source
|
|
101
|
+
# states whether that default can change between action releases
|
|
102
|
+
# -- an automated, unattended job like this one is exactly where a
|
|
103
|
+
# silent shift would go unnoticed.
|
|
104
|
+
#
|
|
105
|
+
# `--fallback-model` (both `model:` and `fallback_model:` are
|
|
106
|
+
# deprecated action inputs -- claude-code-action's docs/usage.md
|
|
107
|
+
# says to configure both through `claude_args` now) tries the
|
|
108
|
+
# listed model only when the primary is overloaded, unavailable,
|
|
109
|
+
# or returns another non-retryable server error -- never for an
|
|
110
|
+
# auth, billing, rate-limit, request-size, transport, or
|
|
111
|
+
# org-policy failure, a real, currently-unfixed gap
|
|
112
|
+
# (anthropics/claude-code-action#594, redirected to and
|
|
113
|
+
# auto-closed `not_planned` as anthropics/claude-code#8413) -- but
|
|
114
|
+
# worth having for the failure mode it does cover.
|
|
115
|
+
#
|
|
116
|
+
# `--allowedTools` is read-only on purpose: the diff and PR
|
|
117
|
+
# metadata, plus git history (Read/Grep/Glob are allowed by
|
|
118
|
+
# default). `--disallowedTools` removes subagents and every write.
|
|
119
|
+
# `--json-schema` makes the result machine-readable: the action
|
|
120
|
+
# fails the step when Claude returns no `structured_output`.
|
|
121
|
+
claude_args: |
|
|
122
|
+
--model claude-opus-5-5
|
|
123
|
+
--fallback-model claude-sonnet-5-5
|
|
124
|
+
--allowedTools "Bash(gh pr diff:*),Bash(gh pr view:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*)"
|
|
125
|
+
--disallowedTools "Agent,Task,Write,Edit,NotebookEdit"
|
|
126
|
+
--json-schema '{"type":"object","properties":{"summary":{"type":"string"},"inline":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"line":{"type":"integer"},"body":{"type":"string"}},"required":["path","line","body"],"additionalProperties":false}}},"required":["summary","inline"],"additionalProperties":false}'
|
|
127
|
+
prompt: |
|
|
128
|
+
REPO: ${{ github.repository }}
|
|
129
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
130
|
+
BASE: origin/${{ github.base_ref }}
|
|
131
|
+
HEAD: ${{ github.event.pull_request.head.sha }}
|
|
132
|
+
PREVIOUSLY REVIEWED HEAD (empty unless this is a push to an
|
|
133
|
+
already-open PR): ${{ github.event.before }}
|
|
134
|
+
|
|
135
|
+
Review this PR. You already have this repository's CLAUDE.md
|
|
136
|
+
loaded -- use it, don't restate it. Do not dispatch subagents
|
|
137
|
+
and do not try to post anything to GitHub: you have no write
|
|
138
|
+
access, and your only output is the structured result described
|
|
139
|
+
below. Specifically check:
|
|
140
|
+
|
|
141
|
+
1. If this project's CLAUDE.md documents a hub-and-spoke
|
|
142
|
+
workflow for src/tests changes (test-author before
|
|
143
|
+
code-implementer, review spokes run over the diff), judge
|
|
144
|
+
from the PR description and commit history whether it was
|
|
145
|
+
followed, and say what you could and could not establish
|
|
146
|
+
rather than guessing.
|
|
147
|
+
2. Anything the project's own code-review checklist would catch
|
|
148
|
+
(`.claude/agents/code-reviewer.md`, if present): correctness,
|
|
149
|
+
security, SOLID/simplification, test coverage.
|
|
150
|
+
3. If this project uses changesets or another release-versioning
|
|
151
|
+
convention CLAUDE.md documents, whether this PR's public-
|
|
152
|
+
surface change carries the entry that convention requires.
|
|
153
|
+
|
|
154
|
+
Note: the PR branch is already checked out in the current
|
|
155
|
+
working directory with full history. Read the change with
|
|
156
|
+
`git diff BASE...HEAD` (or `gh pr diff`).
|
|
157
|
+
|
|
158
|
+
Return exactly the structured result:
|
|
159
|
+
- `summary`: one markdown summary covering every check above
|
|
160
|
+
(say so explicitly for a check that passes, or that doesn't
|
|
161
|
+
apply to this project), always non-empty. It always covers
|
|
162
|
+
the whole PR, not just the latest push.
|
|
163
|
+
- `inline`: specific code issues, each with the file `path`, the
|
|
164
|
+
1-based `line` in the new version of that file, and a
|
|
165
|
+
`body`. Use an empty array when there are none. When
|
|
166
|
+
PREVIOUSLY REVIEWED HEAD is set, limit these to lines changed
|
|
167
|
+
since that commit (`git diff <previous>..HEAD`), because
|
|
168
|
+
earlier findings are already on the PR. Only use lines that
|
|
169
|
+
appear in the diff, since GitHub rejects any other.
|
|
170
|
+
|
|
171
|
+
# claude-code-action refuses to run on a PR that edits its own workflow
|
|
172
|
+
# file (the file must match the default branch's copy), logs a warning
|
|
173
|
+
# and still exits success with no outputs -- a green job with no review.
|
|
174
|
+
# That one case is expected, so it is detected by two independent facts:
|
|
175
|
+
# the action set no `conclusion` AND this PR really changes this file.
|
|
176
|
+
# Any other empty result is a failure, never a quiet pass.
|
|
177
|
+
- id: gate
|
|
178
|
+
env:
|
|
179
|
+
CONCLUSION: ${{ steps.claude.outputs.conclusion }}
|
|
180
|
+
REVIEW: ${{ steps.claude.outputs.structured_output }}
|
|
181
|
+
BASE: origin/${{ github.base_ref }}
|
|
182
|
+
run: |
|
|
183
|
+
set -euo pipefail
|
|
184
|
+
if [ -n "$REVIEW" ]; then
|
|
185
|
+
echo "skipped=false" >>"$GITHUB_OUTPUT"
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
if [ -z "$CONCLUSION" ] \
|
|
189
|
+
&& ! git diff --quiet "$BASE" HEAD -- .github/workflows/claude-pr-review.yml; then
|
|
190
|
+
echo "::notice::Claude was skipped: this PR edits claude-pr-review.yml, so claude-code-action will not run it until it merges. No review is posted."
|
|
191
|
+
echo "skipped=true" >>"$GITHUB_OUTPUT"
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
echo "::error::Claude returned no structured review (conclusion: '${CONCLUSION:-none}')."
|
|
195
|
+
exit 1
|
|
196
|
+
|
|
197
|
+
post:
|
|
198
|
+
needs: review
|
|
199
|
+
if: needs.review.result == 'success' && needs.review.outputs.skipped != 'true'
|
|
200
|
+
runs-on: ubuntu-latest
|
|
201
|
+
timeout-minutes: 10
|
|
202
|
+
permissions:
|
|
203
|
+
pull-requests: write
|
|
204
|
+
steps:
|
|
205
|
+
# Everything attacker-influenced (the review JSON, comment bodies) goes
|
|
206
|
+
# through `env:` and is only ever quoted as a variable, never
|
|
207
|
+
# interpolated into the script text.
|
|
208
|
+
- name: Post the review
|
|
209
|
+
env:
|
|
210
|
+
GH_TOKEN: ${{ github.token }}
|
|
211
|
+
REPO: ${{ github.repository }}
|
|
212
|
+
PR: ${{ github.event.pull_request.number }}
|
|
213
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
214
|
+
REVIEW: ${{ needs.review.outputs.review }}
|
|
215
|
+
run: |
|
|
216
|
+
set -euo pipefail
|
|
217
|
+
marker='<!-- claude-pr-review -->'
|
|
218
|
+
me='github-actions[bot]'
|
|
219
|
+
|
|
220
|
+
# A review without a summary is a failure, not an empty comment.
|
|
221
|
+
jq -e '(.summary | type == "string" and length > 0) and (.inline | type == "array")' \
|
|
222
|
+
<<<"$REVIEW" >/dev/null
|
|
223
|
+
|
|
224
|
+
# Inline comments first, so any that can't be anchored land in the
|
|
225
|
+
# summary. Skip a finding already on the PR from an earlier push.
|
|
226
|
+
existing=$(mktemp)
|
|
227
|
+
gh api --paginate "repos/$REPO/pulls/$PR/comments" \
|
|
228
|
+
--jq ".[] | select(.user.login == \"$me\") | [.path, (.line // .original_line), .body] | @json" \
|
|
229
|
+
>"$existing"
|
|
230
|
+
unanchored=$(mktemp)
|
|
231
|
+
err=$(mktemp)
|
|
232
|
+
while IFS= read -r finding; do
|
|
233
|
+
path=$(jq -r '.path' <<<"$finding")
|
|
234
|
+
line=$(jq -r '.line' <<<"$finding")
|
|
235
|
+
body=$(jq -r '.body' <<<"$finding")
|
|
236
|
+
key=$(jq -cn --arg p "$path" --argjson l "$line" --arg b "$body" '[$p, $l, $b]')
|
|
237
|
+
if grep -Fxq -- "$key" "$existing"; then
|
|
238
|
+
continue
|
|
239
|
+
fi
|
|
240
|
+
if ! gh api -X POST "repos/$REPO/pulls/$PR/comments" \
|
|
241
|
+
-f body="$body" -f commit_id="$HEAD_SHA" -f path="$path" \
|
|
242
|
+
-F line="$line" -f side=RIGHT >/dev/null 2>"$err"; then
|
|
243
|
+
# 422: the line isn't part of the diff. Anything else is real.
|
|
244
|
+
grep -q 'HTTP 422' "$err" || { cat "$err" >&2; exit 1; }
|
|
245
|
+
printf -- '- `%s:%s` -- %s\n' "$path" "$line" "$body" >>"$unanchored"
|
|
246
|
+
fi
|
|
247
|
+
done < <(jq -c '.inline[]' <<<"$REVIEW")
|
|
248
|
+
|
|
249
|
+
# One sticky summary: edit the marked comment, else create it.
|
|
250
|
+
summary=$(mktemp)
|
|
251
|
+
{
|
|
252
|
+
printf '%s\n' "$marker"
|
|
253
|
+
printf '_Reviewed commit `%s`._\n\n' "${HEAD_SHA:0:7}"
|
|
254
|
+
jq -r '.summary' <<<"$REVIEW"
|
|
255
|
+
if [ -s "$unanchored" ]; then
|
|
256
|
+
printf '\n### Findings that could not be anchored to a diff line\n\n'
|
|
257
|
+
cat "$unanchored"
|
|
258
|
+
fi
|
|
259
|
+
} >"$summary"
|
|
260
|
+
id=$(gh api --paginate "repos/$REPO/issues/$PR/comments" \
|
|
261
|
+
--jq ".[] | select(.user.login == \"$me\" and (.body | startswith(\"$marker\"))) | .id" \
|
|
262
|
+
| tail -n 1)
|
|
263
|
+
if [ -n "$id" ]; then
|
|
264
|
+
gh api -X PATCH "repos/$REPO/issues/comments/$id" -F body=@"$summary" >/dev/null
|
|
265
|
+
else
|
|
266
|
+
gh api -X POST "repos/$REPO/issues/$PR/comments" -F body=@"$summary" >/dev/null
|
|
267
|
+
fi
|