diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json new file mode 100644 index 000000000..36a06dcb3 --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3.trace.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0", + "id": "82c0908d-132f-4835-b47e-e76385858bb9", + "timestamp": "2026-09-03T14:45:29.548Z", + "trajectory": "traj_pwl779phg4f3", + "files": [ + { + "path": "package.json", + "conversations": [ + { + "contributor": { + "type": "ai" + }, + "ranges": [ + { + "start_line": 155, + "end_line": 163, + "revision": "8334fbf7efd9f983bb0712c13783e91096f25390" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md new file mode 100644 index 000000000..fd02bbb1d --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/summary.md @@ -0,0 +1,38 @@ +# Trajectory: Pin @vitejs/devtools-vitest's vitest peer so fresh installs survive vitest 5 + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** September 3, 2026 at 02:45 PM +> **Completed:** September 3, 2026 at 02:45 PM + +--- + +## Summary + +Nested npm override pins @vitejs/devtools-vitest's wildcard vitest peer to the workspace range; verified fresh install with npm 10.9.2 against main's manifests + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Nested override on @vitejs/devtools-vitest rather than a root vitest override +- **Chose:** Nested override on @vitejs/devtools-vitest rather than a root vitest override +- **Reasoning:** Only the wildcard peer edge is rewritten; the root range and every workspace range stay untouched, and npm 10.9's $vitest reference resolution fails on peer edges so the range is literal + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Nested override on @vitejs/devtools-vitest rather than a root vitest override: Nested override on @vitejs/devtools-vitest rather than a root vitest override + +--- + +## Artifacts + +**Commits:** 8334fbf +**Files changed:** 1 diff --git a/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json new file mode 100644 index 000000000..9e16628ae --- /dev/null +++ b/.agentworkforce/trajectories/completed/2026-09/traj_pwl779phg4f3/trajectory.json @@ -0,0 +1,58 @@ +{ + "id": "traj_pwl779phg4f3", + "version": 1, + "task": { + "title": "Pin @vitejs/devtools-vitest's vitest peer so fresh installs survive vitest 5" + }, + "status": "completed", + "startedAt": "2026-09-03T14:45:26.950Z", + "completedAt": "2026-09-03T14:45:29.522Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-09-03T14:45:28.154Z" + } + ], + "chapters": [ + { + "id": "chap_uhzfixybx5dw", + "title": "Work", + "agentName": "default", + "startedAt": "2026-09-03T14:45:28.154Z", + "endedAt": "2026-09-03T14:45:29.522Z", + "events": [ + { + "ts": 1788446728155, + "type": "decision", + "content": "Nested override on @vitejs/devtools-vitest rather than a root vitest override: Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "raw": { + "question": "Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "chosen": "Nested override on @vitejs/devtools-vitest rather than a root vitest override", + "alternatives": [], + "reasoning": "Only the wildcard peer edge is rewritten; the root range and every workspace range stay untouched, and npm 10.9's $vitest reference resolution fails on peer edges so the range is literal" + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Nested npm override pins @vitejs/devtools-vitest's wildcard vitest peer to the workspace range; verified fresh install with npm 10.9.2 against main's manifests", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [ + "8334fbf" + ], + "filesChanged": [ + "package.json" + ], + "projectId": "AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "e87f186938d125811c74341d4371f4f021115b01", + "endRef": "8334fbf7efd9f983bb0712c13783e91096f25390", + "traceId": "82c0908d-132f-4835-b47e-e76385858bb9" + } +} \ No newline at end of file diff --git a/.trajectories/compacted/release-11.10.2.json b/.trajectories/compacted/release-11.10.2.json index 151c30136..ad207dfb6 100644 --- a/.trajectories/compacted/release-11.10.2.json +++ b/.trajectories/compacted/release-11.10.2.json @@ -3,9 +3,7 @@ "version": 1, "type": "compacted", "compactedAt": "2026-09-03T06:52:02.784Z", - "sourceTrajectories": [ - "traj_7yref3wye283" - ], + "sourceTrajectories": ["traj_7yref3wye283"], "dateRange": { "start": "2026-09-02T10:25:41.382Z", "end": "2026-09-02T12:08:45.907Z" @@ -13,9 +11,7 @@ "summary": { "totalDecisions": 7, "totalEvents": 11, - "uniqueAgents": [ - "default" - ] + "uniqueAgents": ["default"] }, "decisionGroups": [ { @@ -103,12 +99,5 @@ "tests/relayflows/cases/1638-attach-input-replay/case.json", "tests/relayflows/cases/1638-attach-input-replay/run.mjs" ], - "commits": [ - "e85f4ca23", - "bf66a2e86", - "13971aa8f", - "1191af9bd", - "cefc1ab4d", - "2559e668a" - ] -} \ No newline at end of file + "commits": ["e85f4ca23", "bf66a2e86", "13971aa8f", "1191af9bd", "cefc1ab4d", "2559e668a"] +} diff --git a/.trajectories/compacted/release-11.10.2.md b/.trajectories/compacted/release-11.10.2.md index e8680f9be..fc7dee8b6 100644 --- a/.trajectories/compacted/release-11.10.2.md +++ b/.trajectories/compacted/release-11.10.2.md @@ -1,6 +1,7 @@ # Trajectory Compaction: Sep 2, 2026 - Sep 2, 2026 ## Summary + - Sessions: 1 - Decisions: 7 - Events: 11 @@ -9,6 +10,7 @@ - Commits: 6 ## Api + - Treat Relaycast publication and recipient reachability as separate observations -> Treat Relaycast publication and recipient reachability as separate observations (traj_7yref3wye283) - Keep the reachability probe outside the publication timeout -> Keep the reachability probe outside the publication timeout (traj_7yref3wye283) - Keep the synchronous reachability snapshot bounded at five seconds -> Keep the synchronous reachability snapshot bounded at five seconds (traj_7yref3wye283) @@ -16,13 +18,17 @@ - Cancel reachability observation when publication fails -> Cancel reachability observation when publication fails (traj_7yref3wye283) ## Security + - Resolve Relaycast @self before reachability probing -> Resolve Relaycast @self before reachability probing (traj_7yref3wye283) ## Other + - Treat legacy away as reachable -> Treat legacy away as reachable (traj_7yref3wye283) ## Key Learnings + - None ## Key Findings -- None \ No newline at end of file + +- None diff --git a/package.json b/package.json index 48ee95c4b..8fe06b720 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,9 @@ "js-yaml": "^4.3.0", "postcss": "^8.5.23", "shell-quote": "^1.10.0", - "tar": "^7.5.22" + "tar": "^7.5.22", + "@vitejs/devtools-vitest": { + "vitest": "^4.1.0" + } } } diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json b/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json new file mode 100644 index 000000000..da54cb3d0 --- /dev/null +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/case.json @@ -0,0 +1,21 @@ +{ + "version": 1, + "id": "1649-fresh-install-vitest-peer", + "kind": "bugfix", + "title": "A lockfile-less npm install on npm 10.9 survives the vitest 5 wildcard peer", + "runner": { + "command": ["node", "tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs"] + }, + "requirements": [], + "timeoutSeconds": 900, + "expected": { + "base": { + "outcome": "bug", + "signature": "fresh_install_crashes_on_wildcard_vitest_peer" + }, + "head": { + "outcome": "fixed", + "signature": "fresh_install_resolves_vitest_4" + } + } +} diff --git a/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs new file mode 100644 index 000000000..a5508f48a --- /dev/null +++ b/tests/relayflows/cases/1649-fresh-install-vitest-peer/run.mjs @@ -0,0 +1,244 @@ +// RelayFlow proof for #1649: a lockfile-less `npm install` on npm 10.9 must +// survive `@vitejs/devtools-vitest`'s wildcard `vitest` peer. +// +// Since vitest 5.0.0 was published (2026-09-03 12:24 UTC), that wildcard +// resolves to vitest 5 even though every workspace range is ^4, and npm 10.9's +// arborist crashes building the peer set. The head arm carries a root +// `overrides` entry that pins that one peer edge to the workspace range. +// +// The probe copies ONLY the workspace manifests (root + packages/*) out of the +// exact target checkout into a scratch directory, without the lockfile, and +// asks a pinned npm 10.9.2 to build an ideal tree (`--package-lock-only`, no +// scripts). The runner's own npm version is irrelevant: `npx npm@10.9.2` is +// the resolver under test on both arms. +import { execFileSync, spawnSync } from 'node:child_process'; +import { copyFile, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const CASE_ID = '1649-fresh-install-vitest-peer'; +const NPM_UNDER_TEST = 'npm@10.9.2'; +const CRASH_MARKER = "Cannot read properties of null (reading 'edgesOut')"; +const COMMAND_TIMEOUT_MS = 10 * 60 * 1000; +// Preflight budget: three required lookups at most 60s each plus three optional +// ones at 20s each is 4 minutes worst case, which with the 10-minute install +// stays inside the 900s case timeout in case.json. +const PREFLIGHT_TIMEOUT_MS = 60 * 1000; +const OPTIONAL_LOOKUP_TIMEOUT_MS = 20 * 1000; + +const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); +const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); +const resultPath = requiredValue('RELAY_PR_PROOF_RESULT_PATH'); +const arm = requiredValue('RELAY_PR_PROOF_ARM'); + +if (arm !== 'base' && arm !== 'head') { + throw new Error(`RELAY_PR_PROOF_ARM must be base or head, received ${JSON.stringify(arm)}.`); +} + +const expectedSha = + arm === 'base' ? process.env.RELAY_PR_PROOF_BASE_SHA : process.env.RELAY_PR_PROOF_HEAD_SHA; +if (!expectedSha) throw new Error(`Missing expected ${arm} SHA.`); +const targetSha = execFileSync('git', ['-C', targetDir, 'rev-parse', 'HEAD'], { + encoding: 'utf8', +}).trim(); +if (targetSha !== expectedSha) { + throw new Error(`Target checkout ${targetSha} does not match exact ${arm} SHA ${expectedSha}.`); +} + +const runnerPath = fileURLToPath(import.meta.url); +if (!isWithin(harnessDir, runnerPath)) { + throw new Error('The RelayFlow runner must execute from the exact-head harness checkout.'); +} + +const probeDir = await mkdtemp(path.join(os.tmpdir(), `relayflow-${CASE_ID}-`)); +try { + const copied = await copyWorkspaceManifests(targetDir, probeDir); + console.log(`Copied ${copied} workspace manifests from ${targetSha} into ${probeDir}`); + + const rootManifest = JSON.parse(await readFile(path.join(probeDir, 'package.json'), 'utf8')); + const rootRange = rootManifest.devDependencies?.vitest ?? rootManifest.dependencies?.vitest; + if (typeof rootRange !== 'string' || !rootRange.startsWith('^4')) { + throw new Error( + `This proof assumes the workspace pins vitest ^4; the root manifest asks for ${JSON.stringify(rootRange)}.` + ); + } + + // Preflight: pin down what the resolver under test actually is and what the + // registry it talks to serves. The bug only reproduces when the wildcard + // peer can land on vitest 5, so a registry (or caching proxy) that still + // serves vitest 4 as latest cannot produce the base arm's red outcome; say + // so explicitly instead of letting that read as "fixed". + const npmVersion = npmUnderTest(['--version'], probeDir).trim(); + if (npmVersion !== NPM_UNDER_TEST.split('@')[1]) { + throw new Error(`Expected ${NPM_UNDER_TEST} to run, but npx executed npm ${npmVersion}.`); + } + const registry = npmUnderTest(['config', 'get', 'registry'], probeDir).trim(); + // Only the vitest lookup gates the case; the other three are best-effort + // context for the log and must not turn a registry hiccup into an infra + // failure when the real precondition holds. + const latest = { vitest: npmUnderTest(['view', 'vitest@latest', 'version'], probeDir).trim() }; + for (const name of ['vite', '@vitejs/devtools', '@vitejs/devtools-vitest']) { + try { + latest[name] = npmUnderTest( + ['view', `${name}@latest`, 'version'], + probeDir, + OPTIONAL_LOOKUP_TIMEOUT_MS + ).trim(); + } catch (error) { + latest[name] = `unavailable (${error instanceof Error ? error.message : String(error)})`; + } + } + console.log(`Resolver: npm ${npmVersion}; registry ${registry}; latest ${JSON.stringify(latest)}`); + const vitestLatestMajor = Number.parseInt(latest.vitest, 10); + if (!Number.isInteger(vitestLatestMajor) || vitestLatestMajor < 5) { + throw new Error( + `Precondition not met: the registry serves vitest ${latest.vitest} as latest, so the wildcard peer cannot resolve to vitest 5 and neither arm can be observed here.` + ); + } + + const install = spawnSync( + 'npx', + [ + '--yes', + '--package', + NPM_UNDER_TEST, + '--', + 'npm', + 'install', + '--package-lock-only', + '--ignore-scripts', + '--no-audit', + '--no-fund', + '--loglevel', + 'error', + ], + { + cwd: probeDir, + env: { ...process.env, npm_config_update_notifier: 'false' }, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + timeout: COMMAND_TIMEOUT_MS, + } + ); + if (install.error) { + // spawnSync reports its own timeout as an error with code ETIMEDOUT; a + // slow registry is an infrastructure failure, not a launch failure. + const cause = + install.error.code === 'ETIMEDOUT' + ? `timed out after ${COMMAND_TIMEOUT_MS}ms` + : `could not start: ${install.error.message}`; + throw new Error(`${NPM_UNDER_TEST} ${cause}`); + } + const stderr = install.stderr ?? ''; + const stdout = install.stdout ?? ''; + console.log(`${NPM_UNDER_TEST} install exited with ${install.status ?? `signal ${install.signal}`}`); + if (stderr.trim()) console.log(stderr.trim().split('\n').slice(-8).join('\n')); + + let outcome; + let signature; + let details; + if (install.status !== 0 && stderr.includes(CRASH_MARKER)) { + outcome = 'bug'; + signature = 'fresh_install_crashes_on_wildcard_vitest_peer'; + details = `${NPM_UNDER_TEST} could not build an ideal tree from the ${arm} manifests: arborist crashed with "${CRASH_MARKER}" while loading the peer set that @vitejs/devtools-vitest's wildcard vitest peer pulls in.`; + } else if (install.status === 0) { + const lock = JSON.parse(await readFile(path.join(probeDir, 'package-lock.json'), 'utf8')); + const resolved = lock.packages?.['node_modules/vitest']?.version; + const chain = Object.fromEntries( + ['node_modules/vite', 'node_modules/@vitejs/devtools', 'node_modules/@vitejs/devtools-vitest'].map( + (key) => [key, lock.packages?.[key]?.version ?? null] + ) + ); + console.log(`Resolved vitest ${resolved}; chain ${JSON.stringify(chain)}`); + if (typeof resolved !== 'string' || !resolved.startsWith('4.')) { + throw new Error( + `Install succeeded but resolved vitest ${JSON.stringify(resolved)}; expected a 4.x matching ${rootRange}.` + ); + } + outcome = 'fixed'; + signature = 'fresh_install_resolves_vitest_4'; + details = `${NPM_UNDER_TEST} built an ideal tree from the ${arm} manifests without a lockfile and resolved vitest ${resolved} for the workspace range ${rootRange}.`; + } else { + throw new Error( + `Unexpected ${NPM_UNDER_TEST} outcome (exit ${install.status ?? install.signal}): ${(stderr || stdout) + .trim() + .split('\n') + .slice(-5) + .join(' | ')}` + ); + } + + await mkdir(path.dirname(resultPath), { recursive: true }); + await writeFile( + resultPath, + `${JSON.stringify({ version: 1, caseId: CASE_ID, arm, outcome, signature, details })}\n`, + 'utf8' + ); +} finally { + await rm(probeDir, { recursive: true, force: true }); +} + +async function copyWorkspaceManifests(sourceRoot, destinationRoot) { + await copyFile(path.join(sourceRoot, 'package.json'), path.join(destinationRoot, 'package.json')); + let count = 1; + const packagesDir = path.join(sourceRoot, 'packages'); + for (const entry of await readdir(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const manifest = path.join(packagesDir, entry.name, 'package.json'); + let source; + try { + source = await readFile(manifest); + } catch (error) { + if (error?.code === 'ENOENT') continue; + throw error; + } + const destination = path.join(destinationRoot, 'packages', entry.name); + await mkdir(destination, { recursive: true }); + await writeFile(path.join(destination, 'package.json'), source); + count += 1; + } + return count; +} + +function npmUnderTest(args, cwd, timeoutMs = PREFLIGHT_TIMEOUT_MS) { + const completed = spawnSync('npx', ['--yes', '--package', NPM_UNDER_TEST, '--', 'npm', ...args], { + cwd, + env: { ...process.env, npm_config_update_notifier: 'false' }, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + timeout: timeoutMs, + }); + if (completed.error) { + const cause = + completed.error.code === 'ETIMEDOUT' + ? `timed out after ${timeoutMs}ms` + : `could not run: ${completed.error.message}`; + throw new Error(`${NPM_UNDER_TEST} ${args[0]} ${cause}`); + } + if (completed.status !== 0) { + throw new Error( + `${NPM_UNDER_TEST} ${args.join(' ')} exited with ${completed.status}: ${(completed.stderr || '').trim().split('\n').slice(-3).join(' | ')}` + ); + } + return completed.stdout; +} + +function requiredValue(name) { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`Missing required environment variable ${name}.`); + return value; +} + +function requiredDirectory(name) { + return path.resolve(requiredValue(name)); +} + +function isWithin(directory, candidate) { + const relative = path.relative(directory, candidate); + return ( + relative === '' || + (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)) + ); +}