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