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