@aiready/agent-grounding 0.11.3 → 0.11.8

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