@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.4
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 +574 -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 +21 -13
- package/dist/packs.js +241 -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 +44 -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 +295 -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 +41 -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,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
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
# Anthropic's official Claude Code GitHub Action (anthropics/claude-code-action),
|
|
4
|
+
# in interactive mode: responds to @claude mentions on issues, PR reviews,
|
|
5
|
+
# and review comments. Claude commits to a branch and links back to a
|
|
6
|
+
# PR-creation page -- it never opens a PR itself.
|
|
7
|
+
#
|
|
8
|
+
# Want automated review on every PR too? That's a separate workflow, not a
|
|
9
|
+
# block to uncomment here (a second `on:`/`jobs:` in one file would collide
|
|
10
|
+
# the moment it's uncommented) -- see Anthropic's own
|
|
11
|
+
# examples/pr-review-comprehensive.yml:
|
|
12
|
+
# https://github.com/anthropics/claude-code-action/blob/main/examples/pr-review-comprehensive.yml
|
|
13
|
+
#
|
|
14
|
+
# This workflow needs one of the three auth methods below (Option 1 is
|
|
15
|
+
# active by default) -- until one is configured it fails cleanly on an
|
|
16
|
+
# @claude mention (an auth error, not a crash or a silent no-op).
|
|
17
|
+
on:
|
|
18
|
+
issue_comment:
|
|
19
|
+
types: [created]
|
|
20
|
+
pull_request_review_comment:
|
|
21
|
+
types: [created]
|
|
22
|
+
issues:
|
|
23
|
+
types: [opened, assigned]
|
|
24
|
+
pull_request_review:
|
|
25
|
+
types: [submitted]
|
|
26
|
+
|
|
27
|
+
# Reset, then grant per job.
|
|
28
|
+
permissions: {}
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
claude:
|
|
32
|
+
# Issues and comments are open to anyone on a public repository, so the
|
|
33
|
+
# `@claude` text match alone would let an outside commenter trigger a job
|
|
34
|
+
# holding `contents: write` and your auth secret. Each branch below also
|
|
35
|
+
# requires the `author_association` of the comment's, review's or
|
|
36
|
+
# issue's author to be OWNER, MEMBER or COLLABORATOR. Two consequences:
|
|
37
|
+
# an org member whose membership is private can surface as CONTRIBUTOR
|
|
38
|
+
# and be skipped, and on `issues` the check is on the issue's author, not
|
|
39
|
+
# on whoever assigned it, so assigning an outsider's issue does not start
|
|
40
|
+
# a run.
|
|
41
|
+
if: |
|
|
42
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
|
|
43
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
|
|
44
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) ||
|
|
45
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association))
|
|
46
|
+
# One run per issue/PR at a time. A later @claude mention queues behind
|
|
47
|
+
# the run in flight without cancelling it; GitHub keeps only the latest
|
|
48
|
+
# pending run per group, so a burst of mentions can drop the middle ones.
|
|
49
|
+
concurrency:
|
|
50
|
+
group: claude-${{ github.event.issue.number || github.event.pull_request.number }}
|
|
51
|
+
cancel-in-progress: false
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
timeout-minutes: 30
|
|
54
|
+
permissions:
|
|
55
|
+
contents: write
|
|
56
|
+
pull-requests: write
|
|
57
|
+
issues: write
|
|
58
|
+
# Required even with a static API key: the default GitHub App
|
|
59
|
+
# authentication path uses this to fetch a GitHub OIDC token for its
|
|
60
|
+
# own token exchange, separate from how the action authenticates to
|
|
61
|
+
# Anthropic.
|
|
62
|
+
id-token: write
|
|
63
|
+
actions: read # lets Claude read CI results on the PR it's tagged in
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
- name: Checkout repository
|
|
67
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
68
|
+
with:
|
|
69
|
+
persist-credentials: false
|
|
70
|
+
fetch-depth: 1
|
|
71
|
+
|
|
72
|
+
- name: Run Claude Code
|
|
73
|
+
uses: anthropics/claude-code-action@ed670b4cf9de2a5a570d130d2f6197b9e543cd64 # v1.0.240
|
|
74
|
+
with:
|
|
75
|
+
# Option 1 (active default): a stored API key. Install the Claude
|
|
76
|
+
# GitHub App (https://github.com/apps/claude) and add
|
|
77
|
+
# ANTHROPIC_API_KEY as a repository secret.
|
|
78
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
79
|
+
|
|
80
|
+
# Commit through the GitHub API instead of local `git`, so GitHub
|
|
81
|
+
# signs the commits -- a branch ruleset requiring signed commits
|
|
82
|
+
# would otherwise reject whatever this job pushes.
|
|
83
|
+
use_commit_signing: true
|
|
84
|
+
|
|
85
|
+
# Option 2: a Claude subscription's OAuth token instead of an API
|
|
86
|
+
# key -- generate once locally with `claude setup-token`, then add
|
|
87
|
+
# it as CLAUDE_CODE_OAUTH_TOKEN and swap it in for the line above.
|
|
88
|
+
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
89
|
+
|
|
90
|
+
# Option 3: Workload Identity Federation -- no stored secret at
|
|
91
|
+
# all, exchanges this workflow's GitHub OIDC token for a
|
|
92
|
+
# short-lived Anthropic token. Needs a one-time setup in the
|
|
93
|
+
# Anthropic Console (Settings -> Workload identity): register a
|
|
94
|
+
# GitHub Actions issuer, create a service account, create a
|
|
95
|
+
# federation rule. Then replace anthropic_api_key above with:
|
|
96
|
+
# anthropic_federation_rule_id: fdrl_xxxxxxxxxxxx
|
|
97
|
+
# anthropic_organization_id: 00000000-0000-0000-0000-000000000000
|
|
98
|
+
# anthropic_service_account_id: svac_xxxxxxxxxxxx
|
|
99
|
+
|
|
100
|
+
# Optional: customize the trigger phrase (default: @claude)
|
|
101
|
+
# trigger_phrase: "/claude"
|
|
102
|
+
|
|
103
|
+
# Optional: configure Claude's behavior with CLI arguments
|
|
104
|
+
# claude_args: |
|
|
105
|
+
# --max-turns 10
|
|
106
|
+
# --allowedTools "Bash(npm install),Bash(npm run build),Bash(npm run test:*)"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "github",
|
|
4
|
+
"description": "GitHub-hosted collaboration on top of the baseline: Anthropic's official Claude Code GitHub Action (anthropics/claude-code-action) wired for @claude mention-mode, a second Action that posts an automated Claude review comment on every PR, and three gh-CLI skills -- reviewing-dependabot-prs (classify and batch-merge open Dependabot PRs), triaging-scan-alerts (triage open code-scanning alerts to file:line, report only), and watching-pr-checks (check a PR's CI status and hand off to a project's own /triaging-ci on a real failure).",
|
|
5
|
+
"modes": ["fresh", "adopt"],
|
|
6
|
+
"budget": {
|
|
7
|
+
"agents": 0,
|
|
8
|
+
"skills": 3,
|
|
9
|
+
"hooks": 0,
|
|
10
|
+
"workflows": 2,
|
|
11
|
+
"scripts": 0
|
|
12
|
+
},
|
|
13
|
+
"wiring": {
|
|
14
|
+
"settings": {},
|
|
15
|
+
"packageScripts": {},
|
|
16
|
+
"verifySteps": []
|
|
17
|
+
},
|
|
18
|
+
"adoptNotes": "claude.yml only reacts to @claude mentions from a repository owner, member or collaborator (author_association), so an outside commenter on a public repository cannot start a job that holds contents: write and your auth secret; an org member with private membership may surface as CONTRIBUTOR and be skipped. It also commits through the GitHub API (use_commit_signing) so a signed-commits ruleset accepts its work. Both workflows need one thing this pack cannot verify or install: an ANTHROPIC_API_KEY repository secret (the active default), or a CLAUDE_CODE_OAUTH_TOKEN from a Claude subscription, or a Workload Identity Federation setup in the Anthropic Console -- see the comments in .github/workflows/claude.yml for all three. Until one is configured, either workflow runs and fails cleanly (an auth error in the run log, not a crash and not a silent no-op): claude.yml on an @claude mention, claude-pr-review.yml on the next PR open/push. An adopted project that already has its own .github/workflows/claude.yml or claude-pr-review.yml surfaces as an ordinary file conflict -- keep the existing one if it's already configured for that project's own auth choice, since this pack's version would just overwrite a working setup with its own generic defaults. triaging-scan-alerts reads code-scanning alerts, and nothing in the baseline or this pack produces them: turn on GitHub's CodeQL default setup for the repository (Settings > Code security), or install the `publishing` pack, whose scorecard.yml uploads SARIF, otherwise the skill's first call returns \"code scanning is not enabled\" and reports nothing. The other two skills have no such dependency: they install anywhere a .claude/ directory exists and need only `gh` CLI auth (already required by every other gh-CLI-based skill this baseline ships). watching-pr-checks' handoff to /triaging-ci degrades gracefully if the project doesn't have that skill installed -- it still reports the real failure, just without the handoff."
|
|
19
|
+
}
|