@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.
@@ -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
- // Sort files by importance (for now just a simple alphabetical sort)
143
- // Later this could be enhanced with more sophisticated prioritization
144
- const sortedFiles = [...files].sort((a, b) => a.relative.localeCompare(b.relative));
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
- // Gather files
237
- const taskConfig = taskType ? this.configManager.getTaskConfig(taskType) : undefined;
238
- const files = await this.gatherFiles(
239
- taskConfig?.includePaths,
240
- taskConfig?.excludePaths
241
- );
242
-
243
- // Convert files to context
244
- // Create an array of all files to process
245
- const allFiles: plugins.smartfile.SmartFile[] = [];
246
-
247
- // Add individual files
248
- if (files.smartfilePackageJSON) allFiles.push(files.smartfilePackageJSON as plugins.smartfile.SmartFile);
249
- if (files.smartfilesReadme) allFiles.push(files.smartfilesReadme as plugins.smartfile.SmartFile);
250
- if (files.smartfilesReadmeHints) allFiles.push(files.smartfilesReadmeHints as plugins.smartfile.SmartFile);
251
- if (files.smartfilesNpmextraJSON) allFiles.push(files.smartfilesNpmextraJSON as plugins.smartfile.SmartFile);
252
-
253
- // Add arrays of files
254
- if (files.smartfilesMod) {
255
- if (Array.isArray(files.smartfilesMod)) {
256
- allFiles.push(...files.smartfilesMod);
257
- } else {
258
- allFiles.push(files.smartfilesMod);
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
- if (files.smartfilesTest) {
263
- if (Array.isArray(files.smartfilesTest)) {
264
- allFiles.push(...files.smartfilesTest);
265
- } else {
266
- allFiles.push(files.smartfilesTest);
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