@aiready/agent-grounding 0.1.1

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/dist/cli.js ADDED
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/analyzer.ts
30
+ var import_fs = require("fs");
31
+ var import_path = require("path");
32
+ var import_typescript_estree = require("@typescript-eslint/typescript-estree");
33
+ var import_core = require("@aiready/core");
34
+ var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
35
+ "utils",
36
+ "helpers",
37
+ "helper",
38
+ "misc",
39
+ "common",
40
+ "shared",
41
+ "tools",
42
+ "util",
43
+ "lib",
44
+ "libs",
45
+ "stuff",
46
+ "functions",
47
+ "methods",
48
+ "handlers",
49
+ "data",
50
+ "temp",
51
+ "tmp",
52
+ "test-utils",
53
+ "test-helpers",
54
+ "mocks"
55
+ ]);
56
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
57
+ var DEFAULT_EXCLUDES = ["node_modules", "dist", ".git", "coverage", ".turbo", "build"];
58
+ function collectEntries(dir, options, depth = 0, dirs = [], files = []) {
59
+ if (depth > (options.maxDepth ?? 20)) return { dirs, files };
60
+ const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
61
+ let entries;
62
+ try {
63
+ entries = (0, import_fs.readdirSync)(dir);
64
+ } catch {
65
+ return { dirs, files };
66
+ }
67
+ for (const entry of entries) {
68
+ if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
69
+ const full = (0, import_path.join)(dir, entry);
70
+ let stat;
71
+ try {
72
+ stat = (0, import_fs.statSync)(full);
73
+ } catch {
74
+ continue;
75
+ }
76
+ if (stat.isDirectory()) {
77
+ dirs.push({ path: full, depth });
78
+ collectEntries(full, options, depth + 1, dirs, files);
79
+ } else if (stat.isFile() && SUPPORTED_EXTENSIONS.has((0, import_path.extname)(full))) {
80
+ if (!options.include || options.include.some((p) => full.includes(p))) {
81
+ files.push(full);
82
+ }
83
+ }
84
+ }
85
+ return { dirs, files };
86
+ }
87
+ function analyzeFile(filePath) {
88
+ let code;
89
+ try {
90
+ code = (0, import_fs.readFileSync)(filePath, "utf-8");
91
+ } catch {
92
+ return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
93
+ }
94
+ let ast;
95
+ try {
96
+ ast = (0, import_typescript_estree.parse)(code, {
97
+ jsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
98
+ range: false,
99
+ loc: false
100
+ });
101
+ } catch {
102
+ return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
103
+ }
104
+ let isBarrel = false;
105
+ let exportedNames = [];
106
+ let untypedExports = 0;
107
+ let totalExports = 0;
108
+ const domainTerms = [];
109
+ for (const node of ast.body) {
110
+ if (node.type === "ExportAllDeclaration") {
111
+ isBarrel = true;
112
+ continue;
113
+ }
114
+ if (node.type === "ExportNamedDeclaration") {
115
+ totalExports++;
116
+ const decl = node.declaration;
117
+ if (decl) {
118
+ const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
119
+ if (name) {
120
+ exportedNames.push(name);
121
+ domainTerms.push(...name.replace(/([A-Z])/g, " $1").toLowerCase().split(/\s+/).filter(Boolean));
122
+ const hasType = decl.returnType != null || decl.declarations?.[0]?.id?.typeAnnotation != null || decl.typeParameters != null;
123
+ if (!hasType) untypedExports++;
124
+ }
125
+ } else if (node.specifiers && node.specifiers.length > 0) {
126
+ isBarrel = true;
127
+ }
128
+ }
129
+ if (node.type === "ExportDefaultDeclaration") {
130
+ totalExports++;
131
+ }
132
+ }
133
+ return { isBarrel, exportedNames, untypedExports, totalExports, domainTerms };
134
+ }
135
+ function detectInconsistentTerms(allTerms) {
136
+ const termFreq = /* @__PURE__ */ new Map();
137
+ for (const term of allTerms) {
138
+ if (term.length >= 3) {
139
+ termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
140
+ }
141
+ }
142
+ const orphans = [...termFreq.values()].filter((count) => count === 1).length;
143
+ const common = [...termFreq.values()].filter((count) => count >= 3).length;
144
+ const vocabularySize = termFreq.size;
145
+ const inconsistent = Math.max(0, orphans - common * 2);
146
+ return { inconsistent, vocabularySize };
147
+ }
148
+ async function analyzeAgentGrounding(options) {
149
+ const rootDir = options.rootDir;
150
+ const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
151
+ const readmeStaleDays = options.readmeStaleDays ?? 90;
152
+ const { dirs, files } = collectEntries(rootDir, options);
153
+ const deepDirectories = dirs.filter((d) => d.depth > maxRecommendedDepth).length;
154
+ const additionalVague = new Set((options.additionalVagueNames ?? []).map((n) => n.toLowerCase()));
155
+ let vagueFileNames = 0;
156
+ for (const f of files) {
157
+ const base = (0, import_path.basename)(f, (0, import_path.extname)(f)).toLowerCase();
158
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
159
+ vagueFileNames++;
160
+ }
161
+ }
162
+ const readmePath = (0, import_path.join)(rootDir, "README.md");
163
+ const hasRootReadme = (0, import_fs.existsSync)(readmePath);
164
+ let readmeIsFresh = false;
165
+ if (hasRootReadme) {
166
+ try {
167
+ const stat = (0, import_fs.statSync)(readmePath);
168
+ const ageDays = (Date.now() - stat.mtimeMs) / (1e3 * 60 * 60 * 24);
169
+ readmeIsFresh = ageDays < readmeStaleDays;
170
+ } catch {
171
+ }
172
+ }
173
+ const allDomainTerms = [];
174
+ let barrelExports = 0;
175
+ let untypedExports = 0;
176
+ let totalExports = 0;
177
+ for (const f of files) {
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 { inconsistent: inconsistentDomainTerms, vocabularySize: domainVocabularySize } = detectInconsistentTerms(allDomainTerms);
185
+ const groundingResult = (0, import_core.calculateAgentGrounding)({
186
+ deepDirectories,
187
+ totalDirectories: dirs.length,
188
+ vagueFileNames,
189
+ totalFiles: files.length,
190
+ hasRootReadme,
191
+ readmeIsFresh,
192
+ barrelExports,
193
+ untypedExports,
194
+ totalExports: Math.max(1, totalExports),
195
+ inconsistentDomainTerms,
196
+ domainVocabularySize: Math.max(1, domainVocabularySize)
197
+ });
198
+ const issues = [];
199
+ if (groundingResult.dimensions.structureClarityScore < 70) {
200
+ issues.push({
201
+ type: "agent-navigation-failure",
202
+ dimension: "structure-clarity",
203
+ severity: "major",
204
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} \u2014 agents struggle to navigate deep trees.`,
205
+ location: { file: rootDir, line: 0 },
206
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`
207
+ });
208
+ }
209
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
210
+ issues.push({
211
+ type: "agent-navigation-failure",
212
+ dimension: "self-documentation",
213
+ severity: "major",
214
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) \u2014 an agent cannot determine their purpose from the name alone.`,
215
+ location: { file: rootDir, line: 0 },
216
+ suggestion: "Rename to domain-specific names: e.g., userAuthUtils \u2192 tokenValidator."
217
+ });
218
+ }
219
+ if (!hasRootReadme) {
220
+ issues.push({
221
+ type: "agent-navigation-failure",
222
+ dimension: "entry-point",
223
+ severity: "critical",
224
+ message: "No root README.md found \u2014 agents have no orientation document to start from.",
225
+ location: { file: (0, import_path.join)(rootDir, "README.md"), line: 0 },
226
+ suggestion: "Add a README.md explaining the project structure, entry points, and key conventions."
227
+ });
228
+ } else if (!readmeIsFresh) {
229
+ issues.push({
230
+ type: "agent-navigation-failure",
231
+ dimension: "entry-point",
232
+ severity: "minor",
233
+ message: `README.md is stale (>${readmeStaleDays} days without updates) \u2014 agents may be misled by outdated context.`,
234
+ location: { file: readmePath, line: 0 },
235
+ suggestion: "Update README.md to reflect the current codebase structure."
236
+ });
237
+ }
238
+ if (groundingResult.dimensions.apiClarityScore < 70) {
239
+ issues.push({
240
+ type: "agent-navigation-failure",
241
+ dimension: "api-clarity",
242
+ severity: "major",
243
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations \u2014 agents cannot infer the API contract.`,
244
+ location: { file: rootDir, line: 0 },
245
+ suggestion: "Add explicit return type and parameter annotations to all exported functions."
246
+ });
247
+ }
248
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
249
+ issues.push({
250
+ type: "agent-navigation-failure",
251
+ dimension: "domain-consistency",
252
+ severity: "major",
253
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently \u2014 agents get confused when one concept has multiple names.`,
254
+ location: { file: rootDir, line: 0 },
255
+ suggestion: "Establish a domain glossary and enforce one term per concept across the codebase."
256
+ });
257
+ }
258
+ return {
259
+ summary: {
260
+ filesAnalyzed: files.length,
261
+ directoriesAnalyzed: dirs.length,
262
+ score: groundingResult.score,
263
+ rating: groundingResult.rating,
264
+ dimensions: groundingResult.dimensions
265
+ },
266
+ issues,
267
+ rawData: {
268
+ deepDirectories,
269
+ totalDirectories: dirs.length,
270
+ vagueFileNames,
271
+ totalFiles: files.length,
272
+ hasRootReadme,
273
+ readmeIsFresh,
274
+ barrelExports,
275
+ untypedExports,
276
+ totalExports,
277
+ inconsistentDomainTerms,
278
+ domainVocabularySize
279
+ },
280
+ recommendations: groundingResult.recommendations
281
+ };
282
+ }
283
+
284
+ // src/scoring.ts
285
+ function calculateGroundingScore(report) {
286
+ const { summary, rawData, issues, recommendations } = report;
287
+ const factors = [
288
+ {
289
+ name: "Structure Clarity",
290
+ impact: Math.round(summary.dimensions.structureClarityScore - 50),
291
+ description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`
292
+ },
293
+ {
294
+ name: "Self-Documentation",
295
+ impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
296
+ description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`
297
+ },
298
+ {
299
+ name: "Entry Points",
300
+ impact: Math.round(summary.dimensions.entryPointScore - 50),
301
+ description: rawData.hasRootReadme ? rawData.readmeIsFresh ? "README present and fresh" : "README present but stale" : "No root README"
302
+ },
303
+ {
304
+ name: "API Clarity",
305
+ impact: Math.round(summary.dimensions.apiClarityScore - 50),
306
+ description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`
307
+ },
308
+ {
309
+ name: "Domain Consistency",
310
+ impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
311
+ description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`
312
+ }
313
+ ];
314
+ const recs = recommendations.map((action) => ({
315
+ action,
316
+ estimatedImpact: 6,
317
+ priority: summary.score < 50 ? "high" : "medium"
318
+ }));
319
+ return {
320
+ toolName: "agent-grounding",
321
+ score: summary.score,
322
+ rawMetrics: {
323
+ ...rawData,
324
+ rating: summary.rating
325
+ },
326
+ factors,
327
+ recommendations: recs
328
+ };
329
+ }
330
+
331
+ // src/cli.ts
332
+ var import_chalk = __toESM(require("chalk"));
333
+ var import_fs2 = require("fs");
334
+ var import_path2 = require("path");
335
+ var import_core2 = require("@aiready/core");
336
+ var program = new import_commander.Command();
337
+ program.name("aiready-agent-grounding").description("Measure how well an AI agent can navigate your codebase autonomously").version("0.1.0").addHelpText("after", `
338
+ GROUNDING DIMENSIONS:
339
+ Structure Clarity Deep directory trees slow and confuse agents
340
+ Self-Documentation Vague file names (utils, helpers) hide intent
341
+ Entry Points README presence, freshness, and barrel exports
342
+ API Clarity Untyped exports prevent API contract inference
343
+ Domain Consistency Inconsistent naming forces agents to guess
344
+
345
+ EXAMPLES:
346
+ aiready-agent-grounding . # Full analysis
347
+ aiready-agent-grounding src/ --output json # JSON report
348
+ aiready-agent-grounding . --max-depth 3 # Stricter depth limit
349
+ `).argument("<directory>", "Directory to analyze").option("--max-depth <n>", "Max recommended directory depth (default: 4)", "4").option("--readme-stale-days <n>", "Days after which README is considered stale (default: 90)", "90").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console|json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
350
+ console.log(import_chalk.default.blue("\u{1F9ED} Analyzing agent grounding...\n"));
351
+ const startTime = Date.now();
352
+ const config = await (0, import_core2.loadConfig)(directory);
353
+ const mergedConfig = (0, import_core2.mergeConfigWithDefaults)(config, {
354
+ maxRecommendedDepth: 4,
355
+ readmeStaleDays: 90
356
+ });
357
+ const finalOptions = {
358
+ rootDir: directory,
359
+ maxRecommendedDepth: parseInt(options.maxDepth ?? "4", 10) || mergedConfig.maxRecommendedDepth,
360
+ readmeStaleDays: parseInt(options.readmeStaleDays ?? "90", 10) || mergedConfig.readmeStaleDays,
361
+ include: options.include?.split(","),
362
+ exclude: options.exclude?.split(",")
363
+ };
364
+ const report = await analyzeAgentGrounding(finalOptions);
365
+ const scoring = calculateGroundingScore(report);
366
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
367
+ if (options.output === "json") {
368
+ const payload = { report, score: scoring };
369
+ const outputPath = (0, import_core2.resolveOutputPath)(
370
+ options.outputFile,
371
+ `agent-grounding-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
372
+ directory
373
+ );
374
+ const dir = (0, import_path2.dirname)(outputPath);
375
+ if (!(0, import_fs2.existsSync)(dir)) (0, import_fs2.mkdirSync)(dir, { recursive: true });
376
+ (0, import_fs2.writeFileSync)(outputPath, JSON.stringify(payload, null, 2));
377
+ console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
378
+ } else {
379
+ displayConsoleReport(report, scoring, elapsed);
380
+ }
381
+ });
382
+ program.parse();
383
+ function scoreColor(score) {
384
+ if (score >= 85) return import_chalk.default.green;
385
+ if (score >= 70) return import_chalk.default.cyan;
386
+ if (score >= 50) return import_chalk.default.yellow;
387
+ if (score >= 30) return import_chalk.default.red;
388
+ return import_chalk.default.bgRed.white;
389
+ }
390
+ function displayConsoleReport(report, scoring, elapsed) {
391
+ const { summary, rawData, issues, recommendations } = report;
392
+ console.log(import_chalk.default.bold("\n\u{1F9ED} Agent Grounding Analysis\n"));
393
+ console.log(`Score: ${scoreColor(summary.score)(summary.score + "/100")} (${summary.rating.toUpperCase()})`);
394
+ console.log(`Files: ${import_chalk.default.cyan(summary.filesAnalyzed)} Directories: ${import_chalk.default.cyan(summary.directoriesAnalyzed)}`);
395
+ console.log(`Analysis: ${import_chalk.default.gray(elapsed + "s")}
396
+ `);
397
+ console.log(import_chalk.default.bold("\u{1F4D0} Dimension Scores\n"));
398
+ const dims = [
399
+ ["Structure Clarity", summary.dimensions.structureClarityScore],
400
+ ["Self-Documentation", summary.dimensions.selfDocumentationScore],
401
+ ["Entry Points", summary.dimensions.entryPointScore],
402
+ ["API Clarity", summary.dimensions.apiClarityScore],
403
+ ["Domain Consistency", summary.dimensions.domainConsistencyScore]
404
+ ];
405
+ for (const [name, val] of dims) {
406
+ const bar = "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
407
+ console.log(` ${String(name).padEnd(22)} ${scoreColor(val)(bar)} ${val}/100`);
408
+ }
409
+ if (issues.length > 0) {
410
+ console.log(import_chalk.default.bold("\n\u26A0\uFE0F Issues Found\n"));
411
+ for (const issue of issues) {
412
+ const sev = issue.severity === "critical" ? import_chalk.default.red : issue.severity === "major" ? import_chalk.default.yellow : import_chalk.default.blue;
413
+ console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
414
+ if (issue.suggestion) console.log(` ${import_chalk.default.dim("\u2192")} ${import_chalk.default.italic(issue.suggestion)}`);
415
+ console.log();
416
+ }
417
+ } else {
418
+ console.log(import_chalk.default.green("\n\u2728 No grounding issues found \u2014 agents can navigate freely!\n"));
419
+ }
420
+ if (recommendations.length > 0) {
421
+ console.log(import_chalk.default.bold("\u{1F4A1} Recommendations\n"));
422
+ recommendations.forEach((rec, i) => console.log(`${i + 1}. ${rec}`));
423
+ }
424
+ console.log();
425
+ }
package/dist/cli.mjs ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ analyzeAgentGrounding,
4
+ calculateGroundingScore
5
+ } from "./chunk-OOB3JMXQ.mjs";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ import chalk from "chalk";
10
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
11
+ import { dirname } from "path";
12
+ import { loadConfig, mergeConfigWithDefaults, resolveOutputPath } from "@aiready/core";
13
+ var program = new Command();
14
+ program.name("aiready-agent-grounding").description("Measure how well an AI agent can navigate your codebase autonomously").version("0.1.0").addHelpText("after", `
15
+ GROUNDING DIMENSIONS:
16
+ Structure Clarity Deep directory trees slow and confuse agents
17
+ Self-Documentation Vague file names (utils, helpers) hide intent
18
+ Entry Points README presence, freshness, and barrel exports
19
+ API Clarity Untyped exports prevent API contract inference
20
+ Domain Consistency Inconsistent naming forces agents to guess
21
+
22
+ EXAMPLES:
23
+ aiready-agent-grounding . # Full analysis
24
+ aiready-agent-grounding src/ --output json # JSON report
25
+ aiready-agent-grounding . --max-depth 3 # Stricter depth limit
26
+ `).argument("<directory>", "Directory to analyze").option("--max-depth <n>", "Max recommended directory depth (default: 4)", "4").option("--readme-stale-days <n>", "Days after which README is considered stale (default: 90)", "90").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console|json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
27
+ console.log(chalk.blue("\u{1F9ED} Analyzing agent grounding...\n"));
28
+ const startTime = Date.now();
29
+ const config = await loadConfig(directory);
30
+ const mergedConfig = mergeConfigWithDefaults(config, {
31
+ maxRecommendedDepth: 4,
32
+ readmeStaleDays: 90
33
+ });
34
+ const finalOptions = {
35
+ rootDir: directory,
36
+ maxRecommendedDepth: parseInt(options.maxDepth ?? "4", 10) || mergedConfig.maxRecommendedDepth,
37
+ readmeStaleDays: parseInt(options.readmeStaleDays ?? "90", 10) || mergedConfig.readmeStaleDays,
38
+ include: options.include?.split(","),
39
+ exclude: options.exclude?.split(",")
40
+ };
41
+ const report = await analyzeAgentGrounding(finalOptions);
42
+ const scoring = calculateGroundingScore(report);
43
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
44
+ if (options.output === "json") {
45
+ const payload = { report, score: scoring };
46
+ const outputPath = resolveOutputPath(
47
+ options.outputFile,
48
+ `agent-grounding-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
49
+ directory
50
+ );
51
+ const dir = dirname(outputPath);
52
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
53
+ writeFileSync(outputPath, JSON.stringify(payload, null, 2));
54
+ console.log(chalk.green(`\u2713 Report saved to ${outputPath}`));
55
+ } else {
56
+ displayConsoleReport(report, scoring, elapsed);
57
+ }
58
+ });
59
+ program.parse();
60
+ function scoreColor(score) {
61
+ if (score >= 85) return chalk.green;
62
+ if (score >= 70) return chalk.cyan;
63
+ if (score >= 50) return chalk.yellow;
64
+ if (score >= 30) return chalk.red;
65
+ return chalk.bgRed.white;
66
+ }
67
+ function displayConsoleReport(report, scoring, elapsed) {
68
+ const { summary, rawData, issues, recommendations } = report;
69
+ console.log(chalk.bold("\n\u{1F9ED} Agent Grounding Analysis\n"));
70
+ console.log(`Score: ${scoreColor(summary.score)(summary.score + "/100")} (${summary.rating.toUpperCase()})`);
71
+ console.log(`Files: ${chalk.cyan(summary.filesAnalyzed)} Directories: ${chalk.cyan(summary.directoriesAnalyzed)}`);
72
+ console.log(`Analysis: ${chalk.gray(elapsed + "s")}
73
+ `);
74
+ console.log(chalk.bold("\u{1F4D0} Dimension Scores\n"));
75
+ const dims = [
76
+ ["Structure Clarity", summary.dimensions.structureClarityScore],
77
+ ["Self-Documentation", summary.dimensions.selfDocumentationScore],
78
+ ["Entry Points", summary.dimensions.entryPointScore],
79
+ ["API Clarity", summary.dimensions.apiClarityScore],
80
+ ["Domain Consistency", summary.dimensions.domainConsistencyScore]
81
+ ];
82
+ for (const [name, val] of dims) {
83
+ const bar = "\u2588".repeat(Math.round(val / 10)).padEnd(10, "\u2591");
84
+ console.log(` ${String(name).padEnd(22)} ${scoreColor(val)(bar)} ${val}/100`);
85
+ }
86
+ if (issues.length > 0) {
87
+ console.log(chalk.bold("\n\u26A0\uFE0F Issues Found\n"));
88
+ for (const issue of issues) {
89
+ const sev = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : chalk.blue;
90
+ console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
91
+ if (issue.suggestion) console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
92
+ console.log();
93
+ }
94
+ } else {
95
+ console.log(chalk.green("\n\u2728 No grounding issues found \u2014 agents can navigate freely!\n"));
96
+ }
97
+ if (recommendations.length > 0) {
98
+ console.log(chalk.bold("\u{1F4A1} Recommendations\n"));
99
+ recommendations.forEach((rec, i) => console.log(`${i + 1}. ${rec}`));
100
+ }
101
+ console.log();
102
+ }
@@ -0,0 +1,66 @@
1
+ import { Issue, ScanOptions, ToolScoringOutput } from '@aiready/core';
2
+
3
+ interface AgentGroundingOptions extends ScanOptions {
4
+ /** Max directory depth before flagging as "too deep" */
5
+ maxRecommendedDepth?: number;
6
+ /** README staleness threshold in days */
7
+ readmeStaleDays?: number;
8
+ /** File names considered "vague" (in addition to built-in list) */
9
+ additionalVagueNames?: string[];
10
+ }
11
+ interface AgentGroundingIssue extends Issue {
12
+ type: 'agent-navigation-failure';
13
+ /** Which grounding dimension is affected */
14
+ dimension: 'structure-clarity' | 'self-documentation' | 'entry-point' | 'api-clarity' | 'domain-consistency';
15
+ }
16
+ interface AgentGroundingReport {
17
+ summary: {
18
+ filesAnalyzed: number;
19
+ directoriesAnalyzed: number;
20
+ score: number;
21
+ rating: 'excellent' | 'good' | 'moderate' | 'poor' | 'disorienting';
22
+ dimensions: {
23
+ structureClarityScore: number;
24
+ selfDocumentationScore: number;
25
+ entryPointScore: number;
26
+ apiClarityScore: number;
27
+ domainConsistencyScore: number;
28
+ };
29
+ };
30
+ issues: AgentGroundingIssue[];
31
+ rawData: {
32
+ deepDirectories: number;
33
+ totalDirectories: number;
34
+ vagueFileNames: number;
35
+ totalFiles: number;
36
+ hasRootReadme: boolean;
37
+ readmeIsFresh: boolean;
38
+ barrelExports: number;
39
+ untypedExports: number;
40
+ totalExports: number;
41
+ inconsistentDomainTerms: number;
42
+ domainVocabularySize: number;
43
+ };
44
+ recommendations: string[];
45
+ }
46
+
47
+ /**
48
+ * Scanner for agent-grounding dimensions.
49
+ *
50
+ * Measures 5 dimensions:
51
+ * 1. Structure clarity — how deep are directory trees?
52
+ * 2. Self-documentation — do file names reveal purpose?
53
+ * 3. Entry points — does a fresh README + barrel exports exist?
54
+ * 4. API clarity — are public exports typed?
55
+ * 5. Domain consistency — is the same concept named the same everywhere?
56
+ */
57
+
58
+ declare function analyzeAgentGrounding(options: AgentGroundingOptions): Promise<AgentGroundingReport>;
59
+
60
+ /**
61
+ * Convert agent grounding report into a ToolScoringOutput
62
+ * for inclusion in the unified AIReady score.
63
+ */
64
+ declare function calculateGroundingScore(report: AgentGroundingReport): ToolScoringOutput;
65
+
66
+ export { type AgentGroundingIssue, type AgentGroundingOptions, type AgentGroundingReport, analyzeAgentGrounding, calculateGroundingScore };
@@ -0,0 +1,66 @@
1
+ import { Issue, ScanOptions, ToolScoringOutput } from '@aiready/core';
2
+
3
+ interface AgentGroundingOptions extends ScanOptions {
4
+ /** Max directory depth before flagging as "too deep" */
5
+ maxRecommendedDepth?: number;
6
+ /** README staleness threshold in days */
7
+ readmeStaleDays?: number;
8
+ /** File names considered "vague" (in addition to built-in list) */
9
+ additionalVagueNames?: string[];
10
+ }
11
+ interface AgentGroundingIssue extends Issue {
12
+ type: 'agent-navigation-failure';
13
+ /** Which grounding dimension is affected */
14
+ dimension: 'structure-clarity' | 'self-documentation' | 'entry-point' | 'api-clarity' | 'domain-consistency';
15
+ }
16
+ interface AgentGroundingReport {
17
+ summary: {
18
+ filesAnalyzed: number;
19
+ directoriesAnalyzed: number;
20
+ score: number;
21
+ rating: 'excellent' | 'good' | 'moderate' | 'poor' | 'disorienting';
22
+ dimensions: {
23
+ structureClarityScore: number;
24
+ selfDocumentationScore: number;
25
+ entryPointScore: number;
26
+ apiClarityScore: number;
27
+ domainConsistencyScore: number;
28
+ };
29
+ };
30
+ issues: AgentGroundingIssue[];
31
+ rawData: {
32
+ deepDirectories: number;
33
+ totalDirectories: number;
34
+ vagueFileNames: number;
35
+ totalFiles: number;
36
+ hasRootReadme: boolean;
37
+ readmeIsFresh: boolean;
38
+ barrelExports: number;
39
+ untypedExports: number;
40
+ totalExports: number;
41
+ inconsistentDomainTerms: number;
42
+ domainVocabularySize: number;
43
+ };
44
+ recommendations: string[];
45
+ }
46
+
47
+ /**
48
+ * Scanner for agent-grounding dimensions.
49
+ *
50
+ * Measures 5 dimensions:
51
+ * 1. Structure clarity — how deep are directory trees?
52
+ * 2. Self-documentation — do file names reveal purpose?
53
+ * 3. Entry points — does a fresh README + barrel exports exist?
54
+ * 4. API clarity — are public exports typed?
55
+ * 5. Domain consistency — is the same concept named the same everywhere?
56
+ */
57
+
58
+ declare function analyzeAgentGrounding(options: AgentGroundingOptions): Promise<AgentGroundingReport>;
59
+
60
+ /**
61
+ * Convert agent grounding report into a ToolScoringOutput
62
+ * for inclusion in the unified AIReady score.
63
+ */
64
+ declare function calculateGroundingScore(report: AgentGroundingReport): ToolScoringOutput;
65
+
66
+ export { type AgentGroundingIssue, type AgentGroundingOptions, type AgentGroundingReport, analyzeAgentGrounding, calculateGroundingScore };