@@ -52,15 +52,38 @@ const logger = getDefaultLogger()
5252// The deterministic linter already handled the unambiguous shapes;
5353// what remains is the structural-rewrite set.
5454const AI_HANDLED_RULES = new Set ( [
55+ // master/slave — context decides main/primary/controller vs
56+ // replica/worker. Other forms (whitelist/blacklist/etc.) auto-fix.
5557 'socket/inclusive-language' ,
56- 'socket/max-file-lines' ,
57- 'socket/no-fetch-prefer-http-request' ,
58- 'socket/no-placeholders' ,
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.
5961 'socket/personal-path-placeholders' ,
60- 'socket/prefer-async-spawn' ,
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.
6166 '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.
6270 '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.
6379 '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' ,
6487] )
6588
6689interface OxlintMessage {
@@ -85,7 +108,7 @@ interface CliArgs {
85108 passthrough : string [ ]
86109}
87110
88- export function parseArgs ( argv : readonly string [ ] ) : CliArgs {
111+ function parseArgs ( argv : readonly string [ ] ) : CliArgs {
89112 const passthrough : string [ ] = [ ]
90113 let noAi = false
91114 let staged = false
@@ -110,7 +133,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
110133 return { all, noAi, passthrough, staged }
111134}
112135
113- export async function runLintJson (
136+ async function runLintJson (
114137 passthrough : readonly string [ ] ,
115138) : Promise < OxlintFile [ ] > {
116139 // Run oxlint directly with --format=json. Bypass `pnpm run lint`
@@ -157,9 +180,7 @@ export async function runLintJson(
157180 }
158181}
159182
160- export function bucketFindings (
161- files : OxlintFile [ ] ,
162- ) : Map < string , OxlintMessage [ ] > {
183+ function bucketFindings ( files : OxlintFile [ ] ) : Map < string , OxlintMessage [ ] > {
163184 const byFile = new Map < string , OxlintMessage [ ] > ( )
164185 for ( const f of files ) {
165186 const handled = f . messages . filter (
@@ -185,6 +206,7 @@ export function bucketFindings(
185206const RULE_GUIDANCE = {
186207 // oxlint-disable-next-line socket/prefer-undefined-over-null -- null-prototype object literal.
187208 __proto__ : null ,
209+ // oxlint-disable-next-line socket/inclusive-language -- rule guidance string documents the legacy terms it scans for.
188210 'socket/inclusive-language' :
189211 'Replace `master`/`slave` with the contextually correct term: `main` (branch), `primary`/`controller` (process), `replica`/`worker`/`secondary`/`follower` (subordinate). Read the surrounding code to pick the right one. Do not autofix when an external API field name forces the legacy term — leave a `// inclusive-language: external-api` comment instead.' ,
190212 'socket/personal-path-placeholders' :
@@ -205,10 +227,7 @@ const RULE_GUIDANCE = {
205227 '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.' ,
206228} as unknown as Readonly < Record < string , string > >
207229
208- export function renderFindings (
209- findings : OxlintMessage [ ] ,
210- _rel : string ,
211- ) : string {
230+ function renderFindings ( findings : OxlintMessage [ ] , _rel : string ) : string {
212231 return findings
213232 . map (
214233 f =>
@@ -222,7 +241,7 @@ export function renderFindings(
222241 . join ( '\n' )
223242}
224243
225- export function renderRuleGuidance ( findings : OxlintMessage [ ] ) : string {
244+ function renderRuleGuidance ( findings : OxlintMessage [ ] ) : string {
226245 const seen = new Set < string > ( )
227246 for ( const f of findings ) {
228247 if ( f . ruleId ) {
@@ -260,10 +279,7 @@ export function renderRuleGuidance(findings: OxlintMessage[]): string {
260279 * the guidance block carries enough context), and how to use Edit /
261280 * Read. Adding boilerplate dilutes the instructions.
262281 */
263- export function buildPrompt (
264- filePath : string ,
265- findings : OxlintMessage [ ] ,
266- ) : string {
282+ function buildPrompt ( filePath : string , findings : OxlintMessage [ ] ) : string {
267283 const rel = path . relative ( process . cwd ( ) , filePath )
268284 const findingsBlock = renderFindings ( findings , rel )
269285 const rulesBlock = renderRuleGuidance ( findings )
@@ -288,7 +304,7 @@ ${rulesBlock}
288304<output>One short sentence summarizing what you changed. No markdown, no code blocks, no preamble.</output>`
289305}
290306
291- export async function runClaudeFix (
307+ async function runClaudeFix (
292308 _filePath : string ,
293309 prompt : string ,
294310 cwd : string ,
@@ -342,7 +358,7 @@ export async function runClaudeFix(
342358 return { exitCode, stderr, stdout }
343359}
344360
345- export async function hasClaudeCli ( ) : Promise < boolean > {
361+ async function hasClaudeCli ( ) : Promise < boolean > {
346362 try {
347363 const result = await spawn ( 'claude' , [ '--version' ] , {
348364 shell : process . platform === 'win32' ,
0 commit comments