@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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-test.log +4 -4
- package/dist/chunk-NHDH733I.mjs +336 -0
- package/dist/cli.js +81 -23
- package/dist/cli.mjs +43 -11
- package/dist/index.js +44 -14
- package/dist/index.mjs +1 -1
- package/package.json +3 -3
- package/src/__tests__/analyzer.test.ts +30 -10
- package/src/analyzer.ts +89 -27
- package/src/cli.ts +58 -17
- package/src/scoring.ts +14 -9
- package/src/types.ts +6 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/agent-grounding@0.1.
|
|
3
|
+
> @aiready/agent-grounding@0.1.6 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
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[
|
|
13
|
-
[32mCJS[39m [1mdist/cli.js [22m[32m16.99 KB[39m
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in 117ms
|
|
15
|
-
[32mESM[39m [1mdist/cli.mjs [22m[32m5.04 KB[39m
|
|
12
|
+
[32mESM[39m [1mdist/chunk-NHDH733I.mjs [22m[32m10.51 KB[39m
|
|
16
13
|
[32mESM[39m [1mdist/index.mjs [22m[32m154.00 B[39m
|
|
17
|
-
[32mESM[39m [1mdist/
|
|
18
|
-
[32mESM[39m ⚡️ Build success in
|
|
14
|
+
[32mESM[39m [1mdist/cli.mjs [22m[32m5.16 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 39ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m11.76 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m17.24 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 39ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 4273ms
|
|
21
21
|
DTS dist/cli.d.ts 20.00 B
|
|
22
22
|
DTS dist/index.d.ts 2.34 KB
|
|
23
23
|
DTS dist/cli.d.mts 20.00 B
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/agent-grounding@0.1.
|
|
3
|
+
> @aiready/agent-grounding@0.1.6 test /Users/pengcao/projects/aiready/packages/agent-grounding
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/agent-grounding[39m
|
|
8
8
|
|
|
9
|
-
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
9
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 16[2mms[22m[39m
|
|
10
10
|
|
|
11
11
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
12
12
|
[2m Tests [22m [1m[32m3 passed[39m[22m[90m (3)[39m
|
|
13
|
-
[2m Start at [22m
|
|
14
|
-
[2m Duration [22m
|
|
13
|
+
[2m Start at [22m 22:19:17
|
|
14
|
+
[2m Duration [22m 829ms[2m (transform 144ms, setup 0ms, import 656ms, tests 16ms, environment 0ms)[22m
|
|
15
15
|
|
|
16
16
|
[?25h
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// src/analyzer.ts
|
|
2
|
+
import { readdirSync, statSync, existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join, extname, basename } from "path";
|
|
4
|
+
import { parse } from "@typescript-eslint/typescript-estree";
|
|
5
|
+
import { calculateAgentGrounding } from "@aiready/core";
|
|
6
|
+
var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
7
|
+
"utils",
|
|
8
|
+
"helpers",
|
|
9
|
+
"helper",
|
|
10
|
+
"misc",
|
|
11
|
+
"common",
|
|
12
|
+
"shared",
|
|
13
|
+
"tools",
|
|
14
|
+
"util",
|
|
15
|
+
"lib",
|
|
16
|
+
"libs",
|
|
17
|
+
"stuff",
|
|
18
|
+
"functions",
|
|
19
|
+
"methods",
|
|
20
|
+
"handlers",
|
|
21
|
+
"data",
|
|
22
|
+
"temp",
|
|
23
|
+
"tmp",
|
|
24
|
+
"test-utils",
|
|
25
|
+
"test-helpers",
|
|
26
|
+
"mocks"
|
|
27
|
+
]);
|
|
28
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
29
|
+
var DEFAULT_EXCLUDES = [
|
|
30
|
+
"node_modules",
|
|
31
|
+
"dist",
|
|
32
|
+
".git",
|
|
33
|
+
"coverage",
|
|
34
|
+
".turbo",
|
|
35
|
+
"build"
|
|
36
|
+
];
|
|
37
|
+
function collectEntries(dir, options, depth = 0, dirs = [], files = []) {
|
|
38
|
+
if (depth > (options.maxDepth ?? 20)) return { dirs, files };
|
|
39
|
+
const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(dir);
|
|
43
|
+
} catch {
|
|
44
|
+
return { dirs, files };
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
|
|
48
|
+
const full = join(dir, entry);
|
|
49
|
+
let stat;
|
|
50
|
+
try {
|
|
51
|
+
stat = statSync(full);
|
|
52
|
+
} catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (stat.isDirectory()) {
|
|
56
|
+
dirs.push({ path: full, depth });
|
|
57
|
+
collectEntries(full, options, depth + 1, dirs, files);
|
|
58
|
+
} else if (stat.isFile() && SUPPORTED_EXTENSIONS.has(extname(full))) {
|
|
59
|
+
if (!options.include || options.include.some((p) => full.includes(p))) {
|
|
60
|
+
files.push(full);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { dirs, files };
|
|
65
|
+
}
|
|
66
|
+
function analyzeFile(filePath) {
|
|
67
|
+
let code;
|
|
68
|
+
try {
|
|
69
|
+
code = readFileSync(filePath, "utf-8");
|
|
70
|
+
} catch {
|
|
71
|
+
return {
|
|
72
|
+
isBarrel: false,
|
|
73
|
+
exportedNames: [],
|
|
74
|
+
untypedExports: 0,
|
|
75
|
+
totalExports: 0,
|
|
76
|
+
domainTerms: []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
let ast;
|
|
80
|
+
try {
|
|
81
|
+
ast = parse(code, {
|
|
82
|
+
jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
|
|
83
|
+
range: false,
|
|
84
|
+
loc: false
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
return {
|
|
88
|
+
isBarrel: false,
|
|
89
|
+
exportedNames: [],
|
|
90
|
+
untypedExports: 0,
|
|
91
|
+
totalExports: 0,
|
|
92
|
+
domainTerms: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
let isBarrel = false;
|
|
96
|
+
const exportedNames = [];
|
|
97
|
+
let untypedExports = 0;
|
|
98
|
+
let totalExports = 0;
|
|
99
|
+
const domainTerms = [];
|
|
100
|
+
for (const node of ast.body) {
|
|
101
|
+
if (node.type === "ExportAllDeclaration") {
|
|
102
|
+
isBarrel = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
106
|
+
totalExports++;
|
|
107
|
+
const decl = node.declaration;
|
|
108
|
+
if (decl) {
|
|
109
|
+
const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
|
|
110
|
+
if (name) {
|
|
111
|
+
exportedNames.push(name);
|
|
112
|
+
domainTerms.push(
|
|
113
|
+
...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
|
|
114
|
+
);
|
|
115
|
+
const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
|
|
116
|
+
if (!hasType) untypedExports++;
|
|
117
|
+
}
|
|
118
|
+
} else if (node.specifiers && node.specifiers.length > 0) {
|
|
119
|
+
isBarrel = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (node.type === "ExportDefaultDeclaration") {
|
|
123
|
+
totalExports++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { isBarrel, exportedNames, untypedExports, totalExports, domainTerms };
|
|
127
|
+
}
|
|
128
|
+
function detectInconsistentTerms(allTerms) {
|
|
129
|
+
const termFreq = /* @__PURE__ */ new Map();
|
|
130
|
+
for (const term of allTerms) {
|
|
131
|
+
if (term.length >= 3) {
|
|
132
|
+
termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const orphans = [...termFreq.values()].filter((count) => count === 1).length;
|
|
136
|
+
const common = [...termFreq.values()].filter((count) => count >= 3).length;
|
|
137
|
+
const vocabularySize = termFreq.size;
|
|
138
|
+
const inconsistent = Math.max(0, orphans - common * 2);
|
|
139
|
+
return { inconsistent, vocabularySize };
|
|
140
|
+
}
|
|
141
|
+
async function analyzeAgentGrounding(options) {
|
|
142
|
+
const rootDir = options.rootDir;
|
|
143
|
+
const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
|
|
144
|
+
const readmeStaleDays = options.readmeStaleDays ?? 90;
|
|
145
|
+
const { dirs, files } = collectEntries(rootDir, options);
|
|
146
|
+
const deepDirectories = dirs.filter(
|
|
147
|
+
(d) => d.depth > maxRecommendedDepth
|
|
148
|
+
).length;
|
|
149
|
+
const additionalVague = new Set(
|
|
150
|
+
(options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
|
|
151
|
+
);
|
|
152
|
+
let vagueFileNames = 0;
|
|
153
|
+
for (const f of files) {
|
|
154
|
+
const base = basename(f, extname(f)).toLowerCase();
|
|
155
|
+
if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
|
|
156
|
+
vagueFileNames++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const readmePath = join(rootDir, "README.md");
|
|
160
|
+
const hasRootReadme = existsSync(readmePath);
|
|
161
|
+
let readmeIsFresh = false;
|
|
162
|
+
if (hasRootReadme) {
|
|
163
|
+
try {
|
|
164
|
+
const stat = statSync(readmePath);
|
|
165
|
+
const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
|
|
166
|
+
readmeIsFresh = ageDays < readmeStaleDays;
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const allDomainTerms = [];
|
|
171
|
+
let barrelExports = 0;
|
|
172
|
+
let untypedExports = 0;
|
|
173
|
+
let totalExports = 0;
|
|
174
|
+
for (const f of files) {
|
|
175
|
+
const analysis = analyzeFile(f);
|
|
176
|
+
if (analysis.isBarrel) barrelExports++;
|
|
177
|
+
untypedExports += analysis.untypedExports;
|
|
178
|
+
totalExports += analysis.totalExports;
|
|
179
|
+
allDomainTerms.push(...analysis.domainTerms);
|
|
180
|
+
}
|
|
181
|
+
const {
|
|
182
|
+
inconsistent: inconsistentDomainTerms,
|
|
183
|
+
vocabularySize: domainVocabularySize
|
|
184
|
+
} = detectInconsistentTerms(allDomainTerms);
|
|
185
|
+
const groundingResult = calculateAgentGrounding({
|
|
186
|
+
deepDirectories,
|
|
187
|
+
totalDirectories: dirs.length,
|
|
188
|
+
vagueFileNames,
|
|
189
|
+
totalFiles: files.length,
|
|
190
|
+
hasRootReadme,
|
|
191
|
+
readmeIsFresh,
|
|
192
|
+
barrelExports,
|
|
193
|
+
untypedExports,
|
|
194
|
+
totalExports: Math.max(1, totalExports),
|
|
195
|
+
inconsistentDomainTerms,
|
|
196
|
+
domainVocabularySize: Math.max(1, domainVocabularySize)
|
|
197
|
+
});
|
|
198
|
+
const issues = [];
|
|
199
|
+
if (groundingResult.dimensions.structureClarityScore < 70) {
|
|
200
|
+
issues.push({
|
|
201
|
+
type: "agent-navigation-failure",
|
|
202
|
+
dimension: "structure-clarity",
|
|
203
|
+
severity: "major",
|
|
204
|
+
message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
|
|
205
|
+
location: { file: rootDir, line: 0 },
|
|
206
|
+
suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (groundingResult.dimensions.selfDocumentationScore < 70) {
|
|
210
|
+
issues.push({
|
|
211
|
+
type: "agent-navigation-failure",
|
|
212
|
+
dimension: "self-documentation",
|
|
213
|
+
severity: "major",
|
|
214
|
+
message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
|
|
215
|
+
location: { file: rootDir, line: 0 },
|
|
216
|
+
suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (!hasRootReadme) {
|
|
220
|
+
issues.push({
|
|
221
|
+
type: "agent-navigation-failure",
|
|
222
|
+
dimension: "entry-point",
|
|
223
|
+
severity: "critical",
|
|
224
|
+
message: "No root README.md found \u2014 agents have no orientation document to start from.",
|
|
225
|
+
location: { file: join(rootDir, "README.md"), line: 0 },
|
|
226
|
+
suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
|
|
227
|
+
});
|
|
228
|
+
} else if (!readmeIsFresh) {
|
|
229
|
+
issues.push({
|
|
230
|
+
type: "agent-navigation-failure",
|
|
231
|
+
dimension: "entry-point",
|
|
232
|
+
severity: "minor",
|
|
233
|
+
message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
|
|
234
|
+
location: { file: readmePath, line: 0 },
|
|
235
|
+
suggestion: "Update README.md to reflect the current codebase structure."
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (groundingResult.dimensions.apiClarityScore < 70) {
|
|
239
|
+
issues.push({
|
|
240
|
+
type: "agent-navigation-failure",
|
|
241
|
+
dimension: "api-clarity",
|
|
242
|
+
severity: "major",
|
|
243
|
+
message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
|
|
244
|
+
location: { file: rootDir, line: 0 },
|
|
245
|
+
suggestion: "Add explicit return type and parameter annotations to all exported functions."
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (groundingResult.dimensions.domainConsistencyScore < 70) {
|
|
249
|
+
issues.push({
|
|
250
|
+
type: "agent-navigation-failure",
|
|
251
|
+
dimension: "domain-consistency",
|
|
252
|
+
severity: "major",
|
|
253
|
+
message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
|
|
254
|
+
location: { file: rootDir, line: 0 },
|
|
255
|
+
suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
summary: {
|
|
260
|
+
filesAnalyzed: files.length,
|
|
261
|
+
directoriesAnalyzed: dirs.length,
|
|
262
|
+
score: groundingResult.score,
|
|
263
|
+
rating: groundingResult.rating,
|
|
264
|
+
dimensions: groundingResult.dimensions
|
|
265
|
+
},
|
|
266
|
+
issues,
|
|
267
|
+
rawData: {
|
|
268
|
+
deepDirectories,
|
|
269
|
+
totalDirectories: dirs.length,
|
|
270
|
+
vagueFileNames,
|
|
271
|
+
totalFiles: files.length,
|
|
272
|
+
hasRootReadme,
|
|
273
|
+
readmeIsFresh,
|
|
274
|
+
barrelExports,
|
|
275
|
+
untypedExports,
|
|
276
|
+
totalExports,
|
|
277
|
+
inconsistentDomainTerms,
|
|
278
|
+
domainVocabularySize
|
|
279
|
+
},
|
|
280
|
+
recommendations: groundingResult.recommendations
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/scoring.ts
|
|
285
|
+
function calculateGroundingScore(report) {
|
|
286
|
+
const { summary, rawData, recommendations } = report;
|
|
287
|
+
const factors = [
|
|
288
|
+
{
|
|
289
|
+
name: "Structure Clarity",
|
|
290
|
+
impact: Math.round(summary.dimensions.structureClarityScore - 50),
|
|
291
|
+
description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "Self-Documentation",
|
|
295
|
+
impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
|
|
296
|
+
description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "Entry Points",
|
|
300
|
+
impact: Math.round(summary.dimensions.entryPointScore - 50),
|
|
301
|
+
description: rawData.hasRootReadme ? rawData.readmeIsFresh ? "README present and fresh" : "README present but stale" : "No root README"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "API Clarity",
|
|
305
|
+
impact: Math.round(summary.dimensions.apiClarityScore - 50),
|
|
306
|
+
description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "Domain Consistency",
|
|
310
|
+
impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
|
|
311
|
+
description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
const recs = recommendations.map(
|
|
315
|
+
(action) => ({
|
|
316
|
+
action,
|
|
317
|
+
estimatedImpact: 6,
|
|
318
|
+
priority: summary.score < 50 ? "high" : "medium"
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
return {
|
|
322
|
+
toolName: "agent-grounding",
|
|
323
|
+
score: summary.score,
|
|
324
|
+
rawMetrics: {
|
|
325
|
+
...rawData,
|
|
326
|
+
rating: summary.rating
|
|
327
|
+
},
|
|
328
|
+
factors,
|
|
329
|
+
recommendations: recs
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export {
|
|
334
|
+
analyzeAgentGrounding,
|
|
335
|
+
calculateGroundingScore
|
|
336
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,14 @@ var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
|
54
54
|
"mocks"
|
|
55
55
|
]);
|
|
56
56
|
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
57
|
-
var DEFAULT_EXCLUDES = [
|
|
57
|
+
var DEFAULT_EXCLUDES = [
|
|
58
|
+
"node_modules",
|
|
59
|
+
"dist",
|
|
60
|
+
".git",
|
|
61
|
+
"coverage",
|
|
62
|
+
".turbo",
|
|
63
|
+
"build"
|
|
64
|
+
];
|
|
58
65
|
function collectEntries(dir, options, depth = 0, dirs = [], files = []) {
|
|
59
66
|
if (depth > (options.maxDepth ?? 20)) return { dirs, files };
|
|
60
67
|
const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
|
|
@@ -89,7 +96,13 @@ function analyzeFile(filePath) {
|
|
|
89
96
|
try {
|
|
90
97
|
code = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
91
98
|
} catch {
|
|
92
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
isBarrel: false,
|
|
101
|
+
exportedNames: [],
|
|
102
|
+
untypedExports: 0,
|
|
103
|
+
totalExports: 0,
|
|
104
|
+
domainTerms: []
|
|
105
|
+
};
|
|
93
106
|
}
|
|
94
107
|
let ast;
|
|
95
108
|
try {
|
|
@@ -99,10 +112,16 @@ function analyzeFile(filePath) {
|
|
|
99
112
|
loc: false
|
|
100
113
|
});
|
|
101
114
|
} catch {
|
|
102
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
isBarrel: false,
|
|
117
|
+
exportedNames: [],
|
|
118
|
+
untypedExports: 0,
|
|
119
|
+
totalExports: 0,
|
|
120
|
+
domainTerms: []
|
|
121
|
+
};
|
|
103
122
|
}
|
|
104
123
|
let isBarrel = false;
|
|
105
|
-
|
|
124
|
+
const exportedNames = [];
|
|
106
125
|
let untypedExports = 0;
|
|
107
126
|
let totalExports = 0;
|
|
108
127
|
const domainTerms = [];
|
|
@@ -118,7 +137,9 @@ function analyzeFile(filePath) {
|
|
|
118
137
|
const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
|
|
119
138
|
if (name) {
|
|
120
139
|
exportedNames.push(name);
|
|
121
|
-
domainTerms.push(
|
|
140
|
+
domainTerms.push(
|
|
141
|
+
...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean)
|
|
142
|
+
);
|
|
122
143
|
const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
|
|
123
144
|
if (!hasType) untypedExports++;
|
|
124
145
|
}
|
|
@@ -150,8 +171,12 @@ async function analyzeAgentGrounding(options) {
|
|
|
150
171
|
const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
|
|
151
172
|
const readmeStaleDays = options.readmeStaleDays ?? 90;
|
|
152
173
|
const { dirs, files } = collectEntries(rootDir, options);
|
|
153
|
-
const deepDirectories = dirs.filter(
|
|
154
|
-
|
|
174
|
+
const deepDirectories = dirs.filter(
|
|
175
|
+
(d) => d.depth > maxRecommendedDepth
|
|
176
|
+
).length;
|
|
177
|
+
const additionalVague = new Set(
|
|
178
|
+
(options.additionalVagueNames ?? []).map((n) => n.toLowerCase())
|
|
179
|
+
);
|
|
155
180
|
let vagueFileNames = 0;
|
|
156
181
|
for (const f of files) {
|
|
157
182
|
const base = (0, import_path.basename)(f, (0, import_path.extname)(f)).toLowerCase();
|
|
@@ -181,7 +206,10 @@ async function analyzeAgentGrounding(options) {
|
|
|
181
206
|
totalExports += analysis.totalExports;
|
|
182
207
|
allDomainTerms.push(...analysis.domainTerms);
|
|
183
208
|
}
|
|
184
|
-
const {
|
|
209
|
+
const {
|
|
210
|
+
inconsistent: inconsistentDomainTerms,
|
|
211
|
+
vocabularySize: domainVocabularySize
|
|
212
|
+
} = detectInconsistentTerms(allDomainTerms);
|
|
185
213
|
const groundingResult = (0, import_core.calculateAgentGrounding)({
|
|
186
214
|
deepDirectories,
|
|
187
215
|
totalDirectories: dirs.length,
|
|
@@ -283,7 +311,7 @@ async function analyzeAgentGrounding(options) {
|
|
|
283
311
|
|
|
284
312
|
// src/scoring.ts
|
|
285
313
|
function calculateGroundingScore(report) {
|
|
286
|
-
const { summary, rawData,
|
|
314
|
+
const { summary, rawData, recommendations } = report;
|
|
287
315
|
const factors = [
|
|
288
316
|
{
|
|
289
317
|
name: "Structure Clarity",
|
|
@@ -311,11 +339,13 @@ function calculateGroundingScore(report) {
|
|
|
311
339
|
description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
|
|
312
340
|
}
|
|
313
341
|
];
|
|
314
|
-
const recs = recommendations.map(
|
|
315
|
-
action
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
342
|
+
const recs = recommendations.map(
|
|
343
|
+
(action) => ({
|
|
344
|
+
action,
|
|
345
|
+
estimatedImpact: 6,
|
|
346
|
+
priority: summary.score < 50 ? "high" : "medium"
|
|
347
|
+
})
|
|
348
|
+
);
|
|
319
349
|
return {
|
|
320
350
|
toolName: "agent-grounding",
|
|
321
351
|
score: summary.score,
|
|
@@ -334,7 +364,11 @@ var import_fs2 = require("fs");
|
|
|
334
364
|
var import_path2 = require("path");
|
|
335
365
|
var import_core2 = require("@aiready/core");
|
|
336
366
|
var program = new import_commander.Command();
|
|
337
|
-
program.name("aiready-agent-grounding").description(
|
|
367
|
+
program.name("aiready-agent-grounding").description(
|
|
368
|
+
"Measure how well an AI agent can navigate your codebase autonomously"
|
|
369
|
+
).version("0.1.0").addHelpText(
|
|
370
|
+
"after",
|
|
371
|
+
`
|
|
338
372
|
GROUNDING DIMENSIONS:
|
|
339
373
|
Structure Clarity Deep directory trees slow and confuse agents
|
|
340
374
|
Self-Documentation Vague file names (utils, helpers) hide intent
|
|
@@ -346,7 +380,16 @@ EXAMPLES:
|
|
|
346
380
|
aiready-agent-grounding . # Full analysis
|
|
347
381
|
aiready-agent-grounding src/ --output json # JSON report
|
|
348
382
|
aiready-agent-grounding . --max-depth 3 # Stricter depth limit
|
|
349
|
-
`
|
|
383
|
+
`
|
|
384
|
+
).argument("<directory>", "Directory to analyze").option(
|
|
385
|
+
"--max-depth <n>",
|
|
386
|
+
"Max recommended directory depth (default: 4)",
|
|
387
|
+
"4"
|
|
388
|
+
).option(
|
|
389
|
+
"--readme-stale-days <n>",
|
|
390
|
+
"Days after which README is considered stale (default: 90)",
|
|
391
|
+
"90"
|
|
392
|
+
).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) => {
|
|
350
393
|
console.log(import_chalk.default.blue("\u{1F9ED} Analyzing agent grounding...\n"));
|
|
351
394
|
const startTime = Date.now();
|
|
352
395
|
const config = await (0, import_core2.loadConfig)(directory);
|
|
@@ -388,10 +431,14 @@ function scoreColor(score) {
|
|
|
388
431
|
return import_chalk.default.bgRed.white;
|
|
389
432
|
}
|
|
390
433
|
function displayConsoleReport(report, scoring, elapsed) {
|
|
391
|
-
const { summary,
|
|
434
|
+
const { summary, issues, recommendations } = report;
|
|
392
435
|
console.log(import_chalk.default.bold("\n\u{1F9ED} Agent Grounding Analysis\n"));
|
|
393
|
-
console.log(
|
|
394
|
-
|
|
436
|
+
console.log(
|
|
437
|
+
`Score: ${scoreColor(summary.score)(summary.score + "/100")} (${summary.rating.toUpperCase()})`
|
|
438
|
+
);
|
|
439
|
+
console.log(
|
|
440
|
+
`Files: ${import_chalk.default.cyan(summary.filesAnalyzed)} Directories: ${import_chalk.default.cyan(summary.directoriesAnalyzed)}`
|
|
441
|
+
);
|
|
395
442
|
console.log(`Analysis: ${import_chalk.default.gray(elapsed + "s")}
|
|
396
443
|
`);
|
|
397
444
|
console.log(import_chalk.default.bold("\u{1F4D0} Dimension Scores\n"));
|
|
@@ -404,22 +451,33 @@ function displayConsoleReport(report, scoring, elapsed) {
|
|
|
404
451
|
];
|
|
405
452
|
for (const [name, val] of dims) {
|
|
406
453
|
const bar = "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
|
|
407
|
-
console.log(
|
|
454
|
+
console.log(
|
|
455
|
+
` ${String(name).padEnd(22)} ${scoreColor(val)(bar)} ${val}/100`
|
|
456
|
+
);
|
|
408
457
|
}
|
|
409
458
|
if (issues.length > 0) {
|
|
410
459
|
console.log(import_chalk.default.bold("\n\u26A0\uFE0F Issues Found\n"));
|
|
411
460
|
for (const issue of issues) {
|
|
412
461
|
const sev = issue.severity === "critical" ? import_chalk.default.red : issue.severity === "major" ? import_chalk.default.yellow : import_chalk.default.blue;
|
|
413
462
|
console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
|
|
414
|
-
if (issue.suggestion)
|
|
463
|
+
if (issue.suggestion)
|
|
464
|
+
console.log(
|
|
465
|
+
` ${import_chalk.default.dim("\u2192")} ${import_chalk.default.italic(issue.suggestion)}`
|
|
466
|
+
);
|
|
415
467
|
console.log();
|
|
416
468
|
}
|
|
417
469
|
} else {
|
|
418
|
-
console.log(
|
|
470
|
+
console.log(
|
|
471
|
+
import_chalk.default.green(
|
|
472
|
+
"\n\u2728 No grounding issues found \u2014 agents can navigate freely!\n"
|
|
473
|
+
)
|
|
474
|
+
);
|
|
419
475
|
}
|
|
420
476
|
if (recommendations.length > 0) {
|
|
421
477
|
console.log(import_chalk.default.bold("\u{1F4A1} Recommendations\n"));
|
|
422
|
-
recommendations.forEach(
|
|
478
|
+
recommendations.forEach(
|
|
479
|
+
(rec, i) => console.log(`${i + 1}. ${rec}`)
|
|
480
|
+
);
|
|
423
481
|
}
|
|
424
482
|
console.log();
|
|
425
483
|
}
|