agentsys 5.6.4 → 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.
Files changed (39) hide show
  1. package/.claude-plugin/marketplace.json +6 -17
  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 +3 -3
  12. package/CHANGELOG.md +20 -0
  13. package/README.md +85 -87
  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 +5 -5
  20. package/package.json +3 -3
  21. package/scripts/fix-graduated-repos.js +2 -2
  22. package/scripts/generate-docs.js +10 -13
  23. package/scripts/graduate-plugin.js +1 -1
  24. package/scripts/preflight.js +4 -4
  25. package/scripts/validate-cross-platform-docs.js +2 -2
  26. package/site/content.json +12 -19
  27. package/site/index.html +44 -12
  28. package/site/ux-spec.md +1 -1
  29. package/.kiro/skills/repo-mapping/SKILL.md +0 -83
  30. package/lib/repo-map/concurrency.js +0 -29
  31. package/lib/repo-map/queries/go.js +0 -27
  32. package/lib/repo-map/queries/index.js +0 -100
  33. package/lib/repo-map/queries/java.js +0 -38
  34. package/lib/repo-map/queries/javascript.js +0 -55
  35. package/lib/repo-map/queries/python.js +0 -24
  36. package/lib/repo-map/queries/rust.js +0 -73
  37. package/lib/repo-map/queries/typescript.js +0 -38
  38. package/lib/repo-map/runner.js +0 -1364
  39. package/lib/repo-map/usage-analyzer.js +0 -407
@@ -1,74 +1,90 @@
1
+ 'use strict';
2
+
1
3
  /**
2
- * Repo Map - AST-based repository symbol mapping
4
+ * Repo Map - repository symbol mapping via agent-analyzer
3
5
  *
4
- * Uses ast-grep (sg) for accurate symbol extraction across multiple languages.
5
- * Generates a cached map of exports, functions, classes, and imports.
6
+ * Uses agent-analyzer for symbol extraction. The binary is auto-downloaded
7
+ * on first use - no external tool installation required.
6
8
  *
7
9
  * @module lib/repo-map
8
10
  */
9
11
 
10
- 'use strict';
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execFileSync } = require('child_process');
11
15
 
12
16
  const installer = require('./installer');
13
- const runner = require('./runner');
14
17
  const cache = require('./cache');
15
18
  const updater = require('./updater');
16
- const usageAnalyzer = require('./usage-analyzer');
19
+ const converter = require('./converter');
20
+ const binary = require('../binary');
21
+ const { getStateDirPath } = require('../platform/state-dir');
22
+ const { writeJsonAtomic } = require('../utils/atomic-write');
23
+
24
+ const REPO_INTEL_FILENAME = 'repo-intel.json';
25
+
26
+ function getIntelMapPath(basePath) {
27
+ return path.join(getStateDirPath(basePath), REPO_INTEL_FILENAME);
28
+ }
17
29
 
18
30
  /**
19
31
  * Initialize a new repo map (full scan)
20
32
  * @param {string} basePath - Repository root path
21
33
  * @param {Object} options - Options
22
34
  * @param {boolean} options.force - Force rebuild even if map exists
23
- * @param {string[]} options.languages - Languages to scan (auto-detect if not specified)
24
35
  * @returns {Promise<{success: boolean, map?: Object, error?: string}>}
25
36
  */
