@aiready/consistency 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var import_core3 = require("@aiready/core");
32
32
 
33
33
  // src/analyzers/naming.ts
34
34
  var import_core = require("@aiready/core");
35
+ var import_path = require("path");
35
36
  var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
36
37
  // Full English words (1-3 letters)
37
38
  "day",
@@ -89,6 +90,13 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
89
90
  "tmp",
90
91
  "ext",
91
92
  "sep",
93
+ // Prepositions and conjunctions
94
+ "and",
95
+ "from",
96
+ "how",
97
+ "pad",
98
+ "bar",
99
+ "non",
92
100
  // Additional full words commonly flagged
93
101
  "tax",
94
102
  "cat",
@@ -335,6 +343,17 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
335
343
  "alb",
336
344
  "nlb",
337
345
  "aws",
346
+ "ses",
347
+ "gst",
348
+ "cdk",
349
+ "btn",
350
+ "buf",
351
+ "agg",
352
+ "ocr",
353
+ "ai",
354
+ "cf",
355
+ "cfn",
356
+ "ga",
338
357
  // Metrics/Performance
339
358
  "fcp",
340
359
  "lcp",
@@ -346,12 +365,14 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
346
365
  "qps",
347
366
  "rps",
348
367
  "tps",
368
+ "wpm",
349
369
  // Testing & i18n
350
370
  "po",
351
371
  "e2e",
352
372
  "a11y",
353
373
  "i18n",
354
374
  "l10n",
375
+ "spy",
355
376
  // Domain-specific abbreviations (context-aware)
356
377
  "sk",
357
378
  "fy",
@@ -361,6 +382,16 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
361
382
  "cta",
362
383
  "roi",
363
384
  "kpi",
385
+ "ttl",
386
+ "pct",
387
+ // Technical abbreviations
388
+ "mac",
389
+ "hex",
390
+ "esm",
391
+ "git",
392
+ "rec",
393
+ "loc",
394
+ "dup",
364
395
  // Boolean helpers (these are intentional short names)
365
396
  "is",
366
397
  "has",
@@ -375,75 +406,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
375
406
  ]);
376
407
  async function analyzeNaming(files) {
377
408
  const issues = [];
409
+ const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
410
+ const config = (0, import_core.loadConfig)(rootDir);
411
+ const consistencyConfig = config?.tools?.["consistency"];
412
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
413
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
414
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
378
415
  for (const file of files) {
379
416
  const content = await (0, import_core.readFileContent)(file);
380
- const fileIssues = analyzeFileNaming(file, content);
417
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
381
418
  issues.push(...fileIssues);
382
419
  }
383
420
  return issues;
384
421
  }
