perf: speed up type check gate

This commit is contained in:
Peter Steinberger 2026-04-20 13:14:59 +01:00
parent 8116e638f3
commit 897c50e1a4
No known key found for this signature in database
7 changed files with 90 additions and 6 deletions

View file

@ -132,7 +132,9 @@
- `pnpm tsgo:core:test`: core colocated tests.
- `pnpm tsgo:extensions`: bundled extension production graph.
- `pnpm tsgo:extensions:test`: bundled extension colocated tests.
- `pnpm tsgo:all`: every TypeScript graph above; this is what `pnpm check` runs.
- `pnpm tsgo:core:all`: core production + core test project references (`tsconfig.core.projects.json`).
- `pnpm tsgo:extensions:all`: extension production + extension test project references (`tsconfig.extensions.projects.json`).
- `pnpm tsgo:all`: every TypeScript graph above through the project-reference root (`tsconfig.projects.json`); this is what `pnpm check` runs.
- `pnpm tsgo:profile [core-test|extensions-test|--all]`: profile fresh graph cost into `.artifacts/tsgo-profile/`. Diagnostic-only profile slices (`core-test-agents`, `core-test-non-agents`) exist for investigating agent graph cost; do not treat them as normal user-facing checks.
- Narrow aliases remain for local loops: `pnpm tsgo:test:src`, `pnpm tsgo:test:ui`, `pnpm tsgo:test:packages`.
- Do not add `tsc --noEmit`, `typecheck`, or `check:types` lanes for repo type checking. Use `tsgo` graphs. `tsc` is allowed only when emitting declaration/package-boundary compatibility artifacts that `tsgo` does not replace.
@ -147,6 +149,7 @@
- A landing gate is the broader bar before pushing `main`, usually `pnpm check`, `pnpm test`, and `pnpm build` when the touched surface can affect build output, packaging, lazy-loading/module boundaries, or published surfaces.
- A CI gate is whatever the relevant workflow enforces for that lane (for example `check`, `check-additional`, `build-smoke`, or release validation).
- Local dev gate: prefer `pnpm check` for the normal edit loop. It keeps the repo-architecture policy guards out of the default local loop.
- Timed local gate: use `pnpm check:timed` to see per-stage cost. Add `:architecture` only when investigating the CI architecture gate locally.
- CI architecture gate: `check-additional` enforces architecture and boundary policy guards that are intentionally kept out of the default local loop.
- Formatting gate: the pre-commit hook runs targeted formatting on staged source files before `pnpm check`. If you want a repo-wide formatting-only preflight locally, run `pnpm format:check` explicitly.
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hooks repo-wide `pnpm check`; targeted formatting/linting still runs, so use that only when you are deliberately covering the touched surface some other way.
@ -190,7 +193,7 @@
- New runtime control-flow code should not branch on `error: string` or `reason: string` when a closed code union would be reasonable.
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
- Circular dependencies: keep both `pnpm check:import-cycles` and `pnpm check:madge-import-cycles` green; do not reintroduce runtime import cycles or madge-detected import loops.
- Circular dependencies: `pnpm check` runs the fast runtime import-cycle guard. `pnpm check:architecture` (and CI `check-additional`) also runs the broader madge import-cycle guard; keep both green before landing architecture-sensitive changes.
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
- Extension package boundary guardrail: inside a bundled plugin package, do not use relative imports/exports that resolve outside that same package root. If shared code belongs in the plugin SDK, import `openclaw/plugin-sdk/<subpath>` instead of reaching into `src/plugin-sdk/**` or other repo paths via `../`.
- Extension API surface rule: `openclaw/plugin-sdk/<subpath>` is the only public cross-package contract for extension-facing SDK code. If an extension needs a new seam, add a public subpath first; do not reach into `src/plugin-sdk/**` by relative path.

View file

