@aiready/agent-grounding 0.13.22 → 0.14.0

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,5 +1,5 @@
1
1
 
2
- > @aiready/agent-grounding@0.13.22 build /Users/pengcao/projects/aiready/packages/agent-grounding
2
+ > @aiready/agent-grounding@0.14.0 build /Users/pengcao/projects/aiready/packages/agent-grounding
3
3
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -8,15 +8,15 @@ CLI tsup v8.5.1
8
8
  CLI Target: es2020
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/cli.js 14.19 KB
12
- CJS dist/index.js 11.11 KB
13
- CJS ⚡️ Build success in 33ms
14
- ESM dist/cli.mjs 4.15 KB
15
- ESM dist/chunk-OFAWUHBZ.mjs 8.68 KB
16
11
  ESM dist/index.mjs 1.18 KB
17
- ESM ⚡️ Build success in 34ms
12
+ ESM dist/chunk-FCCOCULS.mjs 8.61 KB
13
+ ESM dist/cli.mjs 4.15 KB
14
+ ESM ⚡️ Build success in 316ms
15
+ CJS dist/index.js 11.04 KB
16
+ CJS dist/cli.js 14.11 KB
17
+ CJS ⚡️ Build success in 317ms
18
18
  DTS Build start
19
- DTS ⚡️ Build success in 1736ms
19
+ DTS ⚡️ Build success in 5500ms
20
20
  DTS dist/cli.d.ts 20.00 B
21
21
  DTS dist/index.d.ts 2.52 KB
22
22
  DTS dist/cli.d.mts 20.00 B
@@ -1,17 +1,17 @@
1
1
 
2
- > @aiready/agent-grounding@0.13.21 test /Users/pengcao/projects/aiready/packages/agent-grounding
2
+ > @aiready/agent-grounding@0.13.23 test /Users/pengcao/projects/aiready/packages/agent-grounding
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/agent-grounding
7
7
 
8
- ✓ src/__tests__/provider.test.ts (2 tests) 19ms
9
8
  ✓ src/__tests__/scoring.test.ts (2 tests) 2ms
10
- ✓ src/__tests__/analyzer.test.ts (3 tests) 771ms
11
- ✓ should detect deep directories and vague file names  502ms
9
+ ✓ src/__tests__/provider.test.ts (2 tests) 3ms
10
+ ✓ src/__tests__/analyzer.test.ts (3 tests) 644ms
11
+ ✓ should detect missing READMEs in directories  370ms
12
12
 
13
13
   Test Files  3 passed (3)
14
14
   Tests  7 passed (7)
15
-  Start at  19:39:17
16
-  Duration  3.68s (transform 2.30s, setup 0ms, import 7.17s, tests 792ms, environment 0ms)
15
+  Start at  15:17:50
16
+  Duration  3.62s (transform 2.55s, setup 0ms, import 6.72s, tests 649ms, environment 0ms)
17
17
 
