@aiready/ai-signal-clarity 0.11.21 → 0.12.0

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/ai-signal-clarity@0.11.20 build /Users/pengcao/projects/aiready/packages/ai-signal-clarity
3
+ > @aiready/ai-signal-clarity@0.11.21 build /Users/pengcao/projects/aiready/packages/ai-signal-clarity
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,16 +9,16 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- ESM dist/chunk-4RIAQ4QQ.mjs 16.98 KB
12
+ ESM dist/chunk-5WOEEOWG.mjs 18.21 KB
13
13
  ESM dist/cli.mjs 6.83 KB
14
14
  ESM dist/index.mjs 1.40 KB
15
- ESM ⚡️ Build success in 128ms
16
- CJS dist/cli.js 25.67 KB
17
- CJS dist/index.js 19.76 KB
18
- CJS ⚡️ Build success in 130ms
15
+ ESM ⚡️ Build success in 67ms
16
+ CJS dist/cli.js 26.94 KB
17
+ CJS dist/index.js 21.03 KB
18
+ CJS ⚡️ Build success in 67ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 1983ms
20
+ DTS ⚡️ Build success in 1438ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 2.80 KB
22
+ DTS dist/index.d.ts 2.90 KB
23
23
  DTS dist/cli.d.mts 20.00 B
24
- DTS dist/index.d.mts 2.80 KB
24
+ DTS dist/index.d.mts 2.90 KB
@@ -1,21 +1,21 @@
1
1
 
2
2
  
3
- > @aiready/ai-signal-clarity@0.11.20 test /Users/pengcao/projects/aiready/packages/ai-signal-clarity
3
+ > @aiready/ai-signal-clarity@0.11.21 test /Users/pengcao/projects/aiready/packages/ai-signal-clarity
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/ai-signal-clarity
8
8
 
9
- ✓ src/__tests__/analyzer.test.ts (2 tests) 4ms
9
+ ✓ src/__tests__/provider.test.ts (2 tests) 12ms
10
+ ✓ src/__tests__/analyzer.test.ts (2 tests) 3ms
10
11
  ✓ src/__tests__/scoring.test.ts (2 tests) 2ms
11
- ✓ src/__tests__/provider.test.ts (2 tests) 8ms
12
+ ✓ src/__tests__/scanner-advanced.test.ts (2 tests) 33ms
12
13
  ✓ src/__tests__/contract.test.ts (1 test) 2ms
13
- ✓ src/__tests__/scanner-advanced.test.ts (2 tests) 35ms
14
- ✓ src/__tests__/scanner.test.ts (6 tests) 29ms
14
+ ✓ src/__tests__/scanner.test.ts (6 tests) 23ms
15
15
 
16
16
   Test Files  6 passed (6)
17
17
   Tests  15 passed (15)
18
-  Start at  15:19:19
19
-  Duration  1.07s (transform 477ms, setup 0ms, import 3.40s, tests 79ms, environment 0ms)
18
+  Start at  18:35:55
19
+  Duration  1.38s (transform 1.44s, setup 0ms, import 6.01s, tests 75ms, environment 1ms)
20
20
 
