agentsys 5.5.0 → 5.7.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/.claude-plugin/marketplace.json +6 -17
- package/.claude-plugin/plugin.json +1 -1
- package/.kiro/agents/exploration-agent.json +1 -1
- package/.kiro/agents/implementation-agent.json +1 -1
- package/.kiro/agents/map-validator.json +2 -2
- package/.kiro/agents/perf-orchestrator.json +1 -1
- package/.kiro/agents/planning-agent.json +1 -1
- package/.kiro/skills/perf-code-paths/SKILL.md +1 -1
- package/.kiro/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/.kiro/skills/repo-intel/SKILL.md +63 -0
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +31 -0
- package/README.md +95 -68
- package/lib/binary/version.js +1 -1
- package/lib/repo-map/converter.js +130 -0
- package/lib/repo-map/index.js +117 -74
- package/lib/repo-map/installer.js +38 -172
- package/lib/repo-map/updater.js +16 -474
- package/meta/skills/maintain-cross-platform/SKILL.md +5 -5
- package/package.json +3 -3
- package/scripts/fix-graduated-repos.js +2 -2
- package/scripts/generate-docs.js +39 -20
- package/scripts/graduate-plugin.js +1 -1
- package/scripts/preflight.js +4 -4
- package/scripts/validate-counts.js +1 -1
- package/scripts/validate-cross-platform-docs.js +2 -2
- package/site/content.json +76 -58
- package/site/index.html +44 -12
- package/site/ux-spec.md +1 -1
- package/.kiro/skills/repo-mapping/SKILL.md +0 -83
- package/lib/repo-map/concurrency.js +0 -29
- package/lib/repo-map/queries/go.js +0 -27
- package/lib/repo-map/queries/index.js +0 -100
- package/lib/repo-map/queries/java.js +0 -38
- package/lib/repo-map/queries/javascript.js +0 -55
- package/lib/repo-map/queries/python.js +0 -24
- package/lib/repo-map/queries/rust.js +0 -73
- package/lib/repo-map/queries/typescript.js +0 -38
- package/lib/repo-map/runner.js +0 -1364
- package/lib/repo-map/usage-analyzer.js +0 -407
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Repo Map Usage Analyzer
|
|
3
|
-
*
|
|
4
|
-
* Cross-file usage tracking to enable deeper analysis:
|
|
5
|
-
* - Unused exports detection
|
|
6
|
-
* - Orphaned infrastructure detection
|
|
7
|
-
* - Symbol dependency mapping
|
|
8
|
-
*
|
|
9
|
-
* @module lib/repo-map/usage-analyzer
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
'use strict';
|
|
13
|
-
|
|
14
|
-
const path = require('path');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Build a reverse index mapping symbols to their importers
|
|
18
|
-
* @param {Object} repoMap - The repo map object from cache.load()
|
|
19
|
-
* @returns {Object} Usage index: { bySymbol: Map<string, Set<string>>, byFile: Map<string, Set<string>> }
|
|
20
|
-
*/
|
|
21
|
-
function buildUsageIndex(repoMap) {
|
|
22
|
-
if (!repoMap || !repoMap.files) {
|
|
23
|
-
return { bySymbol: new Map(), byFile: new Map() };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// bySymbol: symbolName -> Set of files that import it
|
|
27
|
-
// byFile: filePath -> Set of files that depend on it
|
|
28
|
-
const bySymbol = new Map();
|
|
29
|
-
const byFile = new Map();
|
|
30
|
-
|
|
31
|
-
// Build export registry: filePath -> Set of exported symbol names
|
|
32
|
-
const exportsByFile = new Map();
|
|
33
|
-
for (const [filePath, fileData] of Object.entries(repoMap.files)) {
|
|
34
|
-
const exports = new Set();
|
|
35
|
-
if (fileData.symbols?.exports) {
|
|
36
|
-
for (const exp of fileData.symbols.exports) {
|
|
37
|
-
exports.add(exp.name);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
exportsByFile.set(filePath, exports);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Process imports to build reverse index
|
|
44
|
-
for (const [importerPath, fileData] of Object.entries(repoMap.files)) {
|
|
45
|
-
if (!fileData.imports || fileData.imports.length === 0) continue;
|
|
46
|
-
|
|
47
|
-
for (const imp of fileData.imports) {
|
|
48
|
-
const source = imp.source;
|
|
49
|
-
if (!source) continue;
|
|
50
|
-
|
|
51
|
-
// Resolve the import source to a file path
|
|
52
|
-
const resolvedPath = resolveImportSource(importerPath, source, repoMap);
|
|
53
|
-
if (!resolvedPath) continue;
|
|
54
|
-
|
|
55
|
-
// Track file-level dependency
|
|
56
|
-
if (!byFile.has(resolvedPath)) {
|
|
57
|
-
byFile.set(resolvedPath, new Set());
|
|
58
|
-
}
|
|
59
|
-
byFile.get(resolvedPath).add(importerPath);
|
|
60
|
-
|
|
61
|
-
// For named imports, track symbol-level usage
|
|
62
|
-
// The import kind tells us what type of import it is
|
|
63
|
-
if (imp.kind === 'named' || imp.kind === 'import') {
|
|
64
|
-
// Try to extract imported names from the import
|
|
65
|
-
const importedNames = extractImportedNames(imp, source);
|
|
66
|
-
for (const name of importedNames) {
|
|
67
|
-
const symbolKey = `${resolvedPath}:${name}`;
|
|
68
|
-
if (!bySymbol.has(symbolKey)) {
|
|
69
|
-
bySymbol.set(symbolKey, new Set());
|
|
70
|
-
}
|
|
71
|
-
bySymbol.get(symbolKey).add(importerPath);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { bySymbol, byFile, exportsByFile };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Resolve an import source to a file path in the repo map
|
|
82
|
-
* @param {string} importerPath - Path of the importing file
|
|
83
|
-
* @param {string} source - Import source (e.g., './utils', 'lodash', '../lib')
|
|
84
|
-
* @param {Object} repoMap - The repo map
|
|
85
|
-
* @returns {string|null} Resolved file path or null
|
|
86
|
-
*/
|
|
87
|
-
function resolveImportSource(importerPath, source, repoMap) {
|
|
88
|
-
// Skip external packages
|
|
89
|
-
if (!source.startsWith('.') && !source.startsWith('/')) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const importerDir = path.dirname(importerPath);
|
|
94
|
-
let candidatePath = path.join(importerDir, source).replace(/\\/g, '/');
|
|
95
|
-
|
|
96
|
-
// Normalize the path
|
|
97
|
-
candidatePath = path.normalize(candidatePath).replace(/\\/g, '/');
|
|
98
|
-
|
|
99
|
-
// Try direct match
|
|
100
|
-
if (repoMap.files[candidatePath]) {
|
|
101
|
-
return candidatePath;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Try with common extensions
|
|
105
|
-
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
106
|
-
for (const ext of extensions) {
|
|
107
|
-
const withExt = candidatePath + ext;
|
|
108
|
-
if (repoMap.files[withExt]) {
|
|
109
|
-
return withExt;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Try index file
|
|
114
|
-
for (const ext of extensions) {
|
|
115
|
-
const indexPath = candidatePath + '/index' + ext;
|
|
116
|
-
if (repoMap.files[indexPath]) {
|
|
117
|
-
return indexPath;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Extract imported names from an import statement
|
|
126
|
-
* @param {Object} imp - Import object from repo map
|
|
127
|
-
* @param {string} source - Import source
|
|
128
|
-
* @returns {string[]} List of imported symbol names
|
|
129
|
-
*/
|
|
130
|
-
function extractImportedNames(imp, source) {
|
|
131
|
-
const names = [];
|
|
132
|
-
|
|
133
|
-
// If the import has specific names recorded, use them
|
|
134
|
-
if (imp.names && Array.isArray(imp.names)) {
|
|
135
|
-
return imp.names;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// For default imports, use the basename as heuristic
|
|
139
|
-
if (imp.kind === 'default') {
|
|
140
|
-
const basename = path.basename(source).replace(/\.[^.]+$/, '');
|
|
141
|
-
names.push(basename);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return names;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Find which files import a specific symbol
|
|
149
|
-
* @param {Object} usageIndex - Result from buildUsageIndex
|
|
150
|
-
* @param {string} filePath - File containing the symbol
|
|
151
|
-
* @param {string} symbolName - Name of the symbol
|
|
152
|
-
* @returns {string[]} List of file paths that import this symbol
|
|
153
|
-
*/
|
|
154
|
-
function findUsages(usageIndex, filePath, symbolName) {
|
|
155
|
-
const symbolKey = `${filePath}:${symbolName}`;
|
|
156
|
-
const usages = usageIndex.bySymbol.get(symbolKey);
|
|
157
|
-
return usages ? Array.from(usages) : [];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Find files that depend on a given file
|
|
162
|
-
* @param {Object} usageIndex - Result from buildUsageIndex
|
|
163
|
-
* @param {string} filePath - Path to the file
|
|
164
|
-
* @returns {string[]} List of file paths that import from this file
|
|
165
|
-
*/
|
|
166
|
-
function findDependents(usageIndex, filePath) {
|
|
167
|
-
const dependents = usageIndex.byFile.get(filePath);
|
|
168
|
-
return dependents ? Array.from(dependents) : [];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Find exports that are never imported anywhere
|
|
173
|
-
* @param {Object} repoMap - The repo map
|
|
174
|
-
* @param {Object} usageIndex - Result from buildUsageIndex (optional, will build if not provided)
|
|
175
|
-
* @returns {Array<Object>} Unused exports: { file, name, line, kind }
|
|
176
|
-
*/
|
|
177
|
-
function findUnusedExports(repoMap, usageIndex = null) {
|
|
178
|
-
if (!repoMap || !repoMap.files) {
|
|
179
|
-
return [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const index = usageIndex || buildUsageIndex(repoMap);
|
|
183
|
-
const unusedExports = [];
|
|
184
|
-
|
|
185
|
-
for (const [filePath, fileData] of Object.entries(repoMap.files)) {
|
|
186
|
-
if (!fileData.symbols?.exports) continue;
|
|
187
|
-
|
|
188
|
-
// Check if the file itself is used
|
|
189
|
-
const fileDependents = index.byFile.get(filePath);
|
|
190
|
-
const fileIsImported = fileDependents && fileDependents.size > 0;
|
|
191
|
-
|
|
192
|
-
for (const exp of fileData.symbols.exports) {
|
|
193
|
-
const symbolKey = `${filePath}:${exp.name}`;
|
|
194
|
-
const symbolUsages = index.bySymbol.get(symbolKey);
|
|
195
|
-
const symbolIsUsed = symbolUsages && symbolUsages.size > 0;
|
|
196
|
-
|
|
197
|
-
// Check if this specific symbol is unused
|
|
198
|
-
// A file can be imported for some symbols but have other unused exports
|
|
199
|
-
if (!symbolIsUsed) {
|
|
200
|
-
// Skip entry points (index.js, main.js, etc.)
|
|
201
|
-
if (isEntryPoint(filePath)) continue;
|
|
202
|
-
|
|
203
|
-
unusedExports.push({
|
|
204
|
-
file: filePath,
|
|
205
|
-
name: exp.name,
|
|
206
|
-
line: exp.line,
|
|
207
|
-
kind: exp.kind || 'export',
|
|
208
|
-
// Higher certainty if file itself isn't imported at all
|
|
209
|
-
certainty: fileIsImported ? 'LOW' : 'MEDIUM'
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return unusedExports;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Check if a file is likely an entry point
|
|
220
|
-
* @param {string} filePath - File path
|
|
221
|
-
* @returns {boolean}
|
|
222
|
-
*/
|
|
223
|
-
function isEntryPoint(filePath) {
|
|
224
|
-
const basename = path.basename(filePath);
|
|
225
|
-
const entryNames = ['index', 'main', 'app', 'server', 'cli', 'bin'];
|
|
226
|
-
const nameWithoutExt = basename.replace(/\.[^.]+$/, '').toLowerCase();
|
|
227
|
-
|
|
228
|
-
return entryNames.includes(nameWithoutExt);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Find orphaned infrastructure - components that are set up but never used
|
|
233
|
-
* Uses repo map for AST-based detection (higher certainty than regex)
|
|
234
|
-
* @param {Object} repoMap - The repo map
|
|
235
|
-
* @param {Object} usageIndex - Result from buildUsageIndex (optional)
|
|
236
|
-
* @returns {Array<Object>} Orphaned infrastructure: { file, name, line, kind, certainty }
|
|
237
|
-
*/
|
|
238
|
-
function findOrphanedInfrastructure(repoMap, usageIndex = null) {
|
|
239
|
-
if (!repoMap || !repoMap.files) {
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const index = usageIndex || buildUsageIndex(repoMap);
|
|
244
|
-
const orphaned = [];
|
|
245
|
-
|
|
246
|
-
// Infrastructure component suffixes
|
|
247
|
-
const infrastructureSuffixes = [
|
|
248
|
-
'Client', 'Connection', 'Pool', 'Service', 'Provider',
|
|
249
|
-
'Manager', 'Factory', 'Repository', 'Gateway', 'Adapter',
|
|
250
|
-
'Handler', 'Broker', 'Queue', 'Cache', 'Store',
|
|
251
|
-
'Transport', 'Channel', 'Socket', 'Server', 'Database'
|
|
252
|
-
];
|
|
253
|
-
|
|
254
|
-
// Build regex for infrastructure detection
|
|
255
|
-
const suffixPattern = new RegExp(`(${infrastructureSuffixes.join('|')})$`);
|
|
256
|
-
|
|
257
|
-
for (const [filePath, fileData] of Object.entries(repoMap.files)) {
|
|
258
|
-
// Check classes
|
|
259
|
-
if (fileData.symbols?.classes) {
|
|
260
|
-
for (const cls of fileData.symbols.classes) {
|
|
261
|
-
if (!suffixPattern.test(cls.name)) continue;
|
|
262
|
-
|
|
263
|
-
// Check if this class is exported and used
|
|
264
|
-
const isExported = cls.exported === true;
|
|
265
|
-
if (!isExported) continue;
|
|
266
|
-
|
|
267
|
-
const symbolKey = `${filePath}:${cls.name}`;
|
|
268
|
-
const usages = index.bySymbol.get(symbolKey);
|
|
269
|
-
const fileDependents = index.byFile.get(filePath);
|
|
270
|
-
|
|
271
|
-
if ((!usages || usages.size === 0) && (!fileDependents || fileDependents.size === 0)) {
|
|
272
|
-
orphaned.push({
|
|
273
|
-
file: filePath,
|
|
274
|
-
name: cls.name,
|
|
275
|
-
line: cls.line,
|
|
276
|
-
kind: 'class',
|
|
277
|
-
type: 'infrastructure',
|
|
278
|
-
certainty: 'HIGH' // AST-based detection
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Check functions that look like factory/builder patterns
|
|
285
|
-
if (fileData.symbols?.functions) {
|
|
286
|
-
const factoryPatterns = [
|
|
287
|
-
/^create[A-Z]/,
|
|
288
|
-
/^make[A-Z]/,
|
|
289
|
-
/^build[A-Z]/,
|
|
290
|
-
/^new[A-Z]/,
|
|
291
|
-
/^init[A-Z]/,
|
|
292
|
-
/^setup[A-Z]/,
|
|
293
|
-
/^connect[A-Z]/
|
|
294
|
-
];
|
|
295
|
-
|
|
296
|
-
for (const fn of fileData.symbols.functions) {
|
|
297
|
-
const isFactory = factoryPatterns.some(p => p.test(fn.name));
|
|
298
|
-
if (!isFactory) continue;
|
|
299
|
-
|
|
300
|
-
const isExported = fn.exported === true;
|
|
301
|
-
if (!isExported) continue;
|
|
302
|
-
|
|
303
|
-
const symbolKey = `${filePath}:${fn.name}`;
|
|
304
|
-
const usages = index.bySymbol.get(symbolKey);
|
|
305
|
-
const fileDependents = index.byFile.get(filePath);
|
|
306
|
-
|
|
307
|
-
if ((!usages || usages.size === 0) && (!fileDependents || fileDependents.size === 0)) {
|
|
308
|
-
orphaned.push({
|
|
309
|
-
file: filePath,
|
|
310
|
-
name: fn.name,
|
|
311
|
-
line: fn.line,
|
|
312
|
-
kind: 'function',
|
|
313
|
-
type: 'factory',
|
|
314
|
-
certainty: 'HIGH'
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return orphaned;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Get dependency graph for visualization or analysis
|
|
326
|
-
* @param {Object} repoMap - The repo map
|
|
327
|
-
* @returns {Object} Graph: { nodes: string[], edges: Array<{from, to}> }
|
|
328
|
-
*/
|
|
329
|
-
function getDependencyGraph(repoMap) {
|
|
330
|
-
if (!repoMap || !repoMap.files) {
|
|
331
|
-
return { nodes: [], edges: [] };
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const nodes = Object.keys(repoMap.files);
|
|
335
|
-
const edges = [];
|
|
336
|
-
|
|
337
|
-
for (const [filePath, fileData] of Object.entries(repoMap.files)) {
|
|
338
|
-
if (!fileData.imports) continue;
|
|
339
|
-
|
|
340
|
-
for (const imp of fileData.imports) {
|
|
341
|
-
const resolved = resolveImportSource(filePath, imp.source, repoMap);
|
|
342
|
-
if (resolved) {
|
|
343
|
-
edges.push({ from: filePath, to: resolved });
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return { nodes, edges };
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Find circular dependencies
|
|
353
|
-
* @param {Object} repoMap - The repo map
|
|
354
|
-
* @returns {Array<string[]>} List of cycles (each cycle is array of file paths)
|
|
355
|
-
*/
|
|
356
|
-
function findCircularDependencies(repoMap) {
|
|
357
|
-
const graph = getDependencyGraph(repoMap);
|
|
358
|
-
const cycles = [];
|
|
359
|
-
const visited = new Set();
|
|
360
|
-
const recursionStack = new Set();
|
|
361
|
-
const path = [];
|
|
362
|
-
|
|
363
|
-
function dfs(node) {
|
|
364
|
-
visited.add(node);
|
|
365
|
-
recursionStack.add(node);
|
|
366
|
-
path.push(node);
|
|
367
|
-
|
|
368
|
-
const neighbors = graph.edges
|
|
369
|
-
.filter(e => e.from === node)
|
|
370
|
-
.map(e => e.to);
|
|
371
|
-
|
|
372
|
-
for (const neighbor of neighbors) {
|
|
373
|
-
if (!visited.has(neighbor)) {
|
|
374
|
-
dfs(neighbor);
|
|
375
|
-
} else if (recursionStack.has(neighbor)) {
|
|
376
|
-
// Found a cycle
|
|
377
|
-
const cycleStart = path.indexOf(neighbor);
|
|
378
|
-
const cycle = path.slice(cycleStart);
|
|
379
|
-
cycles.push([...cycle, neighbor]);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
path.pop();
|
|
384
|
-
recursionStack.delete(node);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
for (const node of graph.nodes) {
|
|
388
|
-
if (!visited.has(node)) {
|
|
389
|
-
dfs(node);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return cycles;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
module.exports = {
|
|
397
|
-
buildUsageIndex,
|
|
398
|
-
findUsages,
|
|
399
|
-
findDependents,
|
|
400
|
-
findUnusedExports,
|
|
401
|
-
findOrphanedInfrastructure,
|
|
402
|
-
getDependencyGraph,
|
|
403
|
-
findCircularDependencies,
|
|
404
|
-
// Expose helpers for testing
|
|
405
|
-
resolveImportSource,
|
|
406
|
-
isEntryPoint
|
|
407
|
-
};
|