From 0dfb730e1e2cc8b3e246335ec0476ffcf2c5e328 Mon Sep 17 00:00:00 2001 From: Timoteo Date: Fri, 25 Sep 2026 01:15:10 -0300 Subject: [PATCH] fix(repo): verify-perf-ci (thematic slice, <=10 files, 2026-09-25-03:40) --- .gitattributes | 10 ++++++++++ .github/workflows/agent-browser-install.yml | 14 ++++++++++++-- packages/cli/package.json | 4 ++-- packages/core/package.json | 4 ++-- packages/dashboard/package.json | 4 ++-- packages/engine/package.json | 4 ++-- packages/plugin-sdk/package.json | 4 ++-- pnpm-workspace.yaml | 16 ++++++++++++++++ scripts/dev-with-memory.mjs | 16 +++++++++++++--- scripts/verify-fast.mjs | 12 +++++++++++- 10 files changed, 72 insertions(+), 16 deletions(-) diff --git a/.gitattributes b/.gitattributes index b96747000b..b7cc9e96ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,3 +19,13 @@ screenshots/**/*.png filter=lfs diff=lfs merge=lfs -text # FNXC:RepoAssets 2026-08-10-10:00: Graph JSON is byte-stable across platforms only when Git retains LF endings. .fusion-knowledge/**/*.json text eol=lf + +# FNXC:PreJsonMerge 2026-09-23: .changeset/pre.json is generated by changesets and must always follow +# the branch being merged INTO — the exact rule scripts/check-pre-json-anchor.mjs prescribes manually. +# A merge/rebase conflict resolved from the older side silently breaks the beta anchor and blocks +# verification for every task on that branch (observed 2026-09-23: two review tasks parked +# terminal-failed 8-9h; same regression class as #3472). merge=binary is a built-in git driver that +# keeps the INTO-side copy and needs no per-clone configuration (the common merge=ours recipe requires +# `git config merge.ours.driver true` in every clone and otherwise falls back to full conflict +# markers). The check stays as detection; this rule prevents the corruption at resolution time. +.changeset/pre.json merge=binary diff --git a/.github/workflows/agent-browser-install.yml b/.github/workflows/agent-browser-install.yml index b3b5b20844..93e7a8dd22 100644 --- a/.github/workflows/agent-browser-install.yml +++ b/.github/workflows/agent-browser-install.yml @@ -33,7 +33,12 @@ jobs: # contract; post-job cache save otherwise fails). The composite's skip-install # input means the caller installs workspace deps; build the CLI before # `pnpm pack` so the pack guard stays intact as a real published-boundary test. - timeout-minutes: 15 + # FNXC:PackFixtureTimeout 2026-09-24-14:11: + # ensureDesktopRuntimeAssetsBuilt() gives the @fusion/desktop sub-build a 30-minute + # budget (packages/cli/tsup.config.ts). The old 15-minute cap was BELOW that documented + # minimum and killed the pack-fixture with SIGTERM ("The operation was canceled"). + # Matches release.yml (widened to 30 for exactly this reason). Fix (B) - credit: Hermes. + timeout-minutes: 30 steps: - name: Checkout @@ -79,7 +84,12 @@ jobs: name: Agent-browser install (${{ matrix.os }}) needs: pack-fixture runs-on: ${{ matrix.os }} - timeout-minutes: 15 + # FNXC:PackFixtureTimeout 2026-09-24-14:11: + # ensureDesktopRuntimeAssetsBuilt() gives the @fusion/desktop sub-build a 30-minute + # budget (packages/cli/tsup.config.ts). The old 15-minute cap was BELOW that documented + # minimum and killed the pack-fixture with SIGTERM ("The operation was canceled"). + # Matches release.yml (widened to 30 for exactly this reason). Fix (B) - credit: Hermes. + timeout-minutes: 30 strategy: fail-fast: false matrix: diff --git a/packages/cli/package.json b/packages/cli/package.json index 217a97eb40..c49bafa54e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,7 @@ "build:package": "cross-env FUSION_CLI_FULL_PACKAGE=1 tsup", "build:exe": "bun run build.ts", "build:exe:all": "bun run build.ts --all", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --build --noEmit", "test": "vitest run --silent=passed-only --reporter=dot", "test:ci-shape": "vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot", "test:docs-index": "vitest run src/__tests__/docs-readme-index.test.ts --silent=passed-only --reporter=dot", @@ -126,4 +126,4 @@ "engines": { "node": ">=22.4.0" } -} +} \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index c0f29c4bab..79ce81ffb5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -60,7 +60,7 @@ ], "scripts": { "build": "tsc", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --build --noEmit", "test": "vitest run --silent=passed-only --reporter=dot", "test:embedded-postgres": "vitest run src/__tests__/postgres/embedded-lifecycle.test.ts --silent=passed-only --reporter=dot", "test:pg-gate": "FUSION_PG_TEST_SETUP_PARTICIPANT=1 vitest run --config vitest.pg.config.ts src/__tests__/postgres/handoff-to-review-atomicity.pg.test.ts src/__tests__/postgres/task-lifecycle-e2e.pg.test.ts --silent=passed-only --reporter=dot", @@ -93,4 +93,4 @@ "optionalDependencies": { "keytar": "^7.9.0" } -} +} \ No newline at end of file diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 468646e6a2..ee2515f38c 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -91,7 +91,7 @@ "test:idle-animations": "node --test scripts/idle-animation-smoke.mjs", "test:touch-geometry": "vitest run --project dashboard-browser-touch --silent=passed-only --reporter=dot", "test:build": "pnpm build && FUSION_DASHBOARD_DEEP=1 vitest run --project dashboard-app --project dashboard-api --silent=passed-only --reporter=dot app/__tests__/build-output.test.ts src/__tests__/plugin-registry-dist.test.ts", - "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.app.json", + "typecheck": "tsc --build --noEmit && tsc --build --noEmit tsconfig.app.json", "test:quality:api:backfill-1": "node scripts/run-vitest-with-heap.mjs --heap=6144 run --project dashboard-api-quality-backfill --silent=passed-only --reporter=dot --shard=1/2", "test:quality:api:backfill-2": "node scripts/run-vitest-with-heap.mjs --heap=6144 run --project dashboard-api-quality-backfill --silent=passed-only --reporter=dot --shard=2/2" }, @@ -180,4 +180,4 @@ "vitest": "^4.1.10" }, "private": true -} +} \ No newline at end of file diff --git a/packages/engine/package.json b/packages/engine/package.json index c9e3d28fcb..8e640877c7 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -29,7 +29,7 @@ ], "scripts": { "build": "tsc", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --build --noEmit", "test": "vitest run --silent=passed-only --reporter=dot --project=engine-default --project=engine-reliability", "test:core": "vitest run --silent=passed-only --reporter=dot --project=engine-core", "test:slow": "vitest run --silent=passed-only --reporter=dot --project=engine-slow", @@ -60,4 +60,4 @@ "vitest": "^4.1.10" }, "private": true -} +} \ No newline at end of file diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 167b2177c2..6c595b3e30 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -26,7 +26,7 @@ ], "scripts": { "build": "tsc", - "typecheck": "tsc --noEmit --skipLibCheck", + "typecheck": "tsc --build --noEmit", "test": "vitest run --silent=passed-only --reporter=dot" }, "devDependencies": { @@ -41,4 +41,4 @@ "node": ">=22.5.0" }, "private": true -} +} \ No newline at end of file diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df0a54994a..c5ca6879bb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,22 @@ ignoredBuiltDependencies: - keytar - sharp - ssh2 +# FNXC:WorkspaceConcurrency 2026-09-23: Cap a single `pnpm -r` run at 2 packages so its tsc workers +# (500MB-1.4GB each) cannot thrash a 12GB desktop. pnpm 10.33 ignores the ~/.npmrc workspace-concurrency +# key entirely (it echoes the value but behavior stays at the 4-project default); this key is the live +# channel, verified with a process-overlap bench (peak 4 -> 2). +# Scope limit (measured 2026-09-23): this only bounds ONE `pnpm -r` invocation. verify:fast spawns one +# `pnpm --filter typecheck` process per changed package and bypasses it — that 4-wide fan-out is +# what froze the desktop twice (PSI memory full 80%), and it is bounded by TYPECHECK_PARALLEL_LIMIT in +# scripts/verify-fast.mjs. Do not treat this key as the fan-out guard. +# Why it lives here as a constant: pnpm 10 has no live env channel for this knob (~/.npmrc, +# npm_config_workspace_concurrency and pnpm_config_* are all silently ignored), and this manifest is +# the only backstop for an ad-hoc `pnpm -r` that no script wraps. Treat it as a blunt guardrail for +# small boxes, not a project requirement — override per command with --workspace-concurrency on a +# larger machine. Callers this repo owns already do the better thing: scripts/test-changed.mjs derives +# the value from FUSION_TEST_WORKSPACE_CONCURRENCY and passes the flag explicitly. +workspaceConcurrency: 2 + onlyBuiltDependencies: - '@embedded-postgres/darwin-arm64' - '@embedded-postgres/darwin-x64' diff --git a/scripts/dev-with-memory.mjs b/scripts/dev-with-memory.mjs index 9c1a617abd..b39cbdde0b 100644 --- a/scripts/dev-with-memory.mjs +++ b/scripts/dev-with-memory.mjs @@ -20,13 +20,18 @@ import { resolvePrebuildMode, } from "./dev-with-memory-lib.mjs"; import { existsSync as fsExistsSync, mkdirSync as fsMkdirSync } from "node:fs"; +import { totalmem as osTotalmem } from "node:os"; import { spawnSync } from "node:child_process"; import { join as pathJoin, resolve as pathResolve } from "node:path"; import { createDevSourceWatcher } from "./lib/dev-source-watch.mjs"; import { resolveDevTunnelAuth, startDevTunnel } from "./lib/dev-tunnel.mjs"; -// Set increased heap size (8GB) to prevent OOM during initial build/start -const MEMORY_MB = process.env.FUSION_DEV_MEMORY_MB || "8192"; +// Set increased heap size to prevent OOM during initial build/start. +// FNXC:DevMemoryCap 2026-09-23-23:34: the old `|| "8192"` was sized after one workstation, so a 12GB +// box handed every node child an 8GB heap ceiling and V8 would happily hold it. Derive the ceiling +// from the machine (half of RAM, clamped to 1-8GB) and keep FUSION_DEV_MEMORY_MB as the override. +const MEMORY_MB = process.env.FUSION_DEV_MEMORY_MB + || String(Math.min(8192, Math.max(1024, Math.floor(osTotalmem() / 1024 / 1024 / 2)))); // Spawn the actual dev command with all arguments passed through const { spawn } = await import("child_process"); @@ -43,7 +48,12 @@ let { watchSource } = parsedArgs; // NODE_OPTIONS is shared with every spawned node process (build + run + // agents). Heap size belongs here. Inspector flags do NOT — see comment above. -const nodeOptions = `--max-old-space-size=${MEMORY_MB} ${process.env.NODE_OPTIONS || ""}`.trim(); +// Never emit the flag twice: callers such as ~/.fusion/start-capped.sh already pin a tighter +// `--max-old-space-size`, and precedence between repeated V8 flags is not worth relying on. +const inheritedNodeOptions = process.env.NODE_OPTIONS || ""; +const nodeOptions = inheritedNodeOptions.includes("--max-old-space-size") + ? inheritedNodeOptions + : `--max-old-space-size=${MEMORY_MB} ${inheritedNodeOptions}`.trim(); process.env.NODE_OPTIONS = nodeOptions; // In dev we bind the dashboard to 0.0.0.0 so the server is reachable from diff --git a/scripts/verify-fast.mjs b/scripts/verify-fast.mjs index eaf9ded1f2..c6fdde75ed 100644 --- a/scripts/verify-fast.mjs +++ b/scripts/verify-fast.mjs @@ -119,7 +119,17 @@ const cpuCount = (() => { try { return Math.max(1, os.cpus()?.length ?? 1); } catch { return 1; } })(); export const STATIC_CHECK_PARALLEL_LIMIT = Math.max(2, Math.min(8, cpuCount - 1)); -export const TYPECHECK_PARALLEL_LIMIT = Math.max(1, Math.min(4, Math.floor(cpuCount / 2))); +/* FNXC:TestInfrastructure 2026-09-23-21:45: The old cap of 4 came from core count only, and on a +16-core/12GB desktop verify:fast scheduled 4 concurrent `pnpm --filter typecheck` workers at +1-1.4GB RSS each. Measured 2026-09-23: that 4-wide batch sat in D state at 3-7% CPU per worker for +10+ minutes (swap thrash, no progress), drove PSI memory full to 80%, and froze the desktop twice. +Each tsc is memory-bound, not core-bound, so the ceiling derives from RAM (1 worker per 5GB, capped +at 4) rather than core count. This is the ONLY guard over that fan-out — `workspaceConcurrency` in +pnpm-workspace.yaml caps a single `pnpm -r` run, but these are separate pnpm processes per package +and bypass it entirely. +FUSION_VERIFY_FAST_TYPECHECK_CONCURRENCY overrides the derived default (sibling of +FUSION_VERIFY_FAST_SERIAL) so a bigger box is never slowed down by this desktop's ceiling. */ +export const TYPECHECK_PARALLEL_LIMIT = Number(process.env.FUSION_VERIFY_FAST_TYPECHECK_CONCURRENCY) || Math.max(1, Math.min(4, Math.floor(os.totalmem() / (5 * 1024 ** 3)))); /** * Build the scoped typecheck step for a package. Prefers the package's own