21
21
  [?25h
@@ -0,0 +1,526 @@
1
+ // src/scanner.ts
2
+ import { readFileSync } from "fs";
3
+ import { getParser, Severity, IssueType } from "@aiready/core";
4
+ var AMBIGUOUS_NAME_PATTERNS = [
5
+ /^[a-z]$/,
6
+ // single letter: a, b, x, y
7
+ /^(tmp|temp|data|obj|val|res|ret|result|item|elem|thing|stuff|info|misc|util|helper|handler|cb|fn|func)$/i,
8
+ /^[a-z]\d+$/
9
+ // x1, x2, n3
10
+ ];
11
+ var MAGIC_LITERAL_IGNORE = /* @__PURE__ */ new Set([0, 1, -1, 2, 10, 100, 1e3, 1024]);
12
+ var MAGIC_STRING_IGNORE = /* @__PURE__ */ new Set([
13
+ "",
14
+ " ",
15
+ "\n",
16
+ " ",
17
+ "utf8",
18
+ "utf-8",
19
+ "hex",
20
+ "base64",
21
+ "true",
22
+ "false",
23
+ "null"
24
+ ]);
25
+ function isAmbiguousName(name) {
26
+ return AMBIGUOUS_NAME_PATTERNS.some((p) => p.test(name));
27
+ }
28
+ function isMagicNumber(value) {
29
+ return !MAGIC_LITERAL_IGNORE.has(value);
30
+ }
31
+ function isMagicString(value) {
32
+ if (value.length === 0) return false;
33
+ if (MAGIC_STRING_IGNORE.has(value)) return false;
34
+ return value.length <= 20 && !/[/.]/.test(value) && !/^\s+$/.test(value);
35
+ }
36
+ async function scanFile(filePath, options = { rootDir: "." }) {
37
+ let code;
38
+ try {
39
+ code = readFileSync(filePath, "utf-8");
40
+ } catch {
41
+ return emptyResult(filePath);
42
+ }
43
+ const parser = getParser(filePath);
44
+ if (!parser) return emptyResult(filePath);
45
+ try {
46
+ await parser.initialize();
47
+ const result = parser.parse(code, filePath);
48
+ const ast = await parser.getAST(code, filePath);
49
+ const issues = [];
50
+ const lineCount = code.split("\n").length;
51
+ const signals = {
52
+ magicLiterals: 0,
53
+ booleanTraps: 0,
54
+ ambiguousNames: 0,
55
+ undocumentedExports: 0,
56
+ implicitSideEffects: 0,
57
+ deepCallbacks: 0,
58
+ overloadedSymbols: 0,
59
+ largeFiles: 0,
60
+ totalSymbols: result.exports.length + result.imports.length,
61
+ totalExports: result.exports.length,
62
+ totalLines: lineCount
63
+ };
64
+ const symbolCounts = /* @__PURE__ */ new Map();
65
+ if (options.checkLargeFiles !== false) {
66
+ if (lineCount > 750) {
67
+ signals.largeFiles++;
68
+ issues.push({
69
+ type: IssueType.AiSignalClarity,
70
+ category: "large-file",
71
+ severity: Severity.Critical,
72
+ message: `Extreme file length (${lineCount} lines) \u2014 AI context window will overflow or "Lose the Middle" critical details.`,
73
+ location: { file: filePath, line: 1 },
74
+ suggestion: "Split into smaller, single-responsibility modules (< 500 lines)."
75
+ });
76
+ } else if (lineCount > 500) {
77
+ signals.largeFiles++;
78
+ issues.push({
79
+ type: IssueType.AiSignalClarity,
80
+ category: "large-file",
81
+ severity: Severity.Major,
82
+ message: `Large file (${lineCount} lines) \u2014 pushing the limits of effective AI reasoning.`,
83
+ location: { file: filePath, line: 1 },
84
+ suggestion: "Consider refactoring and extracting logic to new files."
85
+ });
86
+ }
87
+ }
88
+ for (const exp of result.exports) {
89
+ symbolCounts.set(exp.name, (symbolCounts.get(exp.name) || 0) + 1);
90
+ if (options.checkUndocumentedExports !== false) {
91
+ if (!exp.documentation || !exp.documentation.content) {
92
+ signals.undocumentedExports++;
93
+ issues.push({
94
+ type: IssueType.AiSignalClarity,
95
+ category: "undocumented-export",
96
+ severity: Severity.Minor,
97
+ message: `Public export "${exp.name}" has no documentation \u2014 AI fabricates behavior from the name alone.`,
98
+ location: {
99
+ file: filePath,
100
+ line: exp.loc?.start.line || 1,
101
+ column: exp.loc?.start.column
102
+ },
103
+ suggestion: "Add a docstring or comment describing parameters, return value, and side effects."
104
+ });
105
+ }
106
+ }
107
+ if (options.checkImplicitSideEffects !== false) {
108
+ if (exp.hasSideEffects && !exp.isPure && exp.type === "function") {
109
+ const lowerName = exp.name.toLowerCase();
110
+ const looksPure = !/(set|update|save|delete|create|write|send|post|sync)/.test(
111
+ lowerName
112
+ );
113
+ if (looksPure) {
114
+ signals.implicitSideEffects++;
115
+ issues.push({
116
+ type: IssueType.AiSignalClarity,
117
+ category: "implicit-side-effect",
118
+ severity: Severity.Major,
119
+ message: `Function "${exp.name}" mutates external state but name doesn't reflect it \u2014 AI misses this contract.`,
120
+ location: {
121
+ file: filePath,
122
+ line: exp.loc?.start.line || 1
123
+ },
124
+ suggestion: "Make side-effects explicit in function name (e.g., updateX) or return a result."
125
+ });
126
+ }
127
+ }
128
+ }
129
+ if (options.checkAmbiguousNames !== false && isAmbiguousName(exp.name)) {
130
+ signals.ambiguousNames++;
131
+ issues.push({
132
+ type: IssueType.AmbiguousApi,
133
+ category: "ambiguous-name",
134
+ severity: Severity.Info,
135
+ message: `Ambiguous public export "${exp.name}" \u2014 AI cannot infer intent and will guess incorrectly.`,
136
+ location: {
137
+ file: filePath,
138
+ line: exp.loc?.start.line || 1
139
+ },
140
+ suggestion: "Use a domain-descriptive name instead."
141
+ });
142
+ }
143
+ }
144
+ if (options.checkOverloadedSymbols !== false) {
145
+ for (const [name, count] of symbolCounts.entries()) {
146
+ if (count > 1 && name !== "default" && name !== "anonymous") {
147
+ signals.overloadedSymbols++;
148
+ issues.push({
149
+ type: IssueType.AiSignalClarity,
150
+ category: "overloaded-symbol",
151
+ severity: Severity.Critical,
152
+ message: `Symbol "${name}" has ${count} overloaded signatures \u2014 AI often picks the wrong one or gets confused by conflicting contracts.`,
153
+ location: {
154
+ file: filePath,
155
+ line: 1
156
+ },
157
+ suggestion: `Rename overloads to unique, descriptive names if possible.`
158
+ });
159
+ }
160
+ }
161
+ }
162
+ if (ast) {
163
+ let callbackDepth = 0;
164
+ let maxCallbackDepth = 0;
165
+ const visitNode = (node) => {
166
+ if (!node) return;
167
+ if (options.checkMagicLiterals !== false) {
168
+ if (node.type === "number") {
169
+ const val = parseFloat(node.text);
170
+ if (!isNaN(val) && isMagicNumber(val)) {
171
+ signals.magicLiterals++;
172
+ issues.push({
173
+ type: IssueType.MagicLiteral,
174
+ category: "magic-literal",
175
+ severity: Severity.Minor,
176
+ message: `Magic number ${node.text} \u2014 AI will invent wrong semantics. Extract to a named constant.`,
177
+ location: {
178
+ file: filePath,
179
+ line: node.startPosition.row + 1,
180
+ column: node.startPosition.column
181
+ },
182
+ suggestion: `const MEANINGFUL_NAME = ${node.text};`
183
+ });
184
+ }
185
+ } else if (node.type === "string" || node.type === "string_literal") {
186
+ const val = node.text.replace(/['"]/g, "");
187
+ if (isMagicString(val)) {
188
+ signals.magicLiterals++;
189
+ issues.push({
190
+ type: IssueType.MagicLiteral,
191
+ category: "magic-literal",
192
+ severity: Severity.Info,
193
+ message: `Magic string "${val}" \u2014 intent is ambiguous to AI. Consider a named constant.`,
194
+ location: {
195
+ file: filePath,
196
+ line: node.startPosition.row + 1
197
+ }
198
+ });
199
+ }
200
+ } else if (node.type === "Literal") {
201
+ if (typeof node.value === "number" && isMagicNumber(node.value)) {
202
+ signals.magicLiterals++;
203
+ issues.push({
204
+ type: IssueType.MagicLiteral,
205
+ category: "magic-literal",
206
+ severity: Severity.Minor,
207
+ message: `Magic number ${node.value} \u2014 AI will invent wrong semantics. Extract to a named constant.`,
208
+ location: {
209
+ file: filePath,
210
+ line: node.loc?.start.line || 1,
211
+ column: node.loc?.start.column
212
+ },
213
+ suggestion: `const MEANINGFUL_NAME = ${node.value};`
214
+ });
215
+ } else if (typeof node.value === "string" && isMagicString(node.value)) {
216
+ signals.magicLiterals++;
217
+ issues.push({
218
+ type: IssueType.MagicLiteral,
219
+ category: "magic-literal",
220
+ severity: Severity.Info,
221
+ message: `Magic string "${node.value}" \u2014 intent is ambiguous to AI. Consider a named constant.`,
222
+ location: {
223
+ file: filePath,
224
+ line: node.loc?.start.line || 1
225
+ }
226
+ });
227
+ }
228
+ }
229
+ }
230
+ if (options.checkBooleanTraps !== false) {
231
+ if (node.type === "argument_list") {
232
+ const hasBool = node.namedChildren?.some(
233
+ (c) => c.type === "true" || c.type === "false" || c.type === "boolean" && (c.text === "true" || c.text === "false")
234
+ );
235
+ if (hasBool) {
236
+ signals.booleanTraps++;
237
+ issues.push({
238
+ type: IssueType.BooleanTrap,
239
+ category: "boolean-trap",
240
+ severity: Severity.Major,
241
+ message: `Boolean trap: positional boolean argument at call site. AI inverts intent ~30% of the time.`,
242
+ location: {
243
+ file: filePath,
244
+ line: (node.startPosition?.row || 0) + 1
245
+ },
246
+ suggestion: "Replace boolean arg with a named options object or separate functions."
247
+ });
248
+ }
249
+ } else if (node.type === "CallExpression") {
250
+ const hasBool = node.arguments.some(
251
+ (arg) => arg.type === "Literal" && typeof arg.value === "boolean"
252
+ );
253
+ if (hasBool) {
254
+ signals.booleanTraps++;
255
+ issues.push({
256
+ type: IssueType.BooleanTrap,
257
+ category: "boolean-trap",
258
+ severity: Severity.Major,
259
+ message: `Boolean trap: positional boolean argument at call site. AI inverts intent ~30% of the time.`,
260
+ location: {
261
+ file: filePath,
262
+ line: node.loc?.start.line || 1
263
+ },
264
+ suggestion: "Replace boolean arg with a named options object or separate functions."
265
+ });
266
+ }
267
+ }
268
+ }
269
+ if (options.checkAmbiguousNames !== false) {
270
+ if (node.type === "variable_declarator") {
271
+ const nameNode = node.childForFieldName("name");
272
+ if (nameNode && isAmbiguousName(nameNode.text)) {
273
+ signals.ambiguousNames++;
274
+ issues.push({
275
+ type: IssueType.AmbiguousApi,
276
+ category: "ambiguous-name",
277
+ severity: Severity.Info,
278
+ message: `Ambiguous variable name "${nameNode.text}" \u2014 AI intent is unclear.`,
279
+ location: {
280
+ file: filePath,
281
+ line: node.startPosition.row + 1
282
+ }
283
+ });
284
+ }
285
+ } else if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
286
+ if (isAmbiguousName(node.id.name)) {
287
+ signals.ambiguousNames++;
288
+ issues.push({
289
+ type: IssueType.AmbiguousApi,
290
+ category: "ambiguous-name",
291
+ severity: Severity.Info,
292
+ message: `Ambiguous variable name "${node.id.name}" \u2014 AI intent is unclear.`,
293
+ location: {
294
+ file: filePath,
295
+ line: node.loc?.start.line || 1
296
+ }
297
+ });
298
+ }
299
+ }
300
+ }
301
+ const nodeType = (node.type || "").toLowerCase();
302
+ const isFunction = nodeType.includes("function") || nodeType.includes("arrow") || nodeType.includes("lambda") || nodeType === "method_declaration";
303
+ if (isFunction) {
304
+ callbackDepth++;
305
+ maxCallbackDepth = Math.max(maxCallbackDepth, callbackDepth);
306
+ }
307
+ if (node.namedChildren) {
308
+ for (const child of node.namedChildren) {
309
+ visitNode(child);
310
+ }
311
+ } else {
312
+ for (const key in node) {
313
+ if (key === "parent" || key === "loc" || key === "range") continue;
314
+ const child = node[key];
315
+ if (child && typeof child === "object") {
316
+ if (Array.isArray(child)) {
317
+ child.forEach(
318
+ (c) => c && typeof c.type === "string" && visitNode(c)
319
+ );
320
+ } else if (typeof child.type === "string") {
321
+ visitNode(child);
322
+ }
323
+ }
324
+ }
325
+ }
326
+ if (isFunction) {
327
+ callbackDepth--;
328
+ }
329
+ };
330
+ if (ast.rootNode) {
331
+ visitNode(ast.rootNode);
332
+ } else {
333
+ visitNode(ast);
334
+ }
335
+ if (options.checkDeepCallbacks !== false && maxCallbackDepth >= 3) {
336
+ signals.deepCallbacks = maxCallbackDepth - 2;
337
+ issues.push({
338
+ type: IssueType.AiSignalClarity,
339
+ category: "deep-callback",
340
+ severity: Severity.Major,
341
+ message: `Deeply nested logic (depth ${maxCallbackDepth}) \u2014 AI loses control flow context beyond 3 levels.`,
342
+ location: {
343
+ file: filePath,
344
+ line: 1
345
+ },
346
+ suggestion: "Extract nested logic into named functions or flatten the structure."
347
+ });
348
+ }
349
+ }
350
+ return {
351
+ filePath,
352
+ issues,
353
+ signals,
354
+ fileName: filePath,
355
+ metrics: {
356
+ totalSymbols: signals.totalSymbols,
357
+ totalExports: signals.totalExports
358
+ }
359
+ };
360
+ } catch (error) {
361
+ console.error(`AI Signal Clarity: Failed to scan ${filePath}: ${error}`);
362
+ return emptyResult(filePath);
363
+ }
364
+ }
365
+ function emptyResult(filePath) {
366
+ return {
367
+ filePath,
368
+ issues: [],
369
+ signals: {
370
+ magicLiterals: 0,
371
+ booleanTraps: 0,
372
+ ambiguousNames: 0,
373
+ undocumentedExports: 0,
374
+ implicitSideEffects: 0,
375
+ deepCallbacks: 0,
376
+ overloadedSymbols: 0,
377
+ largeFiles: 0,
378
+ totalSymbols: 0,
379
+ totalExports: 0,
380
+ totalLines: 0
381
+ },
382
+ fileName: filePath,
383
+ metrics: {
384
+ totalSymbols: 0,
385
+ totalExports: 0
386
+ }
387
+ };
388
+ }
389
+
390
+ // src/analyzer.ts
391
+ import {
392
+ scanFiles,
393
+ calculateAiSignalClarity,
394
+ Severity as Severity2,
395
+ emitProgress
396
+ } from "@aiready/core";
397
+ async function analyzeAiSignalClarity(options) {
398
+ const files = await scanFiles(options);
399
+ const results = [];
400
+ const aggregate = {
401
+ magicLiterals: 0,
402
+ booleanTraps: 0,
403
+ ambiguousNames: 0,
404
+ undocumentedExports: 0,
405
+ implicitSideEffects: 0,
406
+ deepCallbacks: 0,
407
+ overloadedSymbols: 0,
408
+ largeFiles: 0,
409
+ totalSymbols: 0,
410
+ totalExports: 0,
411
+ totalLines: 0
412
+ };
413
+ let processed = 0;
414
+ for (const filePath of files) {
415
+ processed++;
416
+ emitProgress(
417
+ processed,
418
+ files.length,
419
+ "ai-signal-clarity",
420
+ "analyzing files",
421
+ options.onProgress
422
+ );
423
+ const result = await scanFile(filePath, options);
424
+ results.push(result);
425
+ for (const key of Object.keys(aggregate)) {
426
+ aggregate[key] += result.signals[key] ?? 0;
427
+ }
428
+ }
429
+ const riskResult = calculateAiSignalClarity({
430
+ overloadedSymbols: aggregate.overloadedSymbols,
431
+ magicLiterals: aggregate.magicLiterals,
432
+ booleanTraps: aggregate.booleanTraps,
433
+ implicitSideEffects: aggregate.implicitSideEffects,
434
+ deepCallbacks: aggregate.deepCallbacks,
435
+ ambiguousNames: aggregate.ambiguousNames,
436
+ undocumentedExports: aggregate.undocumentedExports,
437
+ largeFiles: aggregate.largeFiles,
438
+ totalSymbols: Math.max(1, aggregate.totalSymbols),
439
+ totalExports: Math.max(1, aggregate.totalExports)
440
+ });
441
+ const getLevel = (s) => {
442
+ if (s === Severity2.Critical || s === "critical") return 4;
443
+ if (s === Severity2.Major || s === "major") return 3;
444
+ if (s === Severity2.Minor || s === "minor") return 2;
445
+ if (s === Severity2.Info || s === "info") return 1;
446
+ return 0;
447
+ };
448
+ const allIssues = results.flatMap((r) => r.issues);
449
+ const criticalSignals = allIssues.filter(
450
+ (i) => getLevel(i.severity) === 4
451
+ ).length;
452
+ const majorSignals = allIssues.filter(
453
+ (i) => getLevel(i.severity) === 3
454
+ ).length;
455
+ const minorSignals = allIssues.filter(
456
+ (i) => getLevel(i.severity) === 2
457
+ ).length;
458
+ const minSev = options.minSeverity ?? Severity2.Info;
459
+ const filteredResults = results.map((r) => ({
460
+ ...r,
461
+ issues: r.issues.filter((i) => getLevel(i.severity) >= getLevel(minSev))
462
+ }));
463
+ return {
464
+ summary: {
465
+ filesAnalyzed: files.length,
466
+ totalSignals: allIssues.length,
467
+ criticalSignals,
468
+ majorSignals,
469
+ minorSignals,
470
+ topRisk: riskResult.topRisk,
471
+ rating: riskResult.rating
472
+ },
473
+ results: filteredResults,
474
+ aggregateSignals: aggregate,
475
+ recommendations: riskResult.recommendations
476
+ };
477
+ }
478
+
479
+ // src/scoring.ts
480
+ import { calculateAiSignalClarity as calculateAiSignalClarity2, ToolName } from "@aiready/core";
481
+ function calculateAiSignalClarityScore(report) {
482
+ const { aggregateSignals } = report;
483
+ const riskResult = calculateAiSignalClarity2({
484
+ overloadedSymbols: aggregateSignals.overloadedSymbols,
485
+ magicLiterals: aggregateSignals.magicLiterals,
486
+ booleanTraps: aggregateSignals.booleanTraps,
487
+ implicitSideEffects: aggregateSignals.implicitSideEffects,
488
+ deepCallbacks: aggregateSignals.deepCallbacks,
489
+ ambiguousNames: aggregateSignals.ambiguousNames,
490
+ undocumentedExports: aggregateSignals.undocumentedExports,
491
+ largeFiles: aggregateSignals.largeFiles,
492
+ totalSymbols: Math.max(1, aggregateSignals.totalSymbols),
493
+ totalExports: Math.max(1, aggregateSignals.totalExports)
494
+ });
495
+ const score = Math.max(0, 100 - riskResult.score);
496
+ const factors = riskResult.signals.map(
497
+ (sig) => ({
498
+ name: sig.name,
499
+ impact: -sig.riskContribution,
500
+ description: sig.description
501
+ })
502
+ );
503
+ const recommendations = riskResult.recommendations.map((rec) => ({
504
+ action: rec,
505
+ estimatedImpact: 8,
506
+ priority: riskResult.score > 50 ? "high" : "medium"
507
+ }));
508
+ return {
509
+ toolName: ToolName.AiSignalClarity,
510
+ score,
511
+ rawMetrics: {
512
+ riskScore: riskResult.score,
513
+ rating: riskResult.rating,
514
+ topRisk: riskResult.topRisk,
515
+ ...aggregateSignals
516
+ },
517
+ factors,
518
+ recommendations
519
+ };
520
+ }
521
+
522
+ export {
523
+ scanFile,
524
+ analyzeAiSignalClarity,
525
+ calculateAiSignalClarityScore
526
+ };
package/dist/cli.js CHANGED
@@ -78,6 +78,7 @@ async function scanFile(filePath, options = { rootDir: "." }) {
78
78
  const result = parser.parse(code, filePath);
79
79
  const ast = await parser.getAST(code, filePath);
80
80
  const issues = [];
81
+ const lineCount = code.split("\n").length;
81
82
  const signals = {
82
83
  magicLiterals: 0,
83
84
  booleanTraps: 0,
@@ -86,10 +87,35 @@ async function scanFile(filePath, options = { rootDir: "." }) {
86
87
  implicitSideEffects: 0,
87
88
  deepCallbacks: 0,
88
89
  overloadedSymbols: 0,
90
+ largeFiles: 0,
89
91
  totalSymbols: result.exports.length + result.imports.length,
90
- totalExports: result.exports.length
92
+ totalExports: result.exports.length,
93
+ totalLines: lineCount
91
94
  };
92
95
  const symbolCounts = /* @__PURE__ */ new Map();
96
+ if (options.checkLargeFiles !== false) {
97
+ if (lineCount > 750) {
98
+ signals.largeFiles++;
99
+ issues.push({
100
+ type: import_core.IssueType.AiSignalClarity,
101
+ category: "large-file",
102
+ severity: import_core.Severity.Critical,
103
+ message: `Extreme file length (${lineCount} lines) \u2014 AI context window will overflow or "Lose the Middle" critical details.`,
104
+ location: { file: filePath, line: 1 },
105
+ suggestion: "Split into smaller, single-responsibility modules (< 500 lines)."
106
+ });
107
+ } else if (lineCount > 500) {
108
+ signals.largeFiles++;
109
+ issues.push({
110
+ type: import_core.IssueType.AiSignalClarity,
111
+ category: "large-file",
112
+ severity: import_core.Severity.Major,
113
+ message: `Large file (${lineCount} lines) \u2014 pushing the limits of effective AI reasoning.`,
114
+ location: { file: filePath, line: 1 },
115
+ suggestion: "Consider refactoring and extracting logic to new files."
116
+ });
117
+ }
118
+ }
93
119
  for (const exp of result.exports) {
94
120
  symbolCounts.set(exp.name, (symbolCounts.get(exp.name) || 0) + 1);
95
121
  if (options.checkUndocumentedExports !== false) {
@@ -379,8 +405,10 @@ function emptyResult(filePath) {
379
405
  implicitSideEffects: 0,
380
406
  deepCallbacks: 0,
381
407
  overloadedSymbols: 0,
408
+ largeFiles: 0,
382
409
  totalSymbols: 0,
383
- totalExports: 0
410
+ totalExports: 0,
411
+ totalLines: 0
384
412
  },
385
413
  fileName: filePath,
386
414
  metrics: {
@@ -402,8 +430,10 @@ async function analyzeAiSignalClarity(options) {
402
430
  implicitSideEffects: 0,
403
431
  deepCallbacks: 0,
404
432
  overloadedSymbols: 0,
433
+ largeFiles: 0,
405
434
  totalSymbols: 0,
406
- totalExports: 0
435
+ totalExports: 0,
436
+ totalLines: 0
407
437
  };
408
438
  let processed = 0;
409
439
  for (const filePath of files) {
@@ -429,6 +459,7 @@ async function analyzeAiSignalClarity(options) {
429
459
  deepCallbacks: aggregate.deepCallbacks,
430
460
  ambiguousNames: aggregate.ambiguousNames,
431
461
  undocumentedExports: aggregate.undocumentedExports,
462
+ largeFiles: aggregate.largeFiles,
432
463
  totalSymbols: Math.max(1, aggregate.totalSymbols),
433
464
  totalExports: Math.max(1, aggregate.totalExports)
434
465
  });
@@ -482,6 +513,7 @@ function calculateAiSignalClarityScore(report) {
482
513
  deepCallbacks: aggregateSignals.deepCallbacks,
483
514
  ambiguousNames: aggregateSignals.ambiguousNames,
484
515
  undocumentedExports: aggregateSignals.undocumentedExports,
516
+ largeFiles: aggregateSignals.largeFiles,
485
517
  totalSymbols: Math.max(1, aggregateSignals.totalSymbols),
486
518
  totalExports: Math.max(1, aggregateSignals.totalExports)
487
519
  });
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeAiSignalClarity,
4
4
  calculateAiSignalClarityScore
5
- } from "./chunk-4RIAQ4QQ.mjs";
5
+ } from "./chunk-5WOEEOWG.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.d.mts CHANGED
@@ -13,11 +13,12 @@ interface AiSignalClarityOptions extends ScanOptions {
13
13
  checkImplicitSideEffects?: boolean;
14
14
  checkDeepCallbacks?: boolean;
15
15
  checkOverloadedSymbols?: boolean;
16
+ checkLargeFiles?: boolean;
16
17
  minSeverity?: string;
17
18
  }
18
19
  interface AiSignalClarityIssue extends Issue {
19
20
  type: IssueType.MagicLiteral | IssueType.BooleanTrap | IssueType.AmbiguousApi | IssueType.AiSignalClarity | IssueType.DeadCode;
20
- category: 'magic-literal' | 'boolean-trap' | 'ambiguous-name' | 'undocumented-export' | 'implicit-side-effect' | 'deep-callback' | 'overloaded-symbol';
21
+ category: 'magic-literal' | 'boolean-trap' | 'ambiguous-name' | 'undocumented-export' | 'implicit-side-effect' | 'deep-callback' | 'overloaded-symbol' | 'large-file';
21
22
  snippet?: string;
22
23
  }
23
24
  interface FileAiSignalClarityResult extends AnalysisResult {
@@ -31,8 +32,10 @@ interface FileAiSignalClarityResult extends AnalysisResult {
31
32
  implicitSideEffects: number;
32
33
  deepCallbacks: number;
33
34
  overloadedSymbols: number;
35
+ largeFiles: number;
34
36
  totalSymbols: number;
35
37
  totalExports: number;
38
+ totalLines: number;
36
39
  };
37
40
  }
38
41
  interface AiSignalClarityReport {
package/dist/index.d.ts CHANGED
@@ -13,11 +13,12 @@ interface AiSignalClarityOptions extends ScanOptions {
13
13
  checkImplicitSideEffects?: boolean;
14
14
  checkDeepCallbacks?: boolean;
15
15
  checkOverloadedSymbols?: boolean;
16
+ checkLargeFiles?: boolean;
16
17
  minSeverity?: string;
17
18
  }
18
19
  interface AiSignalClarityIssue extends Issue {
19
20
  type: IssueType.MagicLiteral | IssueType.BooleanTrap | IssueType.AmbiguousApi | IssueType.AiSignalClarity | IssueType.DeadCode;
20
- category: 'magic-literal' | 'boolean-trap' | 'ambiguous-name' | 'undocumented-export' | 'implicit-side-effect' | 'deep-callback' | 'overloaded-symbol';
21
+ category: 'magic-literal' | 'boolean-trap' | 'ambiguous-name' | 'undocumented-export' | 'implicit-side-effect' | 'deep-callback' | 'overloaded-symbol' | 'large-file';
21
22
  snippet?: string;
22
23
  }
23
24
  interface FileAiSignalClarityResult extends AnalysisResult {
@@ -31,8 +32,10 @@ interface FileAiSignalClarityResult extends AnalysisResult {
31
32
  implicitSideEffects: number;
32
33
  deepCallbacks: number;
33
34
  overloadedSymbols: number;
35
+ largeFiles: number;
34
36
  totalSymbols: number;
35
37
  totalExports: number;
38
+ totalLines: number;
36
39
  };
37
40
  }
38
41
  interface AiSignalClarityReport {
package/dist/index.js CHANGED
@@ -83,6 +83,7 @@ async function scanFile(filePath, options = { rootDir: "." }) {
83
83
  const result = parser.parse(code, filePath);
84
84
  const ast = await parser.getAST(code, filePath);
85
85
  const issues = [];
86
+ const lineCount = code.split("\n").length;
86
87
  const signals = {
87
88
  magicLiterals: 0,
88
89
  booleanTraps: 0,
@@ -91,10 +92,35 @@ async function scanFile(filePath, options = { rootDir: "." }) {
91
92
  implicitSideEffects: 0,
92
93
  deepCallbacks: 0,
93
94
  overloadedSymbols: 0,
95
+ largeFiles: 0,
94
96
  totalSymbols: result.exports.length + result.imports.length,
95
- totalExports: result.exports.length
97
+ totalExports: result.exports.length,
98
+ totalLines: lineCount
96
99
  };
97
100
  const symbolCounts = /* @__PURE__ */ new Map();
101
+ if (options.checkLargeFiles !== false) {
102
+ if (lineCount > 750) {
103
+ signals.largeFiles++;
104
+ issues.push({
105
+ type: import_core.IssueType.AiSignalClarity,
106
+ category: "large-file",
107
+ severity: import_core.Severity.Critical,
108
+ message: `Extreme file length (${lineCount} lines) \u2014 AI context window will overflow or "Lose the Middle" critical details.`,
109
+ location: { file: filePath, line: 1 },
110
+ suggestion: "Split into smaller, single-responsibility modules (< 500 lines)."
111
+ });
112
+ } else if (lineCount > 500) {
113
+ signals.largeFiles++;
114
+ issues.push({
115
+ type: import_core.IssueType.AiSignalClarity,
116
+ category: "large-file",
117
+ severity: import_core.Severity.Major,
118
+ message: `Large file (${lineCount} lines) \u2014 pushing the limits of effective AI reasoning.`,
119
+ location: { file: filePath, line: 1 },
120
+ suggestion: "Consider refactoring and extracting logic to new files."
121
+ });
122
+ }
123
+ }
98
124
  for (const exp of result.exports) {
99
125
  symbolCounts.set(exp.name, (symbolCounts.get(exp.name) || 0) + 1);
100
126
  if (options.checkUndocumentedExports !== false) {
@@ -384,8 +410,10 @@ function emptyResult(filePath) {
384
410
  implicitSideEffects: 0,
385
411
  deepCallbacks: 0,
386
412
  overloadedSymbols: 0,
413
+ largeFiles: 0,
387
414
  totalSymbols: 0,
388
- totalExports: 0
415
+ totalExports: 0,
416
+ totalLines: 0
389
417
  },
390
418
  fileName: filePath,
391
419
  metrics: {
@@ -407,8 +435,10 @@ async function analyzeAiSignalClarity(options) {
407
435
  implicitSideEffects: 0,
408
436
  deepCallbacks: 0,
409
437
  overloadedSymbols: 0,
438
+ largeFiles: 0,
410
439
  totalSymbols: 0,
411
- totalExports: 0
440
+ totalExports: 0,
441
+ totalLines: 0
412
442
  };
413
443
  let processed = 0;
414
444
  for (const filePath of files) {
@@ -434,6 +464,7 @@ async function analyzeAiSignalClarity(options) {
434
464
  deepCallbacks: aggregate.deepCallbacks,
435
465
  ambiguousNames: aggregate.ambiguousNames,
436
466
  undocumentedExports: aggregate.undocumentedExports,
467
+ largeFiles: aggregate.largeFiles,
437
468
  totalSymbols: Math.max(1, aggregate.totalSymbols),
438
469
  totalExports: Math.max(1, aggregate.totalExports)
439
470
  });
@@ -487,6 +518,7 @@ function calculateAiSignalClarityScore(report) {
487
518
  deepCallbacks: aggregateSignals.deepCallbacks,
488
519
  ambiguousNames: aggregateSignals.ambiguousNames,
489
520
  undocumentedExports: aggregateSignals.undocumentedExports,
521
+ largeFiles: aggregateSignals.largeFiles,
490
522
  totalSymbols: Math.max(1, aggregateSignals.totalSymbols),
491
523
  totalExports: Math.max(1, aggregateSignals.totalExports)
492
524
  });
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  analyzeAiSignalClarity,
3
3
  calculateAiSignalClarityScore,
4
4
  scanFile
5
- } from "./chunk-4RIAQ4QQ.mjs";
5
+ } from "./chunk-5WOEEOWG.mjs";
6
6
 
7
7
  // src/index.ts
8
8
  import { ToolRegistry } from "@aiready/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/ai-signal-clarity",
3
- "version": "0.11.21",
3
+ "version": "0.12.0",
4
4
  "description": "Detects code patterns that cause AI models to hallucinate incorrect implementations",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -41,7 +41,7 @@
41
41
  "chalk": "^5.3.0",
42
42
  "commander": "^14.0.0",
43
43
  "glob": "^13.0.0",
44
- "@aiready/core": "0.21.21"
44
+ "@aiready/core": "0.22.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^24.0.0",
@@ -16,17 +16,19 @@ describe('AI Signal Clarity Scoring', () => {
16
16
  },
17
17
  results: [],
18
18
  aggregateSignals: {
19
- magicLiterals: 60,
19
+ magicLiterals: 80,
20
20
  booleanTraps: 2,
21
21
  ambiguousNames: 1,
22
22
  undocumentedExports: 3,
23
23
  implicitSideEffects: 0,
24
24
  deepCallbacks: 0,
25
25
  overloadedSymbols: 0,
26
+ largeFiles: 0,
26
27
  totalSymbols: 100,
27
28
  totalExports: 20,
29
+ totalLines: 1000,
28
30
  },
29
- recommendations: ['Extract 60 magic literals into named constants'],
31
+ recommendations: ['Extract 80 magic literals into named constants'],
30
32
  };
31
33
 
32
34
  it('should map report to ToolScoringOutput correctly', () => {
@@ -37,7 +39,7 @@ describe('AI Signal Clarity Scoring', () => {
37
39
  expect(scoring.score).toBeGreaterThanOrEqual(0);
38
40
  expect(scoring.factors.length).toBeGreaterThan(0);
39
41
  expect(scoring.recommendations[0].action).toBe(
40
- 'Extract 60 magic literals into named constants'
42
+ 'Extract 80 magic literals into named constants'
41
43
  );
42
44
  });
43
45
 
@@ -52,8 +54,10 @@ describe('AI Signal Clarity Scoring', () => {
52
54
  implicitSideEffects: 0,
53
55
  deepCallbacks: 0,
54
56
  overloadedSymbols: 0,
57
+ largeFiles: 0,
55
58
  totalSymbols: 100,
56
59
  totalExports: 20,
60
+ totalLines: 100,
57
61
  },
58
62
  };
59
63
 
package/src/analyzer.ts CHANGED
@@ -32,8 +32,10 @@ export async function analyzeAiSignalClarity(
32
32
  implicitSideEffects: 0,
33
33
  deepCallbacks: 0,
34
34
  overloadedSymbols: 0,
35
+ largeFiles: 0,
35
36
  totalSymbols: 0,
36
37
  totalExports: 0,
38
+ totalLines: 0,
37
39
  };
38
40
 
39
41
  let processed = 0;
@@ -63,6 +65,7 @@ export async function analyzeAiSignalClarity(
63
65
  deepCallbacks: aggregate.deepCallbacks,
64
66
  ambiguousNames: aggregate.ambiguousNames,
65
67
  undocumentedExports: aggregate.undocumentedExports,
68
+ largeFiles: aggregate.largeFiles,
66
69
  totalSymbols: Math.max(1, aggregate.totalSymbols),
67
70
  totalExports: Math.max(1, aggregate.totalExports),
68
71
  });
package/src/scanner.ts CHANGED
@@ -81,6 +81,7 @@ export async function scanFile(
81
81
  const ast = await parser.getAST(code, filePath);
82
82
 
83
83
  const issues: AiSignalClarityIssue[] = [];
84
+ const lineCount = code.split('\n').length;
84
85
  const signals = {
85
86
  magicLiterals: 0,
86
87
  booleanTraps: 0,
@@ -89,13 +90,41 @@ export async function scanFile(
89
90
  implicitSideEffects: 0,
90
91
  deepCallbacks: 0,
91
92
  overloadedSymbols: 0,
93
+ largeFiles: 0,
92
94
  totalSymbols: result.exports.length + result.imports.length,
93
95
  totalExports: result.exports.length,
96
+ totalLines: lineCount,
94
97
  };
95
98
 
96
99
  // Symbol tracking for overloading detection
97
100
  const symbolCounts = new Map<string, number>();
98
101
 
102
+ // 0. Check File Size (Large Files)
103
+ if (options.checkLargeFiles !== false) {
104
+ if (lineCount > 750) {
105
+ signals.largeFiles++;
106
+ issues.push({
107
+ type: IssueType.AiSignalClarity,
108
+ category: 'large-file',
109
+ severity: Severity.Critical,
110
+ message: `Extreme file length (${lineCount} lines) — AI context window will overflow or "Lose the Middle" critical details.`,
111
+ location: { file: filePath, line: 1 },
112
+ suggestion:
113
+ 'Split into smaller, single-responsibility modules (< 500 lines).',
114
+ });
115
+ } else if (lineCount > 500) {
116
+ signals.largeFiles++;
117
+ issues.push({
118
+ type: IssueType.AiSignalClarity,
119
+ category: 'large-file',
120
+ severity: Severity.Major,
121
+ message: `Large file (${lineCount} lines) — pushing the limits of effective AI reasoning.`,
122
+ location: { file: filePath, line: 1 },
123
+ suggestion: 'Consider refactoring and extracting logic to new files.',
124
+ });
125
+ }
126
+ }
127
+
99
128
  // 1. Check Metadata-based signals (Side Effects, Docs, Overloads)
100
129
  for (const exp of result.exports) {
101
130
  // Overload tracking
@@ -448,8 +477,10 @@ function emptyResult(filePath: string): FileAiSignalClarityResult {
448
477
  implicitSideEffects: 0,
449
478
  deepCallbacks: 0,
450
479
  overloadedSymbols: 0,
480
+ largeFiles: 0,
451
481
  totalSymbols: 0,
452
482
  totalExports: 0,
483
+ totalLines: 0,
453
484
  },
454
485
  fileName: filePath,
455
486
  metrics: {
package/src/scoring.ts CHANGED
@@ -22,6 +22,7 @@ export function calculateAiSignalClarityScore(
22
22
  deepCallbacks: aggregateSignals.deepCallbacks,
23
23
  ambiguousNames: aggregateSignals.ambiguousNames,
24
24
  undocumentedExports: aggregateSignals.undocumentedExports,
25
+ largeFiles: aggregateSignals.largeFiles,
25
26
  totalSymbols: Math.max(1, aggregateSignals.totalSymbols),
26
27
  totalExports: Math.max(1, aggregateSignals.totalExports),
27
28
  });
package/src/types.ts CHANGED
@@ -13,6 +13,7 @@ export interface AiSignalClarityOptions extends ScanOptions {
13
13
  checkImplicitSideEffects?: boolean;
14
14
  checkDeepCallbacks?: boolean;
15
15
  checkOverloadedSymbols?: boolean;
16
+ checkLargeFiles?: boolean;
16
17
  minSeverity?: string;
17
18
  }
18
19
 
@@ -30,7 +31,8 @@ export interface AiSignalClarityIssue extends Issue {
30
31
  | 'undocumented-export'
31
32
  | 'implicit-side-effect'
32
33
  | 'deep-callback'
33
- | 'overloaded-symbol';
34
+ | 'overloaded-symbol'
35
+ | 'large-file';
34
36
  snippet?: string;
35
37
  }
36
38
 
@@ -45,8 +47,10 @@ export interface FileAiSignalClarityResult extends AnalysisResult {
45
47
  implicitSideEffects: number;
46
48
  deepCallbacks: number;
47
49
  overloadedSymbols: number;
50
+ largeFiles: number;
48
51
  totalSymbols: number;
49
52
  totalExports: number;
53
+ totalLines: number;
50
54
  };
51
55
  }
52
56