@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/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",
@@ -88,6 +89,13 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
88
89
  "tmp",
89
90
  "ext",
90
91
  "sep",
92
+ // Prepositions and conjunctions
93
+ "and",
94
+ "from",
95
+ "how",
96
+ "pad",
97
+ "bar",
98
+ "non",
91
99
  // Additional full words commonly flagged
92
100
  "tax",
93
101
  "cat",
@@ -334,6 +342,17 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
334
342
  "alb",
335
343
  "nlb",
336
344
  "aws",
345
+ "ses",
346
+ "gst",
347
+ "cdk",
348
+ "btn",
349
+ "buf",
350
+ "agg",
351
+ "ocr",
352
+ "ai",
353
+ "cf",
354
+ "cfn",
355
+ "ga",
337
356
  // Metrics/Performance
338
357
  "fcp",
339
358
  "lcp",
@@ -345,12 +364,14 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
345
364
  "qps",
346
365
  "rps",
347
366
  "tps",
367
+ "wpm",
348
368
  // Testing & i18n
349
369
  "po",
350
370
  "e2e",
351
371
  "a11y",
352
372
  "i18n",
353
373
  "l10n",
374
+ "spy",
354
375
  // Domain-specific abbreviations (context-aware)
355
376
  "sk",
356
377
  "fy",
@@ -360,6 +381,16 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
360
381
  "cta",
361
382
  "roi",
362
383
  "kpi",
384
+ "ttl",
385
+ "pct",
386
+ // Technical abbreviations
387
+ "mac",
388
+ "hex",
389
+ "esm",
390
+ "git",
391
+ "rec",
392
+ "loc",
393
+ "dup",
363
394
  // Boolean helpers (these are intentional short names)
364
395
  "is",
365
396
  "has",
@@ -374,75 +405,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
374
405
  ]);
375
406
  async function analyzeNaming(files) {
376
407
  const issues = [];
408
+ const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
409
+ const config = (0, import_core.loadConfig)(rootDir);
410
+ const consistencyConfig = config?.tools?.["consistency"];
411
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
412
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
413
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
377
414
  for (const file of files) {
378
415
  const content = await (0, import_core.readFileContent)(file);
379
- const fileIssues = analyzeFileNaming(file, content);
416
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
380
417
  issues.push(...fileIssues);
381
418
  }
382
419
  return issues;
383
420
  }
384
- function analyzeFileNaming(file, content) {
421
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
385
422
  const issues = [];
386
423
  const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
387
424
  const lines = content.split("\n");
425
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
426
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
427
+ const getContextWindow = (index, windowSize = 3) => {
428
+ const start = Math.max(0, index - windowSize);
429
+ const end = Math.min(lines.length, index + windowSize + 1);
430
+ return lines.slice(start, end).join("\n");
431
+ };
432
+ const isShortLivedVariable = (varName, declarationIndex) => {
433
+ const searchRange = 5;
434
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
435
+ let usageCount = 0;
436
+ for (let i = declarationIndex; i < endIndex; i++) {
437
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
438
+ const matches = lines[i].match(regex);
439
+ if (matches) {
440
+ usageCount += matches.length;
441
+ }
442
+ }
443
+ return usageCount >= 2 && usageCount <= 3;
444
+ };
388
445
  lines.forEach((line, index) => {
389
446
  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;
447
+ const contextWindow = getContextWindow(index);
448
+ if (!disabledChecks.has("single-letter")) {
449
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
450
+ for (const match of singleLetterMatches) {
451
+ const letter = match[1].toLowerCase();
452
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
453
+ /\w+\s*=>\s*/.test(line);
454
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
455
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
456
+ /[a-z]\s*=>/.test(line) || // s => on same line
457
+ // Multi-line arrow function detection: look for pattern in context window
458
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
459
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
460
+ const isShortLived = isShortLivedVariable(letter, index);
461
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
462
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
463
+ continue;
464
+ }
465
+ issues.push({
466
+ file,
467
+ line: lineNumber,
468
+ type: "poor-naming",
469
+ identifier: match[1],
470
+ severity: "minor",
471
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
472
+ });
401
473
  }
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
474
  }
411
475
  }
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)) {
476
+ if (!disabledChecks.has("abbreviation")) {
477
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
478
+ for (const match of abbreviationMatches) {
479
+ const abbrev = match[1].toLowerCase();
480
+ if (allShortWords.has(abbrev)) {
481
+ continue;
482
+ }
483
+ if (allAbbreviations.has(abbrev)) {
429
484
  continue;
430
485
  }
431
- const isUserContext = /user|auth|account/i.test(line);
432
- if (isUserContext && abbrev === "u") {
486
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
487
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
488
+ // Multi-line arrow function: check context window
489
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
490
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
491
+ if (isArrowFunctionParam) {
433
492
  continue;
434
493
  }
494
+ if (abbrev.length <= 2) {
495
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
496
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
497
+ continue;
498
+ }
499
+ const isUserContext = /user|auth|account/i.test(line);
500
+ if (isUserContext && abbrev === "u") {
501
+ continue;
502
+ }
503
+ }
504
+ issues.push({
505
+ file,
506
+ line: lineNumber,
507
+ type: "abbreviation",
508
+ identifier: match[1],
509
+ severity: "info",
510
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
511
+ });
435
512
  }
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
513
  }
445
- if (file.match(/\.(ts|tsx|js|jsx)$/)) {
514
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
446
515
  const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
447
516
  const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
448
517
  if (snakeCaseVars) {
@@ -456,47 +525,55 @@ function analyzeFileNaming(file, content) {
456
525
  });
457
526
  }
458
527
  }
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
- });
528
+ if (!disabledChecks.has("unclear")) {
529
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
530
+ for (const match of booleanMatches) {
531
+ const name = match[1];
532
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
533
+ issues.push({
534
+ file,
535
+ line: lineNumber,
536
+ type: "unclear",
537
+ identifier: name,
538
+ severity: "info",
539
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
540
+ });
541
+ }
471
542
  }
