@aiready/agent-grounding 0.9.2 → 0.9.3

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/agent-grounding@0.9.1 build /Users/pengcao/projects/aiready/packages/agent-grounding
3
+ > @aiready/agent-grounding@0.9.2 build /Users/pengcao/projects/aiready/packages/agent-grounding
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-NXIMJNCK.mjs 9.55 KB
13
- ESM dist/index.mjs 154.00 B
14
12
  ESM dist/cli.mjs 5.16 KB
15
- ESM ⚡️ Build success in 210ms
16
- CJS dist/cli.js 16.22 KB
17
- CJS dist/index.js 10.75 KB
18
- CJS ⚡️ Build success in 210ms
13
+ ESM dist/chunk-L3MLD6K4.mjs 9.65 KB
14
+ ESM dist/index.mjs 154.00 B
15
+ ESM ⚡️ Build success in 214ms
16
+ CJS dist/index.js 10.96 KB
17
+ CJS dist/cli.js 16.44 KB
18
+ CJS ⚡️ Build success in 214ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 3163ms
20
+ DTS ⚡️ Build success in 2292ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 2.34 KB
22
+ DTS dist/index.d.ts 2.36 KB
23
23
  DTS dist/cli.d.mts 20.00 B
24
- DTS dist/index.d.mts 2.34 KB
24
+ DTS dist/index.d.mts 2.36 KB
@@ -1,16 +1,16 @@
1
1
 
2
2
  
3
- > @aiready/agent-grounding@0.9.1 test /Users/pengcao/projects/aiready/packages/agent-grounding
3
+ > @aiready/agent-grounding@0.9.2 test /Users/pengcao/projects/aiready/packages/agent-grounding
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/agent-grounding
8
8
 
9
- ✓ src/__tests__/analyzer.test.ts (3 tests) 327ms
9
+ ✓ src/__tests__/analyzer.test.ts (3 tests) 317ms
10
10
 
11
11
   Test Files  1 passed (1)
12
12
   Tests  3 passed (3)
13
-  Start at  20:13:24
14
-  Duration  1.96s (transform 202ms, setup 0ms, import 1.19s, tests 327ms, environment 0ms)
13
+  Start at  20:00:26
14
+  Duration  2.35s (transform 316ms, setup 0ms, import 1.51s, tests 317ms, environment 0ms)
15
15
 
