@aiready/consistency 0.3.4 → 0.3.5

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/cli.js CHANGED
@@ -31,6 +31,7 @@ var import_core3 = require("@aiready/core");
31
31
 
32
32
  // src/analyzers/naming.ts
33
33
  var import_core = require("@aiready/core");
34
+ var import_path = require("path");
34
35
  var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
35
36
  // Full English words (1-3 letters)
36
37
  "day",
@@ -374,75 +375,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
374
375
  ]);
375
376
  async function analyzeNaming(files) {
376
377
  const issues = [];
378
+ const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
379
+ const config = (0, import_core.loadConfig)(rootDir);
380
+ const consistencyConfig = config?.tools?.["consistency"];
381
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
382
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
383
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
377
384
  for (const file of files) {
378
385
  const content = await (0, import_core.readFileContent)(file);
379
- const fileIssues = analyzeFileNaming(file, content);
386
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
380
387
  issues.push(...fileIssues);
381
388
  }
382
389
  return issues;
383
390
  }
384
- function analyzeFileNaming(file, content) {
391
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
385
392
  const issues = [];
386
393
  const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
387
394
  const lines = content.split("\n");
395
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
396
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
397
+ const getContextWindow = (index, windowSize = 3) => {
398
+ const start = Math.max(0, index - windowSize);
399
+ const end = Math.min(lines.length, index + windowSize + 1);
400
+ return lines.slice(start, end).join("\n");
401
+ };
402
+ const isShortLivedVariable = (varName, declarationIndex) => {
403
+ const searchRange = 5;
404
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
405
+ let usageCount = 0;
406
+ for (let i = declarationIndex; i < endIndex; i++) {
407
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
408
+ const matches = lines[i].match(regex);
409
+ if (matches) {
410
+ usageCount += matches.length;
411
+ }
412
+ }
413
+ return usageCount >= 2 && usageCount <= 3;
414
+ };
388
415
  lines.forEach((line, index) => {
389
416
  const lineNumber = index + 1;
390
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
391
- for (const match of singleLetterMatches) {
392
- const letter = match[1].toLowerCase();
393
- const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
394
- /\w+\s*=>\s*/.test(line);
395
- const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
396
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
397
- /[a-z]\s*=>/.test(line);
398
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
399
- if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
400
- continue;
417
+ const contextWindow = getContextWindow(index);
418
+ if (!disabledChecks.has("single-letter")) {
419
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
420
+ for (const match of singleLetterMatches) {
421
+ const letter = match[1].toLowerCase();
422
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
423
+ /\w+\s*=>\s*/.test(line);
424
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
425
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
426
+ /[a-z]\s*=>/.test(line) || // s => on same line
427
+ // Multi-line arrow function detection: look for pattern in context window
428
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
429
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
430
+ const isShortLived = isShortLivedVariable(letter, index);
431
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
432
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
433
+ continue;
434
+ }
435
+ issues.push({
436
+ file,
437
+ line: lineNumber,
438
+ type: "poor-naming",
439
+ identifier: match[1],
440
+ severity: "minor",
441
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
442
+ });
401
443
  }
402
- issues.push({
403
- file,
404
- line: lineNumber,
405
- type: "poor-naming",
406
- identifier: match[1],
407
- severity: "minor",
408
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
409
- });
410
444
  }
411
445
  }
412
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
413
- for (const match of abbreviationMatches) {
414
- const abbrev = match[1].toLowerCase();
415
- if (COMMON_SHORT_WORDS.has(abbrev)) {
416
- continue;
417
- }
418
- if (ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
419
- continue;
420
- }
421
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
422
- new RegExp(`\\b${abbrev}\\s*=>`).test(line);
423
- if (isArrowFunctionParam) {
424
- continue;
425
- }
426
- if (abbrev.length <= 2) {
427
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
428
- if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
446
+ if (!disabledChecks.has("abbreviation")) {
447
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
448
+ for (const match of abbreviationMatches) {
449
+ const abbrev = match[1].toLowerCase();
450
+ if (allShortWords.has(abbrev)) {
451
+ continue;
452
+ }
453
+ if (allAbbreviations.has(abbrev)) {
429
454
  continue;
430
455
  }
431
- const isUserContext = /user|auth|account/i.test(line);
432
- if (isUserContext && abbrev === "u") {
456
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
457
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
458
+ // Multi-line arrow function: check context window
459
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
460
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
461
+ if (isArrowFunctionParam) {
433
462
  continue;
434
463
  }
464
+ if (abbrev.length <= 2) {
465
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
466
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
467
+ continue;
468
+ }
469
+ const isUserContext = /user|auth|account/i.test(line);
470
+ if (isUserContext && abbrev === "u") {
471
+ continue;
472
+ }
473
+ }
474
+ issues.push({
475
+ file,
476
+ line: lineNumber,
477
+ type: "abbreviation",
478
+ identifier: match[1],
479
+ severity: "info",
480
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
481
+ });
435
482
  }
436
- issues.push({
437
- file,
438
- line: lineNumber,
439
- type: "abbreviation",
440
- identifier: match[1],
441
- severity: "info",
442
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
443
- });
444
483
  }
445
- if (file.match(/\.(ts|tsx|js|jsx)$/)) {
484
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
446
485
  const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
447
486
  const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
448
487
  if (snakeCaseVars) {
@@ -456,47 +495,51 @@ function analyzeFileNaming(file, content) {
456
495
  });
457
496
  }
458
497
  }
459
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
460
- for (const match of booleanMatches) {
461
- const name = match[1];
462
- if (!name.match(/^(is|has|should|can|will|did)/i)) {
463
- issues.push({
464
- file,
465
- line: lineNumber,
466
- type: "unclear",
467
- identifier: name,
468
- severity: "info",
469
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
470
- });
498
+ if (!disabledChecks.has("unclear")) {
499
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
500
+ for (const match of booleanMatches) {
501
+ const name = match[1];
502
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
503
+ issues.push({
504
+ file,
505
+ line: lineNumber,
506
+ type: "unclear",
507
+ identifier: name,
508
+ severity: "info",
509
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
510
+ });
511
+ }
471
512
  }
472
513
  }
473
- const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
474
- for (const match of functionMatches) {
475
- const name = match[1];
476
- const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
477
- if (isKeyword) {
478
- continue;
479
- }
480
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
481
- if (isEntryPoint) {
482
- continue;
483
- }
484
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
485
- const isEventHandler = name.match(/^on[A-Z]/);
486
- const isDescriptiveLong = name.length > 15;
487
- 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)$/);
488
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
489
- const isCompoundWord = capitalCount >= 3;
490
- 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)/);
491
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
492
- issues.push({
493
- file,
494
- line: lineNumber,
495
- type: "unclear",
496
- identifier: name,
497
- severity: "info",
498
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
499
- });
514
+ if (!disabledChecks.has("unclear")) {
515
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
516
+ for (const match of functionMatches) {
517
+ const name = match[1];
518
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
519
+ if (isKeyword) {
520
+ continue;
521
+ }
522
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
523
+ if (isEntryPoint) {
524
+ continue;
525
+ }
526
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
527
+ const isEventHandler = name.match(/^on[A-Z]/);
528
+ const isDescriptiveLong = name.length > 15;
529
+ 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)$/);
530
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
531
+ const isCompoundWord = capitalCount >= 3;
532
+ 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)/);
533
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
534
+ issues.push({
535
+ file,
536
+ line: lineNumber,
537
+ type: "unclear",
538
+ identifier: name,
539
+ severity: "info",
540
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
541
+ });
542
+ }
500
543
  }
