agentsys 5.6.4 → 5.8.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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +30 -19
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.kiro/agents/exploration-agent.json +1 -1
  4. package/.kiro/agents/implementation-agent.json +1 -1
  5. package/.kiro/agents/map-validator.json +2 -2
  6. package/.kiro/agents/perf-orchestrator.json +1 -1
  7. package/.kiro/agents/planning-agent.json +1 -1
  8. package/.kiro/skills/perf-code-paths/SKILL.md +1 -1
  9. package/.kiro/skills/perf-theory-gatherer/SKILL.md +1 -1
  10. package/.kiro/skills/repo-intel/SKILL.md +63 -0
  11. package/AGENTS.md +10 -8
  12. package/CHANGELOG.md +37 -0
  13. package/README.md +152 -98
  14. package/lib/binary/version.js +1 -1
  15. package/lib/repo-map/converter.js +130 -0
  16. package/lib/repo-map/index.js +117 -74
  17. package/lib/repo-map/installer.js +38 -172
  18. package/lib/repo-map/updater.js +16 -474
  19. package/meta/skills/maintain-cross-platform/SKILL.md +7 -6
  20. package/package.json +3 -3
  21. package/scripts/fix-graduated-repos.js +2 -2
  22. package/scripts/generate-docs.js +22 -16
  23. package/scripts/graduate-plugin.js +1 -1
  24. package/scripts/plugins.txt +7 -1
  25. package/scripts/preflight.js +4 -4
  26. package/scripts/validate-cross-platform-docs.js +2 -2
  27. package/site/content.json +40 -23
  28. package/site/index.html +44 -12
  29. package/site/ux-spec.md +6 -6
  30. package/.kiro/skills/repo-mapping/SKILL.md +0 -83
  31. package/lib/repo-map/concurrency.js +0 -29
  32. package/lib/repo-map/queries/go.js +0 -27
  33. package/lib/repo-map/queries/index.js +0 -100
  34. package/lib/repo-map/queries/java.js +0 -38
  35. package/lib/repo-map/queries/javascript.js +0 -55
  36. package/lib/repo-map/queries/python.js +0 -24
  37. package/lib/repo-map/queries/rust.js +0 -73
  38. package/lib/repo-map/queries/typescript.js +0 -38
  39. package/lib/repo-map/runner.js +0 -1364
  40. 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
- };