-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathaction.yml
More file actions
197 lines (188 loc) · 8.26 KB
/
Copy pathaction.yml
File metadata and controls
197 lines (188 loc) · 8.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
name: 'CALM Guard'
description: >-
Blocks risky code changes using CALM's blast-radius analysis (the same
diff_impact tool an MCP agent's own pre-commit gate runs) -- caller-count,
hub, and signature-change risk scored against the codebase's own call
graph, not a generic linter. One-line CI adoption; closes
KNOWN_LIMITATIONS.md's "No Git/CI-native integration path" for a change
made outside any MCP session (a teammate's native editor, a bot PR).
author: 'Eilodon'
branding:
icon: 'shield'
color: 'blue'
inputs:
project-root:
description: 'Path to the project root to index and guard.'
required: false
default: '.'
base:
description: >-
Ref/SHA to review HEAD against, e.g. "origin/main" -- becomes the
merge-base-relative `<base>...HEAD` range (calm guard --base). Leave
unset to auto-detect: the PR's base branch on a pull_request event,
the pushed-from commit on a push event, or (with neither) fall back
to calm guard's own default of the staged diff.
required: false
default: ''
commits:
description: >-
Raw commit range/rev passed straight through to `calm guard
--commits`, e.g. "HEAD~3..HEAD". Takes precedence over `base` and the
pull_request/push auto-detection when set. Mutually exclusive with
`base` (calm guard itself enforces this).
required: false
default: ''
fail-on:
description: 'Minimum aggregate risk that fails the step: low, medium, or high.'
required: false
default: 'high'
json:
description: 'Emit the raw diff_impact JSON as this step''s stdout instead of a human-readable report.'
required: false
default: 'false'
version:
description: >-
Which @eilodon/calm-mcp npm version to install. Defaults to 'latest',
which the install step resolves to the exact npm version matching
THIS action instance's own tag (release.yml publishes the action and
the npm package from the same tag in one coordinated workflow) rather
than npm's floating `latest` dist-tag -- set this explicitly only to
deliberately override that pinning.
required: false
default: 'latest'
skip-install:
description: >-
Skip the npm install step and use whatever `calm-mcp` is already on
PATH. For dogfooding this action against a not-yet-released commit
(build calm-cli from source, put a `calm-mcp`-named symlink to it on
PATH, then call this action with skip-install: true) -- see
.github/workflows/ci.yml's calm-guard-dogfood job for the pattern.
Most callers should leave this false and let npm install a real
published release.
required: false
default: 'false'
runs:
using: 'composite'
steps:
# Audit 11.3 (release-skew hazard): `action.yml`'s implementation comes
# from whatever ref the caller's `uses: eilodon/calm-mcp-action@X`
# checked out, but a bare npm `latest` install is a SEPARATE, floating
# identity that can drift out of sync with it -- a real past incident
# (see .github/workflows/ci.yml's calm-guard-dogfood job comment): this
# action called a subcommand that existed in source but hadn't been
# published to npm yet under `latest`, so CI had to switch to building
# calm-cli from source + skip-install to work around it. Since
# release.yml stamps the crate version from the release tag and
# publishes the matching npm version from that SAME tag in one
# coordinated workflow, `github.action_ref` (the tag THIS action
# instance was invoked at, e.g. "v0.6.0") is exactly the npm version
# guaranteed to match what this copy of action.yml expects -- resolved
# here instead of trusting npm's independently-moving `latest` dist-tag.
- name: Resolve calm version
if: inputs.skip-install != 'true'
id: calm_version
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
ACTION_REF: ${{ github.action_ref }}
run: |
set -euo pipefail
version="$INPUT_VERSION"
if [ "$version" = "latest" ]; then
if [[ "$ACTION_REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
version="${ACTION_REF#v}"
else
# Not invoked at a release tag (e.g. a branch ref during this
# action's own development) -- no fixed release point to pin
# to, so fall back to real npm latest rather than guessing.
version="latest"
fi
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Install calm
if: inputs.skip-install != 'true'
shell: bash
env:
CALM_VERSION: ${{ steps.calm_version.outputs.version }}
run: npm install --global "@eilodon/calm-mcp@$CALM_VERSION"
# All `github.*`/`inputs.*` context values are threaded in via `env:`
# rather than interpolated directly into the `run:` script -- a branch
# name, PR title, or other attacker-influenced context value spliced
# straight into shell text is a known GitHub Actions script-injection
# vector; reading it back out of an env var instead (quoted) keeps it
# as inert data no matter what it contains.
- name: Resolve review scope
id: scope
shell: bash
env:
INPUT_BASE: ${{ inputs.base }}
INPUT_COMMITS: ${{ inputs.commits }}
EVENT_NAME: ${{ github.event_name }}
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.event.after }}
run: |
set -euo pipefail
commits="$INPUT_COMMITS"
base="$INPUT_BASE"
fetch_ref=""
if [ -z "$commits" ] && [ -z "$base" ]; then
if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_BASE_REF" ]; then
base="origin/$PR_BASE_REF"
fetch_ref="$PR_BASE_REF"
elif [ "$EVENT_NAME" = "push" ] \
&& [ -n "$PUSH_BEFORE" ] \
&& [ "$PUSH_BEFORE" != "0000000000000000000000000000000000000000" ]; then
commits="$PUSH_BEFORE..$PUSH_AFTER"
fi
fi
# A default `actions/checkout` is a shallow (depth=1) clone -- the
# base branch's tip is very unlikely to already be present locally,
# so `calm guard --base` would fail to resolve it. Fetch it
# explicitly rather than requiring every caller to remember
# `fetch-depth: 0` in their own checkout step.
#
# Audit 11.4: a plain `--depth=1` fetch of the base ref (the prior
# version of this fix) has NO history shared with this checkout's
# OWN also-depth=1 HEAD -- `calm guard --base` builds a
# merge-base-relative `<base>...HEAD` range, and `git merge-base`
# then fails outright ("fatal: <base>...HEAD: no merge base")
# unless the two single commits happen to coincide. Reproduced for
# real in calm-guard-dogfood CI. `--depth=100` covers the
# overwhelming common case (most branches diverge by a handful of
# commits from their base); the `--unshallow` fallback guarantees a
# merge-base is found for anything that diverged further, at the
# cost of a slower fetch only in that rarer case.
if [ -n "$fetch_ref" ]; then
git fetch --no-tags --depth=100 origin "$fetch_ref" || true
if ! git merge-base "origin/$fetch_ref" HEAD >/dev/null 2>&1; then
git fetch --no-tags --unshallow origin || true
fi
fi
echo "commits=$commits" >> "$GITHUB_OUTPUT"
echo "base=$base" >> "$GITHUB_OUTPUT"
- name: calm index
shell: bash
env:
PROJECT_ROOT: ${{ inputs.project-root }}
run: calm-mcp index --project-root "$PROJECT_ROOT"
- name: calm guard
shell: bash
env:
PROJECT_ROOT: ${{ inputs.project-root }}
FAIL_ON: ${{ inputs.fail-on }}
EMIT_JSON: ${{ inputs.json }}
SCOPE_COMMITS: ${{ steps.scope.outputs.commits }}
SCOPE_BASE: ${{ steps.scope.outputs.base }}
run: |
set -euo pipefail
args=(guard --project-root "$PROJECT_ROOT" --fail-on "$FAIL_ON")
if [ -n "$SCOPE_COMMITS" ]; then
args+=(--commits "$SCOPE_COMMITS")
elif [ -n "$SCOPE_BASE" ]; then
args+=(--base "$SCOPE_BASE")
fi
if [ "$EMIT_JSON" = "true" ]; then
args+=(--json)
fi
calm-mcp "${args[@]}"