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