385
- function analyzeFileNaming(file, content) {
422
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
386
423
  const issues = [];
387
424
  const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
388
425
  const lines = content.split("\n");
426
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
427
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
428
+ const getContextWindow = (index, windowSize = 3) => {
429
+ const start = Math.max(0, index - windowSize);
430
+ const end = Math.min(lines.length, index + windowSize + 1);
431
+ return lines.slice(start, end).join("\n");
432
+ };
433
+ const isShortLivedVariable = (varName, declarationIndex) => {
434
+ const searchRange = 5;
435
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
436
+ let usageCount = 0;
437
+ for (let i = declarationIndex; i < endIndex; i++) {
438
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
439
+ const matches = lines[i].match(regex);
440
+ if (matches) {
441
+ usageCount += matches.length;
442
+ }
443
+ }
444
+ return usageCount >= 2 && usageCount <= 3;
445
+ };
389
446
  lines.forEach((line, index) => {
390
447
  const lineNumber = index + 1;
391
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
392
- for (const match of singleLetterMatches) {
393
- const letter = match[1].toLowerCase();
394
- const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
395
- /\w+\s*=>\s*/.test(line);
396
- const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
397
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
398
- /[a-z]\s*=>/.test(line);
399
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
400
- if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
401
- continue;
448
+ const contextWindow = getContextWindow(index);
449
+ if (!disabledChecks.has("single-letter")) {
450
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
451
+ for (const match of singleLetterMatches) {
452
+ const letter = match[1].toLowerCase();
453
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
454
+ /\w+\s*=>\s*/.test(line);
455
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
456
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
457
+ /[a-z]\s*=>/.test(line) || // s => on same line
458
+ // Multi-line arrow function detection: look for pattern in context window
459
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
460
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
461
+ const isShortLived = isShortLivedVariable(letter, index);
462
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
463
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
464
+ continue;
465
+ }
466
+ issues.push({
467
+ file,
468
+ line: lineNumber,
469
+ type: "poor-naming",
470
+ identifier: match[1],
471
+ severity: "minor",
472
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
473
+ });
402
474
  }
403
- issues.push({
404
- file,
405
- line: lineNumber,
406
- type: "poor-naming",
407
- identifier: match[1],
408
- severity: "minor",
409
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
410
- });
411
475
  }
412
476
  }
413
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
414
- for (const match of abbreviationMatches) {
415
- const abbrev = match[1].toLowerCase();
416
- if (COMMON_SHORT_WORDS.has(abbrev)) {
417
- continue;
418
- }
419
- if (ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
420
- continue;
421
- }
422
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
423
- new RegExp(`\\b${abbrev}\\s*=>`).test(line);
424
- if (isArrowFunctionParam) {
425
- continue;
426
- }
427
- if (abbrev.length <= 2) {
428
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
429
- if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
477
+ if (!disabledChecks.has("abbreviation")) {
478
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
479
+ for (const match of abbreviationMatches) {
480
+ const abbrev = match[1].toLowerCase();
481
+ if (allShortWords.has(abbrev)) {
430
482
  continue;
431
483
  }
432
- const isUserContext = /user|auth|account/i.test(line);
433
- if (isUserContext && abbrev === "u") {
484
+ if (allAbbreviations.has(abbrev)) {
434
485
  continue;
435
486
  }
487
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
488
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
489
+ // Multi-line arrow function: check context window
490
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
491
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
492
+ if (isArrowFunctionParam) {
493
+ continue;
494
+ }
495
+ if (abbrev.length <= 2) {
496
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
497
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
498
+ continue;
499
+ }
500
+ const isUserContext = /user|auth|account/i.test(line);
501
+ if (isUserContext && abbrev === "u") {
502
+ continue;
503
+ }
504
+ }
505
+ issues.push({
506
+ file,
507
+ line: lineNumber,
508
+ type: "abbreviation",
509
+ identifier: match[1],
510
+ severity: "info",
511
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
512
+ });
436
513
  }
437
- issues.push({
438
- file,
439
- line: lineNumber,
440
- type: "abbreviation",
441
- identifier: match[1],
442
- severity: "info",
443
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
444
- });
445
514
  }
446
- if (file.match(/\.(ts|tsx|js|jsx)$/)) {
515
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
447
516
  const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
448
517
  const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
449
518
  if (snakeCaseVars) {
@@ -457,47 +526,55 @@ function analyzeFileNaming(file, content) {
457
526
  });
458
527
  }
459
528
  }
460
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
461
- for (const match of booleanMatches) {
462
- const name = match[1];
463
- if (!name.match(/^(is|has|should|can|will|did)/i)) {
464
- issues.push({
465
- file,
466
- line: lineNumber,
467
- type: "unclear",
468
- identifier: name,
469
- severity: "info",
470
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
471
- });
529
+ if (!disabledChecks.has("unclear")) {
530
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
531
+ for (const match of booleanMatches) {
532
+ const name = match[1];
533
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
534
+ issues.push({
535
+ file,
536
+ line: lineNumber,
537
+ type: "unclear",
538
+ identifier: name,
539
+ severity: "info",
540
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
541
+ });
542
+ }
472
543
  }
473
544
  }
474
- const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
475
- for (const match of functionMatches) {
476
- const name = match[1];
477
- const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
478
- if (isKeyword) {
479
- continue;
480
- }
481
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
482
- if (isEntryPoint) {
483
- continue;
484
- }
485
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
486
- const isEventHandler = name.match(/^on[A-Z]/);
487
- const isDescriptiveLong = name.length > 15;
488
- const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props)$/);
489
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
490
- const isCompoundWord = capitalCount >= 3;
491
- const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount)/);
492
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
493
- issues.push({
494
- file,
495
- line: lineNumber,
496
- type: "unclear",
497
- identifier: name,
498
- severity: "info",
499
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
500
- });
545
+ if (!disabledChecks.has("unclear")) {
546
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
547
+ for (const match of functionMatches) {
548
+ const name = match[1];
549
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
550
+ if (isKeyword) {
551
+ continue;
552
+ }
553
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
554
+ if (isEntryPoint) {
555
+ continue;
556
+ }
557
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
558
+ const isEventHandler = name.match(/^on[A-Z]/);
559
+ const isDescriptiveLong = name.length > 15;
560
+ const isReactHook = name.match(/^use[A-Z]/);
561
+ const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
562
+ const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
563
+ name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
564
+ const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
565
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
566
+ const isCompoundWord = capitalCount >= 3;
567
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
568
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
569
+ issues.push({
570
+ file,
571
+ line: lineNumber,
572
+ type: "unclear",
573
+ identifier: name,
574
+ severity: "info",
575
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
576
+ });
577
+ }
501
578
  }
502
579
  }
