@aiready/agent-grounding 0.11.16 → 0.11.17

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.15 build /Users/pengcao/projects/aiready/packages/agent-grounding
3
+ > @aiready/agent-grounding@0.11.16 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,15 +9,15 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
+ CJS dist/cli.js 16.65 KB
13
+ CJS dist/index.js 12.42 KB
14
+ CJS ⚡️ Build success in 39ms
12
15
  ESM dist/cli.mjs 5.16 KB
13
- ESM dist/chunk-7DOMO6A5.mjs 9.73 KB
14
16
  ESM dist/index.mjs 1.30 KB
15
- ESM ⚡️ Build success in 166ms
16
- CJS dist/index.js 12.31 KB
17
- CJS dist/cli.js 16.54 KB
18
- CJS ⚡️ Build success in 166ms
17
+ ESM dist/chunk-AWAS2KB5.mjs 9.83 KB
18
+ ESM ⚡️ Build success in 40ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 2597ms
20
+ DTS ⚡️ Build success in 1249ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
22
  DTS dist/index.d.ts 2.49 KB
23
23
  DTS dist/cli.d.mts 20.00 B
@@ -1,16 +1,16 @@
1
1
 
2
2
  
3
- > @aiready/agent-grounding@0.11.15 test /Users/pengcao/projects/aiready/packages/agent-grounding
3
+ > @aiready/agent-grounding@0.11.16 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) 282ms
9
+ ✓ src/__tests__/analyzer.test.ts (3 tests) 59ms
10
10
 
11
11
   Test Files  1 passed (1)
12
12
   Tests  3 passed (3)
13
-  Start at  02:11:32
14
-  Duration  1.48s (transform 207ms, setup 0ms, import 976ms, tests 282ms, environment 0ms)
13
+  Start at  12:45:56
14
+  Duration  439ms (transform 72ms, setup 0ms, import 263ms, tests 59ms, environment 0ms)
15
15
 
