Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions packages/core/audit-self/audit-ai-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ function writeFile(dir: string, relPath: string, content: string): string {
return full;
}

/**
* Fast execSync stub for warm-path probeR4 tests (DN-1 Path C, 2026-05-25).
*
* Mirrors the subprocess outcomes probeR4 cares about without spawning npx:
* - `npx --version` → returns '8.0.0\n' (npx is available)
* - `npx --no-install tsx scripts/audit-r4.ts` → throws ENOENT (tsx not installed)
*
* These assertions exercise CONDITIONAL LOGIC (early-return branches), not
* real-subprocess contract. The :344 contract test deliberately omits this
* stub — semantic preservation is the design intent of Option C.
*/
const fastExecSyncStub = ((cmd: string): Buffer => {
if (cmd.startsWith('npx --version')) return Buffer.from('8.0.0\n');
// `npx --no-install tsx ...` → simulate tsx absent (real behaviour on warm-path test envs)
const err: NodeJS.ErrnoException = new Error('ENOENT: tsx not found');
err.code = 'ENOENT';
throw err;
}) as unknown as typeof import('node:child_process').execSync;

// ─── extractProseText() — remark code-fence-aware extraction ─────────────────

describe('extractProseText()', () => {
Expand Down Expand Up @@ -1502,10 +1521,13 @@ describe('probeR4() — execSync call content (L160-171)', () => {
// Only tsconfig present (no ts-morph) → condition !hasTsconfig && !hasTsMorph is false
// LogicalOperator mutant !hasTsconfig || !hasTsMorph would be TRUE → early-return
// So the mutant would produce 'warn' instead of 'fail'/'warn' from execSync
//
// Uses fastExecSyncStub (DN-1 Path C) — this test asserts on conditional logic,
// not on real-subprocess contract. The :344 sibling keeps real execSync.
mkdirSync(join(dir, 'src/domain'), { recursive: true });
writeFile(dir, 'src/domain/a.ts', 'export const x = 1;\n');
writeFile(dir, 'tsconfig.json', '{"compilerOptions":{}}\n');
const result = probeR4(dir);
const result = probeR4(dir, { execSync: fastExecSyncStub });
// hasTsconfig=true → condition !hasTsconfig && !hasTsMorph = false → does NOT early-return
// → execSync runs → result is fail or warn (from execSync failure)
expect(['warn', 'fail']).toContain(result.result);
Expand All @@ -1522,12 +1544,14 @@ describe('probeR4() — execSync call content (L160-171)', () => {
// With || mutant: !hasTsconfig || !hasTsMorph → if only hasTsMorph is true (hasTsconfig=false)
// → condition is true → early-return warn (wrong!)
// Real code: !hasTsconfig && !hasTsMorph → false when either is present
//
// Uses fastExecSyncStub (DN-1 Path C) — see sibling test above for rationale.
mkdirSync(join(dir, 'src/domain'), { recursive: true });
writeFile(dir, 'src/domain/a.ts', 'export const x = 1;\n');
// Make ts-morph "present" via the existence check
mkdirSync(join(dir, 'node_modules/ts-morph'), { recursive: true });
writeFile(dir, 'node_modules/ts-morph/package.json', '{"name":"ts-morph"}\n');
const result = probeR4(dir);
const result = probeR4(dir, { execSync: fastExecSyncStub });
// hasTsMorph=true → !hasTsconfig && !hasTsMorph = false → does NOT early-return
// The result should be 'fail' or 'warn' (execSync fails, not the early-return warn)
expect(['warn', 'fail']).toContain(result.result);
Expand Down
15 changes: 12 additions & 3 deletions packages/core/audit-self/audit-ai-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,23 @@ export function extractDeclaredSkills(agentsMarkdown: string): string[] {
* R4 probe: every domain export in src/domain has a matching .unit.ts file.
* Delegates to `scripts/audit-r4.ts` in the consumer project via npx tsx.
* Falls back to WARN(skipped) when env lacks Node/tsx/tsconfig.
*
* `opts.execSync` is the dependency-injection seam for warm-path tests
* (DN-1 Path C, 2026-05-25); omit it in prod and contract tests — defaults
* to the real `execSync` from node:child_process, preserving byte-for-byte
* subprocess behaviour.
*/
export function probeR4(cwd: string): { result: 'pass' | 'fail' | 'warn'; message: string } {
export function probeR4(
cwd: string,
opts?: { execSync?: typeof execSync },
): { result: 'pass' | 'fail' | 'warn'; message: string } {
const exec = opts?.execSync ?? execSync;
const RULE = 'R4: Every public export in src/domain has matching .unit.ts (ts-morph)';
if (!existsSync(join(cwd, 'src/domain'))) {
return { result: 'pass', message: `${RULE} (skipped: no src/domain)` };
}
try {
execSync('npx --version', { stdio: 'ignore' });
exec('npx --version', { stdio: 'ignore' });
} catch {
return { result: 'warn', message: `${RULE} (skipped: npx not found)` };
}
Expand All @@ -168,7 +177,7 @@ export function probeR4(cwd: string): { result: 'pass' | 'fail' | 'warn'; messag
return { result: 'warn', message: `${RULE} (skipped: no tsconfig.json and ts-morph not installed)` };
}
try {
execSync('npx --no-install tsx scripts/audit-r4.ts', { cwd, stdio: 'pipe' });
exec('npx --no-install tsx scripts/audit-r4.ts', { cwd, stdio: 'pipe' });
return { result: 'pass', message: RULE };
} catch {
return { result: 'fail', message: RULE };
Expand Down
3 changes: 3 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
// worktrees scope. testTimeout covers cold npm cache + slow connection in
// audit-ai-docs.test.ts:344 (local measurement ~24s; 60s = safe margin).
// Longer-term fix: see .claude/orchestrator-prompts/slow-test-triage/.
//
// Ceiling: testTimeout MUST NOT exceed 120_000 without a paired slow-test-triage R-phase.
// Further bumps require fixing the slow path, not raising the lid (DN-3 Option A, 2026-05-25).

export default {
test: {
Expand Down
Loading