@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.
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Scanner for agent-grounding dimensions.
3
+ *
4
+ * Measures 5 dimensions:
5
+ * 1. Structure clarity — how deep are directory trees?
6
+ * 2. Self-documentation — do file names reveal purpose?
7
+ * 3. Entry points — does a fresh README + barrel exports exist?
8
+ * 4. API clarity — are public exports typed?
9
+ * 5. Domain consistency — is the same concept named the same everywhere?
10
+ */
11
+
12
+ import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
13
+ import { join, extname, basename } from 'path';
14
+ import { parse } from '@typescript-eslint/typescript-estree';
15
+ import type { TSESTree } from '@typescript-eslint/types';
16
+ import type { AgentGroundingOptions, AgentGroundingIssue, AgentGroundingReport } from './types';
17
+ import { calculateAgentGrounding } from '@aiready/core';
18
+
19
+ // File names that don't describe purpose — an agent can't determine what to find here
20
+ const VAGUE_FILE_NAMES = new Set([
21
+ 'utils', 'helpers', 'helper', 'misc', 'common', 'shared', 'tools',
22
+ 'util', 'lib', 'libs', 'stuff', 'functions', 'methods', 'handlers',
23
+ 'data', 'temp', 'tmp', 'test-utils', 'test-helpers', 'mocks',
24
+ ]);
25
+
26
+ const SUPPORTED_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
27
+ const DEFAULT_EXCLUDES = ['node_modules', 'dist', '.git', 'coverage', '.turbo', 'build'];
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // File/dir collection
31
+ // ---------------------------------------------------------------------------
32
+
33
+ interface DirEntry {
34
+ path: string;
35
+ depth: number;
36
+ }
37
+
38
+ function collectEntries(
39
+ dir: string,
40
+ options: AgentGroundingOptions,
41
+ depth = 0,
42
+ dirs: DirEntry[] = [],
43
+ files: string[] = [],
44
+ ): { dirs: DirEntry[]; files: string[] } {
45
+ if (depth > (options.maxDepth ?? 20)) return { dirs, files };
46
+ const excludes = [...DEFAULT_EXCLUDES, ...(options.exclude ?? [])];
47
+
48
+ let entries: string[];
49
+ try {
50
+ entries = readdirSync(dir);
51
+ } catch {
52
+ return { dirs, files };
53
+ }
54
+
55
+ for (const entry of entries) {
56
+ if (excludes.some(ex => entry === ex || entry.includes(ex))) continue;
57
+ const full = join(dir, entry);
58
+ let stat;
59
+ try {
60
+ stat = statSync(full);
61
+ } catch {
62
+ continue;
63
+ }
64
+ if (stat.isDirectory()) {
65
+ dirs.push({ path: full, depth });
66
+ collectEntries(full, options, depth + 1, dirs, files);
67
+ } else if (stat.isFile() && SUPPORTED_EXTENSIONS.has(extname(full))) {
68
+ if (!options.include || options.include.some(p => full.includes(p))) {
69
+ files.push(full);
70
+ }
71
+ }
72
+ }
73
+
74
+ return { dirs, files };
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Per-file analysis
79
+ // ---------------------------------------------------------------------------
80
+
81
+ interface FileAnalysis {
82
+ isBarrel: boolean;
83
+ exportedNames: string[];
84
+ untypedExports: number;
85
+ totalExports: number;
86
+ domainTerms: string[];
87
+ }
88
+
89
+ function analyzeFile(filePath: string): FileAnalysis {
90
+ let code: string;
91
+ try {
92
+ code = readFileSync(filePath, 'utf-8');
93
+ } catch {
94
+ return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
95
+ }
96
+
97
+ let ast: TSESTree.Program;
98
+ try {
99
+ ast = parse(code, {
100
+ jsx: filePath.endsWith('.tsx') || filePath.endsWith('.jsx'),
101
+ range: false,
102
+ loc: false,
103
+ });
104
+ } catch {
105
+ return { isBarrel: false, exportedNames: [], untypedExports: 0, totalExports: 0, domainTerms: [] };
106
+ }
107
+
108
+ let isBarrel = false;
109
+ let exportedNames: string[] = [];
110
+ let untypedExports = 0;
111
+ let totalExports = 0;
112
+
113
+ // Extract "domain terms" from exported identifier names (camelCase split)
114
+ const domainTerms: string[] = [];
115
+
116
+ for (const node of ast.body) {
117
+ if (node.type === 'ExportAllDeclaration') {
118
+ isBarrel = true;
119
+ continue;
120
+ }
121
+ if (node.type === 'ExportNamedDeclaration') {
122
+ totalExports++;
123
+ const decl = (node as any).declaration;
124
+ if (decl) {
125
+ const name = decl.id?.name ?? decl.declarations?.[0]?.id?.name;
126
+ if (name) {
127
+ exportedNames.push(name);
128
+ // Split camelCase into terms
129
+ domainTerms.push(...name.replace(/([A-Z])/g, ' $1').toLowerCase().split(/\s+/).filter(Boolean));
130
+
131
+ // Check if it's typed (TS function/variable with annotation)
132
+ const hasType =
133
+ decl.returnType != null ||
134
+ decl.declarations?.[0]?.id?.typeAnnotation != null ||
135
+ decl.typeParameters != null;
136
+ if (!hasType) untypedExports++;
137
+ }
138
+ } else if (node.specifiers && node.specifiers.length > 0) {
139
+ // Named re-exports from another module — this is barrel-like
140
+ isBarrel = true;
141
+ }
142
+ }
143
+ if (node.type === 'ExportDefaultDeclaration') {
144
+ totalExports++;
145
+ }
146
+ }
147
+
148
+ return { isBarrel, exportedNames, untypedExports, totalExports, domainTerms };
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Domain vocabulary consistency check
153
+ // ---------------------------------------------------------------------------
154
+
155
+ function detectInconsistentTerms(allTerms: string[]): { inconsistent: number; vocabularySize: number } {
156
+ const termFreq = new Map<string, number>();
157
+ for (const term of allTerms) {
158
+ if (term.length >= 3) {
159
+ termFreq.set(term, (termFreq.get(term) ?? 0) + 1);
160
+ }
161
+ }
162
+ // Very simplistic: terms that appear exactly once are "orphan concepts" —
163
+ // they may be inconsistently named variants of common terms.
164
+ const orphans = [...termFreq.values()].filter(count => count === 1).length;
165
+ const common = [...termFreq.values()].filter(count => count >= 3).length;
166
+ const vocabularySize = termFreq.size;
167
+ // Inconsistency ratio: many orphan terms relative to common terms
168
+ const inconsistent = Math.max(0, orphans - common * 2);
169
+ return { inconsistent, vocabularySize };
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Main analyzer
174
+ // ---------------------------------------------------------------------------
175
+
176
+ export async function analyzeAgentGrounding(
177
+ options: AgentGroundingOptions,
178
+ ): Promise<AgentGroundingReport> {
179
+ const rootDir = options.rootDir;
180
+ const maxRecommendedDepth = options.maxRecommendedDepth ?? 4;
181
+ const readmeStaleDays = options.readmeStaleDays ?? 90;
182
+
183
+ const { dirs, files } = collectEntries(rootDir, options);
184
+
185
+ // Structure clarity
186
+ const deepDirectories = dirs.filter(d => d.depth > maxRecommendedDepth).length;
187
+
188
+ // Self-documentation — vague file names
189
+ const additionalVague = new Set((options.additionalVagueNames ?? []).map(n => n.toLowerCase()));
190
+ let vagueFileNames = 0;
191
+ for (const f of files) {
192
+ const base = basename(f, extname(f)).toLowerCase();
193
+ if (VAGUE_FILE_NAMES.has(base) || additionalVague.has(base)) {
194
+ vagueFileNames++;
195
+ }
196
+ }
197
+
198
+ // README presence and freshness
199
+ const readmePath = join(rootDir, 'README.md');
200
+ const hasRootReadme = existsSync(readmePath);
201
+ let readmeIsFresh = false;
202
+ if (hasRootReadme) {
203
+ try {
204
+ const stat = statSync(readmePath);
205
+ const ageDays = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
206
+ readmeIsFresh = ageDays < readmeStaleDays;
207
+ } catch { }
208
+ }
209
+
210
+ // File analysis
211
+ const allDomainTerms: string[] = [];
212
+ let barrelExports = 0;
213
+ let untypedExports = 0;
214
+ let totalExports = 0;
215
+
216
+ for (const f of files) {
217
+ const analysis = analyzeFile(f);
218
+ if (analysis.isBarrel) barrelExports++;
219
+ untypedExports += analysis.untypedExports;
220
+ totalExports += analysis.totalExports;
221
+ allDomainTerms.push(...analysis.domainTerms);
222
+ }
223
+
224
+ // Domain vocabulary consistency
225
+ const { inconsistent: inconsistentDomainTerms, vocabularySize: domainVocabularySize } =
226
+ detectInconsistentTerms(allDomainTerms);
227
+
228
+ // Calculate grounding score using core math
229
+ const groundingResult = calculateAgentGrounding({
230
+ deepDirectories,
231
+ totalDirectories: dirs.length,
232
+ vagueFileNames,
233
+ totalFiles: files.length,
234
+ hasRootReadme,
235
+ readmeIsFresh,
236
+ barrelExports,
237
+ untypedExports,
238
+ totalExports: Math.max(1, totalExports),
239
+ inconsistentDomainTerms,
240
+ domainVocabularySize: Math.max(1, domainVocabularySize),
241
+ });
242
+
243
+ // Build issues list
244
+ const issues: AgentGroundingIssue[] = [];
245
+
246
+ if (groundingResult.dimensions.structureClarityScore < 70) {
247
+ issues.push({
248
+ type: 'agent-navigation-failure',
249
+ dimension: 'structure-clarity',
250
+ severity: 'major',
251
+ message: `${deepDirectories} directories exceed recommended depth of ${maxRecommendedDepth} — agents struggle to navigate deep trees.`,
252
+ location: { file: rootDir, line: 0 },
253
+ suggestion: `Flatten nested directories to ${maxRecommendedDepth} levels or fewer.`,
254
+ });
255
+ }
256
+
257
+ if (groundingResult.dimensions.selfDocumentationScore < 70) {
258
+ issues.push({
259
+ type: 'agent-navigation-failure',
260
+ dimension: 'self-documentation',
261
+ severity: 'major',
262
+ message: `${vagueFileNames} files use vague names (utils, helpers, misc) — an agent cannot determine their purpose from the name alone.`,
263
+ location: { file: rootDir, line: 0 },
264
+ suggestion: 'Rename to domain-specific names: e.g., userAuthUtils → tokenValidator.',
265
+ });
266
+ }
267
+
268
+ if (!hasRootReadme) {
269
+ issues.push({
270
+ type: 'agent-navigation-failure',
271
+ dimension: 'entry-point',
272
+ severity: 'critical',
273
+ message: 'No root README.md found — agents have no orientation document to start from.',
274
+ location: { file: join(rootDir, 'README.md'), line: 0 },
275
+ suggestion: 'Add a README.md explaining the project structure, entry points, and key conventions.',
276
+ });
277
+ } else if (!readmeIsFresh) {
278
+ issues.push({
279
+ type: 'agent-navigation-failure',
280
+ dimension: 'entry-point',
281
+ severity: 'minor',
282
+ message: `README.md is stale (>${readmeStaleDays} days without updates) — agents may be misled by outdated context.`,
283
+ location: { file: readmePath, line: 0 },
284
+ suggestion: 'Update README.md to reflect the current codebase structure.',
285
+ });
286
+ }
287
+
288
+ if (groundingResult.dimensions.apiClarityScore < 70) {
289
+ issues.push({
290
+ type: 'agent-navigation-failure',
291
+ dimension: 'api-clarity',
292
+ severity: 'major',
293
+ message: `${untypedExports} of ${totalExports} public exports lack TypeScript type annotations — agents cannot infer the API contract.`,
294
+ location: { file: rootDir, line: 0 },
295
+ suggestion: 'Add explicit return type and parameter annotations to all exported functions.',
296
+ });
297
+ }
298
+
299
+ if (groundingResult.dimensions.domainConsistencyScore < 70) {
300
+ issues.push({
301
+ type: 'agent-navigation-failure',
302
+ dimension: 'domain-consistency',
303
+ severity: 'major',
304
+ message: `${inconsistentDomainTerms} domain terms appear to be used inconsistently — agents get confused when one concept has multiple names.`,
305
+ location: { file: rootDir, line: 0 },
306
+ suggestion: 'Establish a domain glossary and enforce one term per concept across the codebase.',
307
+ });
308
+ }
309
+
310
+ return {
311
+ summary: {
312
+ filesAnalyzed: files.length,
313
+ directoriesAnalyzed: dirs.length,
314
+ score: groundingResult.score,
315
+ rating: groundingResult.rating,
316
+ dimensions: groundingResult.dimensions,
317
+ },
318
+ issues,
319
+ rawData: {
320
+ deepDirectories,
321
+ totalDirectories: dirs.length,
322
+ vagueFileNames,
323
+ totalFiles: files.length,
324
+ hasRootReadme,
325
+ readmeIsFresh,
326
+ barrelExports,
327
+ untypedExports,
328
+ totalExports,
329
+ inconsistentDomainTerms,
330
+ domainVocabularySize,
331
+ },
332
+ recommendations: groundingResult.recommendations,
333
+ };
334
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { analyzeAgentGrounding } from './analyzer';
5
+ import { calculateGroundingScore } from './scoring';
6
+ import type { AgentGroundingOptions } from './types';
7
+ import chalk from 'chalk';
8
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
9
+ import { dirname } from 'path';
10
+ import { loadConfig, mergeConfigWithDefaults, resolveOutputPath } from '@aiready/core';
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('aiready-agent-grounding')
16
+ .description('Measure how well an AI agent can navigate your codebase autonomously')
17
+ .version('0.1.0')
18
+ .addHelpText('after', `
19
+ GROUNDING DIMENSIONS:
20
+ Structure Clarity Deep directory trees slow and confuse agents
21
+ Self-Documentation Vague file names (utils, helpers) hide intent
22
+ Entry Points README presence, freshness, and barrel exports
23
+ API Clarity Untyped exports prevent API contract inference
24
+ Domain Consistency Inconsistent naming forces agents to guess
25
+
26
+ EXAMPLES:
27
+ aiready-agent-grounding . # Full analysis
28
+ aiready-agent-grounding src/ --output json # JSON report
29
+ aiready-agent-grounding . --max-depth 3 # Stricter depth limit
30
+ `)
31
+ .argument('<directory>', 'Directory to analyze')
32
+ .option('--max-depth <n>', 'Max recommended directory depth (default: 4)', '4')
33
+ .option('--readme-stale-days <n>', 'Days after which README is considered stale (default: 90)', '90')
34
+ .option('--include <patterns>', 'File patterns to include (comma-separated)')
35
+ .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
36
+ .option('-o, --output <format>', 'Output format: console|json', 'console')
37
+ .option('--output-file <path>', 'Output file path (for json)')
38
+ .action(async (directory, options) => {
39
+ console.log(chalk.blue('🧭 Analyzing agent grounding...\n'));
40
+ const startTime = Date.now();
41
+
42
+ const config = await loadConfig(directory);
43
+ const mergedConfig = mergeConfigWithDefaults(config, {
44
+ maxRecommendedDepth: 4,
45
+ readmeStaleDays: 90,
46
+ });
47
+
48
+ const finalOptions: AgentGroundingOptions = {
49
+ rootDir: directory,
50
+ maxRecommendedDepth: parseInt(options.maxDepth ?? '4', 10) || mergedConfig.maxRecommendedDepth,
51
+ readmeStaleDays: parseInt(options.readmeStaleDays ?? '90', 10) || mergedConfig.readmeStaleDays,
52
+ include: options.include?.split(','),
53
+ exclude: options.exclude?.split(','),
54
+ };
55
+
56
+ const report = await analyzeAgentGrounding(finalOptions);
57
+ const scoring = calculateGroundingScore(report);
58
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
59
+
60
+ if (options.output === 'json') {
61
+ const payload = { report, score: scoring };
62
+ const outputPath = resolveOutputPath(
63
+ options.outputFile,
64
+ `agent-grounding-report-${new Date().toISOString().split('T')[0]}.json`,
65
+ directory,
66
+ );
67
+ const dir = dirname(outputPath);
68
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
69
+ writeFileSync(outputPath, JSON.stringify(payload, null, 2));
70
+ console.log(chalk.green(`✓ Report saved to ${outputPath}`));
71
+ } else {
72
+ displayConsoleReport(report, scoring, elapsed);
73
+ }
74
+ });
75
+
76
+ program.parse();
77
+
78
+ function scoreColor(score: number) {
79
+ if (score >= 85) return chalk.green;
80
+ if (score >= 70) return chalk.cyan;
81
+ if (score >= 50) return chalk.yellow;
82
+ if (score >= 30) return chalk.red;
83
+ return chalk.bgRed.white;
84
+ }
85
+
86
+ function displayConsoleReport(report: any, scoring: any, elapsed: string) {
87
+ const { summary, rawData, issues, recommendations } = report;
88
+
89
+ console.log(chalk.bold('\n🧭 Agent Grounding Analysis\n'));
90
+ console.log(`Score: ${scoreColor(summary.score)(summary.score + '/100')} (${summary.rating.toUpperCase()})`);
91
+ console.log(`Files: ${chalk.cyan(summary.filesAnalyzed)} Directories: ${chalk.cyan(summary.directoriesAnalyzed)}`);
92
+ console.log(`Analysis: ${chalk.gray(elapsed + 's')}\n`);
93
+
94
+ console.log(chalk.bold('📐 Dimension Scores\n'));
95
+ const dims = [
96
+ ['Structure Clarity', summary.dimensions.structureClarityScore],
97
+ ['Self-Documentation', summary.dimensions.selfDocumentationScore],
98
+ ['Entry Points', summary.dimensions.entryPointScore],
99
+ ['API Clarity', summary.dimensions.apiClarityScore],
100
+ ['Domain Consistency', summary.dimensions.domainConsistencyScore],
101
+ ];
102
+ for (const [name, val] of dims) {
103
+ const bar = '█'.repeat(Math.round((val as number) / 10)).padEnd(10, '░');
104
+ console.log(` ${String(name).padEnd(22)} ${scoreColor(val as number)(bar)} ${val}/100`);
105
+ }
106
+
107
+ if (issues.length > 0) {
108
+ console.log(chalk.bold('\n⚠️ Issues Found\n'));
109
+ for (const issue of issues) {
110
+ const sev = issue.severity === 'critical' ? chalk.red : issue.severity === 'major' ? chalk.yellow : chalk.blue;
111
+ console.log(`${sev(issue.severity.toUpperCase())} ${issue.message}`);
112
+ if (issue.suggestion) console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
113
+ console.log();
114
+ }
115
+ } else {
116
+ console.log(chalk.green('\n✨ No grounding issues found — agents can navigate freely!\n'));
117
+ }
118
+
119
+ if (recommendations.length > 0) {
120
+ console.log(chalk.bold('💡 Recommendations\n'));
121
+ recommendations.forEach((rec: string, i: number) => console.log(`${i + 1}. ${rec}`));
122
+ }
123
+ console.log();
124
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { analyzeAgentGrounding } from './analyzer';
2
+ export { calculateGroundingScore } from './scoring';
3
+ export type {
4
+ AgentGroundingOptions,
5
+ AgentGroundingReport,
6
+ AgentGroundingIssue,
7
+ } from './types';
package/src/scoring.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { calculateAgentGrounding } from '@aiready/core';
2
+ import type { ToolScoringOutput } from '@aiready/core';
3
+ import type { AgentGroundingReport } from './types';
4
+
5
+ /**
6
+ * Convert agent grounding report into a ToolScoringOutput
7
+ * for inclusion in the unified AIReady score.
8
+ */
9
+ export function calculateGroundingScore(report: AgentGroundingReport): ToolScoringOutput {
10
+ const { summary, rawData, issues, recommendations } = report;
11
+
12
+ const factors: ToolScoringOutput['factors'] = [
13
+ {
14
+ name: 'Structure Clarity',
15
+ impact: Math.round(summary.dimensions.structureClarityScore - 50),
16
+ description: `${rawData.deepDirectories} of ${rawData.totalDirectories} dirs exceed recommended depth`,
17
+ },
18
+ {
19
+ name: 'Self-Documentation',
20
+ impact: Math.round(summary.dimensions.selfDocumentationScore - 50),
21
+ description: `${rawData.vagueFileNames} of ${rawData.totalFiles} files have vague names`,
22
+ },
23
+ {
24
+ name: 'Entry Points',
25
+ impact: Math.round(summary.dimensions.entryPointScore - 50),
26
+ description: rawData.hasRootReadme
27
+ ? rawData.readmeIsFresh ? 'README present and fresh' : 'README present but stale'
28
+ : 'No root README',
29
+ },
30
+ {
31
+ name: 'API Clarity',
32
+ impact: Math.round(summary.dimensions.apiClarityScore - 50),
33
+ description: `${rawData.untypedExports} of ${rawData.totalExports} exports lack type annotations`,
34
+ },
35
+ {
36
+ name: 'Domain Consistency',
37
+ impact: Math.round(summary.dimensions.domainConsistencyScore - 50),
38
+ description: `${rawData.inconsistentDomainTerms} inconsistent domain terms detected`,
39
+ },
40
+ ];
41
+
42
+ const recs: ToolScoringOutput['recommendations'] = recommendations.map(action => ({
43
+ action,
44
+ estimatedImpact: 6,
45
+ priority: summary.score < 50 ? 'high' : 'medium',
46
+ }));
47
+
48
+ return {
49
+ toolName: 'agent-grounding',
50
+ score: summary.score,
51
+ rawMetrics: {
52
+ ...rawData,
53
+ rating: summary.rating,
54
+ },
55
+ factors,
56
+ recommendations: recs,
57
+ };
58
+ }
package/src/types.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { ScanOptions, Issue } from '@aiready/core';
2
+
3
+ export 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
+
12
+ export interface AgentGroundingIssue extends Issue {
13
+ type: 'agent-navigation-failure';
14
+ /** Which grounding dimension is affected */
15
+ dimension: 'structure-clarity' | 'self-documentation' | 'entry-point' | 'api-clarity' | 'domain-consistency';
16
+ }
17
+
18
+ export interface AgentGroundingReport {
19
+ summary: {
20
+ filesAnalyzed: number;
21
+ directoriesAnalyzed: number;
22
+ score: number;
23
+ rating: 'excellent' | 'good' | 'moderate' | 'poor' | 'disorienting';
24
+ dimensions: {
25
+ structureClarityScore: number;
26
+ selfDocumentationScore: number;
27
+ entryPointScore: number;
28
+ apiClarityScore: number;
29
+ domainConsistencyScore: number;
30
+ };
31
+ };
32
+ issues: AgentGroundingIssue[];
33
+ rawData: {
34
+ deepDirectories: number;
35
+ totalDirectories: number;
36
+ vagueFileNames: number;
37
+ totalFiles: number;
38
+ hasRootReadme: boolean;
39
+ readmeIsFresh: boolean;
40
+ barrelExports: number;
41
+ untypedExports: number;
42
+ totalExports: number;
43
+ inconsistentDomainTerms: number;
44
+ domainVocabularySize: number;
45
+ };
46
+ recommendations: string[];
47
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../core/tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }