Skip to content

Commit 57c955b

Browse files
committed
chore(sync): cascade inclusive-language rule + scripts fixes
* : same-line trailing bypass detection + file-level self-disable * : file-level inclusive-language disable * : external-api bypass on git master-branch ref
1 parent 04c4d87 commit 57c955b

3 files changed

Lines changed: 67 additions & 37 deletions

File tree

.config/oxlint-plugin/rules/inclusive-language.mts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* oxlint-disable socket/inclusive-language -- this file IS the rule definition; the legacy terms are lookup-table data, not real usage. */
2+
13
/**
24
* @fileoverview Per CLAUDE.md "Inclusive language" rule (full table
35
* in docs/references/inclusive-language.md).
@@ -144,6 +146,17 @@ const rule = {
144146
return true
145147
}
146148
}
149+
// Fall-back: scan the entire source line containing the node for
150+
// a trailing bypass comment. AST-level "after" comments stop at
151+
// the statement boundary, but a chained method call's string
152+
// literal won't see a trailing comment on the same physical line.
153+
const loc = node.loc
154+
if (loc && loc.start.line === loc.end.line) {
155+
const lineText = sourceCode.lines?.[loc.start.line - 1]
156+
if (lineText && BYPASS_RE.test(lineText)) {
157+
return true
158+
}
159+
}
147160
return false
148161
}
149162

scripts/ai-lint-fix.mts

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,38 @@ const logger = getDefaultLogger()
5252
// The deterministic linter already handled the unambiguous shapes;
5353
// what remains is the structural-rewrite set.
5454
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.
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

6689
interface 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(
185206
const 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',

scripts/lockstep.mts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ type Report =
139139
// Generic helpers.
140140
// ---------------------------------------------------------------------------
141141

142-
export function readManifest(manifestPath: string): Manifest {
142+
function readManifest(manifestPath: string): Manifest {
143143
if (!existsSync(manifestPath)) {
144144
logger.error(`lockstep: manifest not found at ${manifestPath}`)
145145
process.exit(1)
@@ -169,7 +169,7 @@ export function readManifest(manifestPath: string): Manifest {
169169
* flattened view. Each sub-manifest contributes its rows; the top-level
170170
* upstreams/sites maps are merged (top-level wins on conflict).
171171
*/
172-
export function loadManifestTree(rootManifestPath: string): {
172+
function loadManifestTree(rootManifestPath: string): {
173173
areas: Array<{ area: string; manifest: Manifest }>
174174
merged: Manifest
175175
} {
@@ -223,7 +223,7 @@ export function loadManifestTree(rootManifestPath: string): {
223223
}
224224
}
225225

226-
export function gitIn(submoduleDir: string, args: string[]): string {
226+
function gitIn(submoduleDir: string, args: string[]): string {
227227
const result = spawnSync('git', ['-C', submoduleDir, ...args], {
228228
stdio: ['ignore', 'pipe', 'pipe'],
229229
stdioString: true,
@@ -239,7 +239,7 @@ export function gitIn(submoduleDir: string, args: string[]): string {
239239
return String(result.stdout)
240240
}
241241

242-
export function shaIsReachable(submoduleDir: string, sha: string): boolean {
242+
function shaIsReachable(submoduleDir: string, sha: string): boolean {
243243
try {
244244
gitIn(submoduleDir, ['cat-file', '-e', sha])
245245
return true
@@ -248,7 +248,7 @@ export function shaIsReachable(submoduleDir: string, sha: string): boolean {
248248
}
249249
}
250250

251-
export function driftCommitsSince(
251+
function driftCommitsSince(
252252
submoduleDir: string,
253253
sha: string,
254254
pathInRepo: string,
@@ -280,7 +280,7 @@ export function driftCommitsSince(
280280
}
281281
}
282282

283-
export function resolveUpstream(
283+
function resolveUpstream(
284284
manifest: Manifest,
285285
alias: string,
286286
messages: string[],
@@ -294,7 +294,7 @@ export function resolveUpstream(
294294
return upstream
295295
}
296296

297-
export function walkDirFiles(dir: string, extRe: RegExp): string[] {
297+
function walkDirFiles(dir: string, extRe: RegExp): string[] {
298298
const files: string[] = []
299299
if (!existsSync(dir)) {
300300
return files
@@ -329,7 +329,7 @@ export function walkDirFiles(dir: string, extRe: RegExp): string[] {
329329
return files
330330
}
331331

332-
export function countPatternHits(files: string[], patterns: string[]): number {
332+
function countPatternHits(files: string[], patterns: string[]): number {
333333
if (patterns.length === 0) {
334334
return 0
335335
}
@@ -368,7 +368,7 @@ export function countPatternHits(files: string[], patterns: string[]): number {
368368
// Kind checkers.
369369
// ---------------------------------------------------------------------------
370370

371-
export function checkFileFork(
371+
function checkFileFork(
372372
row: FileForkRow,
373373
manifest: Manifest,
374374
area: string,
@@ -429,7 +429,7 @@ export function checkFileFork(
429429
return base
430430
}
431431

432-
export function checkVersionPin(
432+
function checkVersionPin(
433433
row: VersionPinRow,
434434
manifest: Manifest,
435435
area: string,
@@ -496,6 +496,7 @@ export function checkVersionPin(
496496
const pref = [
497497
'refs/remotes/origin/HEAD',
498498
'refs/remotes/origin/main',
499+
// inclusive-language: external-api — git's historical default branch.
499500
'refs/remotes/origin/master',
500501
]
501502
for (const p of pref) {
@@ -532,7 +533,7 @@ export function checkVersionPin(
532533
return base
533534
}
534535

535-
export function checkFeatureParity(
536+
function checkFeatureParity(
536537
row: FeatureParityRow,
537538
_manifest: Manifest,
538539
area: string,
@@ -620,7 +621,7 @@ export function checkFeatureParity(
620621
return base
621622
}
622623

623-
export function checkSpecConformance(
624+
function checkSpecConformance(
624625
row: SpecConformanceRow,
625626
manifest: Manifest,
626627
area: string,
@@ -659,7 +660,7 @@ export function checkSpecConformance(
659660
return base
660661
}
661662

662-
export function checkLangParity(
663+
function checkLangParity(
663664
row: LangParityRow,
664665
manifest: Manifest,
665666
area: string,
@@ -726,7 +727,7 @@ export function checkLangParity(
726727
* already covers per-row shape, enum values, id pattern, and required
727728
* fields — this is the referential-integrity layer on top.
728729
*/
729-
export function checkCrossRowConsistency(
730+
function checkCrossRowConsistency(
730731
rowsWithArea: Array<{ row: Row; area: string }>,
731732
merged: Manifest,
732733
): string[] {
@@ -782,7 +783,7 @@ export function checkCrossRowConsistency(
782783
// Dispatcher.
783784
// ---------------------------------------------------------------------------
784785

785-
export function evaluate(
786+
function evaluate(
786787
rowsWithArea: Array<{ row: Row; area: string }>,
787788
merged: Manifest,
788789
): Report[] {
@@ -837,7 +838,7 @@ interface AreaSummary {
837838
error: number
838839
}
839840

840-
export function summarize(reports: Report[]): AreaSummary[] {
841+
function summarize(reports: Report[]): AreaSummary[] {
841842
const byArea = new Map<string, AreaSummary>()
842843
for (const r of reports) {
843844
let s = byArea.get(r.area)
@@ -855,7 +856,7 @@ export function summarize(reports: Report[]): AreaSummary[] {
855856
// Output.
856857
// ---------------------------------------------------------------------------
857858

858-
export function emitHuman(reports: Report[], summaries: AreaSummary[]): number {
859+
function emitHuman(reports: Report[], summaries: AreaSummary[]): number {
859860
logger.info(
860861
`lockstep — ${reports.length} row(s) across ${summaries.length} area(s)`,
861862
)

0 commit comments

Comments
 (0)