@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
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@git.zone/tsdoc',
|
|
6
|
-
version: '1.
|
|
6
|
+
version: '1.6.0',
|
|
7
7
|
description: 'A comprehensive TypeScript documentation tool that leverages AI to generate and enhance project documentation, including dynamic README creation, API docs via TypeDoc, and smart commit message generation.'
|
|
8
8
|
}
|
|
@@ -77,8 +77,8 @@ interface {
|
|
|
77
77
|
For the recommendedNextVersionDetails, please only add a detail entries to the array if it has an obvious value to the reader.
|
|
78
78
|
|
|
79
79
|
You are being given the files of the project. You should use them to create the commit message.
|
|
80
|
-
Also you are given a diff
|
|
81
|
-
|
|
80
|
+
Also you are given a diff.
|
|
81
|
+
Never mention CLAUDE code, or codex.
|
|
82
82
|
`,
|
|
83
83
|
messageHistory: [],
|
|
84
84
|
userMessage: contextString,
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import type {
|
|
4
|
+
IContextConfig,
|
|
5
|
+
ITrimConfig,
|
|
6
|
+
ITaskConfig,
|
|
7
|
+
TaskType,
|
|
8
|
+
ContextMode,
|
|
9
|
+
ICacheConfig,
|
|
10
|
+
IAnalyzerConfig,
|
|
11
|
+
IPrioritizationWeights,
|
|
12
|
+
ITierConfig
|
|
13
|
+
} from './types.js';
|
|
3
14
|
|
|
4
15
|
/**
|
|
5
16
|
* Manages configuration for context building
|
|
@@ -8,6 +19,7 @@ export class ConfigManager {
|
|
|
8
19
|
private static instance: ConfigManager;
|
|
9
20
|
private config: IContextConfig;
|
|
10
21
|
private projectDir: string = '';
|
|
22
|
+
private configCache: { mtime: number; config: IContextConfig } | null = null;
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
25
|
* Get the singleton instance of ConfigManager
|
|
@@ -65,6 +77,28 @@ export class ConfigManager {
|
|
|
65
77
|
maxFunctionLines: 5,
|
|
66
78
|
removeComments: true,
|
|
67
79
|
removeBlankLines: true
|
|
80
|
+
},
|
|
81
|
+
cache: {
|
|
82
|
+
enabled: true,
|
|
83
|
+
ttl: 3600, // 1 hour
|
|
84
|
+
maxSize: 100, // 100MB
|
|
85
|
+
directory: undefined // Will be set to .nogit/context-cache by ContextCache
|
|
86
|
+
},
|
|
87
|
+
analyzer: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
useAIRefinement: false, // Disabled by default for now
|
|
90
|
+
aiModel: 'haiku'
|
|
91
|
+
},
|
|
92
|
+
prioritization: {
|
|
93
|
+
dependencyWeight: 0.3,
|
|
94
|
+
relevanceWeight: 0.4,
|
|
95
|
+
efficiencyWeight: 0.2,
|
|
96
|
+
recencyWeight: 0.1
|
|
97
|
+
},
|
|
98
|
+
tiers: {
|
|
99
|
+
essential: { minScore: 0.8, trimLevel: 'none' },
|
|
100
|
+
important: { minScore: 0.5, trimLevel: 'light' },
|
|
101
|
+
optional: { minScore: 0.2, trimLevel: 'aggressive' }
|
|
68
102
|
}
|
|
69
103
|
};
|
|
70
104
|
}
|
|
@@ -77,21 +111,40 @@ export class ConfigManager {
|
|
|
77
111
|
if (!this.projectDir) {
|
|
78
112
|
return;
|
|
79
113
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
114
|
+
|
|
115
|
+
const npmextraJsonPath = plugins.path.join(this.projectDir, 'npmextra.json');
|
|
116
|
+
|
|
117
|
+
// Check if file exists
|
|
118
|
+
const fileExists = await plugins.smartfile.fs.fileExists(npmextraJsonPath);
|
|
119
|
+
if (!fileExists) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check cache
|
|
124
|
+
const stats = await fs.promises.stat(npmextraJsonPath);
|
|
125
|
+
const currentMtime = Math.floor(stats.mtimeMs);
|
|
126
|
+
|
|
127
|
+
if (this.configCache && this.configCache.mtime === currentMtime) {
|
|
128
|
+
// Use cached config
|
|
129
|
+
this.config = this.configCache.config;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
84
133
|
// Read the npmextra.json file
|
|
85
|
-
const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath(
|
|
86
|
-
plugins.path.join(this.projectDir, 'npmextra.json')
|
|
87
|
-
);
|
|
134
|
+
const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath(npmextraJsonPath);
|
|
88
135
|
const npmextraContent = JSON.parse(npmextraJsonFile.contents.toString());
|
|
89
|
-
|
|
136
|
+
|
|
90
137
|
// Check for tsdoc context configuration
|
|
91
138
|
if (npmextraContent?.tsdoc?.context) {
|
|
92
139
|
// Merge with default config
|
|
93
140
|
this.config = this.mergeConfigs(this.config, npmextraContent.tsdoc.context);
|
|
94
141
|
}
|
|
142
|
+
|
|
143
|
+
// Cache the config
|
|
144
|
+
this.configCache = {
|
|
145
|
+
mtime: currentMtime,
|
|
146
|
+
config: { ...this.config }
|
|
147
|
+
};
|
|
95
148
|
} catch (error) {
|
|
96
149
|
console.error('Error loading context configuration:', error);
|
|
97
150
|
}
|
|
@@ -131,7 +184,39 @@ export class ConfigManager {
|
|
|
131
184
|
...userConfig.trimming
|
|
132
185
|
};
|
|
133
186
|
}
|
|
134
|
-
|
|
187
|
+
|
|
188
|
+
// Merge cache configuration
|
|
189
|
+
if (userConfig.cache) {
|
|
190
|
+
result.cache = {
|
|
191
|
+
...result.cache,
|
|
192
|
+
...userConfig.cache
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Merge analyzer configuration
|
|
197
|
+
if (userConfig.analyzer) {
|
|
198
|
+
result.analyzer = {
|
|
199
|
+
...result.analyzer,
|
|
200
|
+
...userConfig.analyzer
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Merge prioritization weights
|
|
205
|
+
if (userConfig.prioritization) {
|
|
206
|
+
result.prioritization = {
|
|
207
|
+
...result.prioritization,
|
|
208
|
+
...userConfig.prioritization
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Merge tier configuration
|
|
213
|
+
if (userConfig.tiers) {
|
|
214
|
+
result.tiers = {
|
|
215
|
+
...result.tiers,
|
|
216
|
+
...userConfig.tiers
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
135
220
|
return result;
|
|
136
221
|
}
|
|
137
222
|
|
|
@@ -179,26 +264,29 @@ export class ConfigManager {
|
|
|
179
264
|
public async updateConfig(config: Partial<IContextConfig>): Promise<void> {
|
|
180
265
|
// Merge with existing config
|
|
181
266
|
this.config = this.mergeConfigs(this.config, config);
|
|
182
|
-
|
|
267
|
+
|
|
268
|
+
// Invalidate cache
|
|
269
|
+
this.configCache = null;
|
|
270
|
+
|
|
183
271
|
try {
|
|
184
272
|
if (!this.projectDir) {
|
|
185
273
|
return;
|
|
186
274
|
}
|
|
187
|
-
|
|
275
|
+
|
|
188
276
|
// Read the existing npmextra.json file
|
|
189
277
|
const npmextraJsonPath = plugins.path.join(this.projectDir, 'npmextra.json');
|
|
190
278
|
let npmextraContent = {};
|
|
191
|
-
|
|
279
|
+
|
|
192
280
|
if (await plugins.smartfile.fs.fileExists(npmextraJsonPath)) {
|
|
193
281
|
const npmextraJsonFile = await plugins.smartfile.SmartFile.fromFilePath(npmextraJsonPath);
|
|
194
282
|
npmextraContent = JSON.parse(npmextraJsonFile.contents.toString()) || {};
|
|
195
283
|
}
|
|
196
|
-
|
|
284
|
+
|
|
197
285
|
// Update the tsdoc context configuration
|
|
198
286
|
const typedContent = npmextraContent as any;
|
|
199
287
|
if (!typedContent.tsdoc) typedContent.tsdoc = {};
|
|
200
288
|
typedContent.tsdoc.context = this.config;
|
|
201
|
-
|
|
289
|
+
|
|
202
290
|
// Write back to npmextra.json
|
|
203
291
|
const updatedContent = JSON.stringify(npmextraContent, null, 2);
|
|
204
292
|
await plugins.smartfile.memory.toFs(updatedContent, npmextraJsonPath);
|
|
@@ -206,4 +294,48 @@ export class ConfigManager {
|
|
|
206
294
|
console.error('Error updating context configuration:', error);
|
|
207
295
|
}
|
|
208
296
|
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get cache configuration
|
|
300
|
+
*/
|
|
301
|
+
public getCacheConfig(): ICacheConfig {
|
|
302
|
+
return this.config.cache || { enabled: true, ttl: 3600, maxSize: 100 };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get analyzer configuration
|
|
307
|
+
*/
|
|
308
|
+
public getAnalyzerConfig(): IAnalyzerConfig {
|
|
309
|
+
return this.config.analyzer || { enabled: true, useAIRefinement: false, aiModel: 'haiku' };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get prioritization weights
|
|
314
|
+
*/
|
|
315
|
+
public getPrioritizationWeights(): IPrioritizationWeights {
|
|
316
|
+
return this.config.prioritization || {
|
|
317
|
+
dependencyWeight: 0.3,
|
|
318
|
+
relevanceWeight: 0.4,
|
|
319
|
+
efficiencyWeight: 0.2,
|
|
320
|
+
recencyWeight: 0.1
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Get tier configuration
|
|
326
|
+
*/
|
|
327
|
+
public getTierConfig(): ITierConfig {
|
|
328
|
+
return this.config.tiers || {
|
|
329
|
+
essential: { minScore: 0.8, trimLevel: 'none' },
|
|
330
|
+
important: { minScore: 0.5, trimLevel: 'light' },
|
|
331
|
+
optional: { minScore: 0.2, trimLevel: 'aggressive' }
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Clear the config cache (force reload on next access)
|
|
337
|
+
*/
|
|
338
|
+
public clearCache(): void {
|
|
339
|
+
this.configCache = null;
|
|
340
|
+
}
|
|
209
341
|
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type {
|
|
3
|
+
IFileMetadata,
|
|
4
|
+
IFileDependencies,
|
|
5
|
+
IFileAnalysis,
|
|
6
|
+
IAnalysisResult,
|
|
7
|
+
TaskType,
|
|
8
|
+
IPrioritizationWeights,
|
|
9
|
+
ITierConfig,
|
|
10
|
+
} from './types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ContextAnalyzer provides intelligent file selection and prioritization
|
|
14
|
+
* based on dependency analysis, task relevance, and configurable weights
|
|
15
|
+
*/
|
|
16
|
+
export class ContextAnalyzer {
|
|
17
|
+
private projectRoot: string;
|
|
18
|
+
private weights: Required<IPrioritizationWeights>;
|
|
19
|
+
private tiers: Required<ITierConfig>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new ContextAnalyzer
|
|
23
|
+
* @param projectRoot - Root directory of the project
|
|
24
|
+
* @param weights - Prioritization weights
|
|
25
|
+
* @param tiers - Tier configuration
|
|
26
|
+
*/
|
|
27
|
+
constructor(
|
|
28
|
+
projectRoot: string,
|
|
29
|
+
weights: Partial<IPrioritizationWeights> = {},
|
|
30
|
+
tiers: Partial<ITierConfig> = {}
|
|
31
|
+
) {
|
|
32
|
+
this.projectRoot = projectRoot;
|
|
33
|
+
|
|
34
|
+
// Default weights
|
|
35
|
+
this.weights = {
|
|
36
|
+
dependencyWeight: weights.dependencyWeight ?? 0.3,
|
|
37
|
+
relevanceWeight: weights.relevanceWeight ?? 0.4,
|
|
38
|
+
efficiencyWeight: weights.efficiencyWeight ?? 0.2,
|
|
39
|
+
recencyWeight: weights.recencyWeight ?? 0.1,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Default tiers
|
|
43
|
+
this.tiers = {
|
|
44
|
+
essential: tiers.essential ?? { minScore: 0.8, trimLevel: 'none' },
|
|
45
|
+
important: tiers.important ?? { minScore: 0.5, trimLevel: 'light' },
|
|
46
|
+
optional: tiers.optional ?? { minScore: 0.2, trimLevel: 'aggressive' },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Analyzes files for a specific task type
|
|
52
|
+
* @param metadata - Array of file metadata to analyze
|
|
53
|
+
* @param taskType - Type of task being performed
|
|
54
|
+
* @param changedFiles - Optional list of recently changed files (for commits)
|
|
55
|
+
* @returns Analysis result with scored files
|
|
56
|
+
*/
|
|
57
|
+
public async analyze(
|
|
58
|
+
metadata: IFileMetadata[],
|
|
59
|
+
taskType: TaskType,
|
|
60
|
+
changedFiles: string[] = []
|
|
61
|
+
): Promise<IAnalysisResult> {
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
|
|
64
|
+
// Build dependency graph
|
|
65
|
+
const dependencyGraph = await this.buildDependencyGraph(metadata);
|
|
66
|
+
|
|
67
|
+
// Calculate centrality scores
|
|
68
|
+
this.calculateCentrality(dependencyGraph);
|
|
69
|
+
|
|
70
|
+
// Analyze each file
|
|
71
|
+
const files: IFileAnalysis[] = [];
|
|
72
|
+
for (const meta of metadata) {
|
|
73
|
+
const analysis = await this.analyzeFile(
|
|
74
|
+
meta,
|
|
75
|
+
taskType,
|
|
76
|
+
dependencyGraph,
|
|
77
|
+
changedFiles
|
|
78
|
+
);
|
|
79
|
+
files.push(analysis);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sort by importance score (highest first)
|
|
83
|
+
files.sort((a, b) => b.importanceScore - a.importanceScore);
|
|
84
|
+
|
|
85
|
+
const analysisDuration = Date.now() - startTime;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
taskType,
|
|
89
|
+
files,
|
|
90
|
+
dependencyGraph,
|
|
91
|
+
totalFiles: metadata.length,
|
|
92
|
+
analysisDuration,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds a dependency graph from file metadata
|
|
98
|
+
* @param metadata - Array of file metadata
|
|
99
|
+
* @returns Dependency graph as a map
|
|
100
|
+
*/
|
|
101
|
+
private async buildDependencyGraph(
|
|
102
|
+
metadata: IFileMetadata[]
|
|
103
|
+
): Promise<Map<string, IFileDependencies>> {
|
|
104
|
+
const graph = new Map<string, IFileDependencies>();
|
|
105
|
+
|
|
106
|
+
// Initialize graph entries
|
|
107
|
+
for (const meta of metadata) {
|
|
108
|
+
graph.set(meta.path, {
|
|
109
|
+
path: meta.path,
|
|
110
|
+
imports: [],
|
|
111
|
+
importedBy: [],
|
|
112
|
+
centrality: 0,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse imports from each file
|
|
117
|
+
for (const meta of metadata) {
|
|
118
|
+
try {
|
|
119
|
+
const contents = await plugins.smartfile.fs.toStringSync(meta.path);
|
|
120
|
+
const imports = this.extractImports(contents, meta.path);
|
|
121
|
+
|
|
122
|
+
const deps = graph.get(meta.path)!;
|
|
123
|
+
deps.imports = imports;
|
|
124
|
+
|
|
125
|
+
// Update importedBy for imported files
|
|
126
|
+
for (const importPath of imports) {
|
|
127
|
+
const importedDeps = graph.get(importPath);
|
|
128
|
+
if (importedDeps) {
|
|
129
|
+
importedDeps.importedBy.push(meta.path);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.warn(`Failed to parse imports from ${meta.path}:`, error.message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return graph;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extracts import statements from file contents
|
|
142
|
+
* @param contents - File contents
|
|
143
|
+
* @param filePath - Path of the file being analyzed
|
|
144
|
+
* @returns Array of absolute paths to imported files
|
|
145
|
+
*/
|
|
146
|
+
private extractImports(contents: string, filePath: string): string[] {
|
|
147
|
+
const imports: string[] = [];
|
|
148
|
+
const fileDir = plugins.path.dirname(filePath);
|
|
149
|
+
|
|
150
|
+
// Match various import patterns
|
|
151
|
+
const importRegex = /(?:import|export).*?from\s+['"](.+?)['"]/g;
|
|
152
|
+
let match;
|
|
153
|
+
|
|
154
|
+
while ((match = importRegex.exec(contents)) !== null) {
|
|
155
|
+
const importPath = match[1];
|
|
156
|
+
|
|
157
|
+
// Skip external modules
|
|
158
|
+
if (!importPath.startsWith('.')) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Resolve relative import to absolute path
|
|
163
|
+
let resolvedPath = plugins.path.resolve(fileDir, importPath);
|
|
164
|
+
|
|
165
|
+
// Handle various file extensions
|
|
166
|
+
const extensions = ['.ts', '.js', '.tsx', '.jsx', '/index.ts', '/index.js'];
|
|
167
|
+
let found = false;
|
|
168
|
+
|
|
169
|
+
for (const ext of extensions) {
|
|
170
|
+
const testPath = resolvedPath.endsWith(ext) ? resolvedPath : resolvedPath + ext;
|
|
171
|
+
try {
|
|
172
|
+
// Use synchronous file check to avoid async in this context
|
|
173
|
+
const fs = require('fs');
|
|
174
|
+
const exists = fs.existsSync(testPath);
|
|
175
|
+
if (exists) {
|
|
176
|
+
imports.push(testPath);
|
|
177
|
+
found = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// Continue trying other extensions
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!found && !resolvedPath.includes('.')) {
|
|
186
|
+
// Try with .ts extension as default
|
|
187
|
+
imports.push(resolvedPath + '.ts');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return imports;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Calculates centrality scores for all nodes in the dependency graph
|
|
196
|
+
* Uses a simplified PageRank-like algorithm
|
|
197
|
+
* @param graph - Dependency graph
|
|
198
|
+
*/
|
|
199
|
+
private calculateCentrality(graph: Map<string, IFileDependencies>): void {
|
|
200
|
+
const damping = 0.85;
|
|
201
|
+
const iterations = 10;
|
|
202
|
+
const nodeCount = graph.size;
|
|
203
|
+
|
|
204
|
+
// Initialize scores
|
|
205
|
+
const scores = new Map<string, number>();
|
|
206
|
+
for (const path of graph.keys()) {
|
|
207
|
+
scores.set(path, 1.0 / nodeCount);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Iterative calculation
|
|
211
|
+
for (let i = 0; i < iterations; i++) {
|
|
212
|
+
const newScores = new Map<string, number>();
|
|
213
|
+
|
|
214
|
+
for (const [path, deps] of graph.entries()) {
|
|
215
|
+
let score = (1 - damping) / nodeCount;
|
|
216
|
+
|
|
217
|
+
// Add contributions from nodes that import this file
|
|
218
|
+
for (const importerPath of deps.importedBy) {
|
|
219
|
+
const importerDeps = graph.get(importerPath);
|
|
220
|
+
if (importerDeps) {
|
|
221
|
+
const importerScore = scores.get(importerPath) ?? 0;
|
|
222
|
+
const outgoingCount = importerDeps.imports.length || 1;
|
|
223
|
+
score += damping * (importerScore / outgoingCount);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
newScores.set(path, score);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Update scores
|
|
231
|
+
for (const [path, score] of newScores) {
|
|
232
|
+
scores.set(path, score);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Normalize scores to 0-1 range
|
|
237
|
+
const maxScore = Math.max(...scores.values());
|
|
238
|
+
if (maxScore > 0) {
|
|
239
|
+
for (const deps of graph.values()) {
|
|
240
|
+
const score = scores.get(deps.path) ?? 0;
|
|
241
|
+
deps.centrality = score / maxScore;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Analyzes a single file
|
|
248
|
+
* @param meta - File metadata
|
|
249
|
+
* @param taskType - Task being performed
|
|
250
|
+
* @param graph - Dependency graph
|
|
251
|
+
* @param changedFiles - Recently changed files
|
|
252
|
+
* @returns File analysis
|
|
253
|
+
*/
|
|
254
|
+
private async analyzeFile(
|
|
255
|
+
meta: IFileMetadata,
|
|
256
|
+
taskType: TaskType,
|
|
257
|
+
graph: Map<string, IFileDependencies>,
|
|
258
|
+
changedFiles: string[]
|
|
259
|
+
): Promise<IFileAnalysis> {
|
|
260
|
+
const deps = graph.get(meta.path);
|
|
261
|
+
const centralityScore = deps?.centrality ?? 0;
|
|
262
|
+
|
|
263
|
+
// Calculate task-specific relevance
|
|
264
|
+
const relevanceScore = this.calculateRelevance(meta, taskType);
|
|
265
|
+
|
|
266
|
+
// Calculate efficiency (information per token)
|
|
267
|
+
const efficiencyScore = this.calculateEfficiency(meta);
|
|
268
|
+
|
|
269
|
+
// Calculate recency (for commit tasks)
|
|
270
|
+
const recencyScore = this.calculateRecency(meta, changedFiles);
|
|
271
|
+
|
|
272
|
+
// Calculate combined importance score
|
|
273
|
+
const importanceScore =
|
|
274
|
+
relevanceScore * this.weights.relevanceWeight +
|
|
275
|
+
centralityScore * this.weights.dependencyWeight +
|
|
276
|
+
efficiencyScore * this.weights.efficiencyWeight +
|
|
277
|
+
recencyScore * this.weights.recencyWeight;
|
|
278
|
+
|
|
279
|
+
// Assign tier
|
|
280
|
+
const tier = this.assignTier(importanceScore);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
path: meta.path,
|
|
284
|
+
relevanceScore,
|
|
285
|
+
centralityScore,
|
|
286
|
+
efficiencyScore,
|
|
287
|
+
recencyScore,
|
|
288
|
+
importanceScore,
|
|
289
|
+
tier,
|
|
290
|
+
reason: this.generateReason(meta, taskType, importanceScore, tier),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Calculates task-specific relevance score
|
|
296
|
+
*/
|
|
297
|
+
private calculateRelevance(meta: IFileMetadata, taskType: TaskType): number {
|
|
298
|
+
const relativePath = meta.relativePath.toLowerCase();
|
|
299
|
+
let score = 0.5; // Base score
|
|
300
|
+
|
|
301
|
+
// README generation - prioritize public APIs and main exports
|
|
302
|
+
if (taskType === 'readme') {
|
|
303
|
+
if (relativePath.includes('index.ts')) score += 0.3;
|
|
304
|
+
if (relativePath.match(/^ts\/[^\/]+\.ts$/)) score += 0.2; // Root level exports
|
|
305
|
+
if (relativePath.includes('test/')) score -= 0.3;
|
|
306
|
+
if (relativePath.includes('classes/')) score += 0.1;
|
|
307
|
+
if (relativePath.includes('interfaces/')) score += 0.1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Commit messages - prioritize changed files and their dependencies
|
|
311
|
+
if (taskType === 'commit') {
|
|
312
|
+
if (relativePath.includes('test/')) score -= 0.2;
|
|
313
|
+
// Recency will handle changed files
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Description generation - prioritize main exports and core interfaces
|
|
317
|
+
if (taskType === 'description') {
|
|
318
|
+
if (relativePath.includes('index.ts')) score += 0.4;
|
|
319
|
+
if (relativePath.match(/^ts\/[^\/]+\.ts$/)) score += 0.3;
|
|
320
|
+
if (relativePath.includes('test/')) score -= 0.4;
|
|
321
|
+
if (relativePath.includes('interfaces/')) score += 0.2;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return Math.max(0, Math.min(1, score));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Calculates efficiency score (information density)
|
|
329
|
+
*/
|
|
330
|
+
private calculateEfficiency(meta: IFileMetadata): number {
|
|
331
|
+
// Prefer files that are not too large (good signal-to-noise ratio)
|
|
332
|
+
const optimalSize = 5000; // ~1250 tokens
|
|
333
|
+
const distance = Math.abs(meta.estimatedTokens - optimalSize);
|
|
334
|
+
const normalized = Math.max(0, 1 - distance / optimalSize);
|
|
335
|
+
|
|
336
|
+
return normalized;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Calculates recency score for changed files
|
|
341
|
+
*/
|
|
342
|
+
private calculateRecency(meta: IFileMetadata, changedFiles: string[]): number {
|
|
343
|
+
if (changedFiles.length === 0) {
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check if this file was changed
|
|
348
|
+
const isChanged = changedFiles.some((changed) => changed === meta.path);
|
|
349
|
+
|
|
350
|
+
return isChanged ? 1.0 : 0.0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Assigns a tier based on importance score
|
|
355
|
+
*/
|
|
356
|
+
private assignTier(score: number): 'essential' | 'important' | 'optional' | 'excluded' {
|
|
357
|
+
if (score >= this.tiers.essential.minScore) return 'essential';
|
|
358
|
+
if (score >= this.tiers.important.minScore) return 'important';
|
|
359
|
+
if (score >= this.tiers.optional.minScore) return 'optional';
|
|
360
|
+
return 'excluded';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Generates a human-readable reason for the score
|
|
365
|
+
*/
|
|
366
|
+
private generateReason(
|
|
367
|
+
meta: IFileMetadata,
|
|
368
|
+
taskType: TaskType,
|
|
369
|
+
score: number,
|
|
370
|
+
tier: string
|
|
371
|
+
): string {
|
|
372
|
+
const reasons: string[] = [];
|
|
373
|
+
|
|
374
|
+
if (meta.relativePath.includes('index.ts')) {
|
|
375
|
+
reasons.push('main export file');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (meta.relativePath.includes('test/')) {
|
|
379
|
+
reasons.push('test file (lower priority)');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (taskType === 'readme' && meta.relativePath.match(/^ts\/[^\/]+\.ts$/)) {
|
|
383
|
+
reasons.push('root-level module');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
reasons.push(`score: ${score.toFixed(2)}`);
|
|
387
|
+
reasons.push(`tier: ${tier}`);
|
|
388
|
+
|
|
389
|
+
return reasons.join(', ');
|
|
390
|
+
}
|
|
391
|
+
}
|