Skip to content

Commit 255deb6

Browse files
thymikeeclaude
andauthored
ci: fold single-grep jobs into steps, call named pnpm scripts (#1465)
* ci: fold single-grep jobs into steps, call named pnpm scripts - Merge ios-runner-swift-compat and no-test-di-seams (each just checkout + one rg assertion) into steps of a new static-checks job, keeping each step's own failure message. Removes two job-scheduling/ checkout overheads and two PR status-check lines. - Replace the layering-guard job's inlined copies of check:layering and depgraph:test with the named pnpm scripts, removing the silent-drift risk between the workflow and package.json. - Fix the same drift in conformance-regenerate.yml, which inlined maestro:conformance:regenerate byte-for-byte. - Leave affected-selector's inline node invocation as-is: R8's zero-dep closure check (scripts/layering/zero-dep-jobs.ts) finds a job's entry scripts by matching literal paths in the run: block, so switching to `pnpm check:affected:test` would zero out its entries and make R8 fail closed. Documented inline why this one stays inlined. - Leave publish-mcp-registry.yml's sync-mcp-metadata --check alone: that job never runs the setup-node-pnpm action, so pnpm isn't provisioned there at all. Refs #1462 * ci: teach R8 to resolve pnpm script names, drop affected-selector's inline copy R8's zero-dep-job entry scan matched literal script paths in a run: block, so a bare `pnpm <script>` invocation found zero entries and R8 failed closed — the reason affected-selector kept an inline node command instead of calling pnpm check:affected:test (#1462). zeroDepJobs now also resolves a pnpm script name against package.json and scans the resolved command for entry paths, so affected-selector can call the named script like every other job. Also replaced the other workflows' inlined copies of named package.json scripts (test:replay:*, perf, perf:android, maestro:conformance:differential, check:mcp-metadata, size) with their pnpm names, keeping each job's CI-specific trailing flags — found via a repo-wide sweep for any run: block whose text duplicates a scripts entry. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01L67kDSTAJwaoLCwANEJFRM * fix: revert publish-mcp-registry pnpm regression, recurse R8 alias resolution publish-mcp-registry.yml's job only provisions Node via actions/setup-node, never the repo's setup-node-pnpm action, so pnpm is never installed there — the earlier sweep's `pnpm check:mcp-metadata` would have broken the release path. Reverted to the direct node invocation with a comment explaining why, matching the PR's own stated rationale for leaving it alone. zeroDepJobs' pnpm-alias resolution only expanded one level: a resolved script that itself invoked another named pnpm script had its entries silently dropped from R8's closure. resolveRunEntries now recurses through chained aliases with a per-chain visited set, so a nested alias's entries are found and a cycle stops re-expanding a repeated name instead of recursing forever. Added coverage for both the chained and cyclic cases. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01L67kDSTAJwaoLCwANEJFRM --------- Co-authored-by: Claude <[email protected]>
1 parent 4e4ecde commit 255deb6

12 files changed

Lines changed: 224 additions & 40 deletions

‎.github/workflows/ci.yml‎

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ concurrency:
2222
cancel-in-progress: true
2323

2424
jobs:
25-
ios-runner-swift-compat:
26-
name: iOS Runner Swift Compatibility
25+
# Two single-`rg`-assertion jobs (formerly `ios-runner-swift-compat`,
26+
# `no-test-di-seams`, added independently in b79bd8601 / 9eb060406) folded
27+
# into steps here: each was checkout + one grep, paying full job
28+
# scheduling/checkout overhead and its own PR status-check line for what is
29+
# a single assertion. Each step keeps its own failure message. See #1462.
30+
static-checks:
31+
name: Static Checks
2732
runs-on: ubuntu-latest
2833
timeout-minutes: 10
2934
steps:
@@ -37,6 +42,13 @@ jobs:
3742
exit 1
3843
fi
3944
45+
- name: Fail if test-only DI seams reappear in production code
46+
run: |
47+
if rg '\?\s*:\s*typeof\s+' src/ --glob '!**/__tests__/**' --glob '!*.test.ts'; then
48+
echo "Found test-only DI seams (optional typeof params) in production code."
49+
exit 1
50+
fi
51+
4052
swift-runner-unit-compile:
4153
name: Swift Runner Unit Compile
4254
runs-on: macos-26
@@ -57,21 +69,6 @@ jobs:
5769
xcuitest-platform: macos
5870
xcuitest-destination: platform=macOS,arch=arm64
5971

60-
no-test-di-seams:
61-
name: No test-only DI seams
62-
runs-on: ubuntu-latest
63-
timeout-minutes: 5
64-
steps:
65-
- name: Checkout
66-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
67-
68-
- name: Fail if test-only DI seams reappear in production code
69-
run: |
70-
if rg '\?\s*:\s*typeof\s+' src/ --glob '!**/__tests__/**' --glob '!*.test.ts'; then
71-
echo "Found test-only DI seams (optional typeof params) in production code."
72-
exit 1
73-
fi
74-
7572
lint:
7673
name: Lint & Format
7774
runs-on: ubuntu-latest
@@ -110,16 +107,14 @@ jobs:
110107
# Generalizes the former inline commands/-import grep into a structured
111108
# import-direction lint over the resolved graph. See scripts/layering/check.ts
112109
# and CONTEXT.md (Architecture: folder DAG + layering lint).
113-
run: |
114-
node --experimental-strip-types --test scripts/layering/model.test.ts scripts/layering/zone-policy.test.ts
115-
node --experimental-strip-types scripts/layering/check.ts
110+
run: pnpm check:layering
116111

117112
- name: Check the depgraph report agrees with the gate
118113
# scripts/depgraph reads the same model as the gate, so its inversion count must
119114
# reproduce TYPE_INVERSION_BASELINE. Free two-sources check: if the tree changes
120115
# and only one side is updated, this fails and names the difference. Runs here
121116
# rather than in its own job so the two can never be green independently.
122-
run: node --experimental-strip-types --test scripts/depgraph/model.test.ts
117+
run: pnpm depgraph:test
123118

124119
affected-selector:
125120
name: Affected-check Selector
@@ -135,10 +130,12 @@ jobs:
135130
install-deps: false
136131

137132
# The selector is fail-open and advisory (GitHub CI stays authoritative),
138-
# so the gate only guards the derivation model. Invoked directly with no
139-
# deps, mirroring the layering guard.
133+
# so the gate only guards the derivation model. Invoked with no deps,
134+
# mirroring the layering guard. R8 (scripts/layering/zero-dep-jobs.ts)
135+
# resolves this bare pnpm script name back to package.json's own command
136+
# to find its entry scripts, so this is not a `pnpm install` for the job.
140137
- name: Check affected-selector model
141-
run: node --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/run.test.ts
138+
run: pnpm check:affected:test
142139

143140
maestro-conformance:
144141
name: Maestro Conformance Oracle

‎.github/workflows/conformance-differential.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ jobs:
126126
# burning its budget) — outcome parity alone cannot see that.
127127
- name: Run differential
128128
run: |
129-
node --experimental-strip-types scripts/maestro-conformance/differential/run.ts \
129+
pnpm maestro:conformance:differential \
130130
--platform ios \
131131
--out-dir "${{ github.workspace }}/.tmp/conformance-differential" \
132132
--trace-root "${{ github.workspace }}/.agent-device" \

‎.github/workflows/conformance-regenerate.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
# Verifies the pinned jar SHA-256s against pinned-upstream.json, runs the
5050
# harness over the corpus, and rewrites fixtures/ + corpus/manifest.json.
5151
- name: Regenerate fixtures from pinned upstream
52-
run: node scripts/maestro-conformance/regenerate.mjs
52+
run: pnpm maestro:conformance:regenerate
5353

5454
- name: Fail if regeneration changed anything
5555
run: |

‎.github/workflows/linux.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
- name: Run Linux replay smoke test
105105
run: |
106106
pnpm clean:daemon
107-
node --experimental-strip-types src/bin.ts test test/integration/replays/linux \
107+
pnpm test:replay:linux \
108108
--retries 2 \
109109
--report-junit test/artifacts/replays-linux.junit.xml
110110

‎.github/workflows/macos.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: pnpm build:macos-helper
5050

5151
- name: Run macOS integration test
52-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
52+
run: pnpm test:replay:macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
5353

5454
- name: Upload macOS artifacts
5555
if: always()

‎.github/workflows/perf-nightly.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
6969
- name: Run iOS command perf benchmark
7070
run: |
71-
node --experimental-strip-types scripts/perf/run.ts \
71+
pnpm perf \
7272
--platform ios \
7373
--device "iPhone 17 Pro" \
7474
--n "$PERF_ROUNDS" --warmup 1 \
@@ -112,7 +112,7 @@ jobs:
112112
adb -s emulator-5554 shell settings put global window_animation_scale 0 || true;
113113
adb -s emulator-5554 shell settings put global transition_animation_scale 0 || true;
114114
adb -s emulator-5554 shell settings put global animator_duration_scale 0 || true;
115-
node --experimental-strip-types scripts/perf/run.ts --platform android --serial emulator-5554 --n "$PERF_ROUNDS" --warmup 1 --out-dir "$GITHUB_WORKSPACE/perf-results"
115+
pnpm perf:android --serial emulator-5554 --n "$PERF_ROUNDS" --warmup 1 --out-dir "$GITHUB_WORKSPACE/perf-results"
116116
117117
- name: Upload Android perf report
118118
if: always()

‎.github/workflows/publish-mcp-registry.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ jobs:
5454
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
5555
shell: bash
5656

57+
# This job provisions pnpm via plain actions/setup-node, not the repo's
58+
# ./.github/actions/setup-node-pnpm — so pnpm itself is never installed here,
59+
# and `pnpm check:mcp-metadata` would fail before publishing. Keep the direct
60+
# node invocation; it duplicates check:mcp-metadata's command deliberately.
5761
- name: Check MCP metadata sync
5862
run: node scripts/sync-mcp-metadata.mjs --check
5963

‎.github/workflows/replays-nightly.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ jobs:
114114
profile: pixel_7
115115
target: google_apis_playstore
116116
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -no-metrics
117-
script: node --experimental-strip-types src/bin.ts test test/integration/replays/android --retries 2 --report-junit test/artifacts/replays-android.junit.xml
117+
script: pnpm test:replay:android --retries 2 --report-junit test/artifacts/replays-android.junit.xml
118118

119119
- name: Upload Android artifacts
120120
if: always()
@@ -168,7 +168,7 @@ jobs:
168168
pnpm clean:daemon
169169
170170
- name: Run iOS simulator replay suite
171-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --retries 2 --artifacts-dir test/artifacts/replays-ios-simulator --report-junit test/artifacts/replays-ios-simulator.junit.xml
171+
run: pnpm test:replay:ios --udid "${{ steps.ios-simulator.outputs.simulator-udid }}" --retries 2 --artifacts-dir test/artifacts/replays-ios-simulator --report-junit test/artifacts/replays-ios-simulator.junit.xml
172172

173173
- name: Fetch current fixture app
174174
id: fixture-app
@@ -195,7 +195,7 @@ jobs:
195195
if: env.IOS_UDID != ''
196196
env:
197197
IOS_UDID: ${{ vars.IOS_UDID }}
198-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/ios/device --udid "$IOS_UDID" --retries 2 --artifacts-dir test/artifacts/replays-ios-device --report-junit test/artifacts/replays-ios-device.junit.xml
198+
run: pnpm test:replay:ios-device --udid "$IOS_UDID" --retries 2 --artifacts-dir test/artifacts/replays-ios-device --report-junit test/artifacts/replays-ios-device.junit.xml
199199

200200
- name: Upload iOS artifacts
201201
if: always()
@@ -232,7 +232,7 @@ jobs:
232232
run: pnpm build:macos-helper
233233

234234
- name: Run macOS replay suite
235-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
235+
run: pnpm test:replay:macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
236236

237237
- name: Upload macOS artifacts
238238
if: always()

‎.github/workflows/size.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
git checkout --detach "${{ github.event.pull_request.head.sha }}"
7272
pnpm install --frozen-lockfile
7373
pnpm build
74-
node scripts/size-report.mjs \
74+
pnpm size \
7575
--compare /tmp/agent-device-size-base.json \
7676
--startup-runs 7 \
7777
--json .tmp/size-report.json \
@@ -84,4 +84,4 @@ jobs:
8484
env:
8585
GITHUB_TOKEN: ${{ github.token }}
8686
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
87-
run: node scripts/size-report.mjs --post-comment .tmp/size-report.md
87+
run: pnpm size --post-comment .tmp/size-report.md

‎scripts/layering/check.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,13 @@ function checkZeroDepJobs(): Violation[] {
400400
.split('\n')
401401
.filter(Boolean),
402402
);
403+
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
404+
scripts?: Record<string, string>;
405+
};
406+
const packageScripts = new Map(Object.entries(packageJson.scripts ?? {}));
403407

404408
const violations: Violation[] = [];
405-
for (const job of zeroDepJobs(workflows, fileExists)) {
409+
for (const job of zeroDepJobs(workflows, fileExists, packageScripts)) {
406410
// Fail closed: a zero-dep job whose commands the entry scan cannot recognize would
407411
// otherwise be silently exempt from the rule it is the whole reason for.
408412
if (job.entries.length === 0) {

0 commit comments

Comments
 (0)