16
16
  [?25h
@@ -0,0 +1,304 @@
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 { files: allFiles } = await scanEntries({
97
+ ...options,
98
+ include: ["**/*"]
99
+ });
100
+ const dirs = rawDirs.map((d) => ({
101
+ path: d,
102
+ depth: relative(rootDir, d).split(/[/\\]/).filter(Boolean).length
103
+ }));
104
+ const deepDirectories = dirs.filter(
105
+ (d) => d.depth > maxRecommendedDepth
106
+ ).length;
107
+ const additionalVague = new Set(
108
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
109
+ );
110
+ let vagueFileNames = 0;
111
+ for (const f of allFiles) {
112
+ const base = basename(f, extname(f)).toLowerCase();
113
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
114
+ vagueFileNames++;
115
+ }
116
+ }
117
+ const readmePath = join(rootDir, "README.md");
118
+ const hasRootReadme = existsSync(readmePath);
119
+ let readmeIsFresh = false;
120
+ if (hasRootReadme) {
121
+ try {
122
+ const stat = statSync(readmePath);
123
+ const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
124
+ readmeIsFresh = ageDays < readmeStaleDays;
125
+ } catch {
126
+ }
127
+ }
128
+ const allDomainTerms = [];
129
+ let barrelExports = 0;
130
+ let untypedExports = 0;
131
+ let totalExports = 0;
132
+ let processed = 0;
133
+ for (const f of files) {
134
+ processed++;
135
+ emitProgress(
136
+ processed,
137
+ files.length,
138
+ "agent-grounding",
139
+ "analyzing files",
140
+ options.onProgress
141
+ );
142
+ const analysis = analyzeFile(f);
143
+ if (analysis.isBarrel) barrelExports++;
144
+ untypedExports += analysis.untypedExports;
145
+ totalExports += analysis.totalExports;
146
+ allDomainTerms.push(...analysis.domainTerms);
147
+ }
148
+ const {
149
+ inconsistent: inconsistentDomainTerms,
150
+ vocabularySize: domainVocabularySize
151
+ } = detectInconsistentTerms(allDomainTerms);
152
+ const groundingResult = calculateAgentGrounding({
153
+ deepDirectories,
154
+ totalDirectories: dirs.length,
155
+ vagueFileNames,
156
+ totalFiles: files.length,
157
+ hasRootReadme,
158
+ readmeIsFresh,
159
+ barrelExports,
160
+ untypedExports,
161
+ totalExports: Math.max(1, totalExports),
162
+ inconsistentDomainTerms,
163
+ domainVocabularySize: Math.max(1, domainVocabularySize)
164
+ });
165
+ const issues = [];
166
+ if (groundingResult.dimensions.structureClarityScore < 70) {
167
+ issues.push({
168
+ type: IssueType.AgentNavigationFailure,
169
+ dimension: "structure-clarity",
170
+ severity: Severity.Major,
171
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
172
+ location: { file: rootDir, line: 0 },
173
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
174
+ });
175
+ }
176
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
177
+ issues.push({
178
+ type: IssueType.AgentNavigationFailure,
179
+ dimension: "self-documentation",
180
+ severity: Severity.Major,
181
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
182
+ location: { file: rootDir, line: 0 },
183
+ suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
184
+ });
185
+ }
186
+ if (!hasRootReadme) {
187
+ issues.push({
188
+ type: IssueType.AgentNavigationFailure,
189
+ dimension: "entry-point",
190
+ severity: Severity.Critical,
191
+ message: "No root README.md found \u2014 agents have no orientation document to start from.",
192
+ location: { file: join(rootDir, "README.md"), line: 0 },
193
+ suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
194
+ });
195
+ } else if (!readmeIsFresh) {
196
+ issues.push({
197
+ type: IssueType.AgentNavigationFailure,
198
+ dimension: "entry-point",
199
+ severity: Severity.Minor,
200
+ message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
201
+ location: { file: readmePath, line: 0 },
202
+ suggestion: "Update README.md to reflect the current codebase structure."
203
+ });
204
+ }
205
+ if (groundingResult.dimensions.apiClarityScore < 70) {
206
+ issues.push({
207
+ type: IssueType.AgentNavigationFailure,
208
+ dimension: "api-clarity",
209
+ severity: Severity.Major,
210
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
211
+ location: { file: rootDir, line: 0 },
212
+ suggestion: "Add explicit return type and parameter annotations to all exported functions."
213
+ });
214
+ }
215
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
216
+ issues.push({
217
+ type: IssueType.AgentNavigationFailure,
218
+ dimension: "domain-consistency",
219
+ severity: Severity.Major,
220
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
221
+ location: { file: rootDir, line: 0 },
222
+ suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
223
+ });
224
+ }
225
+ return {
226
+ summary: {
227
+ filesAnalyzed: files.length,
228
+ directoriesAnalyzed: dirs.length,
229
+ score: groundingResult.score,
230
+ rating: groundingResult.rating,
231
+ dimensions: groundingResult.dimensions
232
+ },
233
+ issues,
234
+ rawData: {
235
+ deepDirectories,
236
+ totalDirectories: dirs.length,
237
+ vagueFileNames,
238
+ totalFiles: files.length,
239
+ hasRootReadme,
240
+ readmeIsFresh,
241
+ barrelExports,
242
+ untypedExports,
243
+ totalExports,
244
+ inconsistentDomainTerms,
245
+ domainVocabularySize
246
+ },
247
+ recommendations: groundingResult.recommendations
248
+ };
249
+ }
250
+
251
+ // src/scoring.ts
252
+ import { ToolName } from "@aiready/core";
253
+ function calculateGroundingScore(report) {
254
+ const { summary, rawData, recommendations } = report;
255
+ const factors = [
256
+ {
257
+ name: "Structure Clarity",
258
+ impact: Math.round(summary.dimensions.structureClarityScore - 50),
259
+ description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`
260
+ },
261
+ {
262
+ name: "Self-Documentation",
263
+ impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
264
+ description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`
265
+ },
266
+ {
267
+ name: "Entry Points",
268
+ impact: Math.round(summary.dimensions.entryPointScore - 50),
269
+ description: rawData.hasRootReadme ? rawData.readmeIsFresh ? "README present and fresh" : "README present but stale" : "No root README"
270
+ },
271
+ {
272
+ name: "API Clarity",
273
+ impact: Math.round(summary.dimensions.apiClarityScore - 50),
274
+ description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`
275
+ },
276
+ {
277
+ name: "Domain Consistency",
278
+ impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
279
+ description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
280
+ }
281
+ ];
282
+ const recs = recommendations.map(
283
+ (action) => ({
284
+ action,
285
+ estimatedImpact: 6,
286
+ priority: summary.score < 50 ? "high" : "medium"
287
+ })
288
+ );
289
+ return {
290
+ toolName: ToolName.AgentGrounding,
291
+ score: summary.score,
292
+ rawMetrics: {
293
+ ...rawData,
294
+ rating: summary.rating
295
+ },
296
+ factors,
297
+ recommendations: recs
298
+ };
299
+ }
300
+
301
+ export {
302
+ analyzeAgentGrounding,
303
+ calculateGroundingScore
304
+ };
package/dist/cli.js CHANGED
@@ -114,6 +114,10 @@ async function analyzeAgentGrounding(options) {
114
114
  ...options,
115
115
  include: options.include || ["**/*.{ts,tsx,js,jsx}"]
116
116
  });
117
+ const { files: allFiles } = await (0, import_core.scanEntries)({
118
+ ...options,
119
+ include: ["**/*"]
120
+ });
117
121
  const dirs = rawDirs.map((d) => ({
118
122
  path: d,
119
123
  depth: (0, import_path.relative)(rootDir, d).split(/[/\\]/).filter(Boolean).length
@@ -125,7 +129,7 @@ async function analyzeAgentGrounding(options) {
125
129
  (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
126
130
  );
127
131
  let vagueFileNames = 0;
128
- for (const f of files) {
132
+ for (const f of allFiles) {
129
133
  const base = (0, import_path.basename)(f, (0, import_path.extname)(f)).toLowerCase();
130
134
  if (import_core.VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
131
135
  vagueFileNames++;
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeAgentGrounding,
4
4
  calculateGroundingScore
5
- } from "./chunk-7DOMO6A5.mjs";
5
+ } from "./chunk-AWAS2KB5.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -118,6 +118,10 @@ async function analyzeAgentGrounding(options) {
118
118
  ...options,
119
119
  include: options.include || ["**/*.{ts,tsx,js,jsx}"]
120
120
  });
121
+ const { files: allFiles } = await (0, import_core.scanEntries)({
122
+ ...options,
123
+ include: ["**/*"]
124
+ });
121
125
  const dirs = rawDirs.map((d) => ({
122
126
  path: d,
123
127
  depth: (0, import_path.relative)(rootDir, d).split(/[/\\]/).filter(Boolean).length
@@ -129,7 +133,7 @@ async function analyzeAgentGrounding(options) {
129
133
  (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
130
134
  );
131
135
  let vagueFileNames = 0;
132
- for (const f of files) {
136
+ for (const f of allFiles) {
133
137
  const base = (0, import_path.basename)(f, (0, import_path.extname)(f)).toLowerCase();
134
138
  if (import_core.VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
135
139
  vagueFileNames++;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeAgentGrounding,
3
3
  calculateGroundingScore
4
- } from "./chunk-7DOMO6A5.mjs";
4
+ } from "./chunk-AWAS2KB5.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.16",
3
+ "version": "0.11.17",
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.16"
43
+ "@aiready/core": "0.21.17"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
package/src/analyzer.ts CHANGED
@@ -155,11 +155,18 @@ export async function analyzeAgentGrounding(
155
155
  const readmeStaleDays = options.readmeStaleDays ?? 90;
156
156
 
157
157
  // Use core scanEntries which respects .gitignore recursively
158
+ // First scan for metrics that need code analysis (limited to JS/TS)
158
159
  const { files, dirs: rawDirs } = await scanEntries({
159
160
  ...options,
160
161
  include: options.include || ['**/*.{ts,tsx,js,jsx}'],
161
162
  });
162
163
 
164
+ // Second scan for ALL files to catch vague names (e.g. data.txt, tmp.log)
165
+ const { files: allFiles } = await scanEntries({
166
+ ...options,
167
+ include: ['**/*'],
168
+ });
169
+
163
170
  const dirs = rawDirs.map((d: string) => ({
164
171
  path: d,
165
172
  depth: relative(rootDir, d).split(/[/\\]/).filter(Boolean).length,
@@ -175,7 +182,7 @@ export async function analyzeAgentGrounding(
175
182
  (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
176
183
  );
177
184
  let vagueFileNames = 0;
178
- for (const f of files) {
185
+ for (const f of allFiles) {
179
186
  const base = basename(f, extname(f)).toLowerCase();
180
187
  if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
181
188
  vagueFileNames++;