ai-first-cli 1.3.6 → 1.3.8
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/CHANGELOG.md +123 -0
- package/README.es.md +14 -1
- package/README.md +14 -1
- package/ai/graph/knowledge-graph.json +1 -1
- package/ai-context/index-state.json +86 -2
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +43 -0
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/commands/ai-first.d.ts.map +1 -1
- package/dist/commands/ai-first.js +78 -4
- package/dist/commands/ai-first.js.map +1 -1
- package/dist/config/configLoader.d.ts +6 -0
- package/dist/config/configLoader.d.ts.map +1 -0
- package/dist/config/configLoader.js +232 -0
- package/dist/config/configLoader.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +101 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/core/content/contentProcessor.d.ts +4 -0
- package/dist/core/content/contentProcessor.d.ts.map +1 -0
- package/dist/core/content/contentProcessor.js +235 -0
- package/dist/core/content/contentProcessor.js.map +1 -0
- package/dist/core/content/index.d.ts +3 -0
- package/dist/core/content/index.d.ts.map +1 -0
- package/dist/core/content/index.js +2 -0
- package/dist/core/content/index.js.map +1 -0
- package/dist/core/content/types.d.ts +32 -0
- package/dist/core/content/types.d.ts.map +1 -0
- package/dist/core/content/types.js +2 -0
- package/dist/core/content/types.js.map +1 -0
- package/dist/core/gitAnalyzer.d.ts +14 -0
- package/dist/core/gitAnalyzer.d.ts.map +1 -1
- package/dist/core/gitAnalyzer.js +98 -0
- package/dist/core/gitAnalyzer.js.map +1 -1
- package/dist/core/multiRepo/index.d.ts +3 -0
- package/dist/core/multiRepo/index.d.ts.map +1 -0
- package/dist/core/multiRepo/index.js +2 -0
- package/dist/core/multiRepo/index.js.map +1 -0
- package/dist/core/multiRepo/multiRepoScanner.d.ts +18 -0
- package/dist/core/multiRepo/multiRepoScanner.d.ts.map +1 -0
- package/dist/core/multiRepo/multiRepoScanner.js +131 -0
- package/dist/core/multiRepo/multiRepoScanner.js.map +1 -0
- package/dist/core/rag/index.d.ts +3 -0
- package/dist/core/rag/index.d.ts.map +1 -0
- package/dist/core/rag/index.js +2 -0
- package/dist/core/rag/index.js.map +1 -0
- package/dist/core/rag/vectorIndex.d.ts +28 -0
- package/dist/core/rag/vectorIndex.d.ts.map +1 -0
- package/dist/core/rag/vectorIndex.js +71 -0
- package/dist/core/rag/vectorIndex.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +154 -0
- package/dist/mcp/server.js.map +1 -0
- package/docs/planning/evaluator-v1.0.0/README.md +112 -0
- package/docs/planning/evaluator-v1.0.0/improvements_plan_2026-03-28.md +237 -0
- package/package.json +13 -3
- package/src/analyzers/techStack.ts +47 -1
- package/src/commands/ai-first.ts +83 -4
- package/src/config/configLoader.ts +274 -0
- package/src/config/index.ts +27 -0
- package/src/config/types.ts +117 -0
- package/src/core/content/contentProcessor.ts +292 -0
- package/src/core/content/index.ts +9 -0
- package/src/core/content/types.ts +35 -0
- package/src/core/gitAnalyzer.ts +130 -0
- package/src/core/multiRepo/index.ts +2 -0
- package/src/core/multiRepo/multiRepoScanner.ts +177 -0
- package/src/core/rag/index.ts +2 -0
- package/src/core/rag/vectorIndex.ts +105 -0
- package/src/mcp/index.ts +1 -0
- package/src/mcp/server.ts +179 -0
- package/tests/v1.3.8-integration.test.ts +361 -0
- package/ai-context-evaluation-report-1774223059505.md +0 -206
- package/scripts/ai-context-evaluator.ts +0 -440
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type {
|
|
4
|
+
AIFirstConfig,
|
|
5
|
+
AnalysisPreset,
|
|
6
|
+
ConfigLoadOptions,
|
|
7
|
+
ConfigLoadResult,
|
|
8
|
+
PresetConfig,
|
|
9
|
+
ValidationResult,
|
|
10
|
+
ValidationError,
|
|
11
|
+
ValidationWarning,
|
|
12
|
+
OutputConfig,
|
|
13
|
+
IndexConfig,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONFIG_PATH = 'ai-first.config.json';
|
|
17
|
+
|
|
18
|
+
const PRESETS: Record<AnalysisPreset, PresetConfig> = {
|
|
19
|
+
full: {
|
|
20
|
+
name: 'full',
|
|
21
|
+
description: 'Full analysis with all analyzers enabled',
|
|
22
|
+
analysis: {
|
|
23
|
+
detailLevel: 'full',
|
|
24
|
+
inclusionLevel: 'full',
|
|
25
|
+
analyzers: {
|
|
26
|
+
architecture: { enabled: true },
|
|
27
|
+
techStack: { enabled: true },
|
|
28
|
+
entrypoints: { enabled: true },
|
|
29
|
+
conventions: { enabled: true },
|
|
30
|
+
symbols: { enabled: true },
|
|
31
|
+
dependencies: { enabled: true },
|
|
32
|
+
aiRules: { enabled: true },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
output: {
|
|
36
|
+
directory: 'ai-context',
|
|
37
|
+
formats: ['md', 'json'],
|
|
38
|
+
prettyPrint: true,
|
|
39
|
+
includeMetadata: true,
|
|
40
|
+
},
|
|
41
|
+
index: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
type: 'sqlite',
|
|
44
|
+
incremental: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
quick: {
|
|
48
|
+
name: 'quick',
|
|
49
|
+
description: 'Fast analysis with basic information',
|
|
50
|
+
analysis: {
|
|
51
|
+
detailLevel: 'skeleton',
|
|
52
|
+
inclusionLevel: 'directory',
|
|
53
|
+
maxFileSize: 10000,
|
|
54
|
+
analyzers: {
|
|
55
|
+
architecture: { enabled: true },
|
|
56
|
+
techStack: { enabled: true },
|
|
57
|
+
entrypoints: { enabled: true },
|
|
58
|
+
conventions: { enabled: false },
|
|
59
|
+
symbols: { enabled: false },
|
|
60
|
+
dependencies: { enabled: false },
|
|
61
|
+
aiRules: { enabled: false },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
directory: 'ai-context',
|
|
66
|
+
formats: ['md'],
|
|
67
|
+
prettyPrint: false,
|
|
68
|
+
includeMetadata: false,
|
|
69
|
+
},
|
|
70
|
+
index: {
|
|
71
|
+
enabled: false,
|
|
72
|
+
type: 'memory',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
api: {
|
|
76
|
+
name: 'api',
|
|
77
|
+
description: 'Focused on API and backend analysis',
|
|
78
|
+
analysis: {
|
|
79
|
+
detailLevel: 'signatures',
|
|
80
|
+
inclusionLevel: 'compress',
|
|
81
|
+
analyzers: {
|
|
82
|
+
architecture: { enabled: true },
|
|
83
|
+
techStack: { enabled: true },
|
|
84
|
+
entrypoints: { enabled: true },
|
|
85
|
+
conventions: { enabled: true },
|
|
86
|
+
symbols: { enabled: true },
|
|
87
|
+
dependencies: { enabled: true },
|
|
88
|
+
aiRules: { enabled: false },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
output: {
|
|
92
|
+
directory: 'ai-context',
|
|
93
|
+
formats: ['md', 'json'],
|
|
94
|
+
prettyPrint: true,
|
|
95
|
+
includeMetadata: true,
|
|
96
|
+
},
|
|
97
|
+
index: {
|
|
98
|
+
enabled: true,
|
|
99
|
+
type: 'sqlite',
|
|
100
|
+
incremental: true,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
docs: {
|
|
104
|
+
name: 'docs',
|
|
105
|
+
description: 'Generate documentation-focused output',
|
|
106
|
+
analysis: {
|
|
107
|
+
detailLevel: 'full',
|
|
108
|
+
inclusionLevel: 'full',
|
|
109
|
+
analyzers: {
|
|
110
|
+
architecture: { enabled: true },
|
|
111
|
+
techStack: { enabled: true },
|
|
112
|
+
entrypoints: { enabled: true },
|
|
113
|
+
conventions: { enabled: true },
|
|
114
|
+
symbols: { enabled: true },
|
|
115
|
+
dependencies: { enabled: false },
|
|
116
|
+
aiRules: { enabled: false },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
output: {
|
|
120
|
+
directory: 'ai-context',
|
|
121
|
+
formats: ['md', 'html'],
|
|
122
|
+
prettyPrint: true,
|
|
123
|
+
includeMetadata: true,
|
|
124
|
+
},
|
|
125
|
+
index: {
|
|
126
|
+
enabled: false,
|
|
127
|
+
type: 'memory',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const DEFAULT_CONFIG: AIFirstConfig = {
|
|
133
|
+
version: '1.0.0',
|
|
134
|
+
preset: 'full',
|
|
135
|
+
analysis: PRESETS.full.analysis as NonNullable<AIFirstConfig['analysis']>,
|
|
136
|
+
output: PRESETS.full.output as OutputConfig,
|
|
137
|
+
index: PRESETS.full.index as IndexConfig,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function validateConfig(config: unknown): ValidationResult {
|
|
141
|
+
const errors: ValidationError[] = [];
|
|
142
|
+
const warnings: ValidationWarning[] = [];
|
|
143
|
+
|
|
144
|
+
if (!config || typeof config !== 'object') {
|
|
145
|
+
errors.push({ field: 'root', message: 'Config must be an object' });
|
|
146
|
+
return { valid: false, errors, warnings };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const cfg = config as Record<string, unknown>;
|
|
150
|
+
|
|
151
|
+
if (!cfg.version || typeof cfg.version !== 'string') {
|
|
152
|
+
errors.push({ field: 'version', message: 'Version is required and must be a string' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (cfg.preset !== undefined) {
|
|
156
|
+
const validPresets: AnalysisPreset[] = ['full', 'quick', 'api', 'docs'];
|
|
157
|
+
if (!validPresets.includes(cfg.preset as AnalysisPreset)) {
|
|
158
|
+
errors.push({
|
|
159
|
+
field: 'preset',
|
|
160
|
+
message: `Invalid preset. Must be one of: ${validPresets.join(', ')}`,
|
|
161
|
+
value: cfg.preset,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (cfg.analysis && typeof cfg.analysis !== 'object') {
|
|
167
|
+
errors.push({ field: 'analysis', message: 'Analysis must be an object' });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (cfg.output && typeof cfg.output !== 'object') {
|
|
171
|
+
errors.push({ field: 'output', message: 'Output must be an object' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (cfg.index && typeof cfg.index !== 'object') {
|
|
175
|
+
errors.push({ field: 'index', message: 'Index must be an object' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function mergeConfig(base: AIFirstConfig, override: Partial<AIFirstConfig>): AIFirstConfig {
|
|
182
|
+
const result: AIFirstConfig = {
|
|
183
|
+
...base,
|
|
184
|
+
...override,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (override.analysis) {
|
|
188
|
+
result.analysis = { ...base.analysis, ...override.analysis };
|
|
189
|
+
}
|
|
190
|
+
if (override.output) {
|
|
191
|
+
result.output = { ...base.output, ...override.output } as OutputConfig;
|
|
192
|
+
}
|
|
193
|
+
if (override.index) {
|
|
194
|
+
result.index = { ...base.index, ...override.index } as IndexConfig;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getPreset(name: AnalysisPreset): PresetConfig | undefined {
|
|
201
|
+
return PRESETS[name];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function listPresets(): PresetConfig[] {
|
|
205
|
+
return Object.values(PRESETS);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function loadConfig(options: ConfigLoadOptions = {}): ConfigLoadResult {
|
|
209
|
+
const {
|
|
210
|
+
configPath = DEFAULT_CONFIG_PATH,
|
|
211
|
+
preset,
|
|
212
|
+
overrides,
|
|
213
|
+
validate = true,
|
|
214
|
+
throwOnError = false,
|
|
215
|
+
} = options;
|
|
216
|
+
|
|
217
|
+
let config: AIFirstConfig = { ...DEFAULT_CONFIG };
|
|
218
|
+
let source: ConfigLoadResult['source'] = 'default';
|
|
219
|
+
|
|
220
|
+
if (fs.existsSync(configPath)) {
|
|
221
|
+
try {
|
|
222
|
+
const fileContent = fs.readFileSync(configPath, 'utf-8');
|
|
223
|
+
const loadedConfig = JSON.parse(fileContent) as Partial<AIFirstConfig>;
|
|
224
|
+
config = mergeConfig(DEFAULT_CONFIG, loadedConfig);
|
|
225
|
+
source = 'file';
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const parseError: ValidationError = {
|
|
228
|
+
field: 'configFile',
|
|
229
|
+
message: `Failed to parse config file: ${error instanceof Error ? error.message : String(error)}`,
|
|
230
|
+
value: configPath,
|
|
231
|
+
};
|
|
232
|
+
if (throwOnError) {
|
|
233
|
+
throw new Error(parseError.message);
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
config: DEFAULT_CONFIG,
|
|
237
|
+
source: 'default',
|
|
238
|
+
validation: { valid: false, errors: [parseError], warnings: [] },
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (preset && PRESETS[preset]) {
|
|
244
|
+
const p = PRESETS[preset];
|
|
245
|
+
const presetData: Partial<AIFirstConfig> = {
|
|
246
|
+
analysis: p.analysis as AIFirstConfig['analysis'],
|
|
247
|
+
output: p.output as AIFirstConfig['output'],
|
|
248
|
+
index: p.index as AIFirstConfig['index'],
|
|
249
|
+
};
|
|
250
|
+
config = mergeConfig(config, presetData);
|
|
251
|
+
source = source === 'default' ? 'preset' : 'merged';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (overrides) {
|
|
255
|
+
config = mergeConfig(config, overrides);
|
|
256
|
+
source = 'merged';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const validation = validateConfig(config);
|
|
260
|
+
|
|
261
|
+
if (!validation.valid && throwOnError) {
|
|
262
|
+
const errorMessages = validation.errors.map(e => e.message).join('; ');
|
|
263
|
+
throw new Error(`Configuration validation failed: ${errorMessages}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { config, source, validation };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function resolveConfigPath(customPath?: string): string {
|
|
270
|
+
if (customPath) {
|
|
271
|
+
return path.resolve(customPath);
|
|
272
|
+
}
|
|
273
|
+
return path.resolve(DEFAULT_CONFIG_PATH);
|
|
274
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
AnalysisPreset,
|
|
3
|
+
ConfigDetailLevel,
|
|
4
|
+
ConfigInclusionLevel,
|
|
5
|
+
FilePatternConfig,
|
|
6
|
+
AnalyzerConfig,
|
|
7
|
+
AnalysisConfig,
|
|
8
|
+
FileInclusionConfig,
|
|
9
|
+
OutputConfig,
|
|
10
|
+
GitConfig,
|
|
11
|
+
IndexConfig,
|
|
12
|
+
ContextConfig,
|
|
13
|
+
PresetConfig,
|
|
14
|
+
AIFirstConfig,
|
|
15
|
+
ValidationResult,
|
|
16
|
+
ValidationError,
|
|
17
|
+
ValidationWarning,
|
|
18
|
+
ConfigLoadOptions,
|
|
19
|
+
ConfigLoadResult,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
loadConfig,
|
|
24
|
+
getPreset,
|
|
25
|
+
listPresets,
|
|
26
|
+
resolveConfigPath,
|
|
27
|
+
} from './configLoader.js';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export type AnalysisPreset = 'full' | 'quick' | 'api' | 'docs';
|
|
2
|
+
|
|
3
|
+
export type ConfigDetailLevel = 'full' | 'signatures' | 'skeleton';
|
|
4
|
+
|
|
5
|
+
export type ConfigInclusionLevel = 'full' | 'compress' | 'directory' | 'exclude';
|
|
6
|
+
|
|
7
|
+
export interface FilePatternConfig {
|
|
8
|
+
include?: string[];
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AnalyzerConfig {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
options?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AnalysisConfig {
|
|
18
|
+
preset?: AnalysisPreset;
|
|
19
|
+
detailLevel?: ConfigDetailLevel;
|
|
20
|
+
inclusionLevel?: ConfigInclusionLevel;
|
|
21
|
+
filePatterns?: FilePatternConfig;
|
|
22
|
+
maxFileSize?: number;
|
|
23
|
+
includePatterns?: string[];
|
|
24
|
+
excludePatterns?: string[];
|
|
25
|
+
analyzers?: {
|
|
26
|
+
architecture?: AnalyzerConfig;
|
|
27
|
+
techStack?: AnalyzerConfig;
|
|
28
|
+
entrypoints?: AnalyzerConfig;
|
|
29
|
+
conventions?: AnalyzerConfig;
|
|
30
|
+
symbols?: AnalyzerConfig;
|
|
31
|
+
dependencies?: AnalyzerConfig;
|
|
32
|
+
aiRules?: AnalyzerConfig;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FileInclusionConfig {
|
|
37
|
+
level: ConfigInclusionLevel;
|
|
38
|
+
patterns: string[];
|
|
39
|
+
maxFileSize?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OutputConfig {
|
|
43
|
+
directory: string;
|
|
44
|
+
formats: ('md' | 'json' | 'html')[];
|
|
45
|
+
prettyPrint?: boolean;
|
|
46
|
+
includeMetadata?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface GitConfig {
|
|
50
|
+
includeCommits?: number;
|
|
51
|
+
excludePatterns?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IndexConfig {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
type: 'sqlite' | 'memory';
|
|
57
|
+
incremental?: boolean;
|
|
58
|
+
watch?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ContextConfig {
|
|
62
|
+
maxTokens?: number;
|
|
63
|
+
includeEmbeddings?: boolean;
|
|
64
|
+
semanticSearch?: boolean;
|
|
65
|
+
maxResults?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PresetConfig {
|
|
69
|
+
name: AnalysisPreset;
|
|
70
|
+
description: string;
|
|
71
|
+
analysis: Partial<AnalysisConfig>;
|
|
72
|
+
output: Partial<OutputConfig>;
|
|
73
|
+
index: Partial<IndexConfig>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AIFirstConfig {
|
|
77
|
+
version: string;
|
|
78
|
+
preset?: AnalysisPreset;
|
|
79
|
+
analysis?: AnalysisConfig;
|
|
80
|
+
output?: OutputConfig;
|
|
81
|
+
index?: IndexConfig;
|
|
82
|
+
context?: ContextConfig;
|
|
83
|
+
git?: GitConfig;
|
|
84
|
+
fileInclusion?: FileInclusionConfig;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ValidationResult {
|
|
88
|
+
valid: boolean;
|
|
89
|
+
errors: ValidationError[];
|
|
90
|
+
warnings: ValidationWarning[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ValidationError {
|
|
94
|
+
field: string;
|
|
95
|
+
message: string;
|
|
96
|
+
value?: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ValidationWarning {
|
|
100
|
+
field: string;
|
|
101
|
+
message: string;
|
|
102
|
+
suggestion?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ConfigLoadOptions {
|
|
106
|
+
configPath?: string;
|
|
107
|
+
preset?: AnalysisPreset;
|
|
108
|
+
overrides?: Partial<AIFirstConfig>;
|
|
109
|
+
validate?: boolean;
|
|
110
|
+
throwOnError?: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ConfigLoadResult {
|
|
114
|
+
config: AIFirstConfig;
|
|
115
|
+
source: 'default' | 'file' | 'preset' | 'merged';
|
|
116
|
+
validation: ValidationResult;
|
|
117
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { ContentProcessorOptions, ProcessedContent, DetailLevel, InclusionLevel } from './types.js';
|
|
2
|
+
|
|
3
|
+
export function processContent(
|
|
4
|
+
content: string,
|
|
5
|
+
options: ContentProcessorOptions = {}
|
|
6
|
+
): ProcessedContent {
|
|
7
|
+
const {
|
|
8
|
+
inclusionLevel = 'full',
|
|
9
|
+
detailLevel = 'full',
|
|
10
|
+
language = detectLanguage(content),
|
|
11
|
+
preserveComments = true,
|
|
12
|
+
preserveImports = true
|
|
13
|
+
} = options;
|
|
14
|
+
|
|
15
|
+
const originalLength = content.length;
|
|
16
|
+
let processedContent = content;
|
|
17
|
+
|
|
18
|
+
if (inclusionLevel === 'directory') {
|
|
19
|
+
return {
|
|
20
|
+
originalLength,
|
|
21
|
+
processedLength: 0,
|
|
22
|
+
compressionRatio: 100,
|
|
23
|
+
content: '',
|
|
24
|
+
tokens: 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (inclusionLevel === 'exclude') {
|
|
29
|
+
return {
|
|
30
|
+
originalLength,
|
|
31
|
+
processedLength: 0,
|
|
32
|
+
compressionRatio: 100,
|
|
33
|
+
content: '',
|
|
34
|
+
tokens: 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (inclusionLevel === 'compress' || detailLevel !== 'full') {
|
|
39
|
+
processedContent = compressContent(content, detailLevel, language, preserveComments, preserveImports);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const processedLength = processedContent.length;
|
|
43
|
+
const compressionRatio = originalLength > 0
|
|
44
|
+
? ((originalLength - processedLength) / originalLength) * 100
|
|
45
|
+
: 0;
|
|
46
|
+
const tokens = estimateTokens(processedContent);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
originalLength,
|
|
50
|
+
processedLength,
|
|
51
|
+
compressionRatio,
|
|
52
|
+
content: processedContent,
|
|
53
|
+
tokens
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function compressContent(
|
|
58
|
+
content: string,
|
|
59
|
+
detailLevel: DetailLevel,
|
|
60
|
+
language: string,
|
|
61
|
+
preserveComments: boolean,
|
|
62
|
+
preserveImports: boolean
|
|
63
|
+
): string {
|
|
64
|
+
const lines = content.split('\n');
|
|
65
|
+
const result: string[] = [];
|
|
66
|
+
let inFunction = false;
|
|
67
|
+
let braceCount = 0;
|
|
68
|
+
let functionSignature: string | null = null;
|
|
69
|
+
let inComment = false;
|
|
70
|
+
let commentBlock: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < lines.length; i++) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
|
|
76
|
+
if (trimmed.startsWith('/*')) {
|
|
77
|
+
inComment = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (inComment) {
|
|
81
|
+
if (preserveComments && detailLevel !== 'skeleton') {
|
|
82
|
+
commentBlock.push(line);
|
|
83
|
+
}
|
|
84
|
+
if (trimmed.includes('*/')) {
|
|
85
|
+
inComment = false;
|
|
86
|
+
if (commentBlock.length > 0) {
|
|
87
|
+
result.push(...commentBlock);
|
|
88
|
+
commentBlock = [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
|
|
95
|
+
if (preserveComments && detailLevel !== 'skeleton') {
|
|
96
|
+
result.push(line);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (preserveImports && isImportLine(trimmed, language)) {
|
|
102
|
+
result.push(line);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isExportLine(trimmed, language)) {
|
|
107
|
+
if (detailLevel === 'skeleton') {
|
|
108
|
+
const simplified = simplifyExport(line, language);
|
|
109
|
+
if (simplified) result.push(simplified);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isFunctionSignature(line, language)) {
|
|
115
|
+
if (inFunction) {
|
|
116
|
+
braceCount = 0;
|
|
117
|
+
}
|
|
118
|
+
inFunction = true;
|
|
119
|
+
functionSignature = line;
|
|
120
|
+
braceCount += countBraces(line);
|
|
121
|
+
|
|
122
|
+
if (detailLevel === 'signatures' || detailLevel === 'skeleton') {
|
|
123
|
+
const signature = extractSignature(line, language);
|
|
124
|
+
if (signature) result.push(signature);
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (inFunction) {
|
|
130
|
+
braceCount += countBraces(line);
|
|
131
|
+
if (braceCount <= 0) {
|
|
132
|
+
inFunction = false;
|
|
133
|
+
functionSignature = null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (detailLevel === 'full') {
|
|
137
|
+
result.push(line);
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (detailLevel === 'full') {
|
|
143
|
+
result.push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isImportLine(line: string, language: string): boolean {
|
|
151
|
+
const importPatterns = [
|
|
152
|
+
/^import\s+/,
|
|
153
|
+
/^from\s+\w+\s+import/,
|
|
154
|
+
/^require\s*\(/,
|
|
155
|
+
/^using\s+/,
|
|
156
|
+
/^#include/,
|
|
157
|
+
/^#import/,
|
|
158
|
+
/^const\s+\w+\s+=\s+require\s*\(/,
|
|
159
|
+
];
|
|
160
|
+
return importPatterns.some(pattern => pattern.test(line));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isExportLine(line: string, language: string): boolean {
|
|
164
|
+
const exportPatterns = [
|
|
165
|
+
/^export\s+/,
|
|
166
|
+
/^module\.exports\s*=/,
|
|
167
|
+
/^exports\./,
|
|
168
|
+
/^public\s+/,
|
|
169
|
+
];
|
|
170
|
+
return exportPatterns.some(pattern => pattern.test(line));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function simplifyExport(line: string, language: string): string | null {
|
|
174
|
+
const simplified = line.replace(/\{[\s\S]*\}/, '{ ... }');
|
|
175
|
+
return simplified;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isFunctionSignature(line: string, language: string): boolean {
|
|
179
|
+
const patterns = [
|
|
180
|
+
/^(export\s+)?(async\s+)?function\s+\w+\s*\(/,
|
|
181
|
+
/^(export\s+)?(async\s+)?function\s*\(/,
|
|
182
|
+
/^(export\s+)?const\s+\w+\s*=\s*(async\s*)?\(/,
|
|
183
|
+
/^(export\s+)?(async\s+)?\w+\s*\([^)]*\)\s*{/,
|
|
184
|
+
/^(export\s+)?(async\s+)?\w+\s*\([^)]*\)\s*:\s*\w+\s*{/,
|
|
185
|
+
/^(export\s+)?class\s+\w+/,
|
|
186
|
+
/^(export\s+)?interface\s+\w+/,
|
|
187
|
+
/^(export\s+)?type\s+\w+\s*=/,
|
|
188
|
+
/^(public|private|protected)\s+(async\s+)?\w+\s*\(/,
|
|
189
|
+
/^\s*(async\s+)?def\s+\w+\s*\(/,
|
|
190
|
+
/^func\s+\w+\s*\(/,
|
|
191
|
+
];
|
|
192
|
+
return patterns.some(pattern => pattern.test(line));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function extractSignature(line: string, language: string): string | null {
|
|
196
|
+
const signatureMatch = line.match(/^(.*\{)/);
|
|
197
|
+
if (signatureMatch) {
|
|
198
|
+
return signatureMatch[1] + ' ... }';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const arrowMatch = line.match(/^(.*=>).*$/);
|
|
202
|
+
if (arrowMatch) {
|
|
203
|
+
return arrowMatch[1] + ' ...;';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const classMatch = line.match(/^(export\s+)?class\s+\w+([^{]*)(\{)?/);
|
|
207
|
+
if (classMatch) {
|
|
208
|
+
return classMatch[1] || '' + 'class ' + line.match(/class\s+(\w+)/)?.[1] + ' { ... }';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const interfaceMatch = line.match(/^(export\s+)?interface\s+\w+/);
|
|
212
|
+
if (interfaceMatch) {
|
|
213
|
+
return line + ' { ... }';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return line + ';';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function countBraces(line: string): number {
|
|
220
|
+
let count = 0;
|
|
221
|
+
for (const char of line) {
|
|
222
|
+
if (char === '{') count++;
|
|
223
|
+
if (char === '}') count--;
|
|
224
|
+
}
|
|
225
|
+
return count;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function detectLanguage(content: string): string {
|
|
229
|
+
if (content.includes('interface ') || content.includes(': string') || content.includes(': number')) {
|
|
230
|
+
return 'typescript';
|
|
231
|
+
}
|
|
232
|
+
if (content.includes('def ') || content.includes('import ') && content.includes(':')) {
|
|
233
|
+
return 'python';
|
|
234
|
+
}
|
|
235
|
+
if (content.includes('func ') || content.includes('package main')) {
|
|
236
|
+
return 'go';
|
|
237
|
+
}
|
|
238
|
+
if (content.includes('fn ') || content.includes('let mut ')) {
|
|
239
|
+
return 'rust';
|
|
240
|
+
}
|
|
241
|
+
return 'javascript';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function estimateTokens(content: string): number {
|
|
245
|
+
return Math.ceil(content.length / 4);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function classifyFile(
|
|
249
|
+
filePath: string,
|
|
250
|
+
includePatterns: string[],
|
|
251
|
+
compressPatterns: string[],
|
|
252
|
+
directoryPatterns: string[],
|
|
253
|
+
excludePatterns: string[]
|
|
254
|
+
): InclusionLevel {
|
|
255
|
+
if (matchesPatterns(filePath, excludePatterns)) {
|
|
256
|
+
return 'exclude';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (matchesPatterns(filePath, directoryPatterns)) {
|
|
260
|
+
return 'directory';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (matchesPatterns(filePath, compressPatterns)) {
|
|
264
|
+
return 'compress';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (includePatterns.length === 0 || matchesPatterns(filePath, includePatterns)) {
|
|
268
|
+
return 'full';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return 'exclude';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function matchesPatterns(filePath: string, patterns: string[]): boolean {
|
|
275
|
+
if (patterns.length === 0) return false;
|
|
276
|
+
|
|
277
|
+
return patterns.some(pattern => {
|
|
278
|
+
const regex = globToRegex(pattern);
|
|
279
|
+
return regex.test(filePath);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function globToRegex(pattern: string): RegExp {
|
|
284
|
+
let regex = pattern
|
|
285
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
286
|
+
.replace(/\*/g, '[^/]*')
|
|
287
|
+
.replace(/\?/g, '.')
|
|
288
|
+
.replace(/\./g, '\\.')
|
|
289
|
+
.replace('{{GLOBSTAR}}', '.*');
|
|
290
|
+
|
|
291
|
+
return new RegExp(`^${regex}$`);
|
|
292
|
+
}
|