26
37
  async function init(basePath, options = {}) {
27
- // Check if ast-grep is installed
28
38
  const installed = await installer.checkInstalled();
29
39
  if (!installed.found) {
30
40
  return {
31
41
  success: false,
32
- error: 'ast-grep not found',
42
+ error: 'agent-analyzer binary unavailable: ' + (installed.error || 'unknown error'),
33
43
  installSuggestion: installer.getInstallInstructions()
34
44
  };
35
45
  }
36
46
 
37
- if (!installer.meetsMinimumVersion(installed.version)) {
47
+ const existing = cache.load(basePath);
48
+ if (existing && !options.force) {
38
49
  return {
39
50
  success: false,
40
- error: `ast-grep version ${installed.version || 'unknown'} is too old. Minimum required: ${installer.getMinimumVersion()}`,
41
- installSuggestion: installer.getInstallInstructions()
51
+ error: 'Repo map already exists. Use --force to rebuild or /repo-map update to refresh.',
52
+ existing: cache.getStatus(basePath)
42
53
  };
43
54
  }
44
55
 
45
- // Check if map already exists
46
- const existing = cache.load(basePath);
47
- if (existing && !options.force) {
56
+ const startTime = Date.now();
57
+
58
+ let intelJson;
59
+ try {
60
+ intelJson = await binary.runAnalyzerAsync(['repo-intel', 'init', basePath]);
61
+ } catch (e) {
48
62
  return {
49
63
  success: false,
50
- error: 'Repo map already exists. Use --force to rebuild or /repo-map update to refresh.',
51
- existing: cache.getStatus(basePath)
64
+ error: 'agent-analyzer repo-intel init failed: ' + e.message
52
65
  };
53
66
  }
54
67
 
55
- // Detect languages in the project
56
- const languages = options.languages || await runner.detectLanguages(basePath);
57
- if (languages.length === 0) {
68
+ let intel;
69
+ try {
70
+ intel = JSON.parse(intelJson);
71
+ } catch (e) {
58
72
  return {
59
73
  success: false,
60
- error: 'No supported languages detected in repository'
74
+ error: 'Failed to parse repo-intel output: ' + e.message
61
75
  };
62
76
  }
63
77
 
64
- // Run full scan
65
- const startTime = Date.now();
66
- const map = await runner.fullScan(basePath, languages, {
67
- fileLimit: options.fileLimit
68
- });
69
- map.stats.scanDurationMs = Date.now() - startTime;
78
+ // Persist repo-intel.json for future incremental updates
79
+ const intelPath = getIntelMapPath(basePath);
80
+ try {
81
+ writeJsonAtomic(intelPath, intel);
82
+ } catch {
83
+ // Non-fatal: update() will fall back to full init
84
+ }
70
85
 
71
- // Save map
86
+ const map = converter.convertIntelToRepoMap(intel);
87
+ map.stats.scanDurationMs = Date.now() - startTime;
72
88
  cache.save(basePath, map);
73
89
 
74
90
  return {
@@ -84,53 +100,83 @@ async function init(basePath, options = {}) {
84
100
  }
85
101
 
86
102
  /**
87
- * Update an existing repo map (incremental)
103
+ * Update an existing repo map (incremental via agent-analyzer)
88
104
  * @param {string} basePath - Repository root path
89
105
  * @param {Object} options - Options
90
106
  * @param {boolean} options.full - Force full rebuild instead of incremental
91
- * @returns {Promise<{success: boolean, changes?: Object, error?: string}>}
107
+ * @returns {Promise<{success: boolean, summary?: Object, error?: string}>}
92
108
  */
93
109
  async function update(basePath, options = {}) {
94
- // Check if ast-grep is installed
95
110
  const installed = await installer.checkInstalled();
96
111
  if (!installed.found) {
97
112
  return {
98
113
  success: false,
99
- error: 'ast-grep not found',
114
+ error: 'agent-analyzer binary unavailable: ' + (installed.error || 'unknown error'),
100
115
  installSuggestion: installer.getInstallInstructions()
101
116
  };
102
117
  }
103
118
 
104
- if (!installer.meetsMinimumVersion(installed.version)) {
119
+ if (!cache.exists(basePath)) {
105
120
  return {
106
121
  success: false,
107
- error: `ast-grep version ${installed.version || 'unknown'} is too old. Minimum required: ${installer.getMinimumVersion()}`,
108
- installSuggestion: installer.getInstallInstructions()
122
+ error: 'No repo map found. Run /repo-map init first.'
109
123
  };
110
124
  }
111
125
 
112
- // Load existing map
113
- const existing = cache.load(basePath);
114
- if (!existing) {
126
+ if (options.full) {
127
+ return init(basePath, { force: true });
128
+ }
129
+
130
+ const intelPath = getIntelMapPath(basePath);
131
+ if (!fs.existsSync(intelPath)) {
132
+ return init(basePath, { force: true });
133
+ }
134
+
135
+ const startTime = Date.now();
136
+
137
+ let intelJson;
138
+ try {
139
+ intelJson = await binary.runAnalyzerAsync([
140
+ 'repo-intel', 'update',
141
+ '--map-file', intelPath,
142
+ basePath
143
+ ]);
144
+ } catch (e) {
115
145
  return {
116
146
  success: false,
117
- error: 'No repo map found. Run /repo-map init first.'
147
+ error: 'agent-analyzer repo-intel update failed: ' + e.message
118
148
  };
119
149
  }
120
150
 
121
- // Force full rebuild if requested
122
- if (options.full) {
123
- return init(basePath, { force: true });
151
+ let intel;
152
+ try {
153
+ intel = JSON.parse(intelJson);
154
+ } catch (e) {
155
+ return {
156
+ success: false,
157
+ error: 'Failed to parse repo-intel update output: ' + e.message
158
+ };
124
159
  }
125
160
 
126
- // Incremental update
127
- const result = await updater.incrementalUpdate(basePath, existing);
128
-
129
- if (result.success) {
130
- cache.save(basePath, result.map);
161
+ try {
162
+ writeJsonAtomic(intelPath, intel);
163
+ } catch {
164
+ // Non-fatal
131
165
  }
132
166
 
133
- return result;
167
+ const map = converter.convertIntelToRepoMap(intel);
168
+ map.stats.scanDurationMs = Date.now() - startTime;
169
+ cache.save(basePath, map);
170
+
171
+ return {
172
+ success: true,
173
+ map,
174
+ summary: {
175
+ files: Object.keys(map.files).length,
176
+ symbols: map.stats.totalSymbols,
177
+ duration: map.stats.scanDurationMs
178
+ }
179
+ };
134
180
  }
135
181
 
136
182
  /**
@@ -146,13 +192,20 @@ function status(basePath) {
146
192
 
147
193
  const staleness = updater.checkStaleness(basePath, map);
148
194
 
195
+ let branch;
196
+ try {
197
+ branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: basePath, encoding: 'utf8' }).trim();
198
+ } catch {
199
+ // Non-fatal
200
+ }
201
+
149
202
  return {
150
203
  exists: true,
151
204
  status: {
152
205
  generated: map.generated,
153
206
  updated: map.updated,
154
207
  commit: map.git?.commit,
155
- branch: map.git?.branch,
208
+ branch,
156
209
  files: Object.keys(map.files).length,
157
210
  symbols: map.stats?.totalSymbols || 0,
158
211
  languages: map.project?.languages || [],
@@ -164,37 +217,38 @@ function status(basePath) {
164
217
  /**
165
218
  * Load repo map (if exists)
166
219
  * @param {string} basePath - Repository root path
167
- * @returns {Object|null} - The map or null if not found
220
+ * @returns {Object|null}
168
221
  */
169
222
  function load(basePath) {
170
223
  return cache.load(basePath);
171
224
  }
172
225
 
173
226
  /**
174
- * Check if ast-grep is installed
175
- * @returns {Promise<{found: boolean, version?: string, path?: string}>}
227
+ * Check if repo map exists
228
+ * @param {string} basePath - Repository root path
229
+ * @returns {boolean}
230
+ */
231
+ function exists(basePath) {
232
+ return cache.exists(basePath);
233
+ }
234
+
235
+ /**
236
+ * Check if agent-analyzer is available (compat alias for checkInstalled).
237
+ * Previously checked ast-grep; now checks agent-analyzer.
238
+ * @returns {Promise<{found: boolean, version?: string, tool: string}>}
176
239
  */
177
240
  async function checkAstGrepInstalled() {
178
241
  return installer.checkInstalled();
179
242
  }
180
243
 
181
244
  /**
182
- * Get install instructions for ast-grep
245
+ * Get install instructions (compat alias).
183
246
  * @returns {string}
184
247
  */
185
248
  function getInstallInstructions() {
186
249
  return installer.getInstallInstructions();
187
250
  }
188
251
 
189
- /**
190
- * Check if repo map exists
191
- * @param {string} basePath - Repository root path
192
- * @returns {boolean}
193
- */
194
- function exists(basePath) {
195
- return cache.exists(basePath);
196
- }
197
-
198
252
  module.exports = {
199
253
  init,
200
254
  update,
@@ -204,19 +258,8 @@ module.exports = {
204
258
  checkAstGrepInstalled,
205
259
  getInstallInstructions,
206
260
 
207
- // Usage analysis functions
208
- buildUsageIndex: usageAnalyzer.buildUsageIndex,
209
- findUsages: usageAnalyzer.findUsages,
210
- findDependents: usageAnalyzer.findDependents,
211
- findUnusedExports: usageAnalyzer.findUnusedExports,
212
- findOrphanedInfrastructure: usageAnalyzer.findOrphanedInfrastructure,
213
- getDependencyGraph: usageAnalyzer.getDependencyGraph,
214
- findCircularDependencies: usageAnalyzer.findCircularDependencies,
215
-
216
261
  // Re-export submodules for advanced usage
217
262
  installer,
218
- runner,
219
263
  cache,
220
- updater,
221
- usageAnalyzer
264
+ updater
222
265
  };
@@ -1,212 +1,78 @@
1
+ 'use strict';
2
+
1
3
  /**
2
- * ast-grep installation detection and helpers
4
+ * agent-analyzer binary availability check.
5
+ *
6
+ * Replaces the former ast-grep installer. The agent-analyzer binary is
7
+ * auto-downloaded by agent-core on first use - no manual install required.
3
8
  *
4
9
  * @module lib/repo-map/installer
5
10
  */
6
11
 
7
- 'use strict';
8
-
9
- const { execSync, execFileSync, execFile } = require('child_process');
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { promisify } = require('util');
13
-
14
- const execFileAsync = promisify(execFile);
15
-
16
- // Commands to try (sg is the common alias)
17
- const AST_GREP_COMMANDS = ['sg', 'ast-grep'];
18
-
19
- function pickCommandPath(pathOutput) {
20
- if (!pathOutput) return null;
21
- const candidates = pathOutput.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
22
- if (candidates.length === 0) return null;
23
-
24
- if (process.platform !== 'win32') {
25
- return candidates[0];
26
- }
27
-
28
- const exe = candidates.find(candidate => candidate.toLowerCase().endsWith('.exe'));
29
- if (exe) return exe;
30
-
31
- const primary = candidates[0];
32
- if (!primary) return null;
33
-
34
- const baseDir = path.dirname(primary);
35
- const npmExe = path.join(baseDir, 'node_modules', '@ast-grep', 'cli', 'sg.exe');
36
- if (fs.existsSync(npmExe)) return npmExe;
37
-
38
- return primary;
39
- }
12
+ const binary = require('../binary');
40
13
 
41
14
  /**
42
- * Check if ast-grep is installed
43
- * @returns {Promise<{found: boolean, version?: string, path?: string, command?: string}>}
15
+ * Check if agent-analyzer is available (async). Downloads if missing.
16
+ * @returns {Promise<{found: boolean, version?: string, tool: string}>}
44
17
  */
45
18
  async function checkInstalled() {
46
- for (const cmd of AST_GREP_COMMANDS) {
47
- try {
48
- // Try to get version using execFileAsync (no shell, safe from injection)
49
- const { stdout } = await execFileAsync(cmd, ['--version'], {
50
- timeout: 5000,
51
- windowsHide: true,
52
- shell: process.platform === 'win32' // Windows needs shell for PATH lookup
53
- });
54
-
55
- const version = stdout.trim().replace(/^ast-grep\s*/i, '');
56
-
57
- // Try to get path
58
- let cmdPath = null;
59
- try {
60
- const whereCmd = process.platform === 'win32' ? 'where' : 'which';
61
- // Use execFileAsync with args array (no shell interpolation)
62
- const { stdout: pathOut } = await execFileAsync(whereCmd, [cmd], {
63
- timeout: 5000,
64
- windowsHide: true,
65
- shell: process.platform === 'win32'
66
- });
67
- cmdPath = pickCommandPath(pathOut);
68
- } catch {
69
- // Path lookup failed, but command works
70
- }
71
-
72
- return {
73
- found: true,
74
- version,
75
- path: cmdPath,
76
- command: cmdPath || cmd
77
- };
78
- } catch {
79
- // This command not found, try next
80
- continue;
81
- }
19
+ if (binary.isAvailable()) {
20
+ return { found: true, version: binary.getVersion(), tool: 'agent-analyzer' };
21
+ }
22
+ try {
23
+ await binary.ensureBinary();
24
+ return { found: true, version: binary.getVersion(), tool: 'agent-analyzer' };
25
+ } catch (e) {
26
+ return { found: false, error: e.message, tool: 'agent-analyzer' };
82
27
  }
83
-
84
- return { found: false };
85
28
  }
86
29
 
87
30
  /**
88
- * Check if ast-grep is installed (sync version)
89
- * @returns {{found: boolean, version?: string, command?: string, path?: string}}
31
+ * Check if agent-analyzer is available (sync). Downloads if missing.
32
+ * @returns {{found: boolean, version?: string, tool: string}}
90
33
  */
91
34
  function checkInstalledSync() {
92
- for (const cmd of AST_GREP_COMMANDS) {
93
- try {
94
- // Use execFileSync with args array (no shell interpolation, safe from injection)
95
- const stdout = execFileSync(cmd, ['--version'], {
96
- timeout: 5000,
97
- windowsHide: true,
98
- encoding: 'utf8',
99
- stdio: ['pipe', 'pipe', 'pipe'],
100
- shell: process.platform === 'win32' // Windows needs shell for PATH lookup
101
- });
102
-
103
- const version = stdout.trim().replace(/^ast-grep\s*/i, '');
104
- let cmdPath = null;
105
-
106
- try {
107
- const whereCmd = process.platform === 'win32' ? 'where' : 'which';
108
- // Use execFileSync with args array (no shell interpolation)
109
- const pathOut = execFileSync(whereCmd, [cmd], {
110
- timeout: 5000,
111
- windowsHide: true,
112
- encoding: 'utf8',
113
- stdio: ['pipe', 'pipe', 'pipe'],
114
- shell: process.platform === 'win32'
115
- });
116
- cmdPath = pickCommandPath(pathOut);
117
- } catch {
118
- // Path lookup failed, but command works
119
- }
120
-
121
- return { found: true, version, command: cmdPath || cmd, path: cmdPath };
122
- } catch {
123
- continue;
124
- }
35
+ if (binary.isAvailable()) {
36
+ return { found: true, version: binary.getVersion(), tool: 'agent-analyzer' };
37
+ }
38
+ try {
39
+ binary.ensureBinarySync();
40
+ return { found: true, version: binary.getVersion(), tool: 'agent-analyzer' };
41
+ } catch (e) {
42
+ return { found: false, error: e.message, tool: 'agent-analyzer' };
125
43
  }
126
-
127
- return { found: false };
128
44
  }
129
45
 
130
46
  /**
131
- * Get the working ast-grep command
132
- * @returns {string|null}
47
+ * Version check is handled by the binary module - always true when found.
48
+ * @returns {boolean}
133
49
  */
134
- function getCommand() {
135
- const result = checkInstalledSync();
136
- return result.found ? result.command : null;
50
+ function meetsMinimumVersion() {
51
+ return true;
137
52
  }
138
53
 
139
54
  /**
140
- * Get installation instructions for ast-grep
55
+ * Get install instructions (binary is auto-downloaded, but here for compat).
141
56
  * @returns {string}
142
57
  */
143
58
  function getInstallInstructions() {
144
- return `ast-grep (sg) is required for repo-map functionality.
145
-
146
- Install using one of these methods:
147
-
148
- npm: npm install -g @ast-grep/cli
149
- pip: pip install ast-grep-cli
150
- brew: brew install ast-grep
151
- cargo: cargo install ast-grep --locked
152
- scoop: scoop install main/ast-grep
153
-
154
- After installation, verify with: sg --version
155
-
156
- Documentation: https://ast-grep.github.io/`;
59
+ return 'agent-analyzer is downloaded automatically on first use from https://github.com/agent-sh/agent-analyzer/releases';
157
60
  }
158
61
 
159
62
  /**
160
- * Get a short install suggestion (one line)
161
- * @returns {string}
162
- */
163
- function getShortInstallSuggestion() {
164
- if (process.platform === 'win32') {
165
- return 'Install ast-grep: npm i -g @ast-grep/cli (or scoop install ast-grep)';
166
- } else if (process.platform === 'darwin') {
167
- return 'Install ast-grep: brew install ast-grep (or npm i -g @ast-grep/cli)';
168
- } else {
169
- return 'Install ast-grep: npm i -g @ast-grep/cli (or pip install ast-grep-cli)';
170
- }
171
- }
172
-
173
- /**
174
- * Get minimum required version
63
+ * Get minimum version string.
175
64
  * @returns {string}
176
65
  */
177
66
  function getMinimumVersion() {
178
- return '0.20.0'; // Require at least this version for JSON output support
179
- }
180
-
181
- /**
182
- * Check if installed version meets minimum requirements
183
- * @param {string} version - Installed version
184
- * @returns {boolean}
185
- */
186
- function meetsMinimumVersion(version) {
187
- if (!version) return false;
188
-
189
- // Parse version (e.g., "0.25.0" or "0.25.0-beta.1")
190
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
191
- if (!match) return false;
192
-
193
- const [, major, minor, patch] = match.map(Number);
194
- const [reqMajor, reqMinor, reqPatch] = getMinimumVersion().split('.').map(Number);
195
-
196
- if (major > reqMajor) return true;
197
- if (major < reqMajor) return false;
198
- if (minor > reqMinor) return true;
199
- if (minor < reqMinor) return false;
200
- return patch >= reqPatch;
67
+ return '0.3.0';
201
68
  }
202
69
 
203
70
  module.exports = {
204
71
  checkInstalled,
205
72
  checkInstalledSync,
206
- getCommand,
73
+ meetsMinimumVersion,
207
74
  getInstallInstructions,
208
- getShortInstallSuggestion,
209
75
  getMinimumVersion,
210
- meetsMinimumVersion,
211
- AST_GREP_COMMANDS
76
+ // Stub: runner.js references this but is no longer the scan path
77
+ getCommand: () => null
212
78
  };