16
16
  [?25h
@@ -0,0 +1,295 @@
1
+ // src/analyzer.ts
2
+ import {
3
+ scanEntries,
4
+ calculateAgentGrounding,
5
+ VAGUE_FILE_NAMES,
6
+ Severity
7
+ } from "@aiready/core";
8
+ import { readFileSync, existsSync, statSync } from "fs";
9
+ import { join, extname, basename, relative } from "path";
10
+ import { parse } from "@typescript-eslint/typescript-estree";
11
+ function analyzeFile(filePath) {
12
+ let code;
13
+ try {
14
+ code = readFileSync(filePath, "utf-8");
15
+ } catch {
16
+ return {
17
+ isBarrel: false,
18
+ exportedNames: [],
19
+ untypedExports: 0,
20
+ totalExports: 0,
21
+ domainTerms: []
22
+ };
23
+ }
24
+ let ast;
25
+ try {
26
+ ast = parse(code, {
27
+ jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
28
+ range: false,
29
+ loc: false
30
+ });
31
+ } catch {
32
+ return {
33
+ isBarrel: false,
34
+ exportedNames: [],
35
+ untypedExports: 0,
36
+ totalExports: 0,
37
+ domainTerms: []
38
+ };
39
+ }
40
+ let isBarrel = false;
41
+ const exportedNames = [];
42
+ let untypedExports = 0;
43
+ let totalExports = 0;
44
+ const domainTerms = [];
45
+ for (const node of ast.body) {
46
+ if (node.type === "ExportAllDeclaration") {
47
+ isBarrel = true;
48
+ continue;
49
+ }
50
+ if (node.type === "ExportNamedDeclaration") {
51
+ totalExports++;
52
+ const decl = node.declaration;
53
+ if (decl) {
54
+ const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
55
+ if (name) {
56
+ exportedNames.push(name);
57
+ domainTerms.push(
58
+ ...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
59
+ );
60
+ const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
61
+ if (!hasType) untypedExports++;
62
+ }
63
+ } else if (node.specifiers && node.specifiers.length > 0) {
64
+ isBarrel = true;
65
+ }
66
+ }
67
+ if (node.type === "ExportDefaultDeclaration") {
68
+ totalExports++;
69
+ }
70
+ }
71
+ return { isBarrel, exportedNames, untypedExports, totalExports, domainTerms };
72
+ }
73
+ function detectInconsistentTerms(allTerms) {
74
+ const termFreq = /* @__PURE__ */ new Map();
75
+ for (const term of allTerms) {
76
+ if (term.length >= 3) {
77
+ termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
78
+ }
79
+ }
80
+ const orphans = [...termFreq.values()].filter((count) => count === 1).length;
81
+ const common = [...termFreq.values()].filter((count) => count >= 3).length;
82
+ const vocabularySize = termFreq.size;
83
+ const inconsistent = Math.max(0, orphans - common * 2);
84
+ return { inconsistent, vocabularySize };
85
+ }
86
+ async function analyzeAgentGrounding(options) {
87
+ const rootDir = options.rootDir;
88
+ const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
89
+ const readmeStaleDays = options.readmeStaleDays ?? 90;
90
+ const { files, dirs: rawDirs } = await scanEntries({
91
+ ...options,
92
+ include: options.include || ["**/*.{ts,tsx,js,jsx}"]
93
+ });
94
+ const dirs = rawDirs.map((d) => ({
95
+ path: d,
96
+ depth: relative(rootDir, d).split(/[/\\]/).filter(Boolean).length
97
+ }));
98
+ const deepDirectories = dirs.filter(
99
+ (d) => d.depth > maxRecommendedDepth
100
+ ).length;
101
+ const additionalVague = new Set(
102
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
103
+ );
104
+ let vagueFileNames = 0;
105
+ for (const f of files) {
106
+ const base = basename(f, extname(f)).toLowerCase();
107
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
108
+ vagueFileNames++;
109
+ }
110
+ }
111
+ const readmePath = join(rootDir, "README.md");
112
+ const hasRootReadme = existsSync(readmePath);
113
+ let readmeIsFresh = false;
114
+ if (hasRootReadme) {
115
+ try {
116
+ const stat = statSync(readmePath);
117
+ const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
118
+ readmeIsFresh = ageDays < readmeStaleDays;
119
+ } catch {
120
+ }
121
+ }
122
+ const allDomainTerms = [];
123
+ let barrelExports = 0;
124
+ let untypedExports = 0;
125
+ let totalExports = 0;
126
+ let processed = 0;
127
+ for (const f of files) {
128
+ processed++;
129
+ options.onProgress?.(
130
+ processed,
131
+ files.length,
132
+ `agent-grounding: analyzing files`
133
+ );
134
+ const analysis = analyzeFile(f);
135
+ if (analysis.isBarrel) barrelExports++;
136
+ untypedExports += analysis.untypedExports;
137
+ totalExports += analysis.totalExports;
138
+ allDomainTerms.push(...analysis.domainTerms);
139
+ }
140
+ const {
141
+ inconsistent: inconsistentDomainTerms,
142
+ vocabularySize: domainVocabularySize
143
+ } = detectInconsistentTerms(allDomainTerms);
144
+ const groundingResult = calculateAgentGrounding({
145
+ deepDirectories,
146
+ totalDirectories: dirs.length,
147
+ vagueFileNames,
148
+ totalFiles: files.length,
149
+ hasRootReadme,
150
+ readmeIsFresh,
151
+ barrelExports,
152
+ untypedExports,
153
+ totalExports: Math.max(1, totalExports),
154
+ inconsistentDomainTerms,
155
+ domainVocabularySize: Math.max(1, domainVocabularySize)
156
+ });
157
+ const issues = [];
158
+ if (groundingResult.dimensions.structureClarityScore < 70) {
159
+ issues.push({
160
+ type: "agent-navigation-failure",
161
+ dimension: "structure-clarity",
162
+ severity: Severity.Major,
163
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
164
+ location: { file: rootDir, line: 0 },
165
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
166
+ });
167
+ }
168
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
169
+ issues.push({
170
+ type: "agent-navigation-failure",
171
+ dimension: "self-documentation",
172
+ severity: Severity.Major,
173
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
174
+ location: { file: rootDir, line: 0 },
175
+ suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
176
+ });
177
+ }
178
+ if (!hasRootReadme) {
179
+ issues.push({
180
+ type: "agent-navigation-failure",
181
+ dimension: "entry-point",
182
+ severity: Severity.Critical,
183
+ message: "No root README.md found \u2014 agents have no orientation document to start from.",
184
+ location: { file: join(rootDir, "README.md"), line: 0 },
185
+ suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
186
+ });
187
+ } else if (!readmeIsFresh) {
188
+ issues.push({
189
+ type: "agent-navigation-failure",
190
+ dimension: "entry-point",
191
+ severity: Severity.Minor,
192
+ message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
193
+ location: { file: readmePath, line: 0 },
194
+ suggestion: "Update README.md to reflect the current codebase structure."
195
+ });
196
+ }
197
+ if (groundingResult.dimensions.apiClarityScore < 70) {
198
+ issues.push({
199
+ type: "agent-navigation-failure",
200
+ dimension: "api-clarity",
201
+ severity: Severity.Major,
202
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
203
+ location: { file: rootDir, line: 0 },
204
+ suggestion: "Add explicit return type and parameter annotations to all exported functions."
205
+ });
206
+ }
207
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
208
+ issues.push({
209
+ type: "agent-navigation-failure",
210
+ dimension: "domain-consistency",
211
+ severity: Severity.Major,
212
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
213
+ location: { file: rootDir, line: 0 },
214
+ suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
215
+ });
216
+ }
217
+ return {
218
+ summary: {
219
+ filesAnalyzed: files.length,
220
+ directoriesAnalyzed: dirs.length,
221
+ score: groundingResult.score,
222
+ rating: groundingResult.rating,
223
+ dimensions: groundingResult.dimensions
224
+ },
225
+ issues,
226
+ rawData: {
227
+ deepDirectories,
228
+ totalDirectories: dirs.length,
229
+ vagueFileNames,
230
+ totalFiles: files.length,
231
+ hasRootReadme,
232
+ readmeIsFresh,
233
+ barrelExports,
234
+ untypedExports,
235
+ totalExports,
236
+ inconsistentDomainTerms,
237
+ domainVocabularySize
238
+ },
239
+ recommendations: groundingResult.recommendations
240
+ };
241
+ }
242
+
243
+ // src/scoring.ts
244
+ function calculateGroundingScore(report) {
245
+ const { summary, rawData, recommendations } = report;
246
+ const factors = [
247
+ {
248
+ name: "Structure Clarity",
249
+ impact: Math.round(summary.dimensions.structureClarityScore - 50),
250
+ description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`
251
+ },
252
+ {
253
+ name: "Self-Documentation",
254
+ impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
255
+ description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`
256
+ },
257
+ {
258
+ name: "Entry Points",
259
+ impact: Math.round(summary.dimensions.entryPointScore - 50),
260
+ description: rawData.hasRootReadme ? rawData.readmeIsFresh ? "README present and fresh" : "README present but stale" : "No root README"
261
+ },
262
+ {
263
+ name: "API Clarity",
264
+ impact: Math.round(summary.dimensions.apiClarityScore - 50),
265
+ description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`
266
+ },
267
+ {
268
+ name: "Domain Consistency",
269
+ impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
270
+ description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
271
+ }
272
+ ];
273
+ const recs = recommendations.map(
274
+ (action) => ({
275
+ action,
276
+ estimatedImpact: 6,
277
+ priority: summary.score < 50 ? "high" : "medium"
278
+ })
279
+ );
280
+ return {
281
+ toolName: "agent-grounding",
282
+ score: summary.score,
283
+ rawMetrics: {
284
+ ...rawData,
285
+ rating: summary.rating
286
+ },
287
+ factors,
288
+ recommendations: recs
289
+ };
290
+ }
291
+
292
+ export {
293
+ analyzeAgentGrounding,
294
+ calculateGroundingScore
295
+ };
@@ -0,0 +1,296 @@
1
+ // src/analyzer.ts
2
+ import {
3
+ scanEntries,
4
+ calculateAgentGrounding,
5
+ VAGUE_FILE_NAMES,
6
+ Severity,
7
+ IssueType
8
+ } from "@aiready/core";
9
+ import { readFileSync, existsSync, statSync } from "fs";
10
+ import { join, extname, basename, relative } from "path";
11
+ import { parse } from "@typescript-eslint/typescript-estree";
12
+ function analyzeFile(filePath) {
13
+ let code;
14
+ try {
15
+ code = readFileSync(filePath, "utf-8");
16
+ } catch {
17
+ return {
18
+ isBarrel: false,
19
+ exportedNames: [],
20
+ untypedExports: 0,
21
+ totalExports: 0,
22
+ domainTerms: []
23
+ };
24
+ }
25
+ let ast;
26
+ try {
27
+ ast = parse(code, {
28
+ jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
29
+ range: false,
30
+ loc: false
31
+ });
32
+ } catch {
33
+ return {
34
+ isBarrel: false,
35
+ exportedNames: [],
36
+ untypedExports: 0,
37
+ totalExports: 0,
38
+ domainTerms: []
39
+ };
40
+ }
41
+ let isBarrel = false;
42
+ const exportedNames = [];
43
+ let untypedExports = 0;
44
+ let totalExports = 0;
45
+ const domainTerms = [];
46
+ for (const node of ast.body) {
47
+ if (node.type === "ExportAllDeclaration") {
48
+ isBarrel = true;
49
+ continue;
50
+ }
51
+ if (node.type === "ExportNamedDeclaration") {
52
+ totalExports++;
53
+ const decl = node.declaration;
54
+ if (decl) {
55
+ const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
56
+ if (name) {
57
+ exportedNames.push(name);
58
+ domainTerms.push(
59
+ ...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
60
+ );
61
+ const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
62
+ if (!hasType) untypedExports++;
63
+ }
64
+ } else if (node.specifiers && node.specifiers.length > 0) {
65
+ isBarrel = true;
66
+ }
67
+ }
68
+ if (node.type === "ExportDefaultDeclaration") {
69
+ totalExports++;
70
+ }
71
+ }
72
+ return { isBarrel, exportedNames, untypedExports, totalExports, domainTerms };
73
+ }
74
+ function detectInconsistentTerms(allTerms) {
75
+ const termFreq = /* @__PURE__ */ new Map();
76
+ for (const term of allTerms) {
77
+ if (term.length >= 3) {
78
+ termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
79
+ }
80
+ }
81
+ const orphans = [...termFreq.values()].filter((count) => count === 1).length;
82
+ const common = [...termFreq.values()].filter((count) => count >= 3).length;
83
+ const vocabularySize = termFreq.size;
84
+ const inconsistent = Math.max(0, orphans - common * 2);
85
+ return { inconsistent, vocabularySize };
86
+ }
87
+ async function analyzeAgentGrounding(options) {
88
+ const rootDir = options.rootDir;
89
+ const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
90
+ const readmeStaleDays = options.readmeStaleDays ?? 90;
91
+ const { files, dirs: rawDirs } = await scanEntries({
92
+ ...options,
93
+ include: options.include || ["**/*.{ts,tsx,js,jsx}"]
94
+ });
95
+ const dirs = rawDirs.map((d) => ({
96
+ path: d,
97
+ depth: relative(rootDir, d).split(/[/\\]/).filter(Boolean).length
98
+ }));
99
+ const deepDirectories = dirs.filter(
100
+ (d) => d.depth > maxRecommendedDepth
101
+ ).length;
102
+ const additionalVague = new Set(
103
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
104
+ );
105
+ let vagueFileNames = 0;
106
+ for (const f of files) {
107
+ const base = basename(f, extname(f)).toLowerCase();
108
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
109
+ vagueFileNames++;
110
+ }
111
+ }
112
+ const readmePath = join(rootDir, "README.md");
113
+ const hasRootReadme = existsSync(readmePath);
114
+ let readmeIsFresh = false;
115
+ if (hasRootReadme) {
116
+ try {
117
+ const stat = statSync(readmePath);
118
+ const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
119
+ readmeIsFresh = ageDays < readmeStaleDays;
120
+ } catch {
121
+ }
122
+ }
123
+ const allDomainTerms = [];
124
+ let barrelExports = 0;
125
+ let untypedExports = 0;
126
+ let totalExports = 0;
127
+ let processed = 0;
128
+ for (const f of files) {
129
+ processed++;
130
+ options.onProgress?.(
131
+ processed,
132
+ files.length,
133
+ `agent-grounding: analyzing files`
134
+ );
135
+ const analysis = analyzeFile(f);
136
+ if (analysis.isBarrel) barrelExports++;
137
+ untypedExports += analysis.untypedExports;
138
+ totalExports += analysis.totalExports;
139
+ allDomainTerms.push(...analysis.domainTerms);
140
+ }
141
+ const {
142
+ inconsistent: inconsistentDomainTerms,
143
+ vocabularySize: domainVocabularySize
144
+ } = detectInconsistentTerms(allDomainTerms);
145
+ const groundingResult = calculateAgentGrounding({
146
+ deepDirectories,
147
+ totalDirectories: dirs.length,
148
+ vagueFileNames,
149
+ totalFiles: files.length,
150
+ hasRootReadme,
151
+ readmeIsFresh,
152
+ barrelExports,
153
+ untypedExports,
154
+ totalExports: Math.max(1, totalExports),
155
+ inconsistentDomainTerms,
156
+ domainVocabularySize: Math.max(1, domainVocabularySize)
157
+ });
158
+ const issues = [];
159
+ if (groundingResult.dimensions.structureClarityScore < 70) {
160
+ issues.push({
161
+ type: IssueType.AgentNavigationFailure,
162
+ dimension: "structure-clarity",
163
+ severity: Severity.Major,
164
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
165
+ location: { file: rootDir, line: 0 },
166
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
167
+ });
168
+ }
169
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
170
+ issues.push({
171
+ type: IssueType.AgentNavigationFailure,
172
+ dimension: "self-documentation",
173
+ severity: Severity.Major,
174
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
175
+ location: { file: rootDir, line: 0 },
176
+ suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
177
+ });
178
+ }
179
+ if (!hasRootReadme) {
180
+ issues.push({
181
+ type: IssueType.AgentNavigationFailure,
182
+ dimension: "entry-point",
183
+ severity: Severity.Critical,
184
+ message: "No root README.md found \u2014 agents have no orientation document to start from.",
185
+ location: { file: join(rootDir, "README.md"), line: 0 },
186
+ suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
187
+ });
188
+ } else if (!readmeIsFresh) {
189
+ issues.push({
190
+ type: IssueType.AgentNavigationFailure,
191
+ dimension: "entry-point",
192
+ severity: Severity.Minor,
193
+ message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
194
+ location: { file: readmePath, line: 0 },
195
+ suggestion: "Update README.md to reflect the current codebase structure."
196
+ });
197
+ }
198
+ if (groundingResult.dimensions.apiClarityScore < 70) {
199
+ issues.push({
200
+ type: IssueType.AgentNavigationFailure,
201
+ dimension: "api-clarity",
202
+ severity: Severity.Major,
203
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
204
+ location: { file: rootDir, line: 0 },
205
+ suggestion: "Add explicit return type and parameter annotations to all exported functions."
206
+ });
207
+ }
208
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
209
+ issues.push({
210
+ type: IssueType.AgentNavigationFailure,
211
+ dimension: "domain-consistency",
212
+ severity: Severity.Major,
213
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
214
+ location: { file: rootDir, line: 0 },
215
+ suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
216
+ });
217
+ }
218
+ return {
219
+ summary: {
220
+ filesAnalyzed: files.length,
221
+ directoriesAnalyzed: dirs.length,
222
+ score: groundingResult.score,
223
+ rating: groundingResult.rating,
224
+ dimensions: groundingResult.dimensions
225
+ },
226
+ issues,
227
+ rawData: {
228
+ deepDirectories,
229
+ totalDirectories: dirs.length,
230
+ vagueFileNames,
231
+ totalFiles: files.length,
232
+ hasRootReadme,
233
+ readmeIsFresh,
234
+ barrelExports,
235
+ untypedExports,
236
+ totalExports,
237
+ inconsistentDomainTerms,
238
+ domainVocabularySize
239
+ },
240
+ recommendations: groundingResult.recommendations
241
+ };
242
+ }
243
+
244
+ // src/scoring.ts
245
+ function calculateGroundingScore(report) {
246
+ const { summary, rawData, recommendations } = report;
247
+ const factors = [
248
+ {
249
+ name: "Structure Clarity",
250
+ impact: Math.round(summary.dimensions.structureClarityScore - 50),
251
+ description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`
252
+ },
253
+ {
254
+ name: "Self-Documentation",
255
+ impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
256
+ description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`
257
+ },
258
+ {
259
+ name: "Entry Points",
260
+ impact: Math.round(summary.dimensions.entryPointScore - 50),
261
+ description: rawData.hasRootReadme ? rawData.readmeIsFresh ? "README present and fresh" : "README present but stale" : "No root README"
262
+ },
263
+ {
264
+ name: "API Clarity",
265
+ impact: Math.round(summary.dimensions.apiClarityScore - 50),
266
+ description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`
267
+ },
268
+ {
269
+ name: "Domain Consistency",
270
+ impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
271
+ description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
272
+ }
273
+ ];
274
+ const recs = recommendations.map(
275
+ (action) => ({
276
+ action,
277
+ estimatedImpact: 6,
278
+ priority: summary.score < 50 ? "high" : "medium"
279
+ })
280
+ );
281
+ return {
282
+ toolName: "agent-grounding",
283
+ score: summary.score,
284
+ rawMetrics: {
285
+ ...rawData,
286
+ rating: summary.rating
287
+ },
288
+ factors,
289
+ recommendations: recs
290
+ };
291
+ }
292
+
293
+ export {
294
+ analyzeAgentGrounding,
295
+ calculateGroundingScore
296
+ };
package/dist/cli.js CHANGED
@@ -180,9 +180,9 @@ async function analyzeAgentGrounding(options) {
180
180
  const issues = [];
181
181
  if (groundingResult.dimensions.structureClarityScore < 70) {
182
182
  issues.push({
183
- type: "agent-navigation-failure",
183
+ type: import_core.IssueType.AgentNavigationFailure,
184
184
  dimension: "structure-clarity",
185
- severity: "major",
185
+ severity: import_core.Severity.Major,
186
186
  message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
187
187
  location: { file: rootDir, line: 0 },
188
188
  suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
@@ -190,9 +190,9 @@ async function analyzeAgentGrounding(options) {
190
190
  }
191
191
  if (groundingResult.dimensions.selfDocumentationScore < 70) {
192
192
  issues.push({
193
- type: "agent-navigation-failure",
193
+ type: import_core.IssueType.AgentNavigationFailure,
194
194
  dimension: "self-documentation",
195
- severity: "major",
195
+ severity: import_core.Severity.Major,
196
196
  message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
197
197
  location: { file: rootDir, line: 0 },
198
198
  suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
@@ -200,18 +200,18 @@ async function analyzeAgentGrounding(options) {
200
200
  }
201
201
  if (!hasRootReadme) {
202
202
  issues.push({
203
- type: "agent-navigation-failure",
203
+ type: import_core.IssueType.AgentNavigationFailure,
204
204
  dimension: "entry-point",
205
- severity: "critical",
205
+ severity: import_core.Severity.Critical,
206
206
  message: "No root README.md found \u2014 agents have no orientation document to start from.",
207
207
  location: { file: (0, import_path.join)(rootDir, "README.md"), line: 0 },
208
208
  suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
209
209
  });
210
210
  } else if (!readmeIsFresh) {
211
211
  issues.push({
212
- type: "agent-navigation-failure",
212
+ type: import_core.IssueType.AgentNavigationFailure,
213
213
  dimension: "entry-point",
214
- severity: "minor",
214
+ severity: import_core.Severity.Minor,
215
215
  message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
216
216
  location: { file: readmePath, line: 0 },
217
217
  suggestion: "Update README.md to reflect the current codebase structure."
@@ -219,9 +219,9 @@ async function analyzeAgentGrounding(options) {
219
219
  }
220
220
  if (groundingResult.dimensions.apiClarityScore < 70) {
221
221
  issues.push({
222
- type: "agent-navigation-failure",
222
+ type: import_core.IssueType.AgentNavigationFailure,
223
223
  dimension: "api-clarity",
224
- severity: "major",
224
+ severity: import_core.Severity.Major,
225
225
  message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
226
226
  location: { file: rootDir, line: 0 },
227
227
  suggestion: "Add explicit return type and parameter annotations to all exported functions."
@@ -229,9 +229,9 @@ async function analyzeAgentGrounding(options) {
229
229
  }
230
230
  if (groundingResult.dimensions.domainConsistencyScore < 70) {
231
231
  issues.push({
232
- type: "agent-navigation-failure",
232
+ type: import_core.IssueType.AgentNavigationFailure,
233
233
  dimension: "domain-consistency",
234
- severity: "major",
234
+ severity: import_core.Severity.Major,
235
235
  message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
236
236
  location: { file: rootDir, line: 0 },
237
237
  suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeAgentGrounding,
4
4
  calculateGroundingScore
5
- } from "./chunk-NXIMJNCK.mjs";
5
+ } from "./chunk-L3MLD6K4.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Issue, ScanOptions, ToolScoringOutput } from '@aiready/core';
1
+ import { Issue, IssueType, ScanOptions, ToolScoringOutput } from '@aiready/core';
2
2
 
3
3
  interface AgentGroundingOptions extends ScanOptions {
4
4
  /** Max directory depth before flagging as "too deep" */
@@ -9,7 +9,7 @@ interface AgentGroundingOptions extends ScanOptions {
9
9
  additionalVagueNames?: string[];
10
10
  }
11
11
  interface AgentGroundingIssue extends Issue {
12
- type: 'agent-navigation-failure';
12
+ type: IssueType.AgentNavigationFailure;
13
13
  /** Which grounding dimension is affected */
14
14
  dimension: 'structure-clarity' | 'self-documentation' | 'entry-point' | 'api-clarity' | 'domain-consistency';
15
15
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Issue, ScanOptions, ToolScoringOutput } from '@aiready/core';
1
+ import { Issue, IssueType, ScanOptions, ToolScoringOutput } from '@aiready/core';
2
2
 
3
3
  interface AgentGroundingOptions extends ScanOptions {
4
4
  /** Max directory depth before flagging as "too deep" */
@@ -9,7 +9,7 @@ interface AgentGroundingOptions extends ScanOptions {
9
9
  additionalVagueNames?: string[];
10
10
  }
11
11
  interface AgentGroundingIssue extends Issue {
12
- type: 'agent-navigation-failure';
12
+ type: IssueType.AgentNavigationFailure;
13
13
  /** Which grounding dimension is affected */
14
14
  dimension: 'structure-clarity' | 'self-documentation' | 'entry-point' | 'api-clarity' | 'domain-consistency';
15
15
  }
package/dist/index.js CHANGED
@@ -179,9 +179,9 @@ async function analyzeAgentGrounding(options) {
179
179
  const issues = [];
180
180
  if (groundingResult.dimensions.structureClarityScore < 70) {
181
181
  issues.push({
182
- type: "agent-navigation-failure",
182
+ type: import_core.IssueType.AgentNavigationFailure,
183
183
  dimension: "structure-clarity",
184
- severity: "major",
184
+ severity: import_core.Severity.Major,
185
185
  message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
186
186
  location: { file: rootDir, line: 0 },
187
187
  suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
@@ -189,9 +189,9 @@ async function analyzeAgentGrounding(options) {
189
189
  }
190
190
  if (groundingResult.dimensions.selfDocumentationScore < 70) {
191
191
  issues.push({
192
- type: "agent-navigation-failure",
192
+ type: import_core.IssueType.AgentNavigationFailure,
193
193
  dimension: "self-documentation",
194
- severity: "major",
194
+ severity: import_core.Severity.Major,
195
195
  message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
196
196
  location: { file: rootDir, line: 0 },
197
197
  suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
@@ -199,18 +199,18 @@ async function analyzeAgentGrounding(options) {
199
199
  }
200
200
  if (!hasRootReadme) {
201
201
  issues.push({
202
- type: "agent-navigation-failure",
202
+ type: import_core.IssueType.AgentNavigationFailure,
203
203
  dimension: "entry-point",
204
- severity: "critical",
204
+ severity: import_core.Severity.Critical,
205
205
  message: "No root README.md found \u2014 agents have no orientation document to start from.",
206
206
  location: { file: (0, import_path.join)(rootDir, "README.md"), line: 0 },
207
207
  suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
208
208
  });
209
209
  } else if (!readmeIsFresh) {
210
210
  issues.push({
211
- type: "agent-navigation-failure",
211
+ type: import_core.IssueType.AgentNavigationFailure,
212
212
  dimension: "entry-point",
213
- severity: "minor",
213
+ severity: import_core.Severity.Minor,
214
214
  message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
215
215
  location: { file: readmePath, line: 0 },
216
216
  suggestion: "Update README.md to reflect the current codebase structure."
@@ -218,9 +218,9 @@ async function analyzeAgentGrounding(options) {
218
218
  }
219
219
  if (groundingResult.dimensions.apiClarityScore < 70) {
220
220
  issues.push({
221
- type: "agent-navigation-failure",
221
+ type: import_core.IssueType.AgentNavigationFailure,
222
222
  dimension: "api-clarity",
223
- severity: "major",
223
+ severity: import_core.Severity.Major,
224
224
  message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
225
225
  location: { file: rootDir, line: 0 },
226
226
  suggestion: "Add explicit return type and parameter annotations to all exported functions."
@@ -228,9 +228,9 @@ async function analyzeAgentGrounding(options) {
228
228
  }
229
229
  if (groundingResult.dimensions.domainConsistencyScore < 70) {
230
230
  issues.push({
231
- type: "agent-navigation-failure",
231
+ type: import_core.IssueType.AgentNavigationFailure,
232
232
  dimension: "domain-consistency",
233
- severity: "major",
233
+ severity: import_core.Severity.Major,
234
234
  message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
235
235
  location: { file: rootDir, line: 0 },
236
236
  suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeAgentGrounding,
3
3
  calculateGroundingScore
4
- } from "./chunk-NXIMJNCK.mjs";
4
+ } from "./chunk-L3MLD6K4.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.9.2",
3
+ "version": "0.9.3",
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",
@@ -40,7 +40,7 @@
40
40
  "chalk": "^5.3.0",
41
41
  "commander": "^14.0.0",
42
42
  "glob": "^13.0.0",
43
- "@aiready/core": "0.19.2"
43
+ "@aiready/core": "0.19.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
package/src/analyzer.ts CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  scanEntries,
14
14
  calculateAgentGrounding,
15
15
  VAGUE_FILE_NAMES,
16
+ Severity,
17
+ IssueType,
16
18
  } from '@aiready/core';
17
19
  import { readFileSync, existsSync, statSync } from 'fs';
18
20
  import { join, extname, basename, relative } from 'path';
@@ -241,9 +243,9 @@ export async function analyzeAgentGrounding(
241
243
 
242
244
  if (groundingResult.dimensions.structureClarityScore < 70) {
243
245
  issues.push({
244
- type: 'agent-navigation-failure',
246
+ type: IssueType.AgentNavigationFailure,
245
247
  dimension: 'structure-clarity',
246
- severity: 'major',
248
+ severity: Severity.Major,
247
249
  message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} — agents struggle to navigate deep trees.`,
248
250
  location: { file: rootDir, line: 0 },
249
251
  suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`,
@@ -252,9 +254,9 @@ export async function analyzeAgentGrounding(
252
254
 
253
255
  if (groundingResult.dimensions.selfDocumentationScore < 70) {
254
256
  issues.push({
255
- type: 'agent-navigation-failure',
257
+ type: IssueType.AgentNavigationFailure,
256
258
  dimension: 'self-documentation',
257
- severity: 'major',
259
+ severity: Severity.Major,
258
260
  message: `${vagueFileNames} files use vague names (utils, helpers, misc) — an agent cannot determine their purpose from the name alone.`,
259
261
  location: { file: rootDir, line: 0 },
260
262
  suggestion:
@@ -264,9 +266,9 @@ export async function analyzeAgentGrounding(
264
266
 
265
267
  if (!hasRootReadme) {
266
268
  issues.push({
267
- type: 'agent-navigation-failure',
269
+ type: IssueType.AgentNavigationFailure,
268
270
  dimension: 'entry-point',
269
- severity: 'critical',
271
+ severity: Severity.Critical,
270
272
  message:
271
273
  'No root README.md found — agents have no orientation document to start from.',
272
274
  location: { file: join(rootDir, 'README.md'), line: 0 },
@@ -275,9 +277,9 @@ export async function analyzeAgentGrounding(
275
277
  });
276
278
  } else if (!readmeIsFresh) {
277
279
  issues.push({
278
- type: 'agent-navigation-failure',
280
+ type: IssueType.AgentNavigationFailure,
279
281
  dimension: 'entry-point',
280
- severity: 'minor',
282
+ severity: Severity.Minor,
281
283
  message: `README.md is stale (>${readmeStaleDays} days without updates) — agents may be misled by outdated context.`,
282
284
  location: { file: readmePath, line: 0 },
283
285
  suggestion: 'Update README.md to reflect the current codebase structure.',
@@ -286,9 +288,9 @@ export async function analyzeAgentGrounding(
286
288
 
287
289
  if (groundingResult.dimensions.apiClarityScore < 70) {
288
290
  issues.push({
289
- type: 'agent-navigation-failure',
291
+ type: IssueType.AgentNavigationFailure,
290
292
  dimension: 'api-clarity',
291
- severity: 'major',
293
+ severity: Severity.Major,
292
294
  message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations — agents cannot infer the API contract.`,
293
295
  location: { file: rootDir, line: 0 },
294
296
  suggestion:
@@ -298,9 +300,9 @@ export async function analyzeAgentGrounding(
298
300
 
299
301
  if (groundingResult.dimensions.domainConsistencyScore < 70) {
300
302
  issues.push({
301
- type: 'agent-navigation-failure',
303
+ type: IssueType.AgentNavigationFailure,
302
304
  dimension: 'domain-consistency',
303
- severity: 'major',
305
+ severity: Severity.Major,
304
306
  message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently — agents get confused when one concept has multiple names.`,
305
307
  location: { file: rootDir, line: 0 },
306
308
  suggestion:
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ScanOptions, Issue } from '@aiready/core';
1
+ import type { ScanOptions, Issue, IssueType } from '@aiready/core';
2
2
 
3
3
  export interface AgentGroundingOptions extends ScanOptions {
4
4
  /** Max directory depth before flagging as "too deep" */
@@ -10,7 +10,7 @@ export interface AgentGroundingOptions extends ScanOptions {
10
10
  }
11
11
 
12
12
  export interface AgentGroundingIssue extends Issue {
13
- type: 'agent-navigation-failure';
13
+ type: IssueType.AgentNavigationFailure;
14
14
  /** Which grounding dimension is affected */
15
15
  dimension:
16
16
  | 'structure-clarity'