@aiready/agent-grounding 0.1.4 → 0.1.6

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.mjs CHANGED
@@ -2,16 +2,24 @@
2
2
  import {
3
3
  analyzeAgentGrounding,
4
4
  calculateGroundingScore
5
- } from "./chunk-OOB3JMXQ.mjs";
5
+ } from "./chunk-NHDH733I.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
9
9
  import chalk from "chalk";
10
10
  import { writeFileSync, mkdirSync, existsSync } from "fs";
11
11
  import { dirname } from "path";
12
- import { loadConfig, mergeConfigWithDefaults, resolveOutputPath } from "@aiready/core";
12
+ import {
13
+ loadConfig,
14
+ mergeConfigWithDefaults,
15
+ resolveOutputPath
16
+ } from "@aiready/core";
13
17
  var program = new Command();
14
- program.name("aiready-agent-grounding").description("Measure how well an AI agent can navigate your codebase autonomously").version("0.1.0").addHelpText("after", `
18
+ program.name("aiready-agent-grounding").description(
19
+ "Measure how well an AI agent can navigate your codebase autonomously"
20
+ ).version("0.1.0").addHelpText(
21
+ "after",
22
+ `
15
23
  GROUNDING DIMENSIONS:
16
24
  Structure Clarity Deep directory trees slow and confuse agents
17
25
  Self-Documentation Vague file names (utils, helpers) hide intent
@@ -23,7 +31,16 @@ EXAMPLES:
23
31
  aiready-agent-grounding . # Full analysis
24
32
  aiready-agent-grounding src/ --output json # JSON report
25
33
  aiready-agent-grounding . --max-depth 3 # Stricter depth limit
26
- `).argument("<directory>", "Directory to analyze").option("--max-depth <n>", "Max recommended directory depth (default: 4)", "4").option("--readme-stale-days <n>", "Days after which README is considered stale (default: 90)", "90").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console|json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
34
+ `
35
+ ).argument("<directory>", "Directory to analyze").option(
36
+ "--max-depth <n>",
37
+ "Max recommended directory depth (default: 4)",
38
+ "4"
39
+ ).option(
40
+ "--readme-stale-days <n>",
41
+ "Days after which README is considered stale (default: 90)",
42
+ "90"
43
+ ).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console|json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
27
44
  console.log(chalk.blue("\u{1F9ED} Analyzing agent grounding...\n"));
28
45
  const startTime = Date.now();
29
46
  const config = await loadConfig(directory);
@@ -65,10 +82,14 @@ function scoreColor(score) {
65
82
  return chalk.bgRed.white;
66
83
  }
67
84
  function displayConsoleReport(report, scoring, elapsed) {
68
- const { summary, rawData, issues, recommendations } = report;
85
+ const { summary, issues, recommendations } = report;
69
86
  console.log(chalk.bold("\n\u{1F9ED} Agent Grounding Analysis\n"));
70
- console.log(`Score: ${scoreColor(summary.score)(summary.score + "/100")} (${summary.rating.toUpperCase()})`);
71
- console.log(`Files: ${chalk.cyan(summary.filesAnalyzed)} Directories: ${chalk.cyan(summary.directoriesAnalyzed)}`);
87
+ console.log(
88
+ `Score: ${scoreColor(summary.score)(summary.score + "/100")} (${summary.rating.toUpperCase()})`
89
+ );
90
+ console.log(
91
+ `Files: ${chalk.cyan(summary.filesAnalyzed)} Directories: ${chalk.cyan(summary.directoriesAnalyzed)}`
92
+ );
72
93
  console.log(`Analysis: ${chalk.gray(elapsed + "s")}
73
94
  `);
74
95
  console.log(chalk.bold("\u{1F4D0} Dimension Scores\n"));
@@ -81,22 +102,33 @@ function displayConsoleReport(report, scoring, elapsed) {
81
102
  ];
82
103
  for (const [name, val] of dims) {
83
104
  const bar = "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
84
- console.log(` ${String(name).padEnd(22)} ${scoreColor(val)(bar)} ${val}/100`);
105
+ console.log(
106
+ ` ${String(name).padEnd(22)} ${scoreColor(val)(bar)} ${val}/100`
107
+ );
85
108
  }
86
109
  if (issues.length > 0) {
87
110
  console.log(chalk.bold("\n\u26A0\uFE0F Issues Found\n"));
88
111
  for (const issue of issues) {
89
112
  const sev = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : chalk.blue;
90
113
  console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
91
- if (issue.suggestion) console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
114
+ if (issue.suggestion)
115
+ console.log(
116
+ ` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`
117
+ );
92
118
  console.log();
93
119
  }
94
120
  } else {
95
- console.log(chalk.green("\n\u2728 No grounding issues found \u2014 agents can navigate freely!\n"));
121
+ console.log(
122
+ chalk.green(
123
+ "\n\u2728 No grounding issues found \u2014 agents can navigate freely!\n"
124
+ )
125
+ );
96
126
  }
97
127
  if (recommendations.length > 0) {
98
128
  console.log(chalk.bold("\u{1F4A1} Recommendations\n"));
99
- recommendations.forEach((rec, i) => console.log(`${i + 1}. ${rec}`));
129
+ recommendations.forEach(
130
+ (rec, i) => console.log(`${i + 1}. ${rec}`)
131
+ );
100
132
  }
101
133
  console.log();
102
134
  }
