Skip to content

Commit d62710d

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 ef96f41 commit d62710d

3 files changed

Lines changed: 74 additions & 26 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: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,40 @@ 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(['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'])
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+
])
5588

5689
interface OxlintMessage {
5790
ruleId?: string
@@ -75,7 +108,7 @@ interface CliArgs {
75108
passthrough: string[]
76109
}
77110

78-
export function parseArgs(argv: readonly string[]): CliArgs {
111+
function parseArgs(argv: readonly string[]): CliArgs {
79112
const passthrough: string[] = []
80113
let noAi = false
81114
let staged = false
@@ -100,7 +133,7 @@ export function parseArgs(argv: readonly string[]): CliArgs {
100133
return { all, noAi, passthrough, staged }
101134
}
102135

103-
export async function runLintJson(
136+
async function runLintJson(
104137
passthrough: readonly string[],
105138
): Promise<OxlintFile[]> {
106139
// Run oxlint directly with --format=json. Bypass `pnpm run lint`
@@ -147,7 +180,7 @@ export async function runLintJson(
147180
}
148181
}
149182

150-
export function bucketFindings(files: OxlintFile[]): Map<string, OxlintMessage[]> {
183+
function bucketFindings(files: OxlintFile[]): Map<string, OxlintMessage[]> {
151184
const byFile = new Map<string, OxlintMessage[]>()
152185
for (const f of files) {
153186
const handled = f.messages.filter(
@@ -173,6 +206,7 @@ export function bucketFindings(files: OxlintFile[]): Map<string, OxlintMessage[]
173206
const RULE_GUIDANCE = {
174207
// oxlint-disable-next-line socket/prefer-undefined-over-null -- null-prototype object literal.
175208
__proto__: null,
209+
// oxlint-disable-next-line socket/inclusive-language -- rule guidance string documents the legacy terms it scans for.
176210
'socket/inclusive-language':
177211
'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.',
178212
'socket/personal-path-placeholders':
@@ -193,7 +227,7 @@ const RULE_GUIDANCE = {
193227
'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.',
194228
} as unknown as Readonly<Record<string, string>>
195229

196-
export function renderFindings(findings: OxlintMessage[], _rel: string): string {
230+
function renderFindings(findings: OxlintMessage[], _rel: string): string {
197231
return findings
198232
.map(
199233
f =>
@@ -207,7 +241,7 @@ export function renderFindings(findings: OxlintMessage[], _rel: string): string
207241
.join('\n')
208242
}
209243

210-
export function renderRuleGuidance(findings: OxlintMessage[]): string {
244+
function renderRuleGuidance(findings: OxlintMessage[]): string {
211245
const seen = new Set<string>()
212246
for (const f of findings) {
213247
if (f.ruleId) {
@@ -245,7 +279,7 @@ export function renderRuleGuidance(findings: OxlintMessage[]): string {
245279
* the guidance block carries enough context), and how to use Edit /
246280
* Read. Adding boilerplate dilutes the instructions.
247281
*/
248-
export function buildPrompt(filePath: string, findings: OxlintMessage[]): string {
282+
function buildPrompt(filePath: string, findings: OxlintMessage[]): string {
249283
const rel = path.relative(process.cwd(), filePath)
250284
const findingsBlock = renderFindings(findings, rel)
251285
const rulesBlock = renderRuleGuidance(findings)
@@ -270,7 +304,7 @@ ${rulesBlock}
270304
<output>One short sentence summarizing what you changed. No markdown, no code blocks, no preamble.</output>`
271305
}
272306

273-
export async function runClaudeFix(
307+
async function runClaudeFix(
274308
_filePath: string,
275309
prompt: string,
276310
cwd: string,
@@ -324,7 +358,7 @@ export async function runClaudeFix(
324358
return { exitCode, stderr, stdout }
325359
}
326360

327-
export async function hasClaudeCli(): Promise<boolean> {
361+
async function hasClaudeCli(): Promise<boolean> {
328362
try {
329363
const result = await spawn('claude', ['--version'], {
330364
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)