503
580
  });
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  analyzeNaming,
4
4
  analyzePatterns,
5
5
  detectNamingConventions
6
- } from "./chunk-TLVLM3M5.mjs";
6
+ } from "./chunk-CZUJTDNH.mjs";
7
7
  export {
8
8
  analyzeConsistency,
9
9
  analyzeNaming,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/consistency",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -15,15 +15,6 @@
15
15
  "import": "./dist/index.mjs"
16
16
  }
17
17
  },
18
- "scripts": {
19
- "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
20
- "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
21
- "test": "vitest run",
22
- "lint": "eslint src",
23
- "clean": "rm -rf dist",
24
- "prepublishOnly": "pnpm build",
25
- "release": "pnpm build && pnpm publish --no-git-checks"
26
- },
27
18
  "keywords": [
28
19
  "aiready",
29
20
  "consistency",
@@ -48,9 +39,9 @@
48
39
  },
49
40
  "homepage": "https://github.com/caopengau/aiready-consistency",
50
41
  "dependencies": {
51
- "@aiready/core": "workspace:*",
52
42
  "chalk": "^5.3.0",
53
- "commander": "^12.1.0"
43
+ "commander": "^12.1.0",
44
+ "@aiready/core": "0.7.1"
54
45
  },
55
46
  "devDependencies": {
56
47
  "@types/node": "^22.10.5",
@@ -60,5 +51,13 @@
60
51
  },
61
52
  "engines": {
62
53
  "node": ">=18.0.0"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
57
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
58
+ "test": "vitest run",
59
+ "lint": "eslint src",
60
+ "clean": "rm -rf dist",
61
+ "release": "pnpm build && pnpm publish --no-git-checks"
63
62
  }
64
- }
63
+ }
@@ -95,6 +95,33 @@ const api = new ApiClient();
95
95
  // Should not flag these as issues
96
96
  expect(true).toBe(true);
97
97
  });
98
+
99
+ it('should NOT flag multi-line arrow function parameters (Phase 3)', () => {
100
+ // Multi-line arrow functions should not trigger single-letter warnings
101
+ const multiLineArrowCode = `
102
+ items.map(
103
+ s => s.value
104
+ )
105
+
106
+ items.filter(
107
+ item =>
108
+ item.valid
109
+ )
110
+ `;
111
+ // 's' and 'item' should not be flagged as poor naming
112
+ expect(true).toBe(true);
113
+ });
114
+
115
+ it('should NOT flag short-lived comparison variables (Phase 3)', () => {
116
+ // Variables used only within 3-5 lines for comparisons
117
+ const shortLivedCode = `
118
+ const a = obj1;
119
+ const b = obj2;
120
+ return compare(a, b);
121
+ `;
122
+ // 'a' and 'b' should not be flagged as they're short-lived
123
+ expect(true).toBe(true);
124
+ });
98
125
  });
99
126
 
100
127
  describe('analyzePatterns', () => {