@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.
Files changed (170) hide show
  1. package/README.md +13 -5
  2. package/bin/m3l-groundwork.mjs +36 -2
  3. package/dist/assets.d.ts +10 -0
  4. package/dist/assets.js +14 -0
  5. package/dist/baseline-stage.d.ts +173 -0
  6. package/dist/baseline-stage.js +215 -0
  7. package/dist/caps.d.ts +4 -1
  8. package/dist/caps.js +17 -3
  9. package/dist/conflicts.d.ts +23 -2
  10. package/dist/conflicts.js +89 -11
  11. package/dist/customize-paths.d.ts +92 -0
  12. package/dist/customize-paths.js +115 -0
  13. package/dist/emit.d.ts +50 -1
  14. package/dist/emit.js +121 -13
  15. package/dist/fatal.d.ts +70 -0
  16. package/dist/fatal.js +132 -0
  17. package/dist/format-error.d.ts +113 -0
  18. package/dist/format-error.js +553 -0
  19. package/dist/fs-guard.d.ts +142 -0
  20. package/dist/fs-guard.js +222 -0
  21. package/dist/git.js +10 -1
  22. package/dist/harness/conformance.js +2 -0
  23. package/dist/harness/frontmatter.js +2 -13
  24. package/dist/harness/grade.js +37 -8
  25. package/dist/harness/rules.d.ts +20 -3
  26. package/dist/harness/rules.js +108 -6
  27. package/dist/harness/types.d.ts +13 -0
  28. package/dist/harness/types.js +3 -0
  29. package/dist/inventory.d.ts +77 -3
  30. package/dist/inventory.js +103 -12
  31. package/dist/jsonc.d.ts +49 -2
  32. package/dist/jsonc.js +140 -7
  33. package/dist/main.d.ts +62 -3
  34. package/dist/main.js +574 -67
  35. package/dist/merge-json.d.ts +47 -4
  36. package/dist/merge-json.js +120 -8
  37. package/dist/mode.js +12 -2
  38. package/dist/pack-stage.d.ts +210 -0
  39. package/dist/pack-stage.js +287 -0
  40. package/dist/packs.d.ts +21 -13
  41. package/dist/packs.js +241 -28
  42. package/dist/palette.d.ts +23 -0
  43. package/dist/palette.js +22 -0
  44. package/dist/plugin.d.ts +122 -6
  45. package/dist/plugin.js +687 -47
  46. package/dist/report.d.ts +21 -2
  47. package/dist/report.js +93 -5
  48. package/dist/staging.d.ts +176 -0
  49. package/dist/staging.js +375 -0
  50. package/dist/survey/fs-walk.d.ts +34 -2
  51. package/dist/survey/fs-walk.js +70 -5
  52. package/dist/survey/internal/blocked-path.d.ts +27 -0
  53. package/dist/survey/internal/blocked-path.js +129 -0
  54. package/dist/survey/internal/package-json.d.ts +13 -0
  55. package/dist/survey/internal/package-json.js +37 -0
  56. package/dist/survey/internal/read-guard.d.ts +178 -0
  57. package/dist/survey/internal/read-guard.js +276 -0
  58. package/dist/survey/survey-docs.d.ts +17 -2
  59. package/dist/survey/survey-docs.js +61 -21
  60. package/dist/survey/survey-harness.d.ts +20 -2
  61. package/dist/survey/survey-harness.js +87 -46
  62. package/dist/survey/survey-shape.d.ts +17 -2
  63. package/dist/survey/survey-shape.js +60 -46
  64. package/dist/survey/survey-toolchain.d.ts +16 -1
  65. package/dist/survey/survey-toolchain.js +69 -66
  66. package/dist/survey/survey.js +6 -4
  67. package/dist/survey/types.d.ts +79 -0
  68. package/dist/survey/types.js +2 -6
  69. package/dist/term.d.ts +80 -0
  70. package/dist/term.js +145 -0
  71. package/dist/tokens.js +2 -0
  72. package/dist/toolchain/conformance.js +2 -0
  73. package/dist/toolchain/grade.js +26 -8
  74. package/dist/toolchain/rules.d.ts +13 -4
  75. package/dist/toolchain/rules.js +16 -0
  76. package/dist/toolchain/tsconfig-chain.d.ts +2 -0
  77. package/dist/toolchain/tsconfig-chain.js +32 -8
  78. package/dist/toolchain/types.d.ts +16 -4
  79. package/dist/toolchain/types.js +3 -0
  80. package/package.json +4 -3
  81. package/plugin/skills/customize/SKILL.md +292 -18
  82. package/plugin/src/domain-map.ts +39 -14
  83. package/plugin/src/index.ts +5 -1
  84. package/plugin/src/kind-facet-map.ts +25 -8
  85. package/plugin/src/pack-map.ts +147 -25
  86. package/plugin/src/plugin-map.ts +236 -0
  87. package/templates/core/.claude/agents/Explore.md +0 -1
  88. package/templates/core/.claude/agents/code-implementer.md +4 -4
  89. package/templates/core/.claude/agents/code-reviewer.md +4 -4
  90. package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
  91. package/templates/core/.claude/agents/test-author.md +7 -5
  92. package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
  93. package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
  94. package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
  95. package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
  96. package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
  97. package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
  98. package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
  99. package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
  100. package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
  101. package/templates/core/.claude/rules/agent-dispatch.md +7 -0
  102. package/templates/core/.claude/rules/tests.md +2 -2
  103. package/templates/core/.claude/settings.json +5 -0
  104. package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
  105. package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
  106. package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
  107. package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
  108. package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
  109. package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
  110. package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
  111. package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
  112. package/templates/core/.github/dependabot.yml +18 -0
  113. package/templates/core/.github/workflows/ci.yml +15 -15
  114. package/templates/core/.github/workflows/dependency-review.yml +2 -2
  115. package/templates/core/.github/workflows/security-audit.yml +10 -4
  116. package/templates/core/.prettierignore +4 -0
  117. package/templates/core/CLAUDE.md +53 -3
  118. package/templates/core/README.md +30 -10
  119. package/templates/core/_gitignore +17 -0
  120. package/templates/core/bin/check-exports.mjs +11 -5
  121. package/templates/core/bin/lib/agent-roster.mjs +1 -1
  122. package/templates/core/bin/lib/frontmatter.mjs +4 -2
  123. package/templates/core/bin/lib/harness-rules.mjs +169 -20
  124. package/templates/core/bin/lib/protected-paths.mjs +93 -5
  125. package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
  126. package/templates/core/bin/lib/verify-steps.mjs +29 -11
  127. package/templates/core/bin/verify.mjs +12 -0
  128. package/templates/core/eslint.config.js +5 -0
  129. package/templates/core/package.json +7 -7
  130. package/templates/core/vitest.config.ts +8 -1
  131. package/templates/packs/README.md +44 -24
  132. package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
  133. package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
  134. package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
  135. package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
  136. package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
  137. package/templates/packs/github/pack.json +19 -0
  138. package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
  139. package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
  140. package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
  141. package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
  142. package/templates/packs/harness-extras/pack.json +18 -13
  143. package/templates/packs/publishing/files/.changeset/README.md +25 -0
  144. package/templates/packs/publishing/files/.changeset/config.json +7 -0
  145. package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
  146. package/templates/packs/publishing/files/.github/workflows/release.yml +295 -0
  147. package/templates/packs/publishing/files/REUSE.toml +28 -0
  148. package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
  149. package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
  150. package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
  151. package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
  152. package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
  153. package/templates/packs/publishing/pack.json +41 -0
  154. package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
  155. package/templates/packs/quality/pack.json +29 -0
  156. package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
  157. package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
  158. package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
  159. package/templates/packs/supply-chain/pack.json +19 -0
  160. package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
  161. package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
  162. package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
  163. package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
  164. package/templates/packs/worktrees/files/.worktreeinclude +11 -0
  165. package/templates/packs/worktrees/pack.json +64 -0
  166. package/templates/packs/statusline/pack.json +0 -31
  167. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
  168. /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
  169. /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
  170. /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
+ }