@@ -51,40 +51,7 @@ const logger = getDefaultLogger()
5151// safely infer. Each one IS fixable — the AI step does the work.
5252// The deterministic linter already handled the unambiguous shapes;
5353// what remains is the structural-rewrite set.
54- const AI_HANDLED_RULES = new Set ( [
55- // master/slave — context decides main/primary/controller vs
56- // replica/worker. Other forms (whitelist/blacklist/etc.) auto-fix.
57- 'socket/inclusive-language' ,
58- // Literal username in a user-home path. In source: substitute a
59- // placeholder / env-var / delete. In WASM or generated bundles:
60- // the bundler is leaking the path — fix the build config.
61- 'socket/personal-path-placeholders' ,
62- // fs.access / fs.stat existence checks. AI rewrites the try/catch
63- // → if/else and preserves metadata calls when the result is
64- // destructured. Wrapper-name shapes (fileExists / pathExists /
65- // isFile / isDir) auto-fix deterministically.
66- 'socket/prefer-exists-sync' ,
67- // node:fs default/namespace where references are "weird" (computed
68- // access, passed as a value, reassigned). Plain `fs.X` shapes
69- // auto-fix via scope rename.
70- 'socket/prefer-node-builtin-imports' ,
71- // spawnSync where the call site isn't already in async context or
72- // its return value is consumed (assignment, property access).
73- // await/expression-statement shapes auto-fix.
74- 'socket/prefer-async-spawn' ,
75- // null whose surrounding type annotation also mentions null. AI
76- // flips BOTH the annotation and the value in lockstep through the
77- // function signatures / interfaces / return types involved.
78- // Cross-file ripple is handled by per-file passes on the next run.
79- 'socket/prefer-undefined-over-null' ,
80- // File splitting needs to choose natural seams.
81- 'socket/max-file-lines' ,
82- // Placeholder finishes need actual implementation.
83- 'socket/no-placeholders' ,
84- // No-fetch needs httpJson/httpText/httpRequest decision based on
85- // how the response is consumed.
86- 'socket/no-fetch-prefer-http-request' ,
87- ] )
54+ const AI_HANDLED_RULES = new Set ( [ 'socket/inclusive-language' , 'socket/max-file-lines' , 'socket/no-fetch-prefer-http-request' , 'socket/no-placeholders' , 'socket/personal-path-placeholders' , 'socket/prefer-async-spawn' , 'socket/prefer-exists-sync' , 'socket/prefer-node-builtin-imports' , 'socket/prefer-undefined-over-null' ] )
8855
8956interface OxlintMessage {
9057 ruleId ?: string
@@ -108,7 +75,7 @@ interface CliArgs {
10875 passthrough : string [ ]
10976}
11077
111- function parseArgs ( argv : readonly string [ ] ) : CliArgs {
78+ export function parseArgs ( argv : readonly string [ ] ) : CliArgs {
11279 const passthrough : string [ ] = [ ]
11380 let noAi = false
11481 let staged = false
@@ -133,7 +100,7 @@ function parseArgs(argv: readonly string[]): CliArgs {
133100 return { all, noAi, passthrough, staged }
134101}
135102
136- async function runLintJson (
103+ export async function runLintJson (
137104 passthrough : readonly string [ ] ,
138105) : Promise < OxlintFile [ ] > {
139106 // Run oxlint directly with --format=json. Bypass `pnpm run lint`
@@ -180,7 +147,7 @@ async function runLintJson(
180147 }
181148}
182149
183- function bucketFindings ( files : OxlintFile [ ] ) : Map < string , OxlintMessage [ ] > {
150+ export function bucketFindings ( files : OxlintFile [ ] ) : Map < string , OxlintMessage [ ] > {
184151 const byFile = new Map < string , OxlintMessage [ ] > ( )
185152 for ( const f of files ) {
186153 const handled = f . messages . filter (
@@ -226,7 +193,7 @@ const RULE_GUIDANCE = {
226193 'Replace `fetch(url, opts)` with the right helper from `@socketsecurity/lib/http-request`: `httpJson` when the caller calls `.json()` on the response, `httpText` when it calls `.text()`, `httpRequest` for raw access. Add the named import.' ,
227194} as unknown as Readonly < Record < string , string > >
228195
229- function renderFindings ( findings : OxlintMessage [ ] , _rel : string ) : string {
196+ export function renderFindings ( findings : OxlintMessage [ ] , _rel : string ) : string {
230197 return findings
231198 . map (
232199 f =>
@@ -240,7 +207,7 @@ function renderFindings(findings: OxlintMessage[], _rel: string): string {
240207 . join ( '\n' )
241208}
242209
243- function renderRuleGuidance ( findings : OxlintMessage [ ] ) : string {
210+ export function renderRuleGuidance ( findings : OxlintMessage [ ] ) : string {
244211 const seen = new Set < string > ( )
245212 for ( const f of findings ) {
246213 if ( f . ruleId ) {
@@ -278,7 +245,7 @@ function renderRuleGuidance(findings: OxlintMessage[]): string {
278245 * the guidance block carries enough context), and how to use Edit /
279246 * Read. Adding boilerplate dilutes the instructions.
280247 */
281- function buildPrompt ( filePath : string , findings : OxlintMessage [ ] ) : string {
248+ export function buildPrompt ( filePath : string , findings : OxlintMessage [ ] ) : string {
282249 const rel = path . relative ( process . cwd ( ) , filePath )
283250 const findingsBlock = renderFindings ( findings , rel )
284251 const rulesBlock = renderRuleGuidance ( findings )
@@ -303,7 +270,7 @@ ${rulesBlock}
303270<output>One short sentence summarizing what you changed. No markdown, no code blocks, no preamble.</output>`
304271}
305272
306- async function runClaudeFix (
273+ export async function runClaudeFix (
307274 _filePath : string ,
308275 prompt : string ,
309276 cwd : string ,
@@ -357,7 +324,7 @@ async function runClaudeFix(
357324 return { exitCode, stderr, stdout }
358325}
359326
360- async function hasClaudeCli ( ) : Promise < boolean > {
327+ export async function hasClaudeCli ( ) : Promise < boolean > {
361328 try {
362329 const result = await spawn ( 'claude' , [ '--version' ] , {
363330 shell : process . platform === 'win32' ,
0 commit comments