@git.zone/tsdoc 1.5.1 → 1.6.0
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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/aidocs_classes/commit.js +2 -2
- package/dist_ts/context/config-manager.d.ts +22 -1
- package/dist_ts/context/config-manager.js +114 -4
- package/dist_ts/context/context-analyzer.d.ts +73 -0
- package/dist_ts/context/context-analyzer.js +311 -0
- package/dist_ts/context/context-cache.d.ts +73 -0
- package/dist_ts/context/context-cache.js +238 -0
- package/dist_ts/context/context-trimmer.d.ts +8 -0
- package/dist_ts/context/context-trimmer.js +60 -1
- package/dist_ts/context/enhanced-context.d.ts +12 -1
- package/dist_ts/context/enhanced-context.js +201 -33
- package/dist_ts/context/index.d.ts +6 -3
- package/dist_ts/context/index.js +5 -2
- package/dist_ts/context/lazy-file-loader.d.ts +60 -0
- package/dist_ts/context/lazy-file-loader.js +164 -0
- package/dist_ts/context/types.d.ts +143 -0
- package/package.json +12 -13
- package/readme.md +441 -587
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/aidocs_classes/commit.ts +2 -2
- package/ts/context/config-manager.ts +147 -15
- package/ts/context/context-analyzer.ts +391 -0
- package/ts/context/context-cache.ts +285 -0
- package/ts/context/context-trimmer.ts +64 -0
- package/ts/context/enhanced-context.ts +246 -40
- package/ts/context/index.ts +31 -5
- package/ts/context/lazy-file-loader.ts +191 -0
- package/ts/context/types.ts +153 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import type { ICacheEntry, ICacheConfig } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ContextCache provides persistent caching of file contents and token counts
|
|
7
|
+
* with automatic invalidation on file changes
|
|
8
|
+
*/
|
|
9
|
+
export class ContextCache {
|
|
10
|
+
private cacheDir: string;
|
|
11
|
+
private cache: Map<string, ICacheEntry> = new Map();
|
|
12
|
+
private config: Required<ICacheConfig>;
|
|
13
|
+
private cacheIndexPath: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new ContextCache
|
|
17
|
+
* @param projectRoot - Root directory of the project
|
|
18
|
+
* @param config - Cache configuration
|
|
19
|
+
*/
|
|
20
|
+
constructor(projectRoot: string, config: Partial<ICacheConfig> = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
enabled: config.enabled ?? true,
|
|
23
|
+
ttl: config.ttl ?? 3600, // 1 hour default
|
|
24
|
+
maxSize: config.maxSize ?? 100, // 100MB default
|
|
25
|
+
directory: config.directory ?? plugins.path.join(projectRoot, '.nogit', 'context-cache'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.cacheDir = this.config.directory;
|
|
29
|
+
this.cacheIndexPath = plugins.path.join(this.cacheDir, 'index.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the cache by loading from disk
|
|
34
|
+
*/
|
|
35
|
+
public async init(): Promise<void> {
|
|
36
|
+
if (!this.config.enabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Ensure cache directory exists
|
|
41
|
+
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
|
42
|
+
|
|
43
|
+
// Load cache index if it exists
|
|
44
|
+
try {
|
|
45
|
+
const indexExists = await plugins.smartfile.fs.fileExists(this.cacheIndexPath);
|
|
46
|
+
if (indexExists) {
|
|
47
|
+
const indexContent = await plugins.smartfile.fs.toStringSync(this.cacheIndexPath);
|
|
48
|
+
const indexData = JSON.parse(indexContent) as ICacheEntry[];
|
|
49
|
+
if (Array.isArray(indexData)) {
|
|
50
|
+
for (const entry of indexData) {
|
|
51
|
+
this.cache.set(entry.path, entry);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn('Failed to load cache index:', error.message);
|
|
57
|
+
// Start with empty cache if loading fails
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clean up expired and invalid entries
|
|
61
|
+
await this.cleanup();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gets a cached entry if it's still valid
|
|
66
|
+
* @param filePath - Absolute path to the file
|
|
67
|
+
* @returns Cache entry if valid, null otherwise
|
|
68
|
+
*/
|
|
69
|
+
public async get(filePath: string): Promise<ICacheEntry | null> {
|
|
70
|
+
if (!this.config.enabled) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const entry = this.cache.get(filePath);
|
|
75
|
+
if (!entry) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if entry is expired
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
if (now - entry.cachedAt > this.config.ttl * 1000) {
|
|
82
|
+
this.cache.delete(filePath);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if file has been modified
|
|
87
|
+
try {
|
|
88
|
+
const stats = await fs.promises.stat(filePath);
|
|
89
|
+
const currentMtime = Math.floor(stats.mtimeMs);
|
|
90
|
+
|
|
91
|
+
if (currentMtime !== entry.mtime) {
|
|
92
|
+
// File has changed, invalidate cache
|
|
93
|
+
this.cache.delete(filePath);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return entry;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// File doesn't exist anymore
|
|
100
|
+
this.cache.delete(filePath);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stores a cache entry
|
|
107
|
+
* @param entry - Cache entry to store
|
|
108
|
+
*/
|
|
109
|
+
public async set(entry: ICacheEntry): Promise<void> {
|
|
110
|
+
if (!this.config.enabled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.cache.set(entry.path, entry);
|
|
115
|
+
|
|
116
|
+
// Check cache size and evict old entries if needed
|
|
117
|
+
await this.enforceMaxSize();
|
|
118
|
+
|
|
119
|
+
// Persist to disk (async, don't await)
|
|
120
|
+
this.persist().catch((error) => {
|
|
121
|
+
console.warn('Failed to persist cache:', error.message);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stores multiple cache entries
|
|
127
|
+
* @param entries - Array of cache entries
|
|
128
|
+
*/
|
|
129
|
+
public async setMany(entries: ICacheEntry[]): Promise<void> {
|
|
130
|
+
if (!this.config.enabled) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
this.cache.set(entry.path, entry);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await this.enforceMaxSize();
|
|
139
|
+
await this.persist();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks if a file is cached and valid
|
|
144
|
+
* @param filePath - Absolute path to the file
|
|
145
|
+
* @returns True if cached and valid
|
|
146
|
+
*/
|
|
147
|
+
public async has(filePath: string): Promise<boolean> {
|
|
148
|
+
const entry = await this.get(filePath);
|
|
149
|
+
return entry !== null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Gets cache statistics
|
|
154
|
+
*/
|
|
155
|
+
public getStats(): {
|
|
156
|
+
entries: number;
|
|
157
|
+
totalSize: number;
|
|
158
|
+
oldestEntry: number | null;
|
|
159
|
+
newestEntry: number | null;
|
|
160
|
+
} {
|
|
161
|
+
let totalSize = 0;
|
|
162
|
+
let oldestEntry: number | null = null;
|
|
163
|
+
let newestEntry: number | null = null;
|
|
164
|
+
|
|
165
|
+
for (const entry of this.cache.values()) {
|
|
166
|
+
totalSize += entry.contents.length;
|
|
167
|
+
|
|
168
|
+
if (oldestEntry === null || entry.cachedAt < oldestEntry) {
|
|
169
|
+
oldestEntry = entry.cachedAt;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (newestEntry === null || entry.cachedAt > newestEntry) {
|
|
173
|
+
newestEntry = entry.cachedAt;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
entries: this.cache.size,
|
|
179
|
+
totalSize,
|
|
180
|
+
oldestEntry,
|
|
181
|
+
newestEntry,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clears all cache entries
|
|
187
|
+
*/
|
|
188
|
+
public async clear(): Promise<void> {
|
|
189
|
+
this.cache.clear();
|
|
190
|
+
await this.persist();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Clears specific cache entries
|
|
195
|
+
* @param filePaths - Array of file paths to clear
|
|
196
|
+
*/
|
|
197
|
+
public async clearPaths(filePaths: string[]): Promise<void> {
|
|
198
|
+
for (const path of filePaths) {
|
|
199
|
+
this.cache.delete(path);
|
|
200
|
+
}
|
|
201
|
+
await this.persist();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Cleans up expired and invalid cache entries
|
|
206
|
+
*/
|
|
207
|
+
private async cleanup(): Promise<void> {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
const toDelete: string[] = [];
|
|
210
|
+
|
|
211
|
+
for (const [path, entry] of this.cache.entries()) {
|
|
212
|
+
// Check expiration
|
|
213
|
+
if (now - entry.cachedAt > this.config.ttl * 1000) {
|
|
214
|
+
toDelete.push(path);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if file still exists and hasn't changed
|
|
219
|
+
try {
|
|
220
|
+
const stats = await fs.promises.stat(path);
|
|
221
|
+
const currentMtime = Math.floor(stats.mtimeMs);
|
|
222
|
+
|
|
223
|
+
if (currentMtime !== entry.mtime) {
|
|
224
|
+
toDelete.push(path);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// File doesn't exist
|
|
228
|
+
toDelete.push(path);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (const path of toDelete) {
|
|
233
|
+
this.cache.delete(path);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (toDelete.length > 0) {
|
|
237
|
+
await this.persist();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Enforces maximum cache size by evicting oldest entries
|
|
243
|
+
*/
|
|
244
|
+
private async enforceMaxSize(): Promise<void> {
|
|
245
|
+
const stats = this.getStats();
|
|
246
|
+
const maxSizeBytes = this.config.maxSize * 1024 * 1024; // Convert MB to bytes
|
|
247
|
+
|
|
248
|
+
if (stats.totalSize <= maxSizeBytes) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Sort entries by age (oldest first)
|
|
253
|
+
const entries = Array.from(this.cache.entries()).sort(
|
|
254
|
+
(a, b) => a[1].cachedAt - b[1].cachedAt
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Remove oldest entries until we're under the limit
|
|
258
|
+
let currentSize = stats.totalSize;
|
|
259
|
+
for (const [path, entry] of entries) {
|
|
260
|
+
if (currentSize <= maxSizeBytes) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
currentSize -= entry.contents.length;
|
|
265
|
+
this.cache.delete(path);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Persists cache index to disk
|
|
271
|
+
*/
|
|
272
|
+
private async persist(): Promise<void> {
|
|
273
|
+
if (!this.config.enabled) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const entries = Array.from(this.cache.values());
|
|
279
|
+
const content = JSON.stringify(entries, null, 2);
|
|
280
|
+
await plugins.smartfile.memory.toFs(content, this.cacheIndexPath);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.warn('Failed to persist cache index:', error.message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -243,4 +243,68 @@ export class ContextTrimmer {
|
|
|
243
243
|
...config
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Trim a file based on its importance tier
|
|
249
|
+
* @param filePath The path to the file
|
|
250
|
+
* @param content The file's contents
|
|
251
|
+
* @param level The trimming level to apply ('none', 'light', 'aggressive')
|
|
252
|
+
* @returns The trimmed file contents
|
|
253
|
+
*/
|
|
254
|
+
public trimFileWithLevel(
|
|
255
|
+
filePath: string,
|
|
256
|
+
content: string,
|
|
257
|
+
level: 'none' | 'light' | 'aggressive'
|
|
258
|
+
): string {
|
|
259
|
+
// No trimming for essential files
|
|
260
|
+
if (level === 'none') {
|
|
261
|
+
return content;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create a temporary config based on level
|
|
265
|
+
const originalConfig = { ...this.config };
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
if (level === 'light') {
|
|
269
|
+
// Light trimming: preserve signatures, remove only complex implementations
|
|
270
|
+
this.config = {
|
|
271
|
+
...this.config,
|
|
272
|
+
removeImplementations: false,
|
|
273
|
+
preserveInterfaces: true,
|
|
274
|
+
preserveTypeDefs: true,
|
|
275
|
+
preserveJSDoc: true,
|
|
276
|
+
maxFunctionLines: 10,
|
|
277
|
+
removeComments: false,
|
|
278
|
+
removeBlankLines: true
|
|
279
|
+
};
|
|
280
|
+
} else if (level === 'aggressive') {
|
|
281
|
+
// Aggressive trimming: remove all implementations, keep only signatures
|
|
282
|
+
this.config = {
|
|
283
|
+
...this.config,
|
|
284
|
+
removeImplementations: true,
|
|
285
|
+
preserveInterfaces: true,
|
|
286
|
+
preserveTypeDefs: true,
|
|
287
|
+
preserveJSDoc: true,
|
|
288
|
+
maxFunctionLines: 3,
|
|
289
|
+
removeComments: true,
|
|
290
|
+
removeBlankLines: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Process based on file type
|
|
295
|
+
let result = content;
|
|
296
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
|
|
297
|
+
result = this.trimTypeScriptFile(content);
|
|
298
|
+
} else if (filePath.endsWith('.md')) {
|
|
299
|
+
result = this.trimMarkdownFile(content);
|
|
300
|
+
} else if (filePath.endsWith('.json')) {
|
|
301
|
+
result = this.trimJsonFile(content);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return result;
|
|
305
|
+
} finally {
|
|
306
|
+
// Restore original config
|
|
307
|
+
this.config = originalConfig;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
246
310
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import type { ContextMode, IContextResult, IFileInfo, TaskType } from './types.js';
|
|
2
|
+
import type { ContextMode, IContextResult, IFileInfo, TaskType, IFileMetadata } from './types.js';
|
|
3
3
|
import { ContextTrimmer } from './context-trimmer.js';
|
|
4
4
|
import { ConfigManager } from './config-manager.js';
|
|
5
|
+
import { LazyFileLoader } from './lazy-file-loader.js';
|
|
6
|
+
import { ContextCache } from './context-cache.js';
|
|
7
|
+
import { ContextAnalyzer } from './context-analyzer.js';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Enhanced ProjectContext that supports context optimization strategies
|
|
@@ -10,6 +13,9 @@ export class EnhancedContext {
|
|
|
10
13
|
private projectDir: string;
|
|
11
14
|
private trimmer: ContextTrimmer;
|
|
12
15
|
private configManager: ConfigManager;
|
|
16
|
+
private lazyLoader: LazyFileLoader;
|
|
17
|
+
private cache: ContextCache;
|
|
18
|
+
private analyzer: ContextAnalyzer;
|
|
13
19
|
private contextMode: ContextMode = 'trimmed';
|
|
14
20
|
private tokenBudget: number = 190000; // Default for o4-mini
|
|
15
21
|
private contextResult: IContextResult = {
|
|
@@ -29,6 +35,13 @@ export class EnhancedContext {
|
|
|
29
35
|
this.projectDir = projectDirArg;
|
|
30
36
|
this.configManager = ConfigManager.getInstance();
|
|
31
37
|
this.trimmer = new ContextTrimmer(this.configManager.getTrimConfig());
|
|
38
|
+
this.lazyLoader = new LazyFileLoader(projectDirArg);
|
|
39
|
+
this.cache = new ContextCache(projectDirArg, this.configManager.getCacheConfig());
|
|
40
|
+
this.analyzer = new ContextAnalyzer(
|
|
41
|
+
projectDirArg,
|
|
42
|
+
this.configManager.getPrioritizationWeights(),
|
|
43
|
+
this.configManager.getTierConfig()
|
|
44
|
+
);
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
/**
|
|
@@ -38,6 +51,7 @@ export class EnhancedContext {
|
|
|
38
51
|
await this.configManager.initialize(this.projectDir);
|
|
39
52
|
this.tokenBudget = this.configManager.getMaxTokens();
|
|
40
53
|
this.trimmer.updateConfig(this.configManager.getTrimConfig());
|
|
54
|
+
await this.cache.init();
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/**
|
|
@@ -138,13 +152,28 @@ export class EnhancedContext {
|
|
|
138
152
|
|
|
139
153
|
let totalTokenCount = 0;
|
|
140
154
|
let totalOriginalTokens = 0;
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
|
|
156
|
+
// Convert SmartFile objects to IFileMetadata for analysis
|
|
157
|
+
const metadata: IFileMetadata[] = files.map(sf => ({
|
|
158
|
+
path: sf.path,
|
|
159
|
+
relativePath: sf.relative,
|
|
160
|
+
size: sf.contents.toString().length,
|
|
161
|
+
mtime: Date.now(), // SmartFile doesn't expose mtime, use current time
|
|
162
|
+
estimatedTokens: this.countTokens(sf.contents.toString()),
|
|
163
|
+
importanceScore: 0
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
// Analyze files using ContextAnalyzer to get smart prioritization
|
|
167
|
+
// (Note: This requires task type which we'll pass from buildContext)
|
|
168
|
+
// For now, sort files by estimated tokens (smaller files first for better efficiency)
|
|
169
|
+
const sortedFiles = [...files].sort((a, b) => {
|
|
170
|
+
const aTokens = this.countTokens(a.contents.toString());
|
|
171
|
+
const bTokens = this.countTokens(b.contents.toString());
|
|
172
|
+
return aTokens - bTokens;
|
|
173
|
+
});
|
|
174
|
+
|
|
146
175
|
const processedFiles: string[] = [];
|
|
147
|
-
|
|
176
|
+
|
|
148
177
|
for (const smartfile of sortedFiles) {
|
|
149
178
|
// Calculate original token count
|
|
150
179
|
const originalContent = smartfile.contents.toString();
|
|
@@ -215,6 +244,154 @@ ${processedContent}
|
|
|
215
244
|
return context;
|
|
216
245
|
}
|
|
217
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Convert files to context with smart analysis and prioritization
|
|
249
|
+
* @param metadata - File metadata to analyze
|
|
250
|
+
* @param taskType - Task type for context-aware prioritization
|
|
251
|
+
* @param mode - Context mode to use
|
|
252
|
+
* @returns Context string
|
|
253
|
+
*/
|
|
254
|
+
public async convertFilesToContextWithAnalysis(
|
|
255
|
+
metadata: IFileMetadata[],
|
|
256
|
+
taskType: TaskType,
|
|
257
|
+
mode: ContextMode = this.contextMode
|
|
258
|
+
): Promise<string> {
|
|
259
|
+
// Reset context result
|
|
260
|
+
this.contextResult = {
|
|
261
|
+
context: '',
|
|
262
|
+
tokenCount: 0,
|
|
263
|
+
includedFiles: [],
|
|
264
|
+
trimmedFiles: [],
|
|
265
|
+
excludedFiles: [],
|
|
266
|
+
tokenSavings: 0
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Analyze files for smart prioritization
|
|
270
|
+
const analysis = await this.analyzer.analyze(metadata, taskType, []);
|
|
271
|
+
|
|
272
|
+
// Sort files by importance score (highest first)
|
|
273
|
+
const sortedAnalysis = [...analysis.files].sort(
|
|
274
|
+
(a, b) => b.importanceScore - a.importanceScore
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Filter out excluded tier
|
|
278
|
+
const relevantFiles = sortedAnalysis.filter(f => f.tier !== 'excluded');
|
|
279
|
+
|
|
280
|
+
let totalTokenCount = 0;
|
|
281
|
+
let totalOriginalTokens = 0;
|
|
282
|
+
const processedFiles: string[] = [];
|
|
283
|
+
|
|
284
|
+
// Load files with cache support
|
|
285
|
+
for (const fileAnalysis of relevantFiles) {
|
|
286
|
+
try {
|
|
287
|
+
// Check cache first
|
|
288
|
+
let contents: string;
|
|
289
|
+
let originalTokenCount: number;
|
|
290
|
+
|
|
291
|
+
const cached = await this.cache.get(fileAnalysis.path);
|
|
292
|
+
if (cached) {
|
|
293
|
+
contents = cached.contents;
|
|
294
|
+
originalTokenCount = cached.tokenCount;
|
|
295
|
+
} else {
|
|
296
|
+
// Load file
|
|
297
|
+
const fileData = await plugins.smartfile.fs.toStringSync(fileAnalysis.path);
|
|
298
|
+
contents = fileData;
|
|
299
|
+
originalTokenCount = this.countTokens(contents);
|
|
300
|
+
|
|
301
|
+
// Cache it
|
|
302
|
+
await this.cache.set({
|
|
303
|
+
path: fileAnalysis.path,
|
|
304
|
+
contents,
|
|
305
|
+
tokenCount: originalTokenCount,
|
|
306
|
+
mtime: Date.now(),
|
|
307
|
+
cachedAt: Date.now()
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
totalOriginalTokens += originalTokenCount;
|
|
312
|
+
|
|
313
|
+
// Apply tier-based trimming
|
|
314
|
+
let processedContent = contents;
|
|
315
|
+
let trimLevel: 'none' | 'light' | 'aggressive' = 'light';
|
|
316
|
+
|
|
317
|
+
if (fileAnalysis.tier === 'essential') {
|
|
318
|
+
trimLevel = 'none';
|
|
319
|
+
} else if (fileAnalysis.tier === 'important') {
|
|
320
|
+
trimLevel = 'light';
|
|
321
|
+
} else if (fileAnalysis.tier === 'optional') {
|
|
322
|
+
trimLevel = 'aggressive';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Apply trimming based on mode and tier
|
|
326
|
+
if (mode !== 'full' && trimLevel !== 'none') {
|
|
327
|
+
const relativePath = plugins.path.relative(this.projectDir, fileAnalysis.path);
|
|
328
|
+
processedContent = this.trimmer.trimFileWithLevel(
|
|
329
|
+
relativePath,
|
|
330
|
+
contents,
|
|
331
|
+
trimLevel
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Calculate token count
|
|
336
|
+
const processedTokenCount = this.countTokens(processedContent);
|
|
337
|
+
|
|
338
|
+
// Check token budget
|
|
339
|
+
if (totalTokenCount + processedTokenCount > this.tokenBudget) {
|
|
340
|
+
// We don't have budget for this file
|
|
341
|
+
const relativePath = plugins.path.relative(this.projectDir, fileAnalysis.path);
|
|
342
|
+
this.contextResult.excludedFiles.push({
|
|
343
|
+
path: fileAnalysis.path,
|
|
344
|
+
contents,
|
|
345
|
+
relativePath,
|
|
346
|
+
tokenCount: originalTokenCount,
|
|
347
|
+
importanceScore: fileAnalysis.importanceScore
|
|
348
|
+
});
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Format the file for context
|
|
353
|
+
const relativePath = plugins.path.relative(this.projectDir, fileAnalysis.path);
|
|
354
|
+
const formattedContent = `
|
|
355
|
+
====== START OF FILE ${relativePath} ======
|
|
356
|
+
|
|
357
|
+
${processedContent}
|
|
358
|
+
|
|
359
|
+
====== END OF FILE ${relativePath} ======
|
|
360
|
+
`;
|
|
361
|
+
|
|
362
|
+
processedFiles.push(formattedContent);
|
|
363
|
+
totalTokenCount += processedTokenCount;
|
|
364
|
+
|
|
365
|
+
// Track file in appropriate list
|
|
366
|
+
const fileInfo: IFileInfo = {
|
|
367
|
+
path: fileAnalysis.path,
|
|
368
|
+
contents: processedContent,
|
|
369
|
+
relativePath,
|
|
370
|
+
tokenCount: processedTokenCount,
|
|
371
|
+
importanceScore: fileAnalysis.importanceScore
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
if (trimLevel === 'none' || processedContent === contents) {
|
|
375
|
+
this.contextResult.includedFiles.push(fileInfo);
|
|
376
|
+
} else {
|
|
377
|
+
this.contextResult.trimmedFiles.push(fileInfo);
|
|
378
|
+
this.contextResult.tokenSavings += (originalTokenCount - processedTokenCount);
|
|
379
|
+
}
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.warn(`Failed to process file ${fileAnalysis.path}:`, error.message);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Join all processed files
|
|
386
|
+
const context = processedFiles.join('\n');
|
|
387
|
+
|
|
388
|
+
// Update context result
|
|
389
|
+
this.contextResult.context = context;
|
|
390
|
+
this.contextResult.tokenCount = totalTokenCount;
|
|
391
|
+
|
|
392
|
+
return context;
|
|
393
|
+
}
|
|
394
|
+
|
|
218
395
|
/**
|
|
219
396
|
* Build context for the project
|
|
220
397
|
* @param taskType Optional task type for task-specific context
|
|
@@ -233,42 +410,71 @@ ${processedContent}
|
|
|
233
410
|
}
|
|
234
411
|
}
|
|
235
412
|
|
|
236
|
-
//
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
413
|
+
// Check if analyzer is enabled in config
|
|
414
|
+
const analyzerConfig = this.configManager.getAnalyzerConfig();
|
|
415
|
+
const useAnalyzer = analyzerConfig.enabled && taskType;
|
|
416
|
+
|
|
417
|
+
if (useAnalyzer) {
|
|
418
|
+
// Use new smart context building with lazy loading and analysis
|
|
419
|
+
const taskConfig = this.configManager.getTaskConfig(taskType!);
|
|
420
|
+
|
|
421
|
+
// Build globs for scanning
|
|
422
|
+
const includeGlobs = taskConfig?.includePaths?.map(p => `${p}/**/*.ts`) || [
|
|
423
|
+
'ts/**/*.ts',
|
|
424
|
+
'ts*/**/*.ts'
|
|
425
|
+
];
|
|
426
|
+
|
|
427
|
+
// Add config files
|
|
428
|
+
const configGlobs = [
|
|
429
|
+
'package.json',
|
|
430
|
+
'readme.md',
|
|
431
|
+
'readme.hints.md',
|
|
432
|
+
'npmextra.json'
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
// Scan files for metadata (fast, doesn't load contents)
|
|
436
|
+
const metadata = await this.lazyLoader.scanFiles([...configGlobs, ...includeGlobs]);
|
|
437
|
+
|
|
438
|
+
// Use analyzer to build context with smart prioritization
|
|
439
|
+
await this.convertFilesToContextWithAnalysis(metadata, taskType!, this.contextMode);
|
|
440
|
+
} else {
|
|
441
|
+
// Fall back to old method for backward compatibility
|
|
442
|
+
const taskConfig = taskType ? this.configManager.getTaskConfig(taskType) : undefined;
|
|
443
|
+
const files = await this.gatherFiles(
|
|
444
|
+
taskConfig?.includePaths,
|
|
445
|
+
taskConfig?.excludePaths
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Convert files to context
|
|
449
|
+
// Create an array of all files to process
|
|
450
|
+
const allFiles: plugins.smartfile.SmartFile[] = [];
|
|
451
|
+
|
|
452
|
+
// Add individual files
|
|
453
|
+
if (files.smartfilePackageJSON) allFiles.push(files.smartfilePackageJSON as plugins.smartfile.SmartFile);
|
|
454
|
+
if (files.smartfilesReadme) allFiles.push(files.smartfilesReadme as plugins.smartfile.SmartFile);
|
|
455
|
+
if (files.smartfilesReadmeHints) allFiles.push(files.smartfilesReadmeHints as plugins.smartfile.SmartFile);
|
|
456
|
+
if (files.smartfilesNpmextraJSON) allFiles.push(files.smartfilesNpmextraJSON as plugins.smartfile.SmartFile);
|
|
457
|
+
|
|
458
|
+
// Add arrays of files
|
|
459
|
+
if (files.smartfilesMod) {
|
|
460
|
+
if (Array.isArray(files.smartfilesMod)) {
|
|
461
|
+
allFiles.push(...files.smartfilesMod);
|
|
462
|
+
} else {
|
|
463
|
+
allFiles.push(files.smartfilesMod);
|
|
464
|
+
}
|
|
259
465
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
466
|
+
|
|
467
|
+
if (files.smartfilesTest) {
|
|
468
|
+
if (Array.isArray(files.smartfilesTest)) {
|
|
469
|
+
allFiles.push(...files.smartfilesTest);
|
|
470
|
+
} else {
|
|
471
|
+
allFiles.push(files.smartfilesTest);
|
|
472
|
+
}
|
|
267
473
|
}
|
|
474
|
+
|
|
475
|
+
await this.convertFilesToContext(allFiles);
|
|
268
476
|
}
|
|
269
|
-
|
|
270
|
-
const context = await this.convertFilesToContext(allFiles);
|
|
271
|
-
|
|
477
|
+
|
|
272
478
|
return this.contextResult;
|
|
273
479
|
}
|
|
274
480
|
|