@@ -0,0 +1,257 @@
1
+ // src/analyzer.ts
2
+ import {
3
+ scanEntries,
4
+ calculateAgentGrounding,
5
+ VAGUE_FILE_NAMES,
6
+ Severity,
7
+ IssueType,
8
+ runBatchAnalysis,
9
+ getParser
10
+ } from "@aiready/core";
11
+ import { readFileSync, existsSync, statSync } from "fs";
12
+ import { join, extname, basename, relative } from "path";
13
+ async function analyzeFile(filePath) {
14
+ const result = {
15
+ isBarrel: false,
16
+ exportedNames: [],
17
+ untypedExports: 0,
18
+ totalExports: 0,
19
+ domainTerms: []
20
+ };
21
+ const parser = await getParser(filePath);
22
+ if (!parser) return result;
23
+ let code;
24
+ try {
25
+ code = readFileSync(filePath, "utf-8");
26
+ } catch {
27
+ return result;
28
+ }
29
+ try {
30
+ await parser.initialize();
31
+ const parseResult = parser.parse(code, filePath);
32
+ for (const exp of parseResult.exports) {
33
+ if (exp.type === "function" || exp.type === "class" || exp.type === "const") {
34
+ result.totalExports++;
35
+ const name = exp.name;
36
+ if (name && name !== "default") {
37
+ result.exportedNames.push(name);
38
+ result.domainTerms.push(
39
+ ...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
40
+ );
41
+ if (exp.isTyped === false) {
42
+ result.untypedExports++;
43
+ }
44
+ }
45
+ }
46
+ if (parseResult.exports.length > 5) result.isBarrel = true;
47
+ }
48
+ } catch (error) {
49
+ console.warn(`Agent Grounding: Failed to parse ${filePath}: ${error}`);
50
+ }
51
+ return result;
52
+ }
53
+ function detectInconsistentTerms(allTerms) {
54
+ const termFreq = /* @__PURE__ */ new Map();
55
+ for (const term of allTerms) {
56
+ if (term.length >= 3) {
57
+ termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
58
+ }
59
+ }
60
+ const orphans = [...termFreq.values()].filter((count) => count === 1).length;
61
+ const common = [...termFreq.values()].filter((count) => count >= 3).length;
62
+ const vocabularySize = termFreq.size;
63
+ const inconsistent = Math.max(0, orphans - common * 2);
64
+ return { inconsistent, vocabularySize };
65
+ }
66
+ async function analyzeAgentGrounding(options) {
67
+ const rootDir = options.rootDir;
68
+ const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
69
+ const readmeStaleDays = options.readmeStaleDays ?? 90;
70
+ const { files, dirs: rawDirs } = await scanEntries({
71
+ ...options,
72
+ include: options.include || ["**/*.{ts,tsx,js,jsx}"]
73
+ });
74
+ const { files: allFiles } = await scanEntries({
75
+ ...options,
76
+ include: ["**/*"]
77
+ });
78
+ const dirs = rawDirs.map((d) => ({
79
+ path: d,
80
+ depth: relative(rootDir, d).split(/[/\\]/).filter(Boolean).length
81
+ }));
82
+ const deepDirectories = dirs.filter(
83
+ (d) => d.depth > maxRecommendedDepth
84
+ ).length;
85
+ const additionalVague = new Set(
86
+ (options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
87
+ );
88
+ let vagueFileNames = 0;
89
+ for (const f of allFiles) {
90
+ const base = basename(f, extname(f)).toLowerCase();
91
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
92
+ vagueFileNames++;
93
+ }
94
+ }
95
+ const readmePath = join(rootDir, "README.md");
96
+ const hasRootReadme = existsSync(readmePath);
97
+ let readmeIsFresh = false;
98
+ if (hasRootReadme) {
99
+ try {
100
+ const stat = statSync(readmePath);
101
+ const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
102
+ readmeIsFresh = ageDays < readmeStaleDays;
103
+ } catch {
104
+ }
105
+ }
106
+ const allDomainTerms = [];
107
+ let barrelExports = 0;
108
+ let untypedExports = 0;
109
+ let totalExports = 0;
110
+ await runBatchAnalysis(
111
+ files,
112
+ "analyzing files",
113
+ "agent-grounding",
114
+ options.onProgress,
115
+ (f) => analyzeFile(f),
116
+ (analysis) => {
117
+ if (analysis.isBarrel) barrelExports++;
118
+ untypedExports += analysis.untypedExports;
119
+ totalExports += analysis.totalExports;
120
+ allDomainTerms.push(...analysis.domainTerms);
121
+ }
122
+ );
123
+ const {
124
+ inconsistent: inconsistentDomainTerms,
125
+ vocabularySize: domainVocabularySize
126
+ } = detectInconsistentTerms(allDomainTerms);
127
+ const groundingResult = calculateAgentGrounding({
128
+ deepDirectories,
129
+ totalDirectories: dirs.length,
130
+ vagueFileNames,
131
+ totalFiles: files.length,
132
+ hasRootReadme,
133
+ readmeIsFresh,
134
+ barrelExports,
135
+ untypedExports,
136
+ totalExports: Math.max(1, totalExports),
137
+ inconsistentDomainTerms,
138
+ domainVocabularySize: Math.max(1, domainVocabularySize)
139
+ });
140
+ const issues = [];
141
+ if (groundingResult.dimensions.structureClarityScore < 70) {
142
+ issues.push({
143
+ type: IssueType.AgentNavigationFailure,
144
+ dimension: "structure-clarity",
145
+ severity: Severity.Major,
146
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
147
+ location: { file: rootDir, line: 0 },
148
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
149
+ });
150
+ }
151
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
152
+ issues.push({
153
+ type: IssueType.AgentNavigationFailure,
154
+ dimension: "self-documentation",
155
+ severity: Severity.Major,
156
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
157
+ location: { file: rootDir, line: 0 },
158
+ suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
159
+ });
160
+ }
161
+ if (!hasRootReadme) {
162
+ issues.push({
163
+ type: IssueType.AgentNavigationFailure,
164
+ dimension: "entry-point",
165
+ severity: Severity.Critical,
166
+ message: "No root README.md found \u2014 agents have no orientation document to start from.",
167
+ location: { file: join(rootDir, "README.md"), line: 0 },
168
+ suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
169
+ });
170
+ } else if (!readmeIsFresh) {
171
+ issues.push({
172
+ type: IssueType.AgentNavigationFailure,
173
+ dimension: "entry-point",
174
+ severity: Severity.Minor,
175
+ message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
176
+ location: { file: readmePath, line: 0 },
177
+ suggestion: "Update README.md to reflect the current codebase structure."
178
+ });
179
+ }
180
+ if (untypedExports > 0) {
181
+ issues.push({
182
+ type: IssueType.AgentNavigationFailure,
183
+ dimension: "api-clarity",
184
+ severity: groundingResult.dimensions.apiClarityScore < 70 ? Severity.Major : Severity.Minor,
185
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
186
+ location: { file: rootDir, line: 0 },
187
+ suggestion: "Add explicit return type and parameter annotations to all exported functions."
188
+ });
189
+ }
190
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
191
+ issues.push({
192
+ type: IssueType.AgentNavigationFailure,
193
+ dimension: "domain-consistency",
194
+ severity: Severity.Major,
195
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
196
+ location: { file: rootDir, line: 0 },
197
+ suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
198
+ });
199
+ }
200
+ return {
201
+ summary: {
202
+ filesAnalyzed: files.length,
203
+ directoriesAnalyzed: dirs.length,
204
+ score: groundingResult.score,
205
+ rating: groundingResult.rating,
206
+ dimensions: groundingResult.dimensions
207
+ },
208
+ issues,
209
+ rawData: {
210
+ deepDirectories,
211
+ totalDirectories: dirs.length,
212
+ vagueFileNames,
213
+ totalFiles: files.length,
214
+ hasRootReadme,
215
+ readmeIsFresh,
216
+ barrelExports,
217
+ untypedExports,
218
+ totalExports,
219
+ inconsistentDomainTerms,
220
+ domainVocabularySize
221
+ },
222
+ recommendations: groundingResult.recommendations
223
+ };
224
+ }
225
+
226
+ // src/scoring.ts
227
+ import { ToolName, buildStandardToolScore } from "@aiready/core";
228
+ function calculateGroundingScore(report) {
229
+ const { summary, rawData, recommendations } = report;
230
+ return buildStandardToolScore({
231
+ toolName: ToolName.AgentGrounding,
232
+ score: summary.score,
233
+ rawData,
234
+ dimensions: {
235
+ structureClarityScore: summary.dimensions.structureClarityScore,
236
+ selfDocumentationScore: summary.dimensions.selfDocumentationScore,
237
+ entryPointScore: summary.dimensions.entryPointScore,
238
+ apiClarityScore: summary.dimensions.apiClarityScore,
239
+ domainConsistencyScore: summary.dimensions.domainConsistencyScore
240
+ },
241
+ dimensionNames: {
242
+ structureClarityScore: "Structure Clarity",
243
+ selfDocumentationScore: "Self-Documentation",
244
+ entryPointScore: "Entry Points",
245
+ apiClarityScore: "API Clarity",
246
+ domainConsistencyScore: "Domain Consistency"
247
+ },
248
+ recommendations,
249
+ recommendationImpact: 6,
250
+ rating: summary.rating
251
+ });
252
+ }
253
+
254
+ export {
255
+ analyzeAgentGrounding,
256
+ calculateGroundingScore
257
+ };
package/dist/cli.js CHANGED
@@ -127,22 +127,19 @@ async function analyzeAgentGrounding(options) {
127
127
  let barrelExports = 0;
128
128
  let untypedExports = 0;
129
129
  let totalExports = 0;
130
- let processed = 0;
131
- for (const f of files) {
132
- processed++;
133
- (0, import_core.emitProgress)(
134
- processed,
135
- files.length,
136
- "agent-grounding",
137
- "analyzing files",
138
- options.onProgress
139
- );
140
- const analysis = await analyzeFile(f);
141
- if (analysis.isBarrel) barrelExports++;
142
- untypedExports += analysis.untypedExports;
143
- totalExports += analysis.totalExports;
144
- allDomainTerms.push(...analysis.domainTerms);
145
- }
130
+ await (0, import_core.runBatchAnalysis)(
131
+ files,
132
+ "analyzing files",
133
+ "agent-grounding",
134
+ options.onProgress,
135
+ (f) => analyzeFile(f),
136
+ (analysis) => {
137
+ if (analysis.isBarrel) barrelExports++;
138
+ untypedExports += analysis.untypedExports;
139
+ totalExports += analysis.totalExports;
140
+ allDomainTerms.push(...analysis.domainTerms);
141
+ }
142
+ );
146
143
  const {
147
144
  inconsistent: inconsistentDomainTerms,
148
145
  vocabularySize: domainVocabularySize
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  analyzeAgentGrounding,
4
4
  calculateGroundingScore
5
- } from "./chunk-OFAWUHBZ.mjs";
5
+ } from "./chunk-FCCOCULS.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -131,22 +131,19 @@ async function analyzeAgentGrounding(options) {
131
131
  let barrelExports = 0;
