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