Files
ruvnet__ruflo/v3/@claude-flow/cli/__tests__/codemod-routing.test.ts
ruv 0988d92ce8 feat(intelligence): deterministic Tier-1 codemods replacing Agent Booster (ADR-143)
The ADR-026 Tier-1 "Agent Booster, $0, no LLM" path was dead code AND wired
wrong (it fed natural-language instructions to a snippet-merge engine that
expects target code), and the module didn't even resolve. Replace it with real
deterministic codemods so the $0/no-LLM claim is literally true.

- engine.ts: TS-compiler codemods (var-to-const, remove-console, add-logging)
  with formatting-preserving text-range edits + a parse-diagnostic safety net.
- scope-analysis.ts: function-scope reassignment resolution (var→const promotes
  more vars to const without ever producing an incorrect const).
- enhanced-model-router: Tier-1 gated on deterministic intents; route-time
  dry-run only emits [CODEMOD_AVAILABLE] when the edit actually changes a file;
  add-types/add-error-handling/async-await honestly route to a model.
- hooks_codemod MCP tool: real executable Tier-1 surface — single file, files[],
  or glob batch; $0; path-contained.
- Honest [CODEMOD_AVAILABLE] emitters replace [AGENT_BOOSTER_AVAILABLE] and the
  phantom agent_booster_edit_file reference; CLAUDE.md Tier-1 tables corrected.
- Benchmark + golden corpus (12 cases, 100% correct, ~0.55ms/edit, $0) with a
  CI guardrail test; run JSON in cost-trend schema.

Tests: codemod-engine (23), codemod-routing (4), codemod-corpus (13); full CLI
suite 2086 pass / 46 skipped. Closes #2238.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-29 15:40:11 -04:00

45 lines
1.8 KiB
TypeScript

import { describe, it, expect, afterAll } from 'vitest';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { getEnhancedModelRouter } from '../src/ruvector/enhanced-model-router.js';
const dir = mkdtempSync(join(tmpdir(), 'codemod-routing-'));
afterAll(() => rmSync(dir, { recursive: true, force: true }));
const router = getEnhancedModelRouter();
describe('route-time dry-run (ADR-143 #3)', () => {
it('keeps Tier-1 codemod when the file actually has work to do', async () => {
const f = join(dir, 'has.js');
writeFileSync(f, 'var a = 1;\nvar b = 2;\n');
const r = await router.route('convert var to const', { filePath: f });
expect(r.tier).toBe(1);
expect(r.handler).toBe('codemod');
expect(r.deterministic).toBe(true);
expect(r.reasoning).toMatch(/\d+ edit/); // edit count from the dry-run
});
it('falls through to model routing when the codemod is a verified no-op', async () => {
const f = join(dir, 'none.js');
writeFileSync(f, 'const a = 1;\n'); // nothing to convert
const r = await router.route('convert var to const', { filePath: f });
expect(r.tier).not.toBe(1);
expect(r.handler).not.toBe('codemod');
});
it('recommends best-effort Tier-1 when no file is available to dry-run', async () => {
const r = await router.route('convert var to const');
expect(r.tier).toBe(1);
expect(r.handler).toBe('codemod');
});
it('never claims Tier-1 for a non-deterministic intent', async () => {
const f = join(dir, 'typed.ts');
writeFileSync(f, 'function f(a) { return a; }\n');
const r = await router.route('add type annotations', { filePath: f });
expect(r.tier).not.toBe(1);
expect(r.handler).not.toBe('codemod');
});
});