132
132
  let untypedExports = 0;
133
133
  let totalExports = 0;
134
- let processed = 0;
135
- for (const f of files) {
136
- processed++;
137
- (0, import_core.emitProgress)(
138
- processed,
139
- files.length,
140
- "agent-grounding",
141
- "analyzing files",
142
- options.onProgress
143
- );
144
- const analysis = await analyzeFile(f);
145
- if (analysis.isBarrel) barrelExports++;
146
- untypedExports += analysis.untypedExports;
147
- totalExports += analysis.totalExports;
148
- allDomainTerms.push(...analysis.domainTerms);
149
- }
134
+ await (0, import_core.runBatchAnalysis)(
135
+ files,
136
+ "analyzing files",
137
+ "agent-grounding",
138
+ options.onProgress,
139
+ (f) => analyzeFile(f),
140
+ (analysis) => {
141
+ if (analysis.isBarrel) barrelExports++;
142
+ untypedExports += analysis.untypedExports;
143
+ totalExports += analysis.totalExports;
144
+ allDomainTerms.push(...analysis.domainTerms);
145
+ }
146
+ );
150
147
  const {
151
148
  inconsistent: inconsistentDomainTerms,
152
149
  vocabularySize: domainVocabularySize
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeAgentGrounding,
3
3
  calculateGroundingScore
4
- } from "./chunk-OFAWUHBZ.mjs";
4
+ } from "./chunk-FCCOCULS.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.13.22",
3
+ "version": "0.14.0",
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.23.23"
43
+ "@aiready/core": "0.24.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^24.0.0",
package/src/analyzer.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  VAGUE_FILE_NAMES,
16
16
  Severity,
