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