fix(rebase): migrate the replayed stack onto current master APIs

The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:

- rebuild subprocess-local spawn.ts on master's tree-exit-observer
  machinery, keeping the branch's win32 childEnv key semantics and the
  Linux zombie-quiescence probe; the zombie test reaps its survivor
  directly since a confirmed-absent verdict is a permanent
  no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
  Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
  new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
  master's exact content (the net-zero code-runtime churn is pruned
  from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
  re-record bilingual pairings, regenerate catalogs, and reconcile the
  lockfile
This commit is contained in:
Tianyi Cui
2026-08-07 20:34:31 +08:00
parent e385c11e8e
commit 18ab9f6db2
35 changed files with 582 additions and 673 deletions

View File

@@ -1,5 +1,16 @@
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import tsconfigPaths from 'vite-tsconfig-paths'
import { resolvePwshPath } from './packages/bash/pwsh-local/src/resolve.ts'
import { defineConfig } from 'vitest/config'
import { vitestExecArgv } from './vitest.shared.ts'
import { COVERAGE_EXEMPT_ENV, coverageExemptHeavySuites } from './scripts/coverage-exempt.ts'
// Prints exact `path:line:col` records for every uncovered statement, branch
// path, and function when a file misses the per-file 100% gate — the built-in
// threshold ERRORs name only the file. Absolute path because istanbul-reports
// require()s custom reporters (which is also why the reporter is CJS).
const uncoveredLocationsReporter = fileURLToPath(new URL('./scripts/coverage-uncovered-locations.cjs', import.meta.url))
// Resolution facade shared by every plugin instance below: tsconfig.base.json
// has no include, which vite-tsconfig-paths treats as match-all, so its paths
@@ -9,7 +20,14 @@ const pathsPlugin = (): ReturnType<typeof tsconfigPaths> => tsconfigPaths({ proj
const windowsUnsupportedPackages = process.platform === 'win32'
? [
'packages/bash/*',
// Bash-requiring suites (a real POSIX shell is unavailable on Windows).
// The pwsh-requiring suites (pwsh-local, tool-pwsh) deliberately stay
// INCLUDED: PowerShell ships with Windows, so they run natively here.
// Replacing the old 'packages/bash/*' glob with this explicit list also
// newly INCLUDES packages/bash/bash (the pure seam package) on Windows.
'packages/bash/bash-local',
'packages/bash/bash-sandbox',
'packages/bash/tool-bash',
'packages/hooks/*',
'packages/subprocess/*',
'packages/pty/pty-local',
@@ -26,10 +44,20 @@ const windowsCoverageExclusions = process.platform === 'win32'
'packages/lsp/lsp-local/src/connection.ts',
'packages/lsp/lsp-local/src/index.ts',
'packages/lsp/lsp-local/src/instance.ts',
'packages/ui/tui/src/index.ts',
]
: []
// Mirrors windowsCoverageExclusions: pwsh-local's run/start/lifecycle suites
// self-skip without a real pwsh (executor.spec.ts hasPwsh), leaving this file
// far below per-file 100% on pwsh-less hosts; the exemption keeps those hosts
// green while CI runners ship pwsh and still enforce the full bar. The probe
// runs the suites' own resolution (the dependency-free resolve.ts module),
// so the exemption is active exactly when the suites skip — a mismatched
// narrower probe could exempt the file on hosts whose suites actually run.
const pwshCoverageExclusions = spawnSync(resolvePwshPath(), ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', '$true'], { encoding: 'utf8' }).status === 0
? []
: ['packages/bash/pwsh-local/src/index.ts']
const testIncludes = [
'packages/*/*/tests/**/*.spec.{ts,tsx}',
'apps/*/tests/**/*.spec.ts',
@@ -37,6 +65,17 @@ const testIncludes = [
'scripts/**/*.spec.ts',
]
// The instrumented coverage gate sets this env; the exempt heavy suites then
// run beside it uninstrumented (membership contract in scripts/coverage-exempt.ts).
// A set-but-not-'1' value is a misconfiguration, not a silent no-op.
const coverageExemptRaw = process.env[COVERAGE_EXEMPT_ENV]
if (coverageExemptRaw !== undefined && coverageExemptRaw !== '' && coverageExemptRaw !== '1') {
throw new Error(`vitest config: ${COVERAGE_EXEMPT_ENV} must be '1' or unset, got ${JSON.stringify(coverageExemptRaw)}.`)
}
const coverageExemptExcludes = coverageExemptRaw === '1'
? coverageExemptHeavySuites.map(suite => suite.exclude)
: []
// These suites exercise process-global state, process APIs, or timing-sensitive process I/O
// that worker threads cannot isolate reliably under aggregate gate contention.
// Keep the narrow exception in forks while the rest of the inventory avoids per-file processes.
@@ -55,22 +94,27 @@ export default defineConfig({
// .tsx: client component specs (jsdom via per-file @vitest-environment pragma).
include: testIncludes,
exclude: windowsUnsupportedPackages.map(path => `${path}/tests/**/*.spec.ts`),
// One coverage invocation aggregates both projects. Most suites use threads
// for lower startup/IPC overhead; only explicit process-bound suites fork.
// One coverage invocation aggregates both projects. Regular suites fork on
// POSIX for Node stability and use threads on Windows; process-bound suites
// always fork.
projects: [
{
plugins: [pathsPlugin()],
test: {
name: 'thread-safe',
// Node 24 has aborted in its CJS lexer from a macOS arm64 worker
// thread. A fork contains that external runtime failure to the test
// process; other hosts retain the lower-overhead thread pool.
pool: process.platform === 'darwin' ? 'forks' : 'threads',
execArgv: vitestExecArgv,
// Node 24 has aborted in its CJS lexer (v8::ToLocalChecked Empty
// MaybeLocal in cjs_lexer::Parse) from worker threads on macOS
// arm64 and later on Linux. A fork contains that external runtime
// failure to the test process; Windows keeps the thread pool, where
// the abort has not reproduced and process spawn is costlier.
pool: process.platform === 'win32' ? 'threads' : 'forks',
setupFiles: ['./scripts/test-invariants.ts'],
include: testIncludes,
exclude: [
...windowsUnsupportedPackages.map(path => `${path}/tests/**/*.spec.ts`),
...processBoundTests,
...coverageExemptExcludes,
],
},
},
@@ -78,10 +122,14 @@ export default defineConfig({
plugins: [pathsPlugin()],
test: {
name: 'process-bound',
execArgv: vitestExecArgv,
pool: 'forks',
setupFiles: ['./scripts/test-invariants.ts'],
include: processBoundTests,
exclude: windowsUnsupportedPackages.map(path => `${path}/tests/**/*.spec.ts`),
exclude: [
...windowsUnsupportedPackages.map(path => `${path}/tests/**/*.spec.ts`),
...coverageExemptExcludes,
],
},
},
],
@@ -98,7 +146,8 @@ export default defineConfig({
'packages/*/*/src/types.ts',
'packages/*/*/src/bin.ts',
'packages/*/*/src/worker.ts',
'packages/code-runtime/code-runtime-subprocess/src/runner.ts',
// A killed executable lint-contract test can leave a non-product source probe behind.
'packages/*/*/src/oxlint-contract-*.ts',
// GUI step-1 skeleton (PR #500): client/web UI files whose remaining
// branches need a browser-grade harness the jsdom lane doesn't cover
// yet. TODO(gui): cover and remove as the client test lane matures.
@@ -107,6 +156,7 @@ export default defineConfig({
'packages/client/ui-primitives/src/markdown/plain-text.ts',
'packages/client/ui-question/src/client/QuestionComposer.tsx',
'packages/client/ui-primitives/src/Menu.tsx',
'packages/client/ui-primitives/src/RiskConfirmation.tsx',
'packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx',
'packages/client/ui-workspace/src/client/WorkspacePicker.tsx',
'packages/client/web-react/src/*',
@@ -155,9 +205,13 @@ export default defineConfig({
'packages/client/ui-sidebar/src/client/index.ts',
'packages/client/ui-skill/src/client/index.ts',
'packages/client/ui-workspace/src/client/index.ts',
'packages/typert/generator/src/analyzer.ts',
'packages/typert/generator/src/renderer.ts',
'packages/typert/generator/src/cordis-catalog.ts',
'packages/client/test-runtime/src/translate.ts',
'packages/client/ui-primitives/src/JsonTree.tsx',
// Typert generator: correctness is pinned by its fixture suites and
// the byte-for-byte catalog reproduction test; per-file coverage
// would put whole-workspace compiler analysis under v8
// instrumentation — the coverage lane's longest tail.
'packages/typert/generator/src/*.ts',
'packages/host/apiproxy/src/index.ts',
'packages/host/apiproxy/src/invariant.ts',
'packages/host/apiproxy/src/api-proxy.ts',
@@ -167,9 +221,9 @@ export default defineConfig({
'packages/ui/commands/src/index.ts',
'packages/ui/commands/src/invariant.ts',
'packages/session-projection/session-projection/src/index.ts',
'packages/ui/tui/src/index.ts',
...windowsUnsupportedPackages.map(path => `${path}/src/**/*.ts`),
...windowsCoverageExclusions,
...pwshCoverageExclusions,
],
// 100% or it doesn't merge (docs/testing.md: excessive tests are welcome).
// Per-file so a well-covered big file can't subsidize a bare one.
@@ -182,7 +236,9 @@ export default defineConfig({
functions: 100,
lines: 100,
},
reporter: process.env.CI ? ['text'] : ['text', 'html'],
reporter: process.env.CI
? ['text', uncoveredLocationsReporter]
: ['text', 'html', uncoveredLocationsReporter],
},
},
})