Files
Michał Pierzchała df0a0f7fd2 perf(package): strip comments from the Apple runner source the npm package ships (#2467)
* perf(package): strip comments from the Apple runner source the npm package ships

The packager copies apple/runner/** into dist/ as Swift source, removing only
its AGENT_DEVICE_RUNNER_UNIT_TESTS blocks, so doc comments and design notes were
downloaded on every install: 71.9 kB of 441.2 kB of packaged runner Swift.

Add a lexical scanner for the removal. A regex cannot do this: `//` and `/*`
open a comment only in code position, raw literals move their own delimiter and
escape with the `#` count, interpolation segments hold code and further
literals, and Swift block comments nest. A construct the scanner cannot account
for throws at packaging time instead of shipping Swift that does not compile.

* fix(package): keep Swift regex literals out of the comment scanner

`#/foo//bar/#` is a valid extended regex literal with no comment in it, but the
scanner only knew the `#"` raw-string family, so it read the literal's `//` as a
line comment and shipped `let pattern = #/foo` — Swift that does not compile.
Add `#/…/#` and `##/…/##` as a literal context: matching `#` counts, the
single- and multi-line forms, Swift's own-line rule for a multi-line closing
delimiter, and the `\/` escape that keeps one from closing early.

Bare `/…/` literals stay unresolvable, because the same `/` opens a comment,
divides, and starts a regex literal, and only the parse separates them. Where
one could begin — an expression position whose `/` is not followed by a space,
a tab or `)` — packaging throws by file and line instead of rewriting bytes it
cannot prove are code. Divisions (`width/2`, `Double(3)/Double(4)`), the
recording scripts' shebang and `(/)` keep flowing through.

* fix(package): keep the packaged runner source on the checkout's line numbers

`dist/apple/runner/**` is the Swift a user's `xcodebuild` and the runner name a
file and line in (it lands in runner.log), so those numbers are only worth
reading if they point at the same line of `apple/runner/**`. Both rewriting
passes now empty the lines they remove instead of deleting them: comment removal
(889 lines, 889 B) and the pre-existing unit-test `#if` block strip, which was
moving everything below a block by up to 883 lines (3,737 lines, 3,737 B).

`dist/apple/runner/` 555,907 B -> 488,635 B (-67,272 B, -12.1%); its Swift alone
441,196 B -> 373,924 B (-15.2%). Parity costs 4,626 B of the 71,898 B the
previous head saved.

Nothing in the repo compiles the packaged source, so a mis-lex that failed to
throw would ship Swift that does not build and no gate would see it. Add
`pnpm check:packaged-runner-swift`: it packages into a throwaway root and asserts
line-count parity plus the line of every declaration each packaged file still
carries, then runs `swiftc -parse` over all 44 files. The parse half reports
itself skipped where no Swift toolchain exists, so the gate is declared on the
macOS lane, where both halves run.
2026-09-11 12:00:02 +02:00

218 lines
10 KiB
TypeScript

// The canonical gate registry: every check this repo can run, and how to run it.
//
// One entry per gate, and one universe — the affected-selector's vocabulary and
// the set of gates CI runs are the same list, because CI may only invoke a gate
// through the structural run-gate action. That lets the manifest derive the
// workflow→check mapping from YAML fields without interpreting shell.
//
// The jobs that run a check are NOT recorded here. They are derived from the
// workflows by scripts/gate/model.ts, so the "GitHub-authoritative" claim an
// agent reads before skipping a check locally cannot go stale.
import { ALL_CHECKS, type CheckId } from './model.ts';
export type CheckKind =
| { readonly type: 'script'; readonly script: string }
| { readonly type: 'vitest-related' };
export type CheckSpec = {
readonly id: CheckId;
readonly label: string;
readonly kind: CheckKind;
// Whether `--run` should attempt the check locally. Device/emulator lanes,
// network/toolchain-gated lanes, long scheduled sweeps (mutation, fuzz,
// torture), and the instrumented coverage run stay authoritative on
// GitHub CI.
readonly localRunnable: boolean;
};
function gate(id: CheckId, label: string, script: string, localRunnable = true): CheckSpec {
return { id, label, kind: { type: 'script', script }, localRunnable };
}
export const CHECK_CATALOG: readonly CheckSpec[] = [
gate('format', 'Formatting (oxfmt)', 'format:check'),
gate('lint', 'Lint (oxlint)', 'lint'),
gate('typecheck', 'Typecheck (tsc)', 'typecheck'),
// The test app intentionally owns a separate Expo dependency graph. Do not
// make every root-checkout validation install it implicitly.
gate('test-app-typecheck', 'Expo test app typecheck', 'test-app:typecheck', false),
gate(
'test-app-security',
'Expo test app image-size parser security test',
'test-app:security',
false,
),
gate('layering', 'Import-direction layering guard', 'check:layering'),
gate('di-seams', 'Test-only DI seam guard', 'check:di-seams'),
gate('fallow', 'Fallow code-quality audit', 'check:fallow'),
gate('mcp-metadata', 'MCP registry metadata sync', 'check:mcp-metadata'),
gate('build', 'Build (tsdown + declarations)', 'build'),
gate('package', 'Published package (publint, attw, clean-install resolution)', 'check:package'),
gate('integration-node', 'Node integration smoke', 'test:integration:node'),
gate('macos-coverage', 'macOS command coverage manifest', 'test:integration:macos-coverage'),
gate(
'ios-snapshot-differential',
'iOS snapshot Swift/TypeScript differential',
'test:ios-snapshot-differential',
false,
),
{
id: 'vitest-related',
label: 'Tests related by Vitest module graph',
kind: { type: 'vitest-related' },
localRunnable: true,
},
gate('unit', 'Unit + smoke suite', 'check:unit'),
// CI owns the default coverage run; the scripts remain available for explicit local diagnosis.
gate('coverage', 'Changed-line coverage', 'check:coverage-changed', false),
gate('provider-integration', 'Provider-backed integration suite', 'test:integration:provider'),
gate(
'integration-progress',
'Integration architecture-progress gate',
'test:integration:progress:check',
),
gate('swift-runner-ios', 'Swift runner build (iOS)', 'build:xcuitest:ios', false),
gate('swift-runner-macos', 'Swift runner build (macOS)', 'build:xcuitest:macos', false),
// `build:android`, not `build:android-snapshot-helper`: the Android lane needs both
// helpers packaged into `android/*/dist` (what the replay host verifies), where the
// build script writes only the snapshot helper into `.tmp/`.
gate('android-helpers', 'Android helper builds (snapshot + IME)', 'build:android', false),
gate('macos-helper', 'macOS helper build', 'build:macos-helper', false),
gate('web-smoke', 'Live web platform smoke', 'test:smoke:web', false),
// Needs full history and tags, so it runs in the shared fetch-depth: 0 job
// rather than inside the shallow-clone-safe unit lane.
gate('replay-compat', 'Replay-compat corpus provenance (released blobs)', 'check:replay-compat'),
gate(
'daemon-wire-compat',
'Daemon RPC wire surface vs. last released tag',
'check:daemon-wire-compat',
),
// --- Tooling gates ---------------------------------------------------------
// Each proves one of the checkers above still behaves. They were always real CI
// steps; before the registry became canonical, nothing in the repo could name
// them, so nothing could ask whether they still ran.
//
// `unit-ci` is the CI form of the unit suite under coverage. Local affected checks use Vitest's
// related graph without instrumentation; CI owns the full coverage run and changed-line verdict.
gate('unit-ci', 'CI unit suite under coverage', 'test:coverage:ci', false),
gate('affected-selector', 'Affected-check selector model', 'check:affected:test'),
gate('gate-manifest', 'Gate manifest — every gate owned and wired', 'check:gate-manifest'),
gate('gate-manifest-model', 'Gate manifest model', 'check:gate-manifest:test'),
gate('depgraph', 'Dependency graph report model', 'depgraph:test'),
gate('tmpdir-leaks', 'Leaked test tmpdir detector', 'check:tmpdir-leaks'),
gate('tmpdir-leaks-model', 'TMPDIR redirection model', 'check:tmpdir-leaks:test'),
gate('coverage-model', 'Changed-line coverage model', 'check:coverage-changed:test'),
gate('wire-compat-model', 'Wire-compat rules model', 'check:daemon-wire-compat:test'),
gate('production-exports', 'Production-unused exports', 'check:production-exports'),
gate('bundle-owner-files', 'Bundle owner-file manifest', 'check:bundle-owner-files'),
// Not locally runnable: the audit takes ~90s, more than the pre-push affected
// path should pay on every scripts/** edit. It runs on plain Node; CI needs
// no runtime beyond the default toolchain.
gate('freerange', 'Numeric range audit', 'check:freerange', false),
gate('fixture-cache', 'Trusted fixture-artifact selection', 'test:fixture-cache'),
gate('fixture-fallback', 'Fixture-app cache-failure fallback', 'test:fixture-fallback'),
gate('command-docs', 'Command reference doc coverage', 'check:command-docs'),
gate('agent-guidance', 'Agent guidance ownership and context budgets', 'check:agent-guidance'),
// Parses ios.yml and the Swift sources; no Xcode, no simulator, so it runs anywhere.
gate(
'xctest-selection',
'Runner XCTest selection and package-source boundary',
'check:xctest-selection',
),
// Line parity needs no toolchain; the `swiftc -parse` half reports itself skipped on a host
// without Swift, so the gate is declared on the macOS lane where both halves run.
gate(
'packaged-runner-swift',
'Packaged runner Swift parses and keeps checkout line numbering',
'check:packaged-runner-swift',
),
// --- Gates that drive their own runner -------------------------------------
// The ones no naming convention could find: an executable terminal for
// `scripts/fuzz/run.ts` and one for `scripts/size-report.mjs` are the same
// shape, and only one of them can fail a build. Registering them is what tells
// the two apart, and `pnpm gate` is what keeps the registration load-bearing —
// delete an entry and its lane stops resolving, instead of the suite quietly
// leaving the universe.
gate('maestro-conformance', 'Maestro conformance fixtures', 'maestro:conformance'),
gate(
'maestro-differential',
'Maestro differential oracle',
'maestro:conformance:differential',
false,
),
gate(
'maestro-regenerate',
'Maestro fixture regeneration is a no-op',
'maestro:conformance:regenerate',
false,
),
gate('fuzz-parsers', 'Parser fuzz invariants', 'fuzz:parsers', false),
gate('mutation', 'Mutation sweep', 'mutation:run', false),
gate('mutation-affected', 'Affected mutation shard selection', 'mutation:affected', false),
gate('mutation-check', 'Mutation score for an existing report', 'mutation:check', false),
gate('mutation-model', 'Mutation harness self-test', 'mutation:test'),
gate(
'concurrency-torture',
'Session/lease/lock torture sweep',
'test:concurrency-torture',
false,
),
gate('replay-ios', 'iOS simulator replay suite', 'test:replay:ios', false),
gate('replay-ios-device', 'iOS physical device replay suite', 'test:replay:ios-device', false),
gate('replay-macos', 'macOS replay suite', 'test:replay:macos', false),
gate('replay-linux', 'Linux replay suite', 'test:replay:linux', false),
gate(
'linux-command-evidence',
'Linux command evidence suite',
'test:linux:command-evidence',
false,
),
gate('replay-android', 'Android replay suite', 'test:replay:android', false),
];
export function getCheckSpec(id: CheckId): CheckSpec {
const spec = CHECK_CATALOG.find((entry) => entry.id === id);
if (!spec) throw new Error(`No catalog entry for check "${id}".`);
return spec;
}
// Resolve the runnable command for a check. Script-backed checks are validated
// against package.json so a renamed/removed script fails loudly instead of
// silently skipping a gate. `fallow` threads the same --base the audit uses.
export function resolveCommand(
spec: CheckSpec,
scripts: Readonly<Record<string, string>>,
base: string,
changedFiles: readonly string[] = [],
): string[] {
if (spec.kind.type === 'vitest-related') {
return ['pnpm', 'exec', 'vitest', 'related', '--run', '--passWithNoTests', ...changedFiles];
}
const { script } = spec.kind;
if (!(script in scripts)) {
throw new Error(
`Check "${spec.id}" references package.json script "${script}", which does not exist.`,
);
}
const command = ['pnpm', 'run', script];
if (spec.id === 'fallow') command.push('--base', base);
return command;
}
// Guard: the catalog must cover exactly the CheckId universe. The self-test
// asserts this so a new check cannot ship half-wired.
export function assertCatalogComplete(): void {
const catalogIds = new Set(CHECK_CATALOG.map((entry) => entry.id));
const missing = ALL_CHECKS.filter((id) => !catalogIds.has(id));
const extra = CHECK_CATALOG.filter((entry) => !ALL_CHECKS.includes(entry.id)).map((e) => e.id);
if (missing.length > 0 || extra.length > 0) {
throw new Error(
`Check catalog out of sync with ALL_CHECKS. Missing: [${missing.join(', ')}]; ` +
`extra: [${extra.join(', ')}].`,
);
}
}