17
17
  IssueType,
18
- emitProgress,
18
+ runBatchAnalysis,
19
19
  getParser,
20
20
  } from '@aiready/core';
21
21
  import { readFileSync, existsSync, statSync } from 'fs';
@@ -187,23 +187,19 @@ export async function analyzeAgentGrounding(
187
187
  let untypedExports = 0;
188
188
  let totalExports = 0;
189
189
 
190
- let processed = 0;
191
- for (const f of files) {
192
- processed++;
193
- emitProgress(
194
- processed,
195
- files.length,
196
- 'agent-grounding',
197
- 'analyzing files',
198
- options.onProgress
199
- );
200
-
201
- const analysis = await analyzeFile(f);
202
- if (analysis.isBarrel) barrelExports++;
203
- untypedExports += analysis.untypedExports;
204
- totalExports += analysis.totalExports;
205
- allDomainTerms.push(...analysis.domainTerms);
206
- }
190
+ await runBatchAnalysis(
191
+ files,
192
+ 'analyzing files',
193
+ 'agent-grounding',
194
+ options.onProgress,
195
+ (f: string) => analyzeFile(f),
196
+ (analysis: FileAnalysis) => {
197
+ if (analysis.isBarrel) barrelExports++;
198
+ untypedExports += analysis.untypedExports;
199
+ totalExports += analysis.totalExports;
200
+ allDomainTerms.push(...analysis.domainTerms);
201
+ }
202
+ );
207
203
 
208
204
  // Domain vocabulary consistency
209
205
  const {