@ -57,9 +57,10 @@ On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull
## Local Equivalents
```bash
pnpm check # types + lint + format
pnpm check # fast local gate: project-reference tsgo + lint + fast guards
pnpm check:timed # same gate with per-stage timings
pnpm build:strict-smoke
pnpm check:import-cycles
pnpm check:architecture
pnpm test:gateway:watch-regression
pnpm test # vitest tests
pnpm test:channels

View file

@ -1236,7 +1236,8 @@
"canon:check:json": "node scripts/canon.mjs check --json",
"canon:enforce": "node scripts/canon.mjs enforce --json",
"canvas:a2ui:bundle": "node scripts/bundle-a2ui.mjs",
"check": "pnpm check:no-conflict-markers && pnpm tool-display:check && pnpm check:host-env-policy:swift && pnpm tsgo:all && pnpm lint && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope && pnpm check:import-cycles && pnpm check:madge-import-cycles",
"check": "pnpm check:no-conflict-markers && pnpm tool-display:check && pnpm check:host-env-policy:swift && pnpm tsgo:all && pnpm lint && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope && pnpm check:import-cycles",
"check:architecture": "pnpm check:import-cycles && pnpm check:madge-import-cycles",
"check:base-config-schema": "node --import tsx scripts/generate-base-config-schema.ts --check",
"check:bundled-channel-config-metadata": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check",
"check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-i18n-glossary && pnpm docs:check-links",
@ -1246,6 +1247,8 @@
"check:madge-import-cycles": "node --import tsx scripts/check-madge-import-cycles.ts",
"check:no-conflict-markers": "node scripts/check-no-conflict-markers.mjs",
"check:static-import-sccs": "pnpm check:madge-import-cycles",
"check:timed": "node scripts/check-timed.mjs",
"check:timed:architecture": "node scripts/check-timed.mjs --include-architecture",
"codex-app-server:protocol:check": "node --import tsx scripts/check-codex-app-server-protocol.ts",
"config:channels:check": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check",
"config:channels:gen": "node --import tsx scripts/generate-bundled-channel-config-metadata.ts --write",
@ -1471,10 +1474,12 @@
"tool-display:write": "node --import tsx scripts/tool-display.ts --write",
"ts-topology": "node --import tsx scripts/ts-topology.ts",
"tsgo": "pnpm tsgo:core",
"tsgo:all": "pnpm tsgo:core && pnpm tsgo:core:test && pnpm tsgo:extensions && pnpm tsgo:extensions:test",
"tsgo:all": "node scripts/run-tsgo.mjs -b tsconfig.projects.json",
"tsgo:core": "node scripts/run-tsgo.mjs -p tsconfig.core.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core.tsbuildinfo",
"tsgo:core:all": "node scripts/run-tsgo.mjs -b tsconfig.core.projects.json",
"tsgo:core:test": "node scripts/run-tsgo.mjs -p tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo",
"tsgo:extensions": "node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo",
"tsgo:extensions:all": "node scripts/run-tsgo.mjs -b tsconfig.extensions.projects.json",
"tsgo:extensions:test": "node scripts/run-tsgo.mjs -p tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo",
"tsgo:prod": "pnpm tsgo:core && pnpm tsgo:extensions",
"tsgo:profile": "node scripts/profile-tsgo.mjs",

57
scripts/check-timed.mjs Normal file
View file

@ -0,0 +1,57 @@
import { spawnSync } from "node:child_process";
import { performance } from "node:perf_hooks";
const includeArchitecture = process.argv.includes("--include-architecture");
const stages = [
{ name: "conflict markers", args: ["check:no-conflict-markers"] },
{ name: "tool display", args: ["tool-display:check"] },
{ name: "host env policy", args: ["check:host-env-policy:swift"] },
{ name: "typecheck", args: ["tsgo:all"] },
{ name: "lint", args: ["lint"] },
{ name: "webhook body guard", args: ["lint:webhook:no-low-level-body-read"] },
{ name: "pairing store guard", args: ["lint:auth:no-pairing-store-group"] },
{ name: "pairing account guard", args: ["lint:auth:pairing-account-scope"] },
{ name: "runtime import cycles", args: ["check:import-cycles"] },
];
if (includeArchitecture) {
stages.push({ name: "architecture import cycles", args: ["check:madge-import-cycles"] });
}
const timings = [];
let exitCode = 0;
for (const { name, args } of stages) {
const startedAt = performance.now();
console.error(`\n[check:timed] ${name}`);
const result = spawnSync("pnpm", args, {
stdio: "inherit",
shell: process.platform === "win32",
});
const durationMs = performance.now() - startedAt;
timings.push({ name, durationMs, status: result.status ?? 1 });
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
exitCode = result.status ?? 1;
break;
}
}
console.error("\n[check:timed] summary");
for (const timing of timings) {
const status = timing.status === 0 ? "ok" : `failed:${timing.status}`;
console.error(`${formatMs(timing.durationMs).padStart(8)} ${status.padEnd(9)} ${timing.name}`);
}
process.exitCode = exitCode;
function formatMs(durationMs) {
if (durationMs < 1000) {
return `${Math.round(durationMs)}ms`;
}
return `${(durationMs / 1000).toFixed(2)}s`;
}

View file

@ -0,0 +1,4 @@
{
"files": [],
"references": [{ "path": "./tsconfig.core.json" }, { "path": "./tsconfig.core.test.json" }]
}

View file

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.extensions.json" },
{ "path": "./tsconfig.extensions.test.json" }
]
}

7
tsconfig.projects.json Normal file
View file

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.core.projects.json" },
{ "path": "./tsconfig.extensions.projects.json" }
]
}