package/dist/index.js CHANGED
@@ -53,7 +53,14 @@ var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
53
53
  "mocks"
54
54
  ]);
55
55
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
56
- var DEFAULT_EXCLUDES = ["node_modules", "dist", ".git", "coverage", ".turbo", "build"];
56
+ var DEFAULT_EXCLUDES = [
57
+ "node_modules",
58
+ "dist",
59
+ ".git",
60
+ "coverage",
61
+ ".turbo",
62
+ "build"
63
+ ];
57
64
  function collectEntries(dir, options, depth = 0, dirs = [], files = []) {
58
65
  if (depth > (options.maxDepth ?? 20)) return { dirs, files };
59
66
  const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
@@ -88,7 +95,13 @@ function analyzeFile(filePath) {
88
95
  try {
89
96
  code = (0, import_fs.readFileSync)(filePath, "utf-8");
90
97
  } catch {
91
- return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
98
+ return {
99
+ isBarrel: false,
100
+ exportedNames: [],
101
+ untypedExports: 0,
102
+ totalExports: 0,
103
+ domainTerms: []
104
+ };
92
105
  }
93
106
  let ast;
94
107
  try {
@@ -98,10 +111,16 @@ function analyzeFile(filePath) {
98
111
  loc: false
99
112
  });
100
113
  } catch {
101
- return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
114
+ return {
115
+ isBarrel: false,
116
+ exportedNames: [],
117
+ untypedExports: 0,
118
+ totalExports: 0,
119
+ domainTerms: []
120
+ };
102
121
  }
103
122
  let isBarrel = false;
104
- let exportedNames = [];
123
+ const exportedNames = [];
105
124
  let untypedExports = 0;
106
125
  let totalExports = 0;
107
126
  const domainTerms = [];
@@ -117,7 +136,9 @@ function analyzeFile(filePath) {
117
136
  const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
118
137
  if (name) {
119
138
  exportedNames.push(name);
120
- domainTerms.push(...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean));
139
+ domainTerms.push(
140
+ ...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
141
+ );
121
142
  const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
122
143
  if (!hasType) untypedExports++;
123
144
  }
@@ -149,8 +170,12 @@ async function analyzeAgentGrounding(options) {
149
170
  const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
150
171
  const readmeStaleDays = options.readmeStaleDays ?? 90;
151
172
  const { dirs, files } = collectEntries(rootDir, options);
152
- const deepDirectories = dirs.filter((d) => d.depth > maxRecommendedDepth).length;
153
- const additionalVague = new Set((options.additionalVagueNames ?? []).map((n) => n.toLowerCase()));
173
+ const deepDirectories = dirs.filter(
174
+ (d) => d.depth > maxRecommendedDepth
175
+ ).length;
176
+ const additionalVague = new Set(
177
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
178
+ );
154
179
  let vagueFileNames = 0;
155
180
  for (const f of files) {
156
181
  const base = (0, import_path.basename)(f, (0, import_path.extname)(f)).toLowerCase();
@@ -180,7 +205,10 @@ async function analyzeAgentGrounding(options) {
180
205
  totalExports += analysis.totalExports;
181
206
  allDomainTerms.push(...analysis.domainTerms);
182
207
  }
183
- const { inconsistent: inconsistentDomainTerms, vocabularySize: domainVocabularySize } = detectInconsistentTerms(allDomainTerms);
208
+ const {
209
+ inconsistent: inconsistentDomainTerms,
210
+ vocabularySize: domainVocabularySize
211
+ } = detectInconsistentTerms(allDomainTerms);
184
212
  const groundingResult = (0, import_core.calculateAgentGrounding)({
185
213
  deepDirectories,
186
214
  totalDirectories: dirs.length,
@@ -282,7 +310,7 @@ async function analyzeAgentGrounding(options) {
282
310
 
283
311
  // src/scoring.ts
284
312
  function calculateGroundingScore(report) {
285
- const { summary, rawData, issues, recommendations } = report;
313
+ const { summary, rawData, recommendations } = report;
286
314
  const factors = [
287
315
  {
288
316
  name: "Structure Clarity",
@@ -310,11 +338,13 @@ function calculateGroundingScore(report) {
310
338
  description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
311
339
  }
312
340
  ];
313
- const recs = recommendations.map((action) => ({
314
- action,
315
- estimatedImpact: 6,
316
- priority: summary.score < 50 ? "high" : "medium"
317
- }));
341
+ const recs = recommendations.map(
342
+ (action) => ({
343
+ action,
344
+ estimatedImpact: 6,
345
+ priority: summary.score < 50 ? "high" : "medium"
346
+ })
347
+ );
318
348
  return {
319
349
  toolName: "agent-grounding",
320
350
  score: summary.score,
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeAgentGrounding,
3
3
  calculateGroundingScore
4
- } from "./chunk-OOB3JMXQ.mjs";
4
+ } from "./chunk-NHDH733I.mjs";
5
5
  export {
6
6
  analyzeAgentGrounding,
7
7
  calculateGroundingScore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/agent-grounding",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Measures how well an AI agent can navigate a codebase autonomously without human assistance",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -39,8 +39,8 @@
39
39
  "@typescript-eslint/typescript-estree": "^8.53.0",
40
40
  "chalk": "^5.3.0",
41
41
  "commander": "^14.0.0",
42
- "glob": "^11.0.0",
43
- "@aiready/core": "0.9.31"
42
+ "glob": "^13.0.0",
43
+ "@aiready/core": "0.9.33"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
@@ -28,23 +28,33 @@ describe('Agent Grounding Analyzer', () => {
28
28
  describe('Deep Directories and Vague Files', () => {
29
29
  it('should detect deep directories and vague file names', async () => {
30
30
  // Mock files deep in the tree and with vague names
31
- createTestFile('src/components/common/utils/helpers/deep/very/deep.ts', 'export const x = 1;');
31
+ createTestFile(
32
+ 'src/components/common/utils/helpers/deep/very/deep.ts',
33
+ 'export const x = 1;'
34
+ );
32
35
  createTestFile('src/utils.ts', 'export const y = 2;');
33
36
 
34
37
  const report = await analyzeAgentGrounding({
35
38
  rootDir: tmpDir,
36
39
  maxRecommendedDepth: 3,
37
- additionalVagueNames: ['utils', 'helpers']
40
+ additionalVagueNames: ['utils', 'helpers'],
38
41
  });
39
42
 
40
43
  expect(report.issues.length).toBeGreaterThanOrEqual(1);
41
44
 
42
- const deepIssues = report.issues.filter(i => i.dimension === 'structure-clarity' && i.message.includes('exceed'));
45
+ const deepIssues = report.issues.filter(
46
+ (i) =>
47
+ i.dimension === 'structure-clarity' && i.message.includes('exceed')
48
+ );
43
49
  // The deep.ts file contributes to the aggregate depth count
44
50
  expect(deepIssues.length).toBeGreaterThan(0);
45
51
 
46
- const vagueIssues = report.issues.filter(i => i.dimension === 'self-documentation');
47
- expect(vagueIssues.some(i => i.message.includes('vague names'))).toBe(true);
52
+ const vagueIssues = report.issues.filter(
53
+ (i) => i.dimension === 'self-documentation'
54
+ );
55
+ expect(vagueIssues.some((i) => i.message.includes('vague names'))).toBe(
56
+ true
57
+ );
48
58
  });
49
59
  });
50
60
 
@@ -54,7 +64,9 @@ describe('Agent Grounding Analyzer', () => {
54
64
  const report = await analyzeAgentGrounding({ rootDir: tmpDir });
55
65
 
56
66
  const issues = report.issues;
57
- const readmeIssues = issues.filter(i => i.dimension === 'entry-point' || i.message.includes('README'));
67
+ const readmeIssues = issues.filter(
68
+ (i) => i.dimension === 'entry-point' || i.message.includes('README')
69
+ );
58
70
 
59
71
  expect(readmeIssues.length).toBeGreaterThan(0);
60
72
  });
@@ -62,16 +74,24 @@ describe('Agent Grounding Analyzer', () => {
62
74
 
63
75
  describe('Untyped Exports', () => {
64
76
  it('should detect JS files or untyped exports', async () => {
65
- createTestFile('src/untyped.js', 'export function doSomething(a, b) { return a + b; }');
66
- createTestFile('src/typed.ts', 'export function doSomething(a: number, b: number): number { return a + b; }');
77
+ createTestFile(
78
+ 'src/untyped.js',
79
+ 'export function doSomething(a, b) { return a + b; }'
80
+ );
81
+ createTestFile(
82
+ 'src/typed.ts',
83
+ 'export function doSomething(a: number, b: number): number { return a + b; }'
84
+ );
67
85
 
68
86
  const report = await analyzeAgentGrounding({ rootDir: tmpDir });
69
87
 
70
88
  const issues = report.issues;
71
- const untypedIssues = issues.filter(i => i.dimension === 'api-clarity');
89
+ const untypedIssues = issues.filter((i) => i.dimension === 'api-clarity');
72
90
 
73
91
  // The JS file untyped export contributes to the aggregate count
74
- expect(untypedIssues.some(i => i.message.includes('lack TypeScript type'))).toBe(true);
92
+ expect(
93
+ untypedIssues.some((i) => i.message.includes('lack TypeScript type'))
94
+ ).toBe(true);
75
95
  });
76
96
  });
77
97
  });
package/src/analyzer.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Scanner for agent-grounding dimensions.
3
- *
3
+ *
4
4
  * Measures 5 dimensions:
5
5
  * 1. Structure clarity — how deep are directory trees?
6
6
  * 2. Self-documentation — do file names reveal purpose?
@@ -13,18 +13,46 @@ import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
13
13
  import { join, extname, basename } from 'path';
14
14
  import { parse } from '@typescript-eslint/typescript-estree';
15
15
  import type { TSESTree } from '@typescript-eslint/types';
16
- import type { AgentGroundingOptions, AgentGroundingIssue, AgentGroundingReport } from './types';
16
+ import type {
17
+ AgentGroundingOptions,
18
+ AgentGroundingIssue,
19
+ AgentGroundingReport,
20
+ } from './types';
17
21
  import { calculateAgentGrounding } from '@aiready/core';
18
22
 
19
23
  // File names that don't describe purpose — an agent can't determine what to find here
20
24
  const VAGUE_FILE_NAMES = new Set([
21
- 'utils', 'helpers', 'helper', 'misc', 'common', 'shared', 'tools',
22
- 'util', 'lib', 'libs', 'stuff', 'functions', 'methods', 'handlers',
23
- 'data', 'temp', 'tmp', 'test-utils', 'test-helpers', 'mocks',
25
+ 'utils',
26
+ 'helpers',
27
+ 'helper',
28
+ 'misc',
29
+ 'common',
30
+ 'shared',
31
+ 'tools',
32
+ 'util',
33
+ 'lib',
34
+ 'libs',
35
+ 'stuff',
36
+ 'functions',
37
+ 'methods',
38
+ 'handlers',
39
+ 'data',
40
+ 'temp',
41
+ 'tmp',
42
+ 'test-utils',
43
+ 'test-helpers',
44
+ 'mocks',
24
45
  ]);
25
46
 
26
47
  const SUPPORTED_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
27
- const DEFAULT_EXCLUDES = ['node_modules', 'dist', '.git', 'coverage', '.turbo', 'build'];
48
+ const DEFAULT_EXCLUDES = [
49
+ 'node_modules',
50
+ 'dist',
51
+ '.git',
52
+ 'coverage',
53
+ '.turbo',
54
+ 'build',
55
+ ];
28
56
 
29
57
  // ---------------------------------------------------------------------------
30
58
  // File/dir collection
@@ -40,7 +68,7 @@ function collectEntries(
40
68
  options: AgentGroundingOptions,
41
69
  depth = 0,
42
70
  dirs: DirEntry[] = [],
43
- files: string[] = [],
71
+ files: string[] = []
44
72
  ): { dirs: DirEntry[]; files: string[] } {
45
73
  if (depth > (options.maxDepth ?? 20)) return { dirs, files };
46
74
  const excludes = [...DEFAULT_EXCLUDES, ...(options.exclude ?? [])];
@@ -53,7 +81,7 @@ function collectEntries(
53
81
  }
54
82
 
55
83
  for (const entry of entries) {
56
- if (excludes.some(ex => entry === ex || entry.includes(ex))) continue;
84
+ if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
57
85
  const full = join(dir, entry);
58
86
  let stat;
59
87
  try {
@@ -65,7 +93,7 @@ function collectEntries(
65
93
  dirs.push({ path: full, depth });
66
94
  collectEntries(full, options, depth + 1, dirs, files);
67
95
  } else if (stat.isFile() && SUPPORTED_EXTENSIONS.has(extname(full))) {
68
- if (!options.include || options.include.some(p => full.includes(p))) {
96
+ if (!options.include || options.include.some((p) => full.includes(p))) {
69
97
  files.push(full);
70
98
  }
71
99
  }
@@ -91,7 +119,13 @@ function analyzeFile(filePath: string): FileAnalysis {
91
119
  try {
92
120
  code = readFileSync(filePath, 'utf-8');
93
121
  } catch {
94
- return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
122
+ return {
123
+ isBarrel: false,
124
+ exportedNames: [],
125
+ untypedExports: 0,
126
+ totalExports: 0,
127
+ domainTerms: [],
128
+ };
95
129
  }
96
130
 
97
131
  let ast: TSESTree.Program;
@@ -102,11 +136,17 @@ function analyzeFile(filePath: string): FileAnalysis {
102
136
  loc: false,
103
137
  });
104
138
  } catch {
105
- return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
139
+ return {
140
+ isBarrel: false,
141
+ exportedNames: [],
142
+ untypedExports: 0,
143
+ totalExports: 0,
144
+ domainTerms: [],
145
+ };
106
146
  }
107
147
 
108
148
  let isBarrel = false;
109
- let exportedNames: string[] = [];
149
+ const exportedNames: string[] = [];
110
150
  let untypedExports = 0;
111
151
  let totalExports = 0;
112
152
 
@@ -126,7 +166,13 @@ function analyzeFile(filePath: string): FileAnalysis {
126
166
  if (name) {
127
167
  exportedNames.push(name);
128
168
  // Split camelCase into terms
129
- domainTerms.push(...name.replace(/([A-Z])/g, ' $1').toLowerCase().split(/\s+/).filter(Boolean));
169
+ domainTerms.push(
170
+ ...name
171
+ .replace(/([A-Z])/g, ' $1')
172
+ .toLowerCase()
173
+ .split(/\s+/)
174
+ .filter(Boolean)
175
+ );
130
176
 
131
177
  // Check if it's typed (TS function/variable with annotation)
132
178
  const hasType =
@@ -152,7 +198,10 @@ function analyzeFile(filePath: string): FileAnalysis {
152
198
  // Domain vocabulary consistency check
153
199
  // ---------------------------------------------------------------------------
154
200
 
155
- function detectInconsistentTerms(allTerms: string[]): { inconsistent: number; vocabularySize: number } {
201
+ function detectInconsistentTerms(allTerms: string[]): {
202
+ inconsistent: number;
203
+ vocabularySize: number;
204
+ } {
156
205
  const termFreq = new Map<string, number>();
157
206
  for (const term of allTerms) {
158
207
  if (term.length >= 3) {
@@ -161,8 +210,8 @@ function detectInconsistentTerms(allTerms: string[]): { inconsistent: number; vo
161
210
  }
162
211
  // Very simplistic: terms that appear exactly once are "orphan concepts" —
163
212
  // they may be inconsistently named variants of common terms.
164
- const orphans = [...termFreq.values()].filter(count => count === 1).length;
165
- const common = [...termFreq.values()].filter(count => count >= 3).length;
213
+ const orphans = [...termFreq.values()].filter((count) => count === 1).length;
214
+ const common = [...termFreq.values()].filter((count) => count >= 3).length;
166
215
  const vocabularySize = termFreq.size;
167
216
  // Inconsistency ratio: many orphan terms relative to common terms
168
217
  const inconsistent = Math.max(0, orphans - common * 2);
@@ -174,7 +223,7 @@ function detectInconsistentTerms(allTerms: string[]): { inconsistent: number; vo
174
223
  // ---------------------------------------------------------------------------
175
224
 
176
225
  export async function analyzeAgentGrounding(
177
- options: AgentGroundingOptions,
226
+ options: AgentGroundingOptions
178
227
  ): Promise<AgentGroundingReport> {
179
228
  const rootDir = options.rootDir;
180
229
  const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
@@ -183,10 +232,14 @@ export async function analyzeAgentGrounding(
183
232
  const { dirs, files } = collectEntries(rootDir, options);
184
233
 
185
234
  // Structure clarity
186
- const deepDirectories = dirs.filter(d => d.depth > maxRecommendedDepth).length;
235
+ const deepDirectories = dirs.filter(
236
+ (d) => d.depth > maxRecommendedDepth
237
+ ).length;
187
238
 
188
239
  // Self-documentation — vague file names
189
- const additionalVague = new Set((options.additionalVagueNames ?? []).map(n => n.toLowerCase()));
240
+ const additionalVague = new Set(
241
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
242
+ );
190
243
  let vagueFileNames = 0;
191
244
  for (const f of files) {
192
245
  const base = basename(f, extname(f)).toLowerCase();
@@ -204,7 +257,9 @@ export async function analyzeAgentGrounding(
204
257
  const stat = statSync(readmePath);
205
258
  const ageDays = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
206
259
  readmeIsFresh = ageDays < readmeStaleDays;
207
- } catch { }
260
+ } catch {
261
+ /* ignore stat errors */
262
+ }
208
263
  }
209
264
 
210
265
  // File analysis
@@ -222,8 +277,10 @@ export async function analyzeAgentGrounding(
222
277
  }
223
278
 
224
279
  // Domain vocabulary consistency
225
- const { inconsistent: inconsistentDomainTerms, vocabularySize: domainVocabularySize } =
226
- detectInconsistentTerms(allDomainTerms);
280
+ const {
281
+ inconsistent: inconsistentDomainTerms,
282
+ vocabularySize: domainVocabularySize,
283
+ } = detectInconsistentTerms(allDomainTerms);
227
284
 
228
285
  // Calculate grounding score using core math
229
286
  const groundingResult = calculateAgentGrounding({
@@ -261,7 +318,8 @@ export async function analyzeAgentGrounding(
261
318
  severity: 'major',
262
319
  message: `${vagueFileNames} files use vague names (utils, helpers, misc) — an agent cannot determine their purpose from the name alone.`,
263
320
  location: { file: rootDir, line: 0 },
264
- suggestion: 'Rename to domain-specific names: e.g., userAuthUtils → tokenValidator.',
321
+ suggestion:
322
+ 'Rename to domain-specific names: e.g., userAuthUtils → tokenValidator.',
265
323
  });
266
324
  }
267
325
 
@@ -270,9 +328,11 @@ export async function analyzeAgentGrounding(
270
328
  type: 'agent-navigation-failure',
271
329
  dimension: 'entry-point',
272
330
  severity: 'critical',
273
- message: 'No root README.md found — agents have no orientation document to start from.',
331
+ message:
332
+ 'No root README.md found — agents have no orientation document to start from.',
274
333
  location: { file: join(rootDir, 'README.md'), line: 0 },
275
- suggestion: 'Add a README.md explaining the project structure, entry points, and key conventions.',
334
+ suggestion:
335
+ 'Add a README.md explaining the project structure, entry points, and key conventions.',
276
336
  });
277
337
  } else if (!readmeIsFresh) {
278
338
  issues.push({
@@ -292,7 +352,8 @@ export async function analyzeAgentGrounding(
292
352
  severity: 'major',
293
353
  message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations — agents cannot infer the API contract.`,
294
354
  location: { file: rootDir, line: 0 },
295
- suggestion: 'Add explicit return type and parameter annotations to all exported functions.',
355
+ suggestion:
356
+ 'Add explicit return type and parameter annotations to all exported functions.',
296
357
  });
297
358
  }
298
359
 
@@ -303,7 +364,8 @@ export async function analyzeAgentGrounding(
303
364
  severity: 'major',
304
365
  message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently — agents get confused when one concept has multiple names.`,
305
366
  location: { file: rootDir, line: 0 },
306
- suggestion: 'Establish a domain glossary and enforce one term per concept across the codebase.',
367
+ suggestion:
368
+ 'Establish a domain glossary and enforce one term per concept across the codebase.',
307
369
  });
308
370
  }
309
371