501
544
  }
502
545
  });
@@ -796,7 +839,7 @@ function generateRecommendations(namingIssues, patternIssues) {
796
839
  // src/cli.ts
797
840
  var import_chalk = __toESM(require("chalk"));
798
841
  var import_fs = require("fs");
799
- var import_path = require("path");
842
+ var import_path2 = require("path");
800
843
  var import_core4 = require("@aiready/core");
801
844
  var program = new import_commander.Command();
802
845
  program.name("aiready-consistency").description("Detect consistency issues in naming, patterns, and architecture").version("0.1.0").addHelpText("after", `
@@ -845,7 +888,7 @@ EXAMPLES:
845
888
  `consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
846
889
  directory
847
890
  );
848
- const dir = (0, import_path.dirname)(outputPath);
891
+ const dir = (0, import_path2.dirname)(outputPath);
849
892
  if (!(0, import_fs.existsSync)(dir)) {
850
893
  (0, import_fs.mkdirSync)(dir, { recursive: true });
851
894
  }
@@ -858,7 +901,7 @@ EXAMPLES:
858
901
  `consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
859
902
  directory
860
903
  );
861
- const dir = (0, import_path.dirname)(outputPath);
904
+ const dir = (0, import_path2.dirname)(outputPath);
862
905
  if (!(0, import_fs.existsSync)(dir)) {
863
906
  (0, import_fs.mkdirSync)(dir, { recursive: true });
864
907
  }
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  analyzeConsistency
4
- } from "./chunk-TLVLM3M5.mjs";
4
+ } from "./chunk-2BTBNG6X.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
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",
@@ -375,75 +376,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
375
376
  ]);
376
377
  async function analyzeNaming(files) {
377
378
  const issues = [];
379
+ const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
380
+ const config = (0, import_core.loadConfig)(rootDir);
381
+ const consistencyConfig = config?.tools?.["consistency"];
382
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
383
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
384
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
378
385
  for (const file of files) {
379
386
  const content = await (0, import_core.readFileContent)(file);
380
- const fileIssues = analyzeFileNaming(file, content);
387
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
381
388
  issues.push(...fileIssues);
382
389
  }
383
390
  return issues;
384
391
  }
385
- function analyzeFileNaming(file, content) {
392
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
386
393
  const issues = [];
387
394
  const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
388
395
  const lines = content.split("\n");
396
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
397
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
398
+ const getContextWindow = (index, windowSize = 3) => {
399
+ const start = Math.max(0, index - windowSize);
400
+ const end = Math.min(lines.length, index + windowSize + 1);
401
+ return lines.slice(start, end).join("\n");
402
+ };
403
+ const isShortLivedVariable = (varName, declarationIndex) => {
404
+ const searchRange = 5;
405
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
406
+ let usageCount = 0;
407
+ for (let i = declarationIndex; i < endIndex; i++) {
408
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
409
+ const matches = lines[i].match(regex);
410
+ if (matches) {
411
+ usageCount += matches.length;
412
+ }
413
+ }
414
+ return usageCount >= 2 && usageCount <= 3;
415
+ };
389
416
  lines.forEach((line, index) => {
390
417
  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;
418
+ const contextWindow = getContextWindow(index);
419
+ if (!disabledChecks.has("single-letter")) {
420
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
421
+ for (const match of singleLetterMatches) {
422
+ const letter = match[1].toLowerCase();
423
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
424
+ /\w+\s*=>\s*/.test(line);
425
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
426
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
427
+ /[a-z]\s*=>/.test(line) || // s => on same line
428
+ // Multi-line arrow function detection: look for pattern in context window
429
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
430
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
431
+ const isShortLived = isShortLivedVariable(letter, index);
432
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
433
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
434
+ continue;
435
+ }
436
+ issues.push({
437
+ file,
438
+ line: lineNumber,
439
+ type: "poor-naming",
440
+ identifier: match[1],
441
+ severity: "minor",
442
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
443
+ });
402
444
  }
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
445
  }
412
446
  }
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)) {
447
+ if (!disabledChecks.has("abbreviation")) {
448
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
449
+ for (const match of abbreviationMatches) {
450
+ const abbrev = match[1].toLowerCase();
451
+ if (allShortWords.has(abbrev)) {
430
452
  continue;
431
453
  }
432
- const isUserContext = /user|auth|account/i.test(line);
433
- if (isUserContext && abbrev === "u") {
454
+ if (allAbbreviations.has(abbrev)) {
434
455
  continue;
435
456
  }
457
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
458
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
459
+ // Multi-line arrow function: check context window
460
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
461
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
462
+ if (isArrowFunctionParam) {
463
+ continue;
464
+ }
465
+ if (abbrev.length <= 2) {
466
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
467
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
468
+ continue;
469
+ }
470
+ const isUserContext = /user|auth|account/i.test(line);
471
+ if (isUserContext && abbrev === "u") {
472
+ continue;
473
+ }
474
+ }
475
+ issues.push({
476
+ file,
477
+ line: lineNumber,
478
+ type: "abbreviation",
479
+ identifier: match[1],
480
+ severity: "info",
481
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
482
+ });
436
483
  }
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
484
  }
446
- if (file.match(/\.(ts|tsx|js|jsx)$/)) {
485
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
447
486
  const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
448
487
  const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
449
488
  if (snakeCaseVars) {
@@ -457,47 +496,51 @@ function analyzeFileNaming(file, content) {
457
496
  });
458
497
  }
459
498
  }
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
- });
499
+ if (!disabledChecks.has("unclear")) {
500
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
501
+ for (const match of booleanMatches) {
502
+ const name = match[1];
503
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
504
+ issues.push({
505
+ file,
506
+ line: lineNumber,
507
+ type: "unclear",
508
+ identifier: name,
509
+ severity: "info",
510
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
511
+ });
512
+ }
472
513
  }
473
514
  }
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
- });
515
+ if (!disabledChecks.has("unclear")) {
516
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
517
+ for (const match of functionMatches) {
518
+ const name = match[1];
519
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
520
+ if (isKeyword) {
521
+ continue;
522
+ }
523
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
524
+ if (isEntryPoint) {
525
+ continue;
526
+ }
527
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
528
+ const isEventHandler = name.match(/^on[A-Z]/);
529
+ const isDescriptiveLong = name.length > 15;
530
+ 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)$/);
531
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
532
+ const isCompoundWord = capitalCount >= 3;
533
+ 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)/);
534
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
535
+ issues.push({
536
+ file,
537
+ line: lineNumber,
538
+ type: "unclear",
539
+ identifier: name,
540
+ severity: "info",
541
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
542
+ });
543
+ }
501
544
  }
502
545
  }
503
546
  });
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-2BTBNG6X.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.3.5",
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
+ }