472
543
  }
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
- });
544
+ if (!disabledChecks.has("unclear")) {
545
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
546
+ for (const match of functionMatches) {
547
+ const name = match[1];
548
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
549
+ if (isKeyword) {
550
+ continue;
551
+ }
552
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
553
+ if (isEntryPoint) {
554
+ continue;
555
+ }
556
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
557
+ const isEventHandler = name.match(/^on[A-Z]/);
558
+ const isDescriptiveLong = name.length > 15;
559
+ const isReactHook = name.match(/^use[A-Z]/);
560
+ 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)$/);
561
+ const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
562
+ name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
563
+ const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
564
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
565
+ const isCompoundWord = capitalCount >= 3;
566
+ 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)/);
567
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
568
+ issues.push({
569
+ file,
570
+ line: lineNumber,
571
+ type: "unclear",
572
+ identifier: name,
573
+ severity: "info",
574
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
575
+ });
576
+ }
500
577
  }
501
578
  }
502
579
  });
@@ -796,7 +873,7 @@ function generateRecommendations(namingIssues, patternIssues) {
796
873
  // src/cli.ts
797
874
  var import_chalk = __toESM(require("chalk"));
798
875
  var import_fs = require("fs");
799
- var import_path = require("path");
876
+ var import_path2 = require("path");
800
877
  var import_core4 = require("@aiready/core");
801
878
  var program = new import_commander.Command();
802
879
  program.name("aiready-consistency").description("Detect consistency issues in naming, patterns, and architecture").version("0.1.0").addHelpText("after", `
@@ -845,7 +922,7 @@ EXAMPLES:
845
922
  `consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
846
923
  directory
847
924
  );
848
- const dir = (0, import_path.dirname)(outputPath);
925
+ const dir = (0, import_path2.dirname)(outputPath);
849
926
  if (!(0, import_fs.existsSync)(dir)) {
850
927
  (0, import_fs.mkdirSync)(dir, { recursive: true });
851
928
  }
@@ -858,7 +935,7 @@ EXAMPLES:
858
935
  `consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
859
936
  directory
860
937
  );
861
- const dir = (0, import_path.dirname)(outputPath);
938
+ const dir = (0, import_path2.dirname)(outputPath);
862
939
  if (!(0, import_fs.existsSync)(dir)) {
863
940
  (0, import_fs.mkdirSync)(dir, { recursive: true });
864
941
  }
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-CZUJTDNH.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";