diff --git a/.gitignore b/.gitignore index 465c44380..e38b0e9bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +# ── Compiled test output ── +dist-test/ + # ── GSD project state (development-only, lives in worktree branches) ── package-lock.json .claude/ diff --git a/package.json b/package.json index 0c925eb9b..4b2bcf3d4 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,12 @@ "copy-resources": "node scripts/copy-resources.cjs", "copy-themes": "node scripts/copy-themes.cjs", "copy-export-html": "node scripts/copy-export-html.cjs", - "test:unit": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts", + "test:compile": "node scripts/compile-tests.mjs", + "test:unit": "npm run test:compile && node --import ./scripts/dist-test-resolve.mjs --experimental-test-isolation=process --test-reporter=./scripts/test-reporter-compact.mjs --test 'dist-test/src/tests/*.test.js' 'dist-test/src/resources/extensions/gsd/tests/*.test.js' 'dist-test/src/resources/extensions/gsd/tests/*.test.mjs' 'dist-test/src/resources/extensions/shared/tests/*.test.js' 'dist-test/src/resources/extensions/claude-code-cli/tests/*.test.js' 'dist-test/src/resources/extensions/github-sync/tests/*.test.js' 'dist-test/src/resources/extensions/universal-config/tests/*.test.js' 'dist-test/src/resources/extensions/voice/tests/*.test.js'", "test:packages": "node --test packages/pi-coding-agent/dist/core/*.test.js", "test:marketplace": "GSD_TEST_CLONE_MARKETPLACES=1 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/claude-import-tui.test.ts src/resources/extensions/gsd/tests/plugin-importer-live.test.ts src/tests/marketplace-discovery.test.ts", - "test:coverage": "c8 --reporter=text --reporter=lcov --exclude='src/resources/extensions/gsd/tests/**' --exclude='src/tests/**' --exclude='scripts/**' --exclude='native/**' --exclude='node_modules/**' --check-coverage --statements=40 --lines=40 --branches=20 --functions=20 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts", - "test:integration": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*integration*.test.ts src/tests/integration/*.test.ts", + "test:coverage": "c8 --reporter=text --reporter=lcov --exclude='src/resources/extensions/gsd/tests/**' --exclude='src/tests/**' --exclude='scripts/**' --exclude='native/**' --exclude='node_modules/**' --check-coverage --statements=40 --lines=40 --branches=20 --functions=20 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts src/resources/extensions/shared/tests/*.test.ts", + "test:integration": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test 'src/tests/integration/*.test.ts' 'src/resources/extensions/gsd/tests/integration/*.test.ts' 'src/resources/extensions/async-jobs/*.test.ts' 'src/resources/extensions/browser-tools/tests/*.test.mjs'", "pretest": "npm run typecheck:extensions", "test": "npm run test:unit && npm run test:integration", "test:smoke": "node --experimental-strip-types tests/smoke/run.ts", @@ -136,6 +137,7 @@ "@types/node": "^24.12.0", "@types/picomatch": "^4.0.2", "c8": "^11.0.0", + "esbuild": "^0.25.12", "jiti": "^2.6.1", "typescript": "^5.4.0" }, diff --git a/scripts/compile-tests.mjs b/scripts/compile-tests.mjs new file mode 100644 index 000000000..066c02e9b --- /dev/null +++ b/scripts/compile-tests.mjs @@ -0,0 +1,214 @@ +#!/usr/bin/env node +/** + * Compile all TypeScript source + test files to dist-test/ using esbuild. + * Run compiled JS directly with node --test (no per-file TS overhead). + * + * Usage: node scripts/compile-tests.mjs + */ + +import { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'; +import { existsSync, symlinkSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const ROOT = join(__dirname, '..'); + +const require = createRequire(import.meta.url); +const esbuild = require(join(ROOT, 'node_modules/esbuild')); + +// Recursively collect files by extension (skip node_modules, templates, etc.) +// Directories to skip during file collection +const SKIP_DIRS = new Set(['node_modules', 'templates', '__tests__', 'integration']); + +async function collectFiles(dir, exts = ['.ts', '.mjs']) { + const results = []; + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + return results; + } + for (const entry of entries) { + if (SKIP_DIRS.has(entry.name)) continue; + const full = join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...await collectFiles(full, exts)); + } else if ( + exts.some(ext => entry.name.endsWith(ext)) && + !entry.name.endsWith('.d.ts') + ) { + results.push(full); + } + } + return results; +} + +// Dirs to skip when copying assets (node_modules are never useful in dist-test) +const ASSET_SKIP_DIRS = new Set(['node_modules', '__tests__', 'integration']); + +/** + * Recursively copy files from srcDir to destDir. + * Skips node_modules only. Copies everything: .ts/.tsx originals (for jiti), + * .mjs helpers, .md/.yaml/.json assets, etc. + * esbuild compiled .js output already lands in dist-test, so we just + * overlay the asset files on top. + */ +async function copyAssets(srcDir, destDir) { + let entries; + try { + entries = await readdir(srcDir, { withFileTypes: true }); + } catch { + return; // directory doesn't exist, nothing to copy + } + for (const entry of entries) { + if (ASSET_SKIP_DIRS.has(entry.name)) continue; + const srcPath = join(srcDir, entry.name); + const destPath = join(destDir, entry.name); + if (entry.isDirectory()) { + await copyAssets(srcPath, destPath); + } else { + await mkdir(destDir, { recursive: true }); + await cp(srcPath, destPath, { force: true }); + } + } +} + +async function main() { + const start = Date.now(); + + // Collect entry points from src/ and packages/*/src/ + const srcFiles = await collectFiles(join(ROOT, 'src')); + + const packagesDir = join(ROOT, 'packages'); + const pkgEntries = await readdir(packagesDir, { withFileTypes: true }); + const packageFiles = []; + for (const entry of pkgEntries) { + if (!entry.isDirectory()) continue; + const pkgSrc = join(packagesDir, entry.name, 'src'); + packageFiles.push(...await collectFiles(pkgSrc)); + } + + // Also compile web/lib/ — some tests import from ../../web/lib/ + const webLibFiles = await collectFiles(join(ROOT, 'web', 'lib')); + + const entryPoints = [...srcFiles, ...packageFiles, ...webLibFiles]; + console.log(`Compiling ${entryPoints.length} files to dist-test/...`); + + // bundle:false transforms TypeScript but keeps import specifiers verbatim. + // We post-process the output to rewrite .ts → .js in import strings. + await esbuild.build({ + entryPoints, + outdir: join(ROOT, 'dist-test'), + outbase: ROOT, + bundle: false, + format: 'esm', + platform: 'node', + target: 'node22', + sourcemap: 'inline', + packages: 'external', + logLevel: 'warning', + }); + + // Copy non-compiled assets from src/ to dist-test/src/ maintaining structure. + // Tests use import.meta.url to resolve sibling .md, .yaml, .json, .ts etc. + // Also copy original .ts files — jiti-based imports load .ts source directly. + const srcDir = join(ROOT, 'src'); + const distSrcDir = join(ROOT, 'dist-test', 'src'); + await copyAssets(srcDir, distSrcDir); + console.log('Copied non-TS assets and .ts source files to dist-test/src/'); + + // Copy packages/*/src/ assets as well + for (const entry of pkgEntries) { + if (!entry.isDirectory()) continue; + const pkgSrc = join(packagesDir, entry.name, 'src'); + const pkgDistSrc = join(ROOT, 'dist-test', 'packages', entry.name, 'src'); + await copyAssets(pkgSrc, pkgDistSrc); + } + + // Copy web/lib/ assets (tests import from ../../web/lib/ relative to dist-test/src/tests/) + await copyAssets(join(ROOT, 'web', 'lib'), join(ROOT, 'dist-test', 'web', 'lib')); + + // Copy scripts/ non-TS files (.cjs etc) — some tests require() scripts directly + await copyAssets(join(ROOT, 'scripts'), join(ROOT, 'dist-test', 'scripts')); + + // Copy root package.json — some tests read it to check version/engines fields + await cp(join(ROOT, 'package.json'), join(ROOT, 'dist-test', 'package.json'), { force: true }); + + // Copy root dist/ into dist-test/dist/ — some tests compute projectRoot as + // 3 levels up from dist-test/src/tests/ which lands at dist-test/, then + // import from dist/mcp-server.js etc. + const rootDistDir = join(ROOT, 'dist'); + const distTestDistDir = join(ROOT, 'dist-test', 'dist'); + await copyAssets(rootDistDir, distTestDistDir); + + // Post-process: rewrite .ts import specifiers to .js in all compiled JS files. + // esbuild with bundle:false preserves original specifiers; Node can't load .ts. + const compiledJsFiles = await collectFiles(join(ROOT, 'dist-test'), ['.js']); + // Regex matches .ts in from/import() strings but not sourceMappingURL comments + const tsImportRe = /(from\s+["'])(\.\.?\/[^"']*?)\.ts(["'])/g; + const tsDynImportRe = /(import\(["'])(\.\.?\/[^"']*?)\.ts(["'])\)/g; + + let rewritten = 0; + await Promise.all(compiledJsFiles.map(async (file) => { + const src = await readFile(file, 'utf-8'); + const out = src + .replace(tsImportRe, (_, a, b, c) => `${a}${b}.js${c}`) + .replace(tsDynImportRe, (_, a, b, c) => `${a}${b}.js${c})`); + if (out !== src) { + await writeFile(file, out, 'utf-8'); + rewritten++; + } + })); + if (rewritten > 0) { + console.log(`Rewrote .ts → .js imports in ${rewritten} files`); + } + + // Remove stale compiled test files: dist-test entries whose source no longer exists + // in a non-integration source directory (e.g. test moved to integration/). + // Only cleans *.test.js and *.test.ts files to avoid touching non-test outputs. + const { rm } = await import('node:fs/promises'); + const { existsSync } = await import('node:fs'); + const testDirsToClean = [ + [join(ROOT, 'dist-test', 'src', 'tests'), join(ROOT, 'src', 'tests')], + [join(ROOT, 'dist-test', 'src', 'resources', 'extensions', 'gsd', 'tests'), + join(ROOT, 'src', 'resources', 'extensions', 'gsd', 'tests')], + ]; + let staleCleaned = 0; + for (const [distDir, srcDir] of testDirsToClean) { + let distEntries; + try { distEntries = await readdir(distDir, { withFileTypes: true }); } catch { continue; } + for (const entry of distEntries) { + if (!entry.isFile()) continue; + if (!entry.name.match(/\.test\.(js|ts)$/)) continue; + const stem = entry.name.replace(/\.(js|ts)$/, ''); + // Source could be .ts or .mjs (esbuild compiles both to .js) + const hasTsSrc = existsSync(join(srcDir, stem + '.ts')); + const hasMjsSrc = existsSync(join(srcDir, stem + '.mjs')); + if (!hasTsSrc && !hasMjsSrc) { + await rm(join(distDir, entry.name)); + staleCleaned++; + } + } + } + if (staleCleaned > 0) { + console.log(`Removed ${staleCleaned} stale compiled test files from dist-test/`); + } + + // Ensure dist-test/node_modules exists so resource-loader.ts (which computes + // packageRoot from import.meta.url) resolves gsdNodeModules to a real path. + // Without this, initResources creates dangling symlinks in test environments. + const distNodeModules = join(ROOT, 'dist-test', 'node_modules'); + if (!existsSync(distNodeModules)) { + symlinkSync(join(ROOT, 'node_modules'), distNodeModules); + } + + const elapsed = ((Date.now() - start) / 1000).toFixed(2); + console.log(`Done in ${elapsed}s`); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/dist-test-resolve.mjs b/scripts/dist-test-resolve.mjs new file mode 100644 index 000000000..a5d94da11 --- /dev/null +++ b/scripts/dist-test-resolve.mjs @@ -0,0 +1,46 @@ +/** + * Minimal Node.js import hook for running tests from dist-test/. + * + * esbuild with bundle:false preserves import specifiers verbatim, so compiled + * .js files still import '../foo.ts'. This hook redirects those to '.js' so + * Node can find the compiled output. + * + * Also redirects @gsd bare imports to their compiled counterparts in dist-test. + */ + +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +// dist-test root — everything compiled lands here +const DIST_TEST = new URL('../dist-test/', import.meta.url).href; + +// Absolute paths to compiled @gsd/* entry points +const GSD_ALIASES = { + '@gsd/pi-coding-agent': new URL('../dist-test/packages/pi-coding-agent/src/index.js', import.meta.url).href, + '@gsd/pi-ai/oauth': new URL('../dist-test/packages/pi-ai/src/utils/oauth/index.js', import.meta.url).href, + '@gsd/pi-ai': new URL('../dist-test/packages/pi-ai/src/index.js', import.meta.url).href, + '@gsd/pi-agent-core': new URL('../dist-test/packages/pi-agent-core/src/index.js', import.meta.url).href, + '@gsd/pi-tui': new URL('../dist-test/packages/pi-tui/src/index.js', import.meta.url).href, + '@gsd/native': new URL('../dist-test/packages/native/src/index.js', import.meta.url).href, +}; + +export function resolve(specifier, context, nextResolve) { + // 1. @gsd/* bare imports → compiled dist-test counterpart + if (specifier in GSD_ALIASES) { + return nextResolve(GSD_ALIASES[specifier], context); + } + + // 2. .ts relative imports inside dist-test → .js + if ( + specifier.endsWith('.ts') && + (specifier.startsWith('./') || specifier.startsWith('../')) && + context.parentURL && + context.parentURL.startsWith(DIST_TEST) + ) { + const jsSpecifier = specifier.slice(0, -3) + '.js'; + return nextResolve(jsSpecifier, context); + } + + return nextResolve(specifier, context); +} diff --git a/scripts/ensure-workspace-builds.cjs b/scripts/ensure-workspace-builds.cjs index 840a818d4..44f7ea2c4 100644 --- a/scripts/ensure-workspace-builds.cjs +++ b/scripts/ensure-workspace-builds.cjs @@ -18,25 +18,6 @@ const { existsSync, statSync, readdirSync } = require('fs') const { resolve, join } = require('path') const { execSync } = require('child_process') -const root = resolve(__dirname, '..') -const packagesDir = join(root, 'packages') - -// Skip if packages/ doesn't exist (published tarball / end-user install) -if (!existsSync(packagesDir)) process.exit(0) - -// Skip in CI — the pipeline runs `npm run build` explicitly -if (process.env.CI === 'true' || process.env.CI === '1') process.exit(0) - -// Workspace packages that need dist/index.js at runtime. -// Order matters: dependencies must build before dependents. -const WORKSPACE_PACKAGES = [ - 'native', - 'pi-tui', - 'pi-ai', - 'pi-agent-core', - 'pi-coding-agent', -] - /** * Returns the most recent mtime (ms) of any .ts file under dir, recursively. * Returns 0 if no .ts files found. @@ -56,31 +37,54 @@ function newestSrcMtime(dir) { return newest } -const stale = [] -for (const pkg of WORKSPACE_PACKAGES) { - const distIndex = join(packagesDir, pkg, 'dist', 'index.js') - if (!existsSync(distIndex)) { - stale.push(pkg) - continue +if (require.main === module) { + const root = resolve(__dirname, '..') + const packagesDir = join(root, 'packages') + + // Skip if packages/ doesn't exist (published tarball / end-user install) + if (!existsSync(packagesDir)) process.exit(0) + + // Skip in CI — the pipeline runs `npm run build` explicitly + if (process.env.CI === 'true' || process.env.CI === '1') process.exit(0) + + // Workspace packages that need dist/index.js at runtime. + // Order matters: dependencies must build before dependents. + const WORKSPACE_PACKAGES = [ + 'native', + 'pi-tui', + 'pi-ai', + 'pi-agent-core', + 'pi-coding-agent', + ] + + const stale = [] + for (const pkg of WORKSPACE_PACKAGES) { + const distIndex = join(packagesDir, pkg, 'dist', 'index.js') + if (!existsSync(distIndex)) { + stale.push(pkg) + continue + } + const distMtime = statSync(distIndex).mtimeMs + const srcMtime = newestSrcMtime(join(packagesDir, pkg, 'src')) + if (srcMtime > distMtime) { + stale.push(pkg) + } } - const distMtime = statSync(distIndex).mtimeMs - const srcMtime = newestSrcMtime(join(packagesDir, pkg, 'src')) - if (srcMtime > distMtime) { - stale.push(pkg) + + if (stale.length === 0) process.exit(0) + + process.stderr.write(` Building ${stale.length} workspace package(s) with stale or missing dist/: ${stale.join(', ')}\n`) + + for (const pkg of stale) { + const pkgDir = join(packagesDir, pkg) + try { + execSync('npm run build', { cwd: pkgDir, stdio: 'pipe' }) + process.stderr.write(` ✓ ${pkg}\n`) + } catch (err) { + process.stderr.write(` ✗ ${pkg} build failed: ${err.message}\n`) + // Non-fatal — the user can run `npm run build` manually + } } } -if (stale.length === 0) process.exit(0) - -process.stderr.write(` Building ${stale.length} workspace package(s) with stale or missing dist/: ${stale.join(', ')}\n`) - -for (const pkg of stale) { - const pkgDir = join(packagesDir, pkg) - try { - execSync('npm run build', { cwd: pkgDir, stdio: 'pipe' }) - process.stderr.write(` ✓ ${pkg}\n`) - } catch (err) { - process.stderr.write(` ✗ ${pkg} build failed: ${err.message}\n`) - // Non-fatal — the user can run `npm run build` manually - } -} +module.exports = { newestSrcMtime } diff --git a/scripts/test-reporter-compact.mjs b/scripts/test-reporter-compact.mjs new file mode 100644 index 000000000..ec87b221d --- /dev/null +++ b/scripts/test-reporter-compact.mjs @@ -0,0 +1,44 @@ +/** + * Compact test reporter: silent on pass, prints failures + final summary. + * Usage: --test-reporter=./scripts/test-reporter-compact.mjs + */ +import { Transform } from 'node:stream'; + +export default class CompactReporter extends Transform { + #pass = 0; + #fail = 0; + #skip = 0; + #failures = []; + + constructor() { + super({ objectMode: true }); + } + + _transform(event, _enc, cb) { + switch (event.type) { + case 'test:pass': + if (!event.data.skip) this.#pass++; + else this.#skip++; + break; + case 'test:fail': { + this.#fail++; + const { name, details } = event.data; + const err = details?.error; + const msg = err?.message ?? String(err ?? 'unknown'); + const loc = err?.cause?.stack?.split('\n')[1]?.trim() ?? ''; + this.#failures.push(` ✖ ${name}\n ${msg}${loc ? `\n ${loc}` : ''}`); + break; + } + } + cb(); + } + + _flush(cb) { + if (this.#failures.length) { + this.push(`\n✖ failing tests:\n${this.#failures.join('\n\n')}\n`); + } + const status = this.#fail === 0 ? '✔' : '✖'; + this.push(`\n${status} ${this.#pass} passed, ${this.#fail} failed, ${this.#skip} skipped\n`); + cb(); + } +} diff --git a/src/resources/extensions/gsd/skill-health.ts b/src/resources/extensions/gsd/skill-health.ts index a59f4d8aa..75217a5b6 100644 --- a/src/resources/extensions/gsd/skill-health.ts +++ b/src/resources/extensions/gsd/skill-health.ts @@ -13,7 +13,7 @@ * research identified as critical for skill quality. */ -import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; import type { UnitMetrics, MetricsLedger } from "./metrics.js"; @@ -210,7 +210,7 @@ export function formatSkillDetail(basePath: string, skillName: string): string { // Check for SKILL.md existence const skillPath = join(homedir(), ".agents", "skills", skillName, "SKILL.md"); if (existsSync(skillPath)) { - const stat = require("node:fs").statSync(skillPath); + const stat = statSync(skillPath); lines.push(""); lines.push(`SKILL.md: ${skillPath}`); lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`); diff --git a/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts b/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts rename to src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts index 0b06d721b..d3a0c7c2e 100644 --- a/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +++ b/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts @@ -31,7 +31,7 @@ import { isInAutoWorktree, getAutoWorktreeOriginalBase, mergeMilestoneToMain, -} from "../auto-worktree.ts"; +} from "../../auto-worktree.ts"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -78,9 +78,9 @@ function createMilestoneArtifacts(dir: string, mid: string): void { // ─── Source-level: verify the merge code exists in the "all complete" path ──── test("auto-loop 'all milestones complete' path merges before stopping (#962)", () => { - const loopSrc = readFileSync(join(__dirname, "..", "auto", "phases.ts"), "utf-8"); + const loopSrc = readFileSync(join(__dirname, "../..", "auto", "phases.ts"), "utf-8"); const resolverSrc = readFileSync( - join(__dirname, "..", "worktree-resolver.ts"), + join(__dirname, "../..", "worktree-resolver.ts"), "utf-8", ); diff --git a/src/resources/extensions/gsd/tests/atomic-task-closeout.test.ts b/src/resources/extensions/gsd/tests/integration/atomic-task-closeout.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/atomic-task-closeout.test.ts rename to src/resources/extensions/gsd/tests/integration/atomic-task-closeout.test.ts index 3e1c58753..e6c4143d8 100644 --- a/src/resources/extensions/gsd/tests/atomic-task-closeout.test.ts +++ b/src/resources/extensions/gsd/tests/integration/atomic-task-closeout.test.ts @@ -9,7 +9,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import test from "node:test"; import assert from "node:assert/strict"; -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; function makeTmp(name: string): string { const dir = join(tmpdir(), `atomic-closeout-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`); diff --git a/src/resources/extensions/gsd/tests/auto-preflight.test.ts b/src/resources/extensions/gsd/tests/integration/auto-preflight.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/auto-preflight.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-preflight.test.ts index 63eb7e60a..1a332c6eb 100644 --- a/src/resources/extensions/gsd/tests/auto-preflight.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-preflight.test.ts @@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { runGSDDoctor, selectDoctorScope, filterDoctorIssues } from "../doctor.js"; +import { runGSDDoctor, selectDoctorScope, filterDoctorIssues } from "../../doctor.js"; test("auto-preflight scopes to active milestone, ignoring historical", async (t) => { const tmpBase = mkdtempSync(join(tmpdir(), "gsd-auto-preflight-test-")); diff --git a/src/resources/extensions/gsd/tests/auto-recovery.test.ts b/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/auto-recovery.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts index a71882f3a..8aef15b20 100644 --- a/src/resources/extensions/gsd/tests/auto-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts @@ -11,19 +11,19 @@ import { diagnoseExpectedArtifact, buildLoopRemediationSteps, hasImplementationArtifacts, -} from "../auto-recovery.ts"; -import { parseRoadmap, parsePlan } from "../parsers-legacy.ts"; -import { parseTaskPlanFile, clearParseCache } from "../files.ts"; -import { invalidateAllCaches } from "../cache.ts"; -import { deriveState, invalidateStateCache } from "../state.ts"; +} from "../../auto-recovery.ts"; +import { parseRoadmap, parsePlan } from "../../parsers-legacy.ts"; +import { parseTaskPlanFile, clearParseCache } from "../../files.ts"; +import { invalidateAllCaches } from "../../cache.ts"; +import { deriveState, invalidateStateCache } from "../../state.ts"; import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, -} from "../gsd-db.ts"; -import { renderPlanFromDb } from "../markdown-renderer.ts"; +} from "../../gsd-db.ts"; +import { renderPlanFromDb } from "../../markdown-renderer.ts"; function makeTmpBase(): string { const base = join(tmpdir(), `gsd-test-${randomUUID()}`); diff --git a/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts b/src/resources/extensions/gsd/tests/integration/auto-secrets-gate.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-secrets-gate.test.ts index 1c970123d..6807647cf 100644 --- a/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-secrets-gate.test.ts @@ -16,8 +16,8 @@ import assert from 'node:assert/strict'; import { mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import { getManifestStatus } from '../files.ts'; -import { collectSecretsFromManifest } from '../../get-secrets-from-user.ts'; +import { getManifestStatus } from '../../files.ts'; +import { collectSecretsFromManifest } from '../../../get-secrets-from-user.ts'; function makeTempDir(prefix: string): string { const dir = join(tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`); diff --git a/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts b/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts similarity index 95% rename from src/resources/extensions/gsd/tests/auto-stash-merge.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts index 5152ba930..71c9173fd 100644 --- a/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts @@ -12,8 +12,8 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execSync } from "node:child_process"; -import { createAutoWorktree, mergeMilestoneToMain } from "../auto-worktree.ts"; -import { nativeMergeSquash } from "../native-git-bridge.ts"; +import { createAutoWorktree, mergeMilestoneToMain } from "../../auto-worktree.ts"; +import { nativeMergeSquash } from "../../native-git-bridge.ts"; function run(cmd: string, cwd: string): string { return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); @@ -88,7 +88,7 @@ test("#2151 bug 1: auto-stash unblocks merge when unrelated files are dirty", () }); test("#2151 bug 2: nativeMergeSquash returns dirty filenames", async () => { - const { nativeMergeSquash } = await import("../native-git-bridge.ts"); + const { nativeMergeSquash } = await import("../../native-git-bridge.ts"); const repo = createTempRepo(); try { run("git checkout -b milestone/M210", repo); diff --git a/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts b/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts index 87af75fa0..bf11a5109 100644 --- a/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts @@ -21,9 +21,9 @@ import { createAutoWorktree, mergeMilestoneToMain, getAutoWorktreeOriginalBase, -} from "../auto-worktree.ts"; -import { getSliceBranchName } from "../worktree.ts"; -import { nativeMergeSquash } from "../native-git-bridge.ts"; +} from "../../auto-worktree.ts"; +import { getSliceBranchName } from "../../worktree.ts"; +import { nativeMergeSquash } from "../../native-git-bridge.ts"; function run(cmd: string, cwd: string): string { // Safe: all inputs are hardcoded test strings, not user input @@ -329,7 +329,7 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => { }); test("#1738 bug 1: nativeMergeSquash detects dirty working tree", async () => { - const { nativeMergeSquash } = await import("../native-git-bridge.ts"); + const { nativeMergeSquash } = await import("../../native-git-bridge.ts"); const repo = freshRepo(); run("git checkout -b milestone/M070", repo); diff --git a/src/resources/extensions/gsd/tests/auto-worktree.test.ts b/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/auto-worktree.test.ts rename to src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts index 3a524f0c3..38aa285b6 100644 --- a/src/resources/extensions/gsd/tests/auto-worktree.test.ts +++ b/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts @@ -20,7 +20,7 @@ import { enterAutoWorktree, getAutoWorktreeOriginalBase, getActiveAutoWorktreeContext, -} from "../auto-worktree.ts"; +} from "../../auto-worktree.ts"; // Note: execSync is used intentionally in tests for git operations with // controlled, hardcoded inputs (no user input). This is safe and matches @@ -150,7 +150,7 @@ describe("auto-worktree lifecycle", () => { run("git commit -m \"add milestone\"", tempDir); // Import createWorktree directly for manual worktree - const { createWorktree } = await import("../worktree-manager.ts"); + const { createWorktree } = await import("../../worktree-manager.ts"); // Create manual worktree (uses worktree/ branch) const manualWt = createWorktree(tempDir, "feature-x"); @@ -164,7 +164,7 @@ describe("auto-worktree lifecycle", () => { // Cleanup both teardownAutoWorktree(tempDir, "M003"); - const { removeWorktree } = await import("../worktree-manager.ts"); + const { removeWorktree } = await import("../../worktree-manager.ts"); removeWorktree(tempDir, "feature-x"); }); @@ -190,7 +190,7 @@ describe("auto-worktree lifecycle", () => { run("git add .", tempDir); run("git commit -m \"add milestone\"", tempDir); - const { GitServiceImpl } = await import("../git-service.ts"); + const { GitServiceImpl } = await import("../../git-service.ts"); // Create worktree const wtPath = createAutoWorktree(tempDir, "M005"); @@ -215,7 +215,7 @@ describe("auto-worktree lifecycle", () => { run("git commit -m \"add milestone\"", tempDir); // Simulate a crash leaving a stale directory with no .git file. - const { worktreePath } = await import("../worktree-manager.ts"); + const { worktreePath } = await import("../../worktree-manager.ts"); const staleDir = worktreePath(tempDir, "M010"); mkdirSync(staleDir, { recursive: true }); writeFileSync(join(staleDir, "orphan.txt"), "stale leftover\n"); diff --git a/src/resources/extensions/gsd/tests/continue-here.test.ts b/src/resources/extensions/gsd/tests/integration/continue-here.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/continue-here.test.ts rename to src/resources/extensions/gsd/tests/integration/continue-here.test.ts index ac28629fa..94f90aab8 100644 --- a/src/resources/extensions/gsd/tests/continue-here.test.ts +++ b/src/resources/extensions/gsd/tests/integration/continue-here.test.ts @@ -12,7 +12,7 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { computeBudgets } from "../context-budget.js"; +import { computeBudgets } from "../../context-budget.js"; // ─── Pure threshold / pipeline tests ────────────────────────────────────────── // These test the budget engine outputs that the continue-here monitor relies on. @@ -164,7 +164,7 @@ describe("continue-here", () => { describe("continueHereFired runtime record field", () => { it("AutoUnitRuntimeRecord includes continueHereFired with default false", async (t) => { // Import writeUnitRuntimeRecord to verify the field is present and defaults - const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js"); + const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../../unit-runtime.js"); const fs = await import("node:fs"); const path = await import("node:path"); const os = await import("node:os"); @@ -202,7 +202,7 @@ describe("continue-here", () => { describe("context-pressure monitor integration", () => { it("should fire wrap-up when context >= threshold and mark continueHereFired", async (t) => { - const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js"); + const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../../unit-runtime.js"); const fs = await import("node:fs"); const path = await import("node:path"); const os = await import("node:os"); diff --git a/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-completion-deferral.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-completion-deferral.test.ts index 35623e2e3..809562d10 100644 --- a/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-completion-deferral.test.ts @@ -10,7 +10,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import test from "node:test"; import assert from "node:assert/strict"; -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; function makeTmp(name: string): string { const dir = join(tmpdir(), `doctor-deferral-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`); diff --git a/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-delimiter-fix.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-delimiter-fix.test.ts index 47b75723a..4a042990a 100644 --- a/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-delimiter-fix.test.ts @@ -10,7 +10,7 @@ import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { runGSDDoctor } from "../doctor.js"; +import { runGSDDoctor } from "../../doctor.js"; test("doctor fix=true sanitizes em-dash in milestone title", async (t) => { const tmpBase = mkdtempSync(join(tmpdir(), "gsd-doctor-delim-")); diff --git a/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-enhancements.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/doctor-enhancements.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-enhancements.test.ts index 352664afe..ba8734f30 100644 --- a/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-enhancements.test.ts @@ -4,8 +4,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node: import { join } from "node:path"; import { tmpdir } from "node:os"; -import { runGSDDoctor } from "../doctor.js"; -import { formatDoctorReportJson } from "../doctor-format.js"; +import { runGSDDoctor } from "../../doctor.js"; +import { formatDoctorReportJson } from "../../doctor-format.js"; // ── Helpers ───────────────────────────────────────────────────────────────── function makeBase(): { base: string; gsd: string; mDir: string } { @@ -230,7 +230,7 @@ describe('doctor-enhancements', async () => { const historyPath = join(gsd, "doctor-history.jsonl"); assert.ok(existsSync(historyPath), "doctor-history.jsonl is created after run"); - const { readDoctorHistory } = await import("../doctor.js"); + const { readDoctorHistory } = await import("../../doctor.js"); const history = await readDoctorHistory(base); assert.ok(history.length >= 1, "history has at least one entry"); assert.ok(typeof history[0]?.ts === "string", "history entry has ts field"); diff --git a/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-environment-worktree.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-environment-worktree.test.ts index 702e4ee6a..fe3ea7614 100644 --- a/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-environment-worktree.test.ts @@ -20,7 +20,7 @@ import { runEnvironmentChecks, environmentResultsToDoctorIssues, checkEnvironmentHealth, -} from "../doctor-environment.ts"; +} from "../../doctor-environment.ts"; /** Create a directory tree with files. */ function createDir(files: Record = {}): string { const dir = mkdtempSync(join(tmpdir(), "gsd-wt-env-")); diff --git a/src/resources/extensions/gsd/tests/doctor-environment.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-environment.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor-environment.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-environment.test.ts index af55c2f66..99fa35363 100644 --- a/src/resources/extensions/gsd/tests/doctor-environment.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-environment.test.ts @@ -26,7 +26,7 @@ import { formatEnvironmentReport, checkEnvironmentHealth, type EnvironmentCheckResult, -} from "../doctor-environment.ts"; +} from "../../doctor-environment.ts"; function createProjectDir(files: Record = {}): string { const dir = mkdtempSync(join(tmpdir(), "gsd-env-test-")); for (const [name, content] of Object.entries(files)) { diff --git a/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts index a1d5a4aba..7b43459c6 100644 --- a/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts @@ -14,8 +14,8 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import test from "node:test"; import assert from "node:assert/strict"; -import { runGSDDoctor } from "../doctor.ts"; -import { closeDatabase } from "../gsd-db.ts"; +import { runGSDDoctor } from "../../doctor.ts"; +import { closeDatabase } from "../../gsd-db.ts"; function makeTmp(name: string): string { const dir = join(tmpdir(), `doctor-fixlevel-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`); diff --git a/src/resources/extensions/gsd/tests/doctor-git.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor-git.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-git.test.ts index 9b87d2714..d307627a3 100644 --- a/src/resources/extensions/gsd/tests/doctor-git.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts @@ -15,7 +15,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execSync } from "node:child_process"; -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; function run(cmd: string, cwd: string): string { return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); } diff --git a/src/resources/extensions/gsd/tests/doctor-proactive.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor-proactive.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts index 29be69b33..af04680ca 100644 --- a/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts @@ -23,7 +23,7 @@ import { checkHealEscalation, resetProactiveHealing, formatHealthSummary, -} from "../doctor-proactive.ts"; +} from "../../doctor-proactive.ts"; function run(cmd: string, cwd: string): string { return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); } diff --git a/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-roadmap-summary-atomicity.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-roadmap-summary-atomicity.test.ts index 140db7f0c..40dc6ffd9 100644 --- a/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-roadmap-summary-atomicity.test.ts @@ -12,7 +12,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import test from "node:test"; import assert from "node:assert/strict"; -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; function makeTmp(name: string): string { const dir = join(tmpdir(), `doctor-roadmap-summary-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`); diff --git a/src/resources/extensions/gsd/tests/doctor-runtime.test.ts b/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor-runtime.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts index a8f560cf6..8d55fd621 100644 --- a/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts @@ -14,7 +14,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execSync } from "node:child_process"; -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; function run(cmd: string, cwd: string): string { return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); } diff --git a/src/resources/extensions/gsd/tests/doctor.test.ts b/src/resources/extensions/gsd/tests/integration/doctor.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/doctor.test.ts rename to src/resources/extensions/gsd/tests/integration/doctor.test.ts index e9a33c28d..7eb482c85 100644 --- a/src/resources/extensions/gsd/tests/doctor.test.ts +++ b/src/resources/extensions/gsd/tests/integration/doctor.test.ts @@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync import { join } from "node:path"; import { tmpdir } from "node:os"; -import { formatDoctorReport, runGSDDoctor, summarizeDoctorIssues, filterDoctorIssues, selectDoctorScope, validateTitle } from "../doctor.js"; +import { formatDoctorReport, runGSDDoctor, summarizeDoctorIssues, filterDoctorIssues, selectDoctorScope, validateTitle } from "../../doctor.js"; const tmpBase = mkdtempSync(join(tmpdir(), "gsd-doctor-test-")); const gsd = join(tmpBase, ".gsd"); const mDir = join(gsd, "milestones", "M001"); diff --git a/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts b/src/resources/extensions/gsd/tests/integration/e2e-workflow-pipeline-integration.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts rename to src/resources/extensions/gsd/tests/integration/e2e-workflow-pipeline-integration.test.ts index 419ac5762..4b3ae61be 100644 --- a/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +++ b/src/resources/extensions/gsd/tests/integration/e2e-workflow-pipeline-integration.test.ts @@ -34,11 +34,11 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { stringify, parse } from "yaml"; -import { CustomWorkflowEngine } from "../custom-workflow-engine.ts"; -import { CustomExecutionPolicy } from "../custom-execution-policy.ts"; -import { createRun, listRuns } from "../run-manager.ts"; -import { readGraph, writeGraph } from "../graph.ts"; -import { validateDefinition } from "../definition-loader.ts"; +import { CustomWorkflowEngine } from "../../custom-workflow-engine.ts"; +import { CustomExecutionPolicy } from "../../custom-execution-policy.ts"; +import { createRun, listRuns } from "../../run-manager.ts"; +import { readGraph, writeGraph } from "../../graph.ts"; +import { validateDefinition } from "../../definition-loader.ts"; // ─── Helpers ───────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts b/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts rename to src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts index 6794a6ea9..e6cb849a8 100644 --- a/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +++ b/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts @@ -26,10 +26,10 @@ import { createAutoWorktree, mergeMilestoneToMain, autoWorktreeBranch, -} from "../auto-worktree.ts"; -import { captureIntegrationBranch, getSliceBranchName } from "../worktree.ts"; -import { writeIntegrationBranch, readIntegrationBranch } from "../git-service.ts"; -import { nextMilestoneId, generateMilestoneSuffix } from "../guided-flow.ts"; +} from "../../auto-worktree.ts"; +import { captureIntegrationBranch, getSliceBranchName } from "../../worktree.ts"; +import { writeIntegrationBranch, readIntegrationBranch } from "../../git-service.ts"; +import { nextMilestoneId, generateMilestoneSuffix } from "../../guided-flow.ts"; // ─── Helpers ──────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/git-locale.test.ts b/src/resources/extensions/gsd/tests/integration/git-locale.test.ts similarity index 93% rename from src/resources/extensions/gsd/tests/git-locale.test.ts rename to src/resources/extensions/gsd/tests/integration/git-locale.test.ts index ef668e1de..e385ea287 100644 --- a/src/resources/extensions/gsd/tests/git-locale.test.ts +++ b/src/resources/extensions/gsd/tests/integration/git-locale.test.ts @@ -12,9 +12,9 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execFileSync } from "node:child_process"; -import { GIT_NO_PROMPT_ENV } from "../git-constants.ts"; -import { nativeAddAllWithExclusions } from "../native-git-bridge.ts"; -import { RUNTIME_EXCLUSION_PATHS } from "../git-service.ts"; +import { GIT_NO_PROMPT_ENV } from "../../git-constants.ts"; +import { nativeAddAllWithExclusions } from "../../native-git-bridge.ts"; +import { RUNTIME_EXCLUSION_PATHS } from "../../git-service.ts"; function git(cwd: string, ...args: string[]): string { return execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); } @@ -101,7 +101,7 @@ describe('git-locale', async () => { // We verify indirectly: the source code must pass env: GIT_NO_PROMPT_ENV. // Read the source and check for the pattern. This is a static check. const src = readFileSync( - join(import.meta.dirname, "..", "native-git-bridge.ts"), + join(import.meta.dirname, "../..", "native-git-bridge.ts"), "utf-8" ); diff --git a/src/resources/extensions/gsd/tests/git-self-heal.test.ts b/src/resources/extensions/gsd/tests/integration/git-self-heal.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/git-self-heal.test.ts rename to src/resources/extensions/gsd/tests/integration/git-self-heal.test.ts index 58bf81d59..092cde31c 100644 --- a/src/resources/extensions/gsd/tests/git-self-heal.test.ts +++ b/src/resources/extensions/gsd/tests/integration/git-self-heal.test.ts @@ -14,7 +14,7 @@ import assert from "node:assert/strict"; import { abortAndReset, formatGitError, -} from "../git-self-heal.js"; +} from "../../git-self-heal.js"; // ─── Helpers ───────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/git-service.test.ts b/src/resources/extensions/gsd/tests/integration/git-service.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/git-service.test.ts rename to src/resources/extensions/gsd/tests/integration/git-service.test.ts index 2a5587d9b..d1ba7a7ff 100644 --- a/src/resources/extensions/gsd/tests/git-service.test.ts +++ b/src/resources/extensions/gsd/tests/integration/git-service.test.ts @@ -20,8 +20,8 @@ import { type CommitOptions, type PreMergeCheckResult, type TaskCommitContext, -} from "../git-service.ts"; -import { nativeAddAllWithExclusions } from "../native-git-bridge.ts"; +} from "../../git-service.ts"; +import { nativeAddAllWithExclusions } from "../../native-git-bridge.ts"; function run(command: string, cwd: string): string { return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); } @@ -1113,7 +1113,7 @@ describe('git-service', async () => { // ─── untrackRuntimeFiles: removes tracked runtime files from index ─── test('untrackRuntimeFiles', async () => { - const { untrackRuntimeFiles } = await import("../gitignore.ts"); + const { untrackRuntimeFiles } = await import("../../gitignore.ts"); const repo = mkdtempSync(join(tmpdir(), "gsd-untrack-")); runGit(repo, ["init", "-b", "main"]); runGit(repo, ["config", "user.email", "test@test.com"]); @@ -1222,7 +1222,7 @@ describe('git-service', async () => { // ─── ensureGitignore: always adds .gsd to gitignore ────────────────── test('ensureGitignore: adds .gsd entry', async () => { - const { ensureGitignore } = await import("../gitignore.ts"); + const { ensureGitignore } = await import("../../gitignore.ts"); const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-external-state-")); // Should add .gsd to gitignore (external state dir is a symlink) diff --git a/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts b/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts rename to src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts index b73512e3d..ed0d56b5f 100644 --- a/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +++ b/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts @@ -22,8 +22,8 @@ import { import { join } from "node:path"; import { tmpdir } from "node:os"; -import { ensureGitignore, hasGitTrackedGsdFiles } from "../gitignore.ts"; -import { migrateToExternalState } from "../migrate-external.ts"; +import { ensureGitignore, hasGitTrackedGsdFiles } from "../../gitignore.ts"; +import { migrateToExternalState } from "../../migrate-external.ts"; // ─── Helpers ───────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/idle-recovery.test.ts b/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/idle-recovery.test.ts rename to src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts index f8940dc61..a2369e4d8 100644 --- a/src/resources/extensions/gsd/tests/idle-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts @@ -7,7 +7,7 @@ import { writeBlockerPlaceholder, verifyExpectedArtifact, buildLoopRemediationSteps, -} from "../auto-recovery.ts"; +} from "../../auto-recovery.ts"; import { describe, test, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; @@ -299,7 +299,7 @@ test('writeBlockerPlaceholder: updates DB task status for execute-task (#2531)', const base = createFixtureBase(); try { const { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, getTask, isDbAvailable } = - await import("../gsd-db.ts"); + await import("../../gsd-db.ts"); const dbPath = join(base, ".gsd", "gsd.db"); // Create the tasks directory (required for artifact path resolution) @@ -334,7 +334,7 @@ test('writeBlockerPlaceholder: does NOT update DB for non-execute-task types', a const base = createFixtureBase(); try { const { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, isDbAvailable } = - await import("../gsd-db.ts"); + await import("../../gsd-db.ts"); const dbPath = join(base, ".gsd", "gsd.db"); mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true }); diff --git a/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts b/src/resources/extensions/gsd/tests/integration/inherited-repo-home-dir.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts rename to src/resources/extensions/gsd/tests/integration/inherited-repo-home-dir.test.ts index 297a5d61c..44e6e7aeb 100644 --- a/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +++ b/src/resources/extensions/gsd/tests/integration/inherited-repo-home-dir.test.ts @@ -24,7 +24,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execFileSync } from "node:child_process"; -import { isInheritedRepo } from "../repo-identity.ts"; +import { isInheritedRepo } from "../../repo-identity.ts"; function run(cmd: string, args: string[], cwd: string): string { return execFileSync(cmd, args, { diff --git a/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts b/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/integration-lifecycle.test.ts rename to src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts index 2cfa31ea8..453ffcbbc 100644 --- a/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +++ b/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts @@ -12,15 +12,15 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync, appendFile import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import { openDatabase, closeDatabase, isDbAvailable, _getAdapter } from '../gsd-db.ts'; -import { migrateFromMarkdown, parseDecisionsTable } from '../md-importer.ts'; +import { openDatabase, closeDatabase, isDbAvailable, _getAdapter } from '../../gsd-db.ts'; +import { migrateFromMarkdown, parseDecisionsTable } from '../../md-importer.ts'; import { queryDecisions, queryRequirements, formatDecisionsForPrompt, formatRequirementsForPrompt, -} from '../context-store.ts'; -import { saveDecisionToDb, generateDecisionsMd } from '../db-writer.ts'; +} from '../../context-store.ts'; +import { saveDecisionToDb, generateDecisionsMd } from '../../db-writer.ts'; import { describe, test, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts b/src/resources/extensions/gsd/tests/integration/integration-mixed-milestones.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts rename to src/resources/extensions/gsd/tests/integration/integration-mixed-milestones.test.ts index 94d2d76b6..f640bb77d 100644 --- a/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +++ b/src/resources/extensions/gsd/tests/integration/integration-mixed-milestones.test.ts @@ -11,15 +11,15 @@ import { execSync } from 'node:child_process'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import { deriveState } from '../state.ts'; -import { indexWorkspace } from '../workspace-index.ts'; -import { inlinePriorMilestoneSummary } from '../files.ts'; -import { getPriorSliceCompletionBlocker } from '../dispatch-guard.ts'; +import { deriveState } from '../../state.ts'; +import { indexWorkspace } from '../../workspace-index.ts'; +import { inlinePriorMilestoneSummary } from '../../files.ts'; +import { getPriorSliceCompletionBlocker } from '../../dispatch-guard.ts'; import { getSliceBranchName, parseSliceBranch, -} from '../worktree.ts'; -import { clearPathCache } from '../paths.ts'; +} from '../../worktree.ts'; +import { clearPathCache } from '../../paths.ts'; import { describe, test, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/src/resources/extensions/gsd/tests/integration-proof.test.ts b/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/integration-proof.test.ts rename to src/resources/extensions/gsd/tests/integration/integration-proof.test.ts index cd48e5f3e..993389b56 100644 --- a/src/resources/extensions/gsd/tests/integration-proof.test.ts +++ b/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts @@ -50,11 +50,11 @@ import { transaction, isDbAvailable, _getAdapter, -} from "../gsd-db.ts"; +} from "../../gsd-db.ts"; // ── Tool handlers ───────────────────────────────────────────────────────── -import { handleCompleteTask } from "../tools/complete-task.ts"; -import { handleCompleteSlice } from "../tools/complete-slice.ts"; +import { handleCompleteTask } from "../../tools/complete-task.ts"; +import { handleCompleteSlice } from "../../tools/complete-slice.ts"; // ── Markdown renderer ───────────────────────────────────────────────────── import { @@ -63,32 +63,32 @@ import { renderAllFromDb, detectStaleRenders, repairStaleRenders, -} from "../markdown-renderer.ts"; +} from "../../markdown-renderer.ts"; // ── State derivation ────────────────────────────────────────────────────── import { deriveStateFromDb, _deriveStateImpl, invalidateStateCache, -} from "../state.ts"; +} from "../../state.ts"; // ── Auto-migration ─────────────────────────────────────────────────────── import { migrateHierarchyToDb, migrateFromMarkdown, -} from "../md-importer.ts"; +} from "../../md-importer.ts"; // ── Post-unit diagnostics ───────────────────────────────────────────────── -import { detectRogueFileWrites } from "../auto-post-unit.ts"; +import { detectRogueFileWrites } from "../../auto-post-unit.ts"; // ── Doctor ──────────────────────────────────────────────────────────────── -import { runGSDDoctor } from "../doctor.ts"; +import { runGSDDoctor } from "../../doctor.ts"; // ── Undo/reset ──────────────────────────────────────────────────────────── -import { handleUndoTask, handleResetSlice } from "../undo.ts"; +import { handleUndoTask, handleResetSlice } from "../../undo.ts"; // ── Cache invalidation ─────────────────────────────────────────────────── -import { invalidateAllCaches } from "../cache.ts"; +import { invalidateAllCaches } from "../../cache.ts"; // ═══════════════════════════════════════════════════════════════════════════ // Helpers @@ -400,7 +400,7 @@ test("full lifecycle: migration through completion through doctor", async (t) => writeFileSync(join(rogueDir, "T99-SUMMARY.md"), "# Rogue Summary\n", "utf-8"); // Clear path cache so resolveTaskFile sees the newly written file - const { clearPathCache } = await import("../paths.ts"); + const { clearPathCache } = await import("../../paths.ts"); clearPathCache(); const rogues = detectRogueFileWrites("execute-task", "M001/S01/T99", base); @@ -458,7 +458,7 @@ test("recovery: DB loss → migrateFromMarkdown restores state, stale render det assert.equal(existsSync(dbPath), false, "DB file should be deleted"); // Clear path caches so gsdRoot re-probes after DB deletion - const { clearPathCache: clearPaths } = await import("../paths.ts"); + const { clearPathCache: clearPaths } = await import("../../paths.ts"); clearPaths(); invalidateAllCaches(); diff --git a/src/resources/extensions/gsd/tests/migrate-command.test.ts b/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/migrate-command.test.ts rename to src/resources/extensions/gsd/tests/integration/migrate-command.test.ts index 52473ed66..5ecc17b0e 100644 --- a/src/resources/extensions/gsd/tests/migrate-command.test.ts +++ b/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts @@ -13,8 +13,8 @@ import { transformToGSD, generatePreview, writeGSDDirectory, -} from '../migrate/index.ts'; -import { deriveState } from '../state.ts'; +} from '../../migrate/index.ts'; +import { deriveState } from '../../state.ts'; import { describe, test, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts b/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts rename to src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts index aaeed23d0..a283a6a8c 100644 --- a/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +++ b/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts @@ -24,7 +24,7 @@ import { isInAutoWorktree, getAutoWorktreeOriginalBase, mergeMilestoneToMain, -} from "../auto-worktree.ts"; +} from "../../auto-worktree.ts"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -124,7 +124,7 @@ test("worktree swap on milestone transition: merge old, create new", () => { test("auto/phases.ts milestone transition block contains worktree lifecycle", () => { const phasesSrc = readFileSync( - join(__dirname, "..", "auto", "phases.ts"), + join(__dirname, "../..", "auto", "phases.ts"), "utf-8", ); @@ -147,7 +147,7 @@ test("auto/phases.ts milestone transition block contains worktree lifecycle", () test("worktree-resolver mergeAndExit preserves branch when roadmap is missing (#1573)", () => { const resolverSrc = readFileSync( - join(__dirname, "..", "worktree-resolver.ts"), + join(__dirname, "../..", "worktree-resolver.ts"), "utf-8", ); diff --git a/src/resources/extensions/gsd/tests/parallel-merge.test.ts b/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/parallel-merge.test.ts rename to src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts index ec943e0a8..038f40f44 100644 --- a/src/resources/extensions/gsd/tests/parallel-merge.test.ts +++ b/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts @@ -32,12 +32,12 @@ import { mergeAllCompleted, formatMergeResults, type MergeResult, -} from "../parallel-merge.ts"; -import type { WorkerInfo } from "../parallel-orchestrator.ts"; +} from "../../parallel-merge.ts"; +import type { WorkerInfo } from "../../parallel-orchestrator.ts"; import { writeSessionStatus, readSessionStatus, -} from "../session-status-io.ts"; +} from "../../session-status-io.ts"; // ─── Helpers ────────────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts b/src/resources/extensions/gsd/tests/integration/parallel-workers-multi-milestone-e2e.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts rename to src/resources/extensions/gsd/tests/integration/parallel-workers-multi-milestone-e2e.test.ts index ae4eccf62..9dc67279e 100644 --- a/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +++ b/src/resources/extensions/gsd/tests/integration/parallel-workers-multi-milestone-e2e.test.ts @@ -26,12 +26,12 @@ import { getWorkerBatches, hasActiveWorkers, resetWorkerRegistry, -} from '../../subagent/worker-registry.ts'; +} from '../../../subagent/worker-registry.ts'; import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, -} from '../auto-budget.ts'; +} from '../../auto-budget.ts'; import { type UnitMetrics, type MetricsLedger, @@ -42,7 +42,7 @@ import { formatCostProjection, getAverageCostPerUnitType, predictRemainingCost, -} from '../metrics.ts'; +} from '../../metrics.ts'; // ─── Fixture helpers ────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/gsd/tests/paths.test.ts b/src/resources/extensions/gsd/tests/integration/paths.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/paths.test.ts rename to src/resources/extensions/gsd/tests/integration/paths.test.ts index 4ffdeaed9..64c186a15 100644 --- a/src/resources/extensions/gsd/tests/paths.test.ts +++ b/src/resources/extensions/gsd/tests/integration/paths.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { spawnSync } from "node:child_process"; -import { gsdRoot, _clearGsdRootCache } from "../paths.ts"; +import { gsdRoot, _clearGsdRootCache } from "../../paths.ts"; /** Create a tmp dir and resolve symlinks + 8.3 short names (macOS /var→/private/var, Windows RUNNER~1→runneradmin). */ function tmp(): string { const p = mkdtempSync(join(tmpdir(), "gsd-paths-test-")); diff --git a/src/resources/extensions/gsd/tests/plugin-importer-live.test.ts b/src/resources/extensions/gsd/tests/integration/plugin-importer-live.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/plugin-importer-live.test.ts rename to src/resources/extensions/gsd/tests/integration/plugin-importer-live.test.ts index 6971a6209..7288ac4a7 100644 --- a/src/resources/extensions/gsd/tests/plugin-importer-live.test.ts +++ b/src/resources/extensions/gsd/tests/integration/plugin-importer-live.test.ts @@ -11,8 +11,8 @@ import { describe, it, before, after } from 'node:test'; import assert from 'node:assert'; -import { PluginImporter, type DiscoveryResult, type ImportManifest } from '../plugin-importer.js'; -import { getMarketplaceFixtures } from './marketplace-test-fixtures.js'; +import { PluginImporter, type DiscoveryResult, type ImportManifest } from '../../plugin-importer.js'; +import { getMarketplaceFixtures } from '../marketplace-test-fixtures.ts'; // ============================================================================ // Live Test Configuration diff --git a/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts b/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts similarity index 96% rename from src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts rename to src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts index 75c1e871a..0f88cf69d 100644 --- a/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +++ b/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts @@ -15,9 +15,9 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { buildExistingMilestonesContext } from "../guided-flow-queue.ts"; -import type { GSDState, MilestoneRegistryEntry } from "../types.ts"; -import { createTestContext } from "./test-helpers.ts"; +import { buildExistingMilestonesContext } from "../../guided-flow-queue.ts"; +import type { GSDState, MilestoneRegistryEntry } from "../../types.ts"; +import { createTestContext } from "../test-helpers.ts"; const { assertTrue, assertEq, report } = createTestContext(); diff --git a/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts b/src/resources/extensions/gsd/tests/integration/queue-reorder-e2e.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts rename to src/resources/extensions/gsd/tests/integration/queue-reorder-e2e.test.ts index f74105f47..f479673a5 100644 --- a/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +++ b/src/resources/extensions/gsd/tests/integration/queue-reorder-e2e.test.ts @@ -17,10 +17,10 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync, existsSync import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import { deriveState, invalidateStateCache } from '../state.ts'; -import { findMilestoneIds } from '../guided-flow.ts'; -import { saveQueueOrder, loadQueueOrder } from '../queue-order.ts'; -import { parseContextDependsOn } from '../files.ts'; +import { deriveState, invalidateStateCache } from '../../state.ts'; +import { findMilestoneIds } from '../../guided-flow.ts'; +import { saveQueueOrder, loadQueueOrder } from '../../queue-order.ts'; +import { parseContextDependsOn } from '../../files.ts'; // ─── Fixture Helpers ─────────────────────────────────────────────────────── function createFixtureBase(): string { @@ -298,7 +298,7 @@ test('E2E: DB-backed path respects queue order (#2556)', async () => { // the dispatch guard (which respects queue order) blocked completion. const base = createFixtureBase(); try { - const { openDatabase, closeDatabase, insertMilestone, isDbAvailable } = await import('../gsd-db.ts'); + const { openDatabase, closeDatabase, insertMilestone, isDbAvailable } = await import('../../gsd-db.ts'); const dbPath = join(base, '.gsd', 'gsd.db'); // Create milestone directories (required for findMilestoneIds) diff --git a/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts b/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts rename to src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts index f707ff902..a4d77703b 100644 --- a/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +++ b/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts @@ -14,8 +14,8 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { execSync } from "node:child_process"; -import { captureIntegrationBranch, getCurrentBranch } from "../worktree.ts"; -import { readIntegrationBranch, QUICK_BRANCH_RE } from "../git-service.ts"; +import { captureIntegrationBranch, getCurrentBranch } from "../../worktree.ts"; +import { readIntegrationBranch, QUICK_BRANCH_RE } from "../../git-service.ts"; function run(command: string, cwd: string): string { return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim(); @@ -139,7 +139,7 @@ test('cleanupQuickBranch: merges back and cleans up (same session)', async () => // Import and call cleanupQuickBranch // Use dynamic import to get a fresh module scope — the in-memory state // won't be set, so it will fall through to disk recovery - const { cleanupQuickBranch } = await import("../quick.ts"); + const { cleanupQuickBranch } = await import("../../quick.ts"); const result = cleanupQuickBranch(); assert.ok(result, "cleanupQuickBranch returns true"); @@ -187,7 +187,7 @@ test('cleanupQuickBranch: recovers from disk state (cross-session)', async () => process.chdir(repo); - const { cleanupQuickBranch } = await import("../quick.ts"); + const { cleanupQuickBranch } = await import("../../quick.ts"); const result = cleanupQuickBranch(); assert.ok(result, "cross-session recovery returns true"); @@ -207,7 +207,7 @@ test('cleanupQuickBranch: no-op without pending state', async () => { const origCwd = process.cwd(); process.chdir(repo); - const { cleanupQuickBranch } = await import("../quick.ts"); + const { cleanupQuickBranch } = await import("../../quick.ts"); const result = cleanupQuickBranch(); assert.ok(!result, "returns false when no pending state"); diff --git a/src/resources/extensions/gsd/tests/run-uat.test.ts b/src/resources/extensions/gsd/tests/integration/run-uat.test.ts similarity index 98% rename from src/resources/extensions/gsd/tests/run-uat.test.ts rename to src/resources/extensions/gsd/tests/integration/run-uat.test.ts index 89c8307bd..cf9d44f74 100644 --- a/src/resources/extensions/gsd/tests/run-uat.test.ts +++ b/src/resources/extensions/gsd/tests/integration/run-uat.test.ts @@ -5,12 +5,12 @@ import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; -import { extractUatType } from '../files.ts'; -import { resolveSliceFile } from '../paths.ts'; -import { checkNeedsRunUat } from '../auto-prompts.ts'; +import { extractUatType } from '../../files.ts'; +import { resolveSliceFile } from '../../paths.ts'; +import { checkNeedsRunUat } from '../../auto-prompts.ts'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const worktreePromptsDir = join(__dirname, '..', 'prompts'); +const worktreePromptsDir = join(__dirname, '../..', 'prompts'); function loadPromptFromWorktree(name: string, vars: Record = {}): string { const path = join(worktreePromptsDir, `${name}.md`); diff --git a/src/resources/extensions/gsd/tests/token-savings.test.ts b/src/resources/extensions/gsd/tests/integration/token-savings.test.ts similarity index 99% rename from src/resources/extensions/gsd/tests/token-savings.test.ts rename to src/resources/extensions/gsd/tests/integration/token-savings.test.ts index a8bf5e669..708c1a787 100644 --- a/src/resources/extensions/gsd/tests/token-savings.test.ts +++ b/src/resources/extensions/gsd/tests/integration/token-savings.test.ts @@ -10,14 +10,14 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'nod import { join } from 'node:path'; import { tmpdir } from 'node:os'; -import { openDatabase, closeDatabase } from '../gsd-db.ts'; -import { migrateFromMarkdown } from '../md-importer.ts'; +import { openDatabase, closeDatabase } from '../../gsd-db.ts'; +import { migrateFromMarkdown } from '../../md-importer.ts'; import { queryDecisions, queryRequirements, formatDecisionsForPrompt, formatRequirementsForPrompt, -} from '../context-store.ts'; +} from '../../context-store.ts'; import { test } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/src/resources/extensions/gsd/tests/worktree-e2e.test.ts b/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts similarity index 97% rename from src/resources/extensions/gsd/tests/worktree-e2e.test.ts rename to src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts index 43bd272a1..fdca0640b 100644 --- a/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +++ b/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts @@ -18,10 +18,10 @@ import { execSync } from "node:child_process"; import { createAutoWorktree, mergeMilestoneToMain, -} from "../auto-worktree.ts"; -import { getSliceBranchName } from "../worktree.ts"; -import { abortAndReset } from "../git-self-heal.ts"; -import { runGSDDoctor } from "../doctor.ts"; +} from "../../auto-worktree.ts"; +import { getSliceBranchName } from "../../worktree.ts"; +import { abortAndReset } from "../../git-self-heal.ts"; +import { runGSDDoctor } from "../../doctor.ts"; import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; diff --git a/src/resources/extensions/gsd/visualizer-overlay.ts b/src/resources/extensions/gsd/visualizer-overlay.ts index 196b2f8ec..68c41d81a 100644 --- a/src/resources/extensions/gsd/visualizer-overlay.ts +++ b/src/resources/extensions/gsd/visualizer-overlay.ts @@ -14,7 +14,10 @@ import { renderHealthView, type ProgressFilter, } from "./visualizer-views.js"; +import { writeFileSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; import { writeExportFile } from "./export.js"; +import { gsdRoot } from "./paths.js"; import { stripAnsi } from "../shared/mod.js"; const TAB_COUNT = 10; @@ -350,9 +353,6 @@ export class GSDVisualizerOverlay { // Capture current active tab's rendered lines as snapshot const snapshotLines = this.renderTabContent(this.activeTab, 80); const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); - const { writeFileSync, mkdirSync } = require("node:fs"); - const { join } = require("node:path"); - const { gsdRoot } = require("./paths.js"); const exportDir = gsdRoot(this.basePath); mkdirSync(exportDir, { recursive: true }); const outPath = join(exportDir, `snapshot-${timestamp}.txt`); diff --git a/src/resources/extensions/shared/format-utils.ts b/src/resources/extensions/shared/format-utils.ts index 122d122bd..226cb4cac 100644 --- a/src/resources/extensions/shared/format-utils.ts +++ b/src/resources/extensions/shared/format-utils.ts @@ -11,7 +11,7 @@ /** Format a millisecond duration as a compact human-readable string. */ export function formatDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; + if (ms > 0 && ms < 1000) return `${ms}ms`; const s = Math.floor(ms / 1000); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); diff --git a/src/resources/extensions/subagent/worker-registry.ts b/src/resources/extensions/subagent/worker-registry.ts index ac52e9289..1f6cb90e2 100644 --- a/src/resources/extensions/subagent/worker-registry.ts +++ b/src/resources/extensions/subagent/worker-registry.ts @@ -54,9 +54,10 @@ export function updateWorker(id: string, status: "completed" | "failed"): void { if (entry) { entry.status = status; // Remove after a brief display window (5 seconds) + // unref() so the timer doesn't keep the process alive in test environments setTimeout(() => { activeWorkers.delete(id); - }, 5000); + }, 5000).unref(); } } diff --git a/src/tests/docker-template.test.ts b/src/tests/docker-template.test.ts index dc01b3551..5fe53b556 100644 --- a/src/tests/docker-template.test.ts +++ b/src/tests/docker-template.test.ts @@ -1,11 +1,9 @@ import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync, existsSync } from "node:fs"; -import { resolve, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; +import { resolve } from "node:path"; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const root = resolve(__dirname, "../.."); +const root = process.cwd(); function readFile(relativePath: string): string { const full = resolve(root, relativePath); diff --git a/src/tests/ensure-workspace-builds.test.ts b/src/tests/ensure-workspace-builds.test.ts new file mode 100644 index 000000000..f256c7afe --- /dev/null +++ b/src/tests/ensure-workspace-builds.test.ts @@ -0,0 +1,64 @@ +import { describe, it, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, writeFileSync, mkdirSync, rmSync, utimesSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +const { newestSrcMtime } = require("../../scripts/ensure-workspace-builds.cjs"); + +describe("newestSrcMtime", () => { + let tmp: string; + + beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), "gsd-mtime-test-")); }); + afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); + + it("returns 0 for a non-existent directory", () => { + assert.equal(newestSrcMtime(join(tmp, "does-not-exist")), 0); + }); + + it("returns 0 when directory has no .ts files", () => { + writeFileSync(join(tmp, "index.js"), ""); + writeFileSync(join(tmp, "config.json"), ""); + assert.equal(newestSrcMtime(tmp), 0); + }); + + it("returns the mtime of a single .ts file", () => { + const file = join(tmp, "index.ts"); + writeFileSync(file, ""); + const mtime = new Date("2024-01-15T10:00:00Z"); + utimesSync(file, mtime, mtime); + assert.equal(newestSrcMtime(tmp), mtime.getTime()); + }); + + it("returns the max mtime across multiple .ts files", () => { + const older = join(tmp, "a.ts"); + const newer = join(tmp, "b.ts"); + writeFileSync(older, ""); + writeFileSync(newer, ""); + utimesSync(older, new Date("2024-01-01T00:00:00Z"), new Date("2024-01-01T00:00:00Z")); + utimesSync(newer, new Date("2024-06-01T00:00:00Z"), new Date("2024-06-01T00:00:00Z")); + assert.equal(newestSrcMtime(tmp), new Date("2024-06-01T00:00:00Z").getTime()); + }); + + it("recurses into subdirectories", () => { + const subdir = join(tmp, "nested", "deep"); + mkdirSync(subdir, { recursive: true }); + const file = join(subdir, "util.ts"); + writeFileSync(file, ""); + const mtime = new Date("2024-03-01T00:00:00Z"); + utimesSync(file, mtime, mtime); + assert.equal(newestSrcMtime(tmp), mtime.getTime()); + }); + + it("skips node_modules entirely", () => { + const nm = join(tmp, "node_modules", "some-pkg"); + mkdirSync(nm, { recursive: true }); + const nmFile = join(nm, "index.ts"); + writeFileSync(nmFile, ""); + const future = new Date("2099-01-01T00:00:00Z"); + utimesSync(nmFile, future, future); + assert.equal(newestSrcMtime(tmp), 0); + }); +}); diff --git a/src/tests/ci_monitor.test.ts b/src/tests/integration/ci_monitor.test.ts similarity index 98% rename from src/tests/ci_monitor.test.ts rename to src/tests/integration/ci_monitor.test.ts index 745df409f..90449ddbf 100644 --- a/src/tests/ci_monitor.test.ts +++ b/src/tests/integration/ci_monitor.test.ts @@ -13,7 +13,7 @@ import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const ROOT = join(__dirname, '..', '..'); +const ROOT = join(__dirname, '..', '..', '..'); const SCRIPT_PATH = join(ROOT, 'scripts', 'ci_monitor.cjs'); let passed = 0; diff --git a/src/tests/web-auth-token.test.ts b/src/tests/integration/web-auth-token.test.ts similarity index 100% rename from src/tests/web-auth-token.test.ts rename to src/tests/integration/web-auth-token.test.ts diff --git a/src/tests/web-boot-node24.test.ts b/src/tests/integration/web-boot-node24.test.ts similarity index 98% rename from src/tests/web-boot-node24.test.ts rename to src/tests/integration/web-boot-node24.test.ts index dd587aefa..8dda73414 100644 --- a/src/tests/web-boot-node24.test.ts +++ b/src/tests/integration/web-boot-node24.test.ts @@ -1,7 +1,7 @@ import test from "node:test" import assert from "node:assert/strict" -import { resolveTypeStrippingFlag } from "../web/ts-subprocess-flags.ts" +import { resolveTypeStrippingFlag } from "../../web/ts-subprocess-flags.ts" // --------------------------------------------------------------------------- // Bug 1 — resolveTypeStrippingFlag selects the correct flag diff --git a/src/tests/web-bridge-contract.test.ts b/src/tests/integration/web-bridge-contract.test.ts similarity index 98% rename from src/tests/web-bridge-contract.test.ts rename to src/tests/integration/web-bridge-contract.test.ts index 1e8218526..3de7fd6f6 100644 --- a/src/tests/web-bridge-contract.test.ts +++ b/src/tests/integration/web-bridge-contract.test.ts @@ -8,12 +8,12 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); -const onboarding = await import("../web/onboarding-service.ts"); +const bridge = await import("../../web/bridge-service.ts"); +const onboarding = await import("../../web/onboarding-service.ts"); const { AuthStorage } = await import("@gsd/pi-coding-agent"); -const bootRoute = await import("../../web/app/api/boot/route.ts"); -const commandRoute = await import("../../web/app/api/session/command/route.ts"); -const eventsRoute = await import("../../web/app/api/session/events/route.ts"); +const bootRoute = await import("../../../web/app/api/boot/route.ts"); +const commandRoute = await import("../../../web/app/api/session/command/route.ts"); +const eventsRoute = await import("../../../web/app/api/session/events/route.ts"); class FakeRpcChild extends EventEmitter { stdin = new PassThrough(); diff --git a/src/tests/web-bridge-package-root.test.ts b/src/tests/integration/web-bridge-package-root.test.ts similarity index 97% rename from src/tests/web-bridge-package-root.test.ts rename to src/tests/integration/web-bridge-package-root.test.ts index f919ce873..8ccab075c 100644 --- a/src/tests/web-bridge-package-root.test.ts +++ b/src/tests/integration/web-bridge-package-root.test.ts @@ -14,7 +14,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { resolve } from "node:path"; -const bridge = await import("../web/bridge-service.ts"); +const bridge = await import("../../web/bridge-service.ts"); test("resolveBridgeRuntimeConfig uses GSD_WEB_PACKAGE_ROOT when set", () => { const env = { diff --git a/src/tests/web-bridge-terminal-contract.test.ts b/src/tests/integration/web-bridge-terminal-contract.test.ts similarity index 97% rename from src/tests/web-bridge-terminal-contract.test.ts rename to src/tests/integration/web-bridge-terminal-contract.test.ts index af604cace..3104c5329 100644 --- a/src/tests/web-bridge-terminal-contract.test.ts +++ b/src/tests/integration/web-bridge-terminal-contract.test.ts @@ -8,10 +8,10 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); -const streamRoute = await import("../../web/app/api/bridge-terminal/stream/route.ts"); -const inputRoute = await import("../../web/app/api/bridge-terminal/input/route.ts"); -const resizeRoute = await import("../../web/app/api/bridge-terminal/resize/route.ts"); +const bridge = await import("../../web/bridge-service.ts"); +const streamRoute = await import("../../../web/app/api/bridge-terminal/stream/route.ts"); +const inputRoute = await import("../../../web/app/api/bridge-terminal/input/route.ts"); +const resizeRoute = await import("../../../web/app/api/bridge-terminal/resize/route.ts"); class FakeRpcChild extends EventEmitter { stdin = new PassThrough(); diff --git a/src/tests/web-cli-entry.test.ts b/src/tests/integration/web-cli-entry.test.ts similarity index 97% rename from src/tests/web-cli-entry.test.ts rename to src/tests/integration/web-cli-entry.test.ts index 022431168..6c69928a0 100644 --- a/src/tests/web-cli-entry.test.ts +++ b/src/tests/integration/web-cli-entry.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { pathToFileURL } from "node:url"; -const { resolveGsdCliEntry } = await import("../web/cli-entry.ts"); +const { resolveGsdCliEntry } = await import("../../web/cli-entry.ts"); function makeFixture(paths: string[]): string { const root = mkdtempSync(join(tmpdir(), "gsd-cli-entry-")); diff --git a/src/tests/web-command-parity-contract.test.ts b/src/tests/integration/web-command-parity-contract.test.ts similarity index 98% rename from src/tests/web-command-parity-contract.test.ts rename to src/tests/integration/web-command-parity-contract.test.ts index 2858f131b..96b6e2640 100644 --- a/src/tests/web-command-parity-contract.test.ts +++ b/src/tests/integration/web-command-parity-contract.test.ts @@ -3,19 +3,19 @@ import assert from "node:assert/strict" import { readFileSync } from "node:fs" import { resolve } from "node:path" -const { BUILTIN_SLASH_COMMANDS } = await import("../../packages/pi-coding-agent/src/core/slash-commands.ts") +const { BUILTIN_SLASH_COMMANDS } = await import("../../../packages/pi-coding-agent/src/core/slash-commands.ts") const { dispatchBrowserSlashCommand, getBrowserSlashCommandTerminalNotice, -} = await import("../../web/lib/browser-slash-command-dispatch.ts") +} = await import("../../../web/lib/browser-slash-command-dispatch.ts") const { applyCommandSurfaceActionResult, createInitialCommandSurfaceState, openCommandSurfaceState, setCommandSurfacePending, surfaceOutcomeToOpenRequest, -} = await import("../../web/lib/command-surface-contract.ts") -const gsdExtension = await import("../resources/extensions/gsd/index.ts") +} = await import("../../../web/lib/command-surface-contract.ts") +const gsdExtension = await import("../../resources/extensions/gsd/index.ts") const EXPECTED_BUILTIN_OUTCOMES = new Map([ ["settings", "surface"], @@ -680,7 +680,7 @@ test("surface action state keeps compaction summaries inspectable", () => { }) test("command-surface session affordances use the shared store action path", () => { - const commandSurfacePath = resolve(import.meta.dirname, "../../web/components/gsd/command-surface.tsx") + const commandSurfacePath = resolve(import.meta.dirname, "../../../web/components/gsd/command-surface.tsx") const commandSurfaceSource = readFileSync(commandSurfacePath, "utf-8") assert.match( diff --git a/src/tests/web-continuity-contract.test.ts b/src/tests/integration/web-continuity-contract.test.ts similarity index 100% rename from src/tests/web-continuity-contract.test.ts rename to src/tests/integration/web-continuity-contract.test.ts diff --git a/src/tests/web-dashboard-rtk-contract.test.ts b/src/tests/integration/web-dashboard-rtk-contract.test.ts similarity index 100% rename from src/tests/web-dashboard-rtk-contract.test.ts rename to src/tests/integration/web-dashboard-rtk-contract.test.ts diff --git a/src/tests/web-diagnostics-contract.test.ts b/src/tests/integration/web-diagnostics-contract.test.ts similarity index 98% rename from src/tests/web-diagnostics-contract.test.ts rename to src/tests/integration/web-diagnostics-contract.test.ts index ede1e68dd..eb698f3ca 100644 --- a/src/tests/web-diagnostics-contract.test.ts +++ b/src/tests/integration/web-diagnostics-contract.test.ts @@ -25,18 +25,18 @@ import type { SkillHealthReport, SkillHealthEntry, SkillHealSuggestion, -} from "../../web/lib/diagnostics-types.ts" +} from "../../../web/lib/diagnostics-types.ts" const { createInitialCommandSurfaceState, commandSurfaceSectionForRequest, -} = await import("../../web/lib/command-surface-contract.ts") +} = await import("../../../web/lib/command-surface-contract.ts") const { dispatchBrowserSlashCommand, -} = await import("../../web/lib/browser-slash-command-dispatch.ts") +} = await import("../../../web/lib/browser-slash-command-dispatch.ts") -const { GSDWorkspaceStore } = await import("../../web/lib/gsd-workspace-store.tsx") +const { GSDWorkspaceStore } = await import("../../../web/lib/gsd-workspace-store.tsx") // ─── Block 1: Type exports (R103, R104, R105) ─────────────────────────────── diff --git a/src/tests/web-live-interaction-contract.test.ts b/src/tests/integration/web-live-interaction-contract.test.ts similarity index 99% rename from src/tests/web-live-interaction-contract.test.ts rename to src/tests/integration/web-live-interaction-contract.test.ts index 4418abb63..5e288b69f 100644 --- a/src/tests/web-live-interaction-contract.test.ts +++ b/src/tests/integration/web-live-interaction-contract.test.ts @@ -8,11 +8,11 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); -const onboarding = await import("../web/onboarding-service.ts"); +const bridge = await import("../../web/bridge-service.ts"); +const onboarding = await import("../../web/onboarding-service.ts"); const { AuthStorage } = await import("@gsd/pi-coding-agent"); -const commandRoute = await import("../../web/app/api/session/command/route.ts"); -const eventsRoute = await import("../../web/app/api/session/events/route.ts"); +const commandRoute = await import("../../../web/app/api/session/command/route.ts"); +const eventsRoute = await import("../../../web/app/api/session/events/route.ts"); // --------------------------------------------------------------------------- // Test infrastructure (reused from web-bridge-contract.test.ts) diff --git a/src/tests/web-live-state-contract.test.ts b/src/tests/integration/web-live-state-contract.test.ts similarity index 97% rename from src/tests/web-live-state-contract.test.ts rename to src/tests/integration/web-live-state-contract.test.ts index c2b1f7ecc..2af24bcc6 100644 --- a/src/tests/web-live-state-contract.test.ts +++ b/src/tests/integration/web-live-state-contract.test.ts @@ -8,13 +8,13 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); -const onboarding = await import("../web/onboarding-service.ts"); +const bridge = await import("../../web/bridge-service.ts"); +const onboarding = await import("../../web/onboarding-service.ts"); const { AuthStorage } = await import("@gsd/pi-coding-agent"); -const commandRoute = await import("../../web/app/api/session/command/route.ts"); -const manageRoute = await import("../../web/app/api/session/manage/route.ts"); -const eventsRoute = await import("../../web/app/api/session/events/route.ts"); -const liveStateRoute = await import("../../web/app/api/live-state/route.ts"); +const commandRoute = await import("../../../web/app/api/session/command/route.ts"); +const manageRoute = await import("../../../web/app/api/session/manage/route.ts"); +const eventsRoute = await import("../../../web/app/api/session/events/route.ts"); +const liveStateRoute = await import("../../../web/app/api/live-state/route.ts"); class FakeRpcChild extends EventEmitter { stdin = new PassThrough(); diff --git a/src/tests/web-mode-cli.test.ts b/src/tests/integration/web-mode-cli.test.ts similarity index 99% rename from src/tests/web-mode-cli.test.ts rename to src/tests/integration/web-mode-cli.test.ts index c1e0ffe6f..249e17568 100644 --- a/src/tests/web-mode-cli.test.ts +++ b/src/tests/integration/web-mode-cli.test.ts @@ -6,8 +6,8 @@ import { tmpdir } from 'node:os' const projectRoot = process.cwd() -const cliWeb = await import('../cli-web-branch.ts') -const webMode = await import('../web-mode.ts') +const cliWeb = await import('../../cli-web-branch.ts') +const webMode = await import('../../web-mode.ts') test('parseCliArgs recognizes --web explicitly', () => { const flags = cliWeb.parseCliArgs(['node', 'dist/loader.js', '--web']) diff --git a/src/tests/web-mode-network-flags.test.ts b/src/tests/integration/web-mode-network-flags.test.ts similarity index 98% rename from src/tests/web-mode-network-flags.test.ts rename to src/tests/integration/web-mode-network-flags.test.ts index 29a57f542..7fb82fd56 100644 --- a/src/tests/web-mode-network-flags.test.ts +++ b/src/tests/integration/web-mode-network-flags.test.ts @@ -4,8 +4,8 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { tmpdir } from 'node:os' -const cliWeb = await import('../cli-web-branch.ts') -const webMode = await import('../web-mode.ts') +const cliWeb = await import('../../cli-web-branch.ts') +const webMode = await import('../../web-mode.ts') // ─── CLI flag parsing ──────────────────────────────────────────────── diff --git a/src/tests/web-multi-project-contract.test.ts b/src/tests/integration/web-multi-project-contract.test.ts similarity index 99% rename from src/tests/web-multi-project-contract.test.ts rename to src/tests/integration/web-multi-project-contract.test.ts index e3dc12660..4fa31c0ea 100644 --- a/src/tests/web-multi-project-contract.test.ts +++ b/src/tests/integration/web-multi-project-contract.test.ts @@ -8,7 +8,7 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); +const bridge = await import("../../web/bridge-service.ts"); // --------------------------------------------------------------------------- // Helpers (same shape as web-bridge-contract.test.ts) diff --git a/src/tests/web-onboarding-contract.test.ts b/src/tests/integration/web-onboarding-contract.test.ts similarity index 98% rename from src/tests/web-onboarding-contract.test.ts rename to src/tests/integration/web-onboarding-contract.test.ts index aedb3e1ce..3ed833368 100644 --- a/src/tests/web-onboarding-contract.test.ts +++ b/src/tests/integration/web-onboarding-contract.test.ts @@ -8,11 +8,11 @@ import { PassThrough } from "node:stream"; import { StringDecoder } from "node:string_decoder"; const repoRoot = process.cwd(); -const bridge = await import("../web/bridge-service.ts"); -const onboarding = await import("../web/onboarding-service.ts"); -const bootRoute = await import("../../web/app/api/boot/route.ts"); -const onboardingRoute = await import("../../web/app/api/onboarding/route.ts"); -const commandRoute = await import("../../web/app/api/session/command/route.ts"); +const bridge = await import("../../web/bridge-service.ts"); +const onboarding = await import("../../web/onboarding-service.ts"); +const bootRoute = await import("../../../web/app/api/boot/route.ts"); +const onboardingRoute = await import("../../../web/app/api/onboarding/route.ts"); +const commandRoute = await import("../../../web/app/api/session/command/route.ts"); const { AuthStorage } = await import("@gsd/pi-coding-agent"); const ONBOARDING_ENV_KEYS = [ diff --git a/src/tests/web-onboarding-presentation.test.ts b/src/tests/integration/web-onboarding-presentation.test.ts similarity index 97% rename from src/tests/web-onboarding-presentation.test.ts rename to src/tests/integration/web-onboarding-presentation.test.ts index f74a0ff59..8cb297c2b 100644 --- a/src/tests/web-onboarding-presentation.test.ts +++ b/src/tests/integration/web-onboarding-presentation.test.ts @@ -1,7 +1,7 @@ import test from "node:test" import assert from "node:assert/strict" -const { getOnboardingPresentation } = await import("../../web/lib/gsd-workspace-store.tsx") +const { getOnboardingPresentation } = await import("../../../web/lib/gsd-workspace-store.tsx") function makeOnboardingState(overrides: Record = {}) { return { diff --git a/src/tests/web-project-discovery-contract.test.ts b/src/tests/integration/web-project-discovery-contract.test.ts similarity index 98% rename from src/tests/web-project-discovery-contract.test.ts rename to src/tests/integration/web-project-discovery-contract.test.ts index cd2c52fdd..51ca44f93 100644 --- a/src/tests/web-project-discovery-contract.test.ts +++ b/src/tests/integration/web-project-discovery-contract.test.ts @@ -4,8 +4,9 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { basename, join } from "node:path"; -import { discoverProjects } from "../web/project-discovery-service.ts"; -import { detectMonorepo } from "../web/bridge-service.ts"; + +import { discoverProjects } from "../../web/project-discovery-service.ts"; +import { detectMonorepo } from "../../web/bridge-service.ts"; // --------------------------------------------------------------------------- // Fixture setup — standard multi-project root diff --git a/src/tests/web-project-url.test.ts b/src/tests/integration/web-project-url.test.ts similarity index 97% rename from src/tests/web-project-url.test.ts rename to src/tests/integration/web-project-url.test.ts index 350b94354..bbe9f918c 100644 --- a/src/tests/web-project-url.test.ts +++ b/src/tests/integration/web-project-url.test.ts @@ -1,7 +1,7 @@ import test from "node:test" import assert from "node:assert/strict" -import { buildProjectAbsoluteUrl, buildProjectPath } from "../../web/lib/project-url.ts" +import { buildProjectAbsoluteUrl, buildProjectPath } from "../../../web/lib/project-url.ts" test("buildProjectPath leaves non-project routes unchanged", () => { assert.equal(buildProjectPath("/api/terminal/input"), "/api/terminal/input") diff --git a/src/tests/web-recovery-diagnostics-contract.test.ts b/src/tests/integration/web-recovery-diagnostics-contract.test.ts similarity index 98% rename from src/tests/web-recovery-diagnostics-contract.test.ts rename to src/tests/integration/web-recovery-diagnostics-contract.test.ts index f3b2de070..110d96e8c 100644 --- a/src/tests/web-recovery-diagnostics-contract.test.ts +++ b/src/tests/integration/web-recovery-diagnostics-contract.test.ts @@ -8,8 +8,8 @@ import { PassThrough } from "node:stream" import { StringDecoder } from "node:string_decoder" const repoRoot = process.cwd() -const bridge = await import("../web/bridge-service.ts") -const recoveryRoute = await import("../../web/app/api/recovery/route.ts") +const bridge = await import("../../web/bridge-service.ts") +const recoveryRoute = await import("../../../web/app/api/recovery/route.ts") class FakeRpcChild extends EventEmitter { stdin = new PassThrough() diff --git a/src/tests/web-responsive.test.ts b/src/tests/integration/web-responsive.test.ts similarity index 99% rename from src/tests/web-responsive.test.ts rename to src/tests/integration/web-responsive.test.ts index 847a7a5e2..f159103e7 100644 --- a/src/tests/web-responsive.test.ts +++ b/src/tests/integration/web-responsive.test.ts @@ -10,7 +10,7 @@ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' -const WEB_ROOT = resolve(import.meta.dirname, '../../web') +const WEB_ROOT = resolve(import.meta.dirname, '../../../web') function readComponent(relativePath: string): string { return readFileSync(resolve(WEB_ROOT, relativePath), 'utf-8') diff --git a/src/tests/web-session-parity-contract.test.ts b/src/tests/integration/web-session-parity-contract.test.ts similarity index 97% rename from src/tests/web-session-parity-contract.test.ts rename to src/tests/integration/web-session-parity-contract.test.ts index 5b5fa628d..9e8b1afcf 100644 --- a/src/tests/web-session-parity-contract.test.ts +++ b/src/tests/integration/web-session-parity-contract.test.ts @@ -9,11 +9,11 @@ import { PassThrough } from "node:stream" import { StringDecoder } from "node:string_decoder" const repoRoot = process.cwd() -const bridge = await import("../web/bridge-service.ts") -const onboarding = await import("../web/onboarding-service.ts") -const browserRoute = await import("../../web/app/api/session/browser/route.ts") -const manageRoute = await import("../../web/app/api/session/manage/route.ts") -const gitRoute = await import("../../web/app/api/git/route.ts") +const bridge = await import("../../web/bridge-service.ts") +const onboarding = await import("../../web/onboarding-service.ts") +const browserRoute = await import("../../../web/app/api/session/browser/route.ts") +const manageRoute = await import("../../../web/app/api/session/manage/route.ts") +const gitRoute = await import("../../../web/app/api/git/route.ts") const { AuthStorage } = await import("@gsd/pi-coding-agent") class FakeRpcChild extends EventEmitter { @@ -635,12 +635,12 @@ test("/api/git exposes an explicit not-a-repo state instead of failing silently" }) test("browser session, settings, and git surfaces keep inspectable browse/manage/state markers on the shared surface", () => { - const rpcTypesSource = readFileSync(resolve(import.meta.dirname, "../../packages/pi-coding-agent/src/modes/rpc/rpc-types.ts"), "utf8") - const contractSource = readFileSync(resolve(import.meta.dirname, "../../web/lib/command-surface-contract.ts"), "utf8") - const storeSource = readFileSync(resolve(import.meta.dirname, "../../web/lib/gsd-workspace-store.tsx"), "utf8") - const surfaceSource = readFileSync(resolve(import.meta.dirname, "../../web/components/gsd/command-surface.tsx"), "utf8") - const sidebarSource = readFileSync(resolve(import.meta.dirname, "../../web/components/gsd/sidebar.tsx"), "utf8") - const gitRouteSource = readFileSync(resolve(import.meta.dirname, "../../web/app/api/git/route.ts"), "utf8") + const rpcTypesSource = readFileSync(resolve(import.meta.dirname, "../../../packages/pi-coding-agent/src/modes/rpc/rpc-types.ts"), "utf8") + const contractSource = readFileSync(resolve(import.meta.dirname, "../../../web/lib/command-surface-contract.ts"), "utf8") + const storeSource = readFileSync(resolve(import.meta.dirname, "../../../web/lib/gsd-workspace-store.tsx"), "utf8") + const surfaceSource = readFileSync(resolve(import.meta.dirname, "../../../web/components/gsd/command-surface.tsx"), "utf8") + const sidebarSource = readFileSync(resolve(import.meta.dirname, "../../../web/components/gsd/sidebar.tsx"), "utf8") + const gitRouteSource = readFileSync(resolve(import.meta.dirname, "../../../web/app/api/git/route.ts"), "utf8") assert.match(rpcTypesSource, /autoRetryEnabled: boolean/, "rpc-types.ts must expose retry-enabled state in get_state") assert.match(rpcTypesSource, /retryInProgress: boolean/, "rpc-types.ts must expose retry-in-progress state in get_state") diff --git a/src/tests/web-state-surfaces-contract.test.ts b/src/tests/integration/web-state-surfaces-contract.test.ts similarity index 90% rename from src/tests/web-state-surfaces-contract.test.ts rename to src/tests/integration/web-state-surfaces-contract.test.ts index d8fc6b556..58d9b89e9 100644 --- a/src/tests/web-state-surfaces-contract.test.ts +++ b/src/tests/integration/web-state-surfaces-contract.test.ts @@ -6,12 +6,12 @@ import { join, resolve } from "node:path"; // ─── Imports ────────────────────────────────────────────────────────── const workspaceIndex = await import( - "../resources/extensions/gsd/workspace-index.ts" + "../../resources/extensions/gsd/workspace-index.ts" ); -const filesRoute = await import("../../web/app/api/files/route.ts"); +const filesRoute = await import("../../../web/app/api/files/route.ts"); // Re-import status helpers from the web-side module -const workspaceStatus = await import("../../web/lib/workspace-status.ts"); +const workspaceStatus = await import("../../../web/lib/workspace-status.ts"); // ─── Helpers ────────────────────────────────────────────────────────── function makeGsdFixture(): { root: string; gsdDir: string; cleanup: () => void } { @@ -384,11 +384,11 @@ const MOCK_DATA_PATTERNS = [ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*Z["'](?:.*,\s*$)/m, // hardcoded ISO timestamps in array literals ]; -const webRoot = resolve(import.meta.dirname, "../../web"); +const webRoot = resolve(import.meta.dirname, "../../../web"); test("view components contain no static mock data arrays", () => { for (const filePath of VIEW_FILES) { - const fullPath = resolve(import.meta.dirname, "../..", filePath); + const fullPath = resolve(import.meta.dirname, "../../..", filePath); const source = readFileSync(fullPath, "utf-8"); for (const pattern of MOCK_DATA_PATTERNS) { const match = source.match(pattern); @@ -416,7 +416,7 @@ test("view components read from real data sources (store or API)", () => { ]; for (const filePath of STORE_VIEWS) { - const fullPath = resolve(import.meta.dirname, "../..", filePath); + const fullPath = resolve(import.meta.dirname, "../../..", filePath); const source = readFileSync(fullPath, "utf-8"); assert.ok( source.includes("gsd-workspace-store"), @@ -425,7 +425,7 @@ test("view components read from real data sources (store or API)", () => { } for (const { path: filePath, apiPattern } of API_VIEWS) { - const fullPath = resolve(import.meta.dirname, "../..", filePath); + const fullPath = resolve(import.meta.dirname, "../../..", filePath); const source = readFileSync(fullPath, "utf-8"); assert.ok( source.includes(apiPattern), @@ -438,7 +438,7 @@ test("view components read from real data sources (store or API)", () => { // from the dashboard. Live signals are visible in the terminal/power mode instead. test("status bar consumes statusTexts from store", () => { - const statusBarPath = resolve(import.meta.dirname, "../../web/components/gsd/status-bar.tsx"); + const statusBarPath = resolve(import.meta.dirname, "../../../web/components/gsd/status-bar.tsx"); const source = readFileSync(statusBarPath, "utf-8"); assert.ok( @@ -452,10 +452,10 @@ test("status bar consumes statusTexts from store", () => { }); test("browser shell renders title overrides, widgets, and editor prefills from store-backed state", () => { - const storePath = resolve(import.meta.dirname, "../../web/lib/gsd-workspace-store.tsx"); - const appShellPath = resolve(import.meta.dirname, "../../web/components/gsd/app-shell.tsx"); - const statusBarPath = resolve(import.meta.dirname, "../../web/components/gsd/status-bar.tsx"); - const terminalPath = resolve(import.meta.dirname, "../../web/components/gsd/terminal.tsx"); + const storePath = resolve(import.meta.dirname, "../../../web/lib/gsd-workspace-store.tsx"); + const appShellPath = resolve(import.meta.dirname, "../../../web/components/gsd/app-shell.tsx"); + const statusBarPath = resolve(import.meta.dirname, "../../../web/components/gsd/status-bar.tsx"); + const terminalPath = resolve(import.meta.dirname, "../../../web/components/gsd/terminal.tsx"); const storeSource = readFileSync(storePath, "utf-8"); const appShellSource = readFileSync(appShellPath, "utf-8"); @@ -478,7 +478,7 @@ test("browser shell renders title overrides, widgets, and editor prefills from s }); test("terminal consumes activeToolExecution from store", () => { - const terminalPath = resolve(import.meta.dirname, "../../web/components/gsd/terminal.tsx"); + const terminalPath = resolve(import.meta.dirname, "../../../web/components/gsd/terminal.tsx"); const source = readFileSync(terminalPath, "utf-8"); assert.ok( @@ -488,12 +488,12 @@ test("terminal consumes activeToolExecution from store", () => { }); test("live browser panels consume live selectors and expose inspectable freshness markers", () => { - const contractPath = resolve(import.meta.dirname, "../../web/lib/command-surface-contract.ts") - const storePath = resolve(import.meta.dirname, "../../web/lib/gsd-workspace-store.tsx") - const dashboardPath = resolve(import.meta.dirname, "../../web/components/gsd/dashboard.tsx") - const sidebarPath = resolve(import.meta.dirname, "../../web/components/gsd/sidebar.tsx") - const roadmapPath = resolve(import.meta.dirname, "../../web/components/gsd/roadmap.tsx") - const statusBarPath = resolve(import.meta.dirname, "../../web/components/gsd/status-bar.tsx") + const contractPath = resolve(import.meta.dirname, "../../../web/lib/command-surface-contract.ts") + const storePath = resolve(import.meta.dirname, "../../../web/lib/gsd-workspace-store.tsx") + const dashboardPath = resolve(import.meta.dirname, "../../../web/components/gsd/dashboard.tsx") + const sidebarPath = resolve(import.meta.dirname, "../../../web/components/gsd/sidebar.tsx") + const roadmapPath = resolve(import.meta.dirname, "../../../web/components/gsd/roadmap.tsx") + const statusBarPath = resolve(import.meta.dirname, "../../../web/components/gsd/status-bar.tsx") const contractSource = readFileSync(contractPath, "utf-8") const storeSource = readFileSync(storePath, "utf-8") @@ -528,9 +528,9 @@ test("live browser panels consume live selectors and expose inspectable freshnes }) test("workflow action surfaces route new-milestone CTAs through the shared command path", () => { - const dashboardPath = resolve(import.meta.dirname, "../../web/components/gsd/dashboard.tsx") - const sidebarPath = resolve(import.meta.dirname, "../../web/components/gsd/sidebar.tsx") - const chatPath = resolve(import.meta.dirname, "../../web/components/gsd/chat-mode.tsx") + const dashboardPath = resolve(import.meta.dirname, "../../../web/components/gsd/dashboard.tsx") + const sidebarPath = resolve(import.meta.dirname, "../../../web/components/gsd/sidebar.tsx") + const chatPath = resolve(import.meta.dirname, "../../../web/components/gsd/chat-mode.tsx") const dashboardSource = readFileSync(dashboardPath, "utf-8") const sidebarSource = readFileSync(sidebarPath, "utf-8") @@ -549,10 +549,10 @@ test("workflow action surfaces route new-milestone CTAs through the shared comma }) test("sidebar Git affordance opens a real git-summary surface with visible repo/not-repo/error states", () => { - const contractPath = resolve(import.meta.dirname, "../../web/lib/command-surface-contract.ts"); - const storePath = resolve(import.meta.dirname, "../../web/lib/gsd-workspace-store.tsx"); - const surfacePath = resolve(import.meta.dirname, "../../web/components/gsd/command-surface.tsx"); - const sidebarPath = resolve(import.meta.dirname, "../../web/components/gsd/sidebar.tsx"); + const contractPath = resolve(import.meta.dirname, "../../../web/lib/command-surface-contract.ts"); + const storePath = resolve(import.meta.dirname, "../../../web/lib/gsd-workspace-store.tsx"); + const surfacePath = resolve(import.meta.dirname, "../../../web/components/gsd/command-surface.tsx"); + const sidebarPath = resolve(import.meta.dirname, "../../../web/components/gsd/sidebar.tsx"); const contractSource = readFileSync(contractPath, "utf-8"); const storeSource = readFileSync(storePath, "utf-8"); @@ -573,11 +573,11 @@ test("sidebar Git affordance opens a real git-summary surface with visible repo/ }); test("recovery diagnostics surface stays on a dedicated route with explicit stale and action state", () => { - const contractPath = resolve(import.meta.dirname, "../../web/lib/command-surface-contract.ts"); - const storePath = resolve(import.meta.dirname, "../../web/lib/gsd-workspace-store.tsx"); - const surfacePath = resolve(import.meta.dirname, "../../web/components/gsd/command-surface.tsx"); - const dashboardPath = resolve(import.meta.dirname, "../../web/components/gsd/dashboard.tsx"); - const sidebarPath = resolve(import.meta.dirname, "../../web/components/gsd/sidebar.tsx"); + const contractPath = resolve(import.meta.dirname, "../../../web/lib/command-surface-contract.ts"); + const storePath = resolve(import.meta.dirname, "../../../web/lib/gsd-workspace-store.tsx"); + const surfacePath = resolve(import.meta.dirname, "../../../web/components/gsd/command-surface.tsx"); + const dashboardPath = resolve(import.meta.dirname, "../../../web/components/gsd/dashboard.tsx"); + const sidebarPath = resolve(import.meta.dirname, "../../../web/components/gsd/sidebar.tsx"); const contractSource = readFileSync(contractPath, "utf-8"); const storeSource = readFileSync(storePath, "utf-8"); diff --git a/src/tests/web-subprocess-module-resolution.test.ts b/src/tests/integration/web-subprocess-module-resolution.test.ts similarity index 99% rename from src/tests/web-subprocess-module-resolution.test.ts rename to src/tests/integration/web-subprocess-module-resolution.test.ts index 3c10d8057..9010eb698 100644 --- a/src/tests/web-subprocess-module-resolution.test.ts +++ b/src/tests/integration/web-subprocess-module-resolution.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path" import { isUnderNodeModules, resolveSubprocessModule, -} from "../web/ts-subprocess-flags.ts" +} from "../../web/ts-subprocess-flags.ts" // --------------------------------------------------------------------------- // isUnderNodeModules — exported utility diff --git a/src/tests/web-switch-project.test.ts b/src/tests/integration/web-switch-project.test.ts similarity index 100% rename from src/tests/web-switch-project.test.ts rename to src/tests/integration/web-switch-project.test.ts diff --git a/src/tests/web-terminal-allowlist.test.ts b/src/tests/integration/web-terminal-allowlist.test.ts similarity index 84% rename from src/tests/web-terminal-allowlist.test.ts rename to src/tests/integration/web-terminal-allowlist.test.ts index c1d36341c..eca747b3f 100644 --- a/src/tests/web-terminal-allowlist.test.ts +++ b/src/tests/integration/web-terminal-allowlist.test.ts @@ -1,8 +1,8 @@ import test from "node:test"; import assert from "node:assert/strict"; -const sessionsRoute = await import("../../web/app/api/terminal/sessions/route.ts"); -const streamRoute = await import("../../web/app/api/terminal/stream/route.ts"); +const sessionsRoute = await import("../../../web/app/api/terminal/sessions/route.ts"); +const streamRoute = await import("../../../web/app/api/terminal/stream/route.ts"); test("terminal session creation rejects disallowed commands", async () => { const response = await sessionsRoute.POST( diff --git a/src/tests/web-workflow-action-execution.test.ts b/src/tests/integration/web-workflow-action-execution.test.ts similarity index 97% rename from src/tests/web-workflow-action-execution.test.ts rename to src/tests/integration/web-workflow-action-execution.test.ts index 3cc052a39..024677baa 100644 --- a/src/tests/web-workflow-action-execution.test.ts +++ b/src/tests/integration/web-workflow-action-execution.test.ts @@ -5,7 +5,7 @@ const { derivePendingWorkflowCommandLabel, executeWorkflowActionInPowerMode, navigateToGSDView, -} = await import("../../web/lib/workflow-action-execution.ts") +} = await import("../../../web/lib/workflow-action-execution.ts") test("derivePendingWorkflowCommandLabel prefers the latest input line while a command is in flight", () => { const label = derivePendingWorkflowCommandLabel({ diff --git a/src/tests/web-workflow-controls-contract.test.ts b/src/tests/integration/web-workflow-controls-contract.test.ts similarity index 98% rename from src/tests/web-workflow-controls-contract.test.ts rename to src/tests/integration/web-workflow-controls-contract.test.ts index 7e91ca9cd..897245290 100644 --- a/src/tests/web-workflow-controls-contract.test.ts +++ b/src/tests/integration/web-workflow-controls-contract.test.ts @@ -2,7 +2,7 @@ import test from "node:test"; import assert from "node:assert/strict"; // ─── Import ────────────────────────────────────────────────────────── -const { deriveWorkflowAction } = await import("../../web/lib/workflow-actions.ts"); +const { deriveWorkflowAction } = await import("../../../web/lib/workflow-actions.ts"); // ─── Helpers ────────────────────────────────────────────────────────── function baseInput(overrides: Partial[0]> = {}) {