agentsys 5.4.1 → 5.5.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,398 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Binary resolver for the agent-analyzer Rust binary.
5
+ *
6
+ * Handles lazy downloading and execution. Since Claude Code plugins have no
7
+ * postinstall hooks, the binary is downloaded at runtime on first use.
8
+ *
9
+ * @module lib/binary
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const https = require('https');
16
+ const cp = require('child_process');
17
+ const { promisify } = require('util');
18
+
19
+ const execFileAsync = promisify(cp.execFile);
20
+
21
+ const { ANALYZER_MIN_VERSION, BINARY_NAME, GITHUB_REPO } = require('./version');
22
+
23
+ const PLATFORM_MAP = {
24
+ 'darwin-arm64': 'aarch64-apple-darwin',
25
+ 'darwin-x64': 'x86_64-apple-darwin',
26
+ 'linux-x64': 'x86_64-unknown-linux-gnu',
27
+ 'linux-arm64': 'aarch64-unknown-linux-gnu',
28
+ 'win32-x64': 'x86_64-pc-windows-msvc'
29
+ };
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Path helpers
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Returns the expected path to the agent-analyzer binary.
37
+ * @returns {string}
38
+ */
39
+ function getBinaryPath() {
40
+ const ext = process.platform === 'win32' ? '.exe' : '';
41
+ return path.join(os.homedir(), '.agent-sh', 'bin', BINARY_NAME + ext);
42
+ }
43
+
44
+ /**
45
+ * Returns the Rust target triple for the current platform.
46
+ * @returns {string|null}
47
+ */
48
+ function getPlatformKey() {
49
+ const key = process.platform + '-' + process.arch;
50
+ return PLATFORM_MAP[key] || null;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Version helpers
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /**
58
+ * Compare a version string against a minimum requirement.
59
+ * @param {string} version
60
+ * @param {string} minVersion
61
+ * @returns {boolean}
62
+ */
63
+ function meetsMinimumVersion(version, minVersion) {
64
+ if (!version) return false;
65
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
66
+ if (!match) return false;
67
+ const parts = match.slice(1).map(Number);
68
+ const req = minVersion.split('.').map(Number);
69
+ if (parts[0] > req[0]) return true;
70
+ if (parts[0] < req[0]) return false;
71
+ if (parts[1] > req[1]) return true;
72
+ if (parts[1] < req[1]) return false;
73
+ return parts[2] >= req[2];
74
+ }
75
+
76
+ /**
77
+ * Run the binary with --version and return the version string, or null on failure.
78
+ * @returns {string|null}
79
+ */
80
+ function getVersion() {
81
+ const binPath = getBinaryPath();
82
+ if (!fs.existsSync(binPath)) return null;
83
+ try {
84
+ const out = cp.execFileSync(binPath, ['--version'], {
85
+ timeout: 5000,
86
+ encoding: 'utf8',
87
+ stdio: ['pipe', 'pipe', 'pipe'],
88
+ windowsHide: true
89
+ });
90
+ const match = out.trim().match(/(\d+\.\d+\.\d+)/);
91
+ return match ? match[1] : out.trim();
92
+ } catch (e) {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Availability checks
99
+ // ---------------------------------------------------------------------------
100
+
101
+ /**
102
+ * Sync check: returns true if the binary exists and meets the minimum version.
103
+ * Does NOT download.
104
+ * @returns {boolean}
105
+ */
106
+ function isAvailable() {
107
+ const binPath = getBinaryPath();
108
+ if (!fs.existsSync(binPath)) return false;
109
+ const ver = getVersion();
110
+ return meetsMinimumVersion(ver, ANALYZER_MIN_VERSION);
111
+ }
112
+
113
+ /**
114
+ * Async check: returns true if the binary exists and meets the minimum version.
115
+ * Does NOT download.
116
+ * @returns {Promise<boolean>}
117
+ */
118
+ async function isAvailableAsync() {
119
+ return isAvailable();
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Download
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /**
127
+ * Build the GitHub release download URL.
128
+ * @param {string} ver
129
+ * @param {string} platformKey
130
+ * @returns {string}
131
+ */
132
+ function buildDownloadUrl(ver, platformKey) {
133
+ const ext = process.platform === 'win32' ? '.zip' : '.tar.gz';
134
+ return 'https://github.com/' + GITHUB_REPO + '/releases/download/v' + ver + '/' + BINARY_NAME + '-' + platformKey + ext;
135
+ }
136
+
137
+ /**
138
+ * Download a URL to a Buffer, following up to 5 redirects.
139
+ * Supports GITHUB_TOKEN / GH_TOKEN for auth.
140
+ * @param {string} url
141
+ * @returns {Promise<Buffer>}
142
+ */
143
+ function downloadToBuffer(url) {
144
+ return new Promise(function(resolve, reject) {
145
+ const ghToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
146
+
147
+ function request(reqUrl, redirectCount) {
148
+ if (redirectCount > 5) {
149
+ reject(new Error('Too many redirects fetching from ' + url));
150
+ return;
151
+ }
152
+ const headers = {
153
+ 'User-Agent': 'agent-core/binary-resolver',
154
+ 'Accept': 'application/octet-stream'
155
+ };
156
+ if (ghToken) headers['Authorization'] = 'Bearer ' + ghToken;
157
+
158
+ https.get(reqUrl, { headers: headers }, function(res) {
159
+ const sc = res.statusCode;
160
+ if (sc === 301 || sc === 302 || sc === 307 || sc === 308) {
161
+ res.resume();
162
+ request(res.headers.location, redirectCount + 1);
163
+ return;
164
+ }
165
+ if (sc !== 200) {
166
+ res.resume();
167
+ const hint = sc === 403 ? ' (rate limited - set GITHUB_TOKEN env var)' : '';
168
+ reject(new Error('HTTP ' + sc + hint + ' fetching ' + reqUrl));
169
+ return;
170
+ }
171
+ const chunks = [];
172
+ res.on('data', function(chunk) { chunks.push(chunk); });
173
+ res.on('end', function() { resolve(Buffer.concat(chunks)); });
174
+ res.on('error', reject);
175
+ }).on('error', reject);
176
+ }
177
+
178
+ request(url, 0);
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Extract a tar.gz buffer into a directory using the system tar command.
184
+ * @param {Buffer} buf
185
+ * @param {string} destDir
186
+ * @returns {Promise<void>}
187
+ */
188
+ function extractTarGz(buf, destDir) {
189
+ return new Promise(function(resolve, reject) {
190
+ const tarDest = process.platform === 'win32' ? destDir.replace(/\\/g, '/') : destDir;
191
+ const tar = cp.spawn('tar', ['xz', '-C', tarDest], {
192
+ stdio: ['pipe', 'pipe', 'pipe']
193
+ });
194
+ let stderr = '';
195
+ tar.stderr.on('data', function(d) { stderr += d; });
196
+ tar.stdin.write(buf);
197
+ tar.stdin.end();
198
+ tar.on('close', function(code) {
199
+ if (code !== 0) {
200
+ reject(new Error('tar extraction failed (code ' + code + '): ' + stderr));
201
+ } else {
202
+ resolve();
203
+ }
204
+ });
205
+ tar.on('error', reject);
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Extract a zip buffer into a directory using PowerShell Expand-Archive (Windows).
211
+ * @param {Buffer} buf
212
+ * @param {string} destDir
213
+ * @param {string} binaryName
214
+ * @returns {Promise<void>}
215
+ */
216
+ function extractZip(buf, destDir, binaryName) {
217
+ return new Promise(function(resolve, reject) {
218
+ const tmpZip = path.join(os.tmpdir(), binaryName + '-' + Date.now() + '.zip');
219
+ fs.writeFileSync(tmpZip, buf);
220
+ const cmd = 'Expand-Archive -Path \'' + tmpZip + '\' -DestinationPath \'' + destDir + '\' -Force';
221
+ const ps = cp.spawn('powershell', ['-NoProfile', '-NonInteractive', '-Command', cmd], {
222
+ stdio: ['ignore', 'pipe', 'pipe']
223
+ });
224
+ let stderr = '';
225
+ ps.stderr.on('data', function(d) { stderr += d; });
226
+ ps.on('close', function(code) {
227
+ try { fs.unlinkSync(tmpZip); } catch (e) { /* ignore */ }
228
+ if (code !== 0) {
229
+ reject(new Error('zip extraction failed (code ' + code + '): ' + stderr));
230
+ } else {
231
+ resolve();
232
+ }
233
+ });
234
+ ps.on('error', reject);
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Download and install the binary for the current platform into ~/.agent-sh/bin/.
240
+ * @param {string} ver
241
+ * @returns {Promise<string>}
242
+ */
243
+ async function downloadBinary(ver) {
244
+ const platformKey = getPlatformKey();
245
+ if (!platformKey) {
246
+ throw new Error(
247
+ 'Unsupported platform: ' + process.platform + '-' + process.arch + '. ' +
248
+ 'Supported platforms: ' + Object.keys(PLATFORM_MAP).join(', ')
249
+ );
250
+ }
251
+
252
+ const url = buildDownloadUrl(ver, platformKey);
253
+ process.stderr.write('Downloading ' + BINARY_NAME + ' v' + ver + ' for ' + platformKey + '...' + '\n');
254
+
255
+ const binPath = getBinaryPath();
256
+ const binDir = path.dirname(binPath);
257
+ fs.mkdirSync(binDir, { recursive: true });
258
+
259
+ let buf;
260
+ try {
261
+ buf = await downloadToBuffer(url);
262
+ } catch (err) {
263
+ throw new Error(
264
+ 'Failed to download ' + BINARY_NAME + ':\n' +
265
+ ' URL: ' + url + '\n' +
266
+ ' Error: ' + err.message + '\n\n' +
267
+ 'To install manually:\n' +
268
+ ' 1. Download: ' + url + '\n' +
269
+ ' 2. Extract the binary to: ' + binDir + '\n' +
270
+ ' 3. Ensure it is named: ' + path.basename(binPath)
271
+ );
272
+ }
273
+
274
+ if (process.platform === 'win32') {
275
+ await extractZip(buf, binDir, path.basename(binPath));
276
+ } else {
277
+ await extractTarGz(buf, binDir);
278
+ }
279
+
280
+ if (process.platform !== 'win32') {
281
+ fs.chmodSync(binPath, 0o755);
282
+ }
283
+
284
+ const installedVer = getVersion();
285
+ if (!installedVer) {
286
+ throw new Error(
287
+ BINARY_NAME + ' was downloaded to ' + binPath + ' but could not be executed. ' +
288
+ 'Check the file is a valid binary for this platform.'
289
+ );
290
+ }
291
+
292
+ return binPath;
293
+ }
294
+
295
+ // ---------------------------------------------------------------------------
296
+ // Public API
297
+ // ---------------------------------------------------------------------------
298
+
299
+ /**
300
+ * Ensure the binary exists and meets the minimum version. Downloads if needed.
301
+ * @param {Object} [options]
302
+ * @param {string} [options.version]
303
+ * @returns {Promise<string>}
304
+ */
305
+ async function ensureBinary(options) {
306
+ const opts = options || {};
307
+ const targetVer = opts.version || ANALYZER_MIN_VERSION;
308
+ const binPath = getBinaryPath();
309
+
310
+ if (fs.existsSync(binPath)) {
311
+ const ver = getVersion();
312
+ if (meetsMinimumVersion(ver, ANALYZER_MIN_VERSION)) {
313
+ return binPath;
314
+ }
315
+ }
316
+
317
+ return downloadBinary(targetVer);
318
+ }
319
+
320
+ /**
321
+ * Sync version of ensureBinary. Downloads if needed via a child node process.
322
+ * Prefer ensureBinary() unless a sync API is strictly required.
323
+ * @param {Object} [options]
324
+ * @param {string} [options.version]
325
+ * @returns {string}
326
+ */
327
+ function ensureBinarySync(options) {
328
+ const binPath = getBinaryPath();
329
+
330
+ if (fs.existsSync(binPath)) {
331
+ const ver = getVersion();
332
+ if (meetsMinimumVersion(ver, ANALYZER_MIN_VERSION)) {
333
+ return binPath;
334
+ }
335
+ }
336
+
337
+ const targetVer = (options && options.version) || ANALYZER_MIN_VERSION;
338
+ const selfPath = __filename;
339
+ const helperLines = [
340
+ 'var b = require(' + JSON.stringify(selfPath) + ');',
341
+ 'b.ensureBinary({ version: ' + JSON.stringify(targetVer) + ' })',
342
+ ' .then(function(p) { process.stdout.write(p); })',
343
+ ' .catch(function(e) { process.stderr.write(e.message); process.exit(1); });'
344
+ ];
345
+
346
+ try {
347
+ const result = cp.execFileSync(process.execPath, ['-e', helperLines.join('\n')], {
348
+ encoding: 'utf8',
349
+ stdio: ['pipe', 'pipe', 'inherit'],
350
+ timeout: 120000
351
+ });
352
+ return result.trim() || binPath;
353
+ } catch (err) {
354
+ throw new Error('Failed to ensure binary (sync): ' + err.message);
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Run agent-analyzer with the given arguments (sync). Downloads binary if needed.
360
+ * @param {string[]} args
361
+ * @param {Object} [options]
362
+ * @returns {string}
363
+ */
364
+ function runAnalyzer(args, options) {
365
+ const binPath = ensureBinarySync();
366
+ const opts = Object.assign({ encoding: 'utf8', windowsHide: true }, options);
367
+ if (!opts.stdio) opts.stdio = ['pipe', 'pipe', 'pipe'];
368
+ const result = cp.execFileSync(binPath, args, opts);
369
+ return typeof result === 'string' ? result : result.toString('utf8');
370
+ }
371
+
372
+ /**
373
+ * Run agent-analyzer with the given arguments asynchronously. Downloads binary if needed.
374
+ * @param {string[]} args
375
+ * @param {Object} [options]
376
+ * @returns {Promise<string>}
377
+ */
378
+ async function runAnalyzerAsync(args, options) {
379
+ const binPath = await ensureBinary();
380
+ const opts = Object.assign({ encoding: 'utf8', windowsHide: true }, options);
381
+ const result = await execFileAsync(binPath, args, opts);
382
+ return result.stdout;
383
+ }
384
+
385
+ module.exports = {
386
+ ensureBinary,
387
+ ensureBinarySync,
388
+ runAnalyzer,
389
+ runAnalyzerAsync,
390
+ getBinaryPath,
391
+ getVersion,
392
+ getPlatformKey,
393
+ isAvailable,
394
+ isAvailableAsync,
395
+ meetsMinimumVersion,
396
+ buildDownloadUrl,
397
+ PLATFORM_MAP
398
+ };
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ // Minimum binary version required by this version of agent-core
4
+ const ANALYZER_MIN_VERSION = '0.1.0';
5
+
6
+ // Binary name
7
+ const BINARY_NAME = 'agent-analyzer';
8
+
9
+ // GitHub repo for releases
10
+ const GITHUB_REPO = 'agent-sh/agent-analyzer';
11
+
12
+ module.exports = {
13
+ ANALYZER_MIN_VERSION,
14
+ BINARY_NAME,
15
+ GITHUB_REPO
16
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Git History Collector
3
+ *
4
+ * Collects git history analysis data using the agent-analyzer binary.
5
+ * Runs a full repo-intel init and extracts key metrics for downstream consumers.
6
+ *
7
+ * @module lib/collectors/git
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const binary = require('../binary');
13
+
14
+ const DEFAULT_OPTIONS = {
15
+ top: 20,
16
+ adjustForAi: false,
17
+ cwd: process.cwd()
18
+ };
19
+
20
+ /**
21
+ * Collect git history data for the given repository.
22
+ *
23
+ * Runs agent-analyzer repo-intel init to produce a full map, then extracts
24
+ * key metrics (hotspots, bus factor, AI ratio, etc.) from the result.
25
+ *
26
+ * @param {Object} [options={}] - Collection options
27
+ * @param {string} [options.cwd] - Repository path (default: process.cwd())
28
+ * @param {number} [options.top=20] - Number of hotspots to return
29
+ * @param {boolean} [options.adjustForAi=false] - Adjust bus factor for AI commits
30
+ * @returns {Object} Git history metrics
31
+ */
32
+ function collectGitData(options = {}) {
33
+ const opts = { ...DEFAULT_OPTIONS, ...options };
34
+ const cwd = opts.cwd || process.cwd();
35
+
36
+ try {
37
+ binary.ensureBinarySync();
38
+ } catch (err) {
39
+ return {
40
+ available: false,
41
+ error: `Binary not available: ${err.message}`
42
+ };
43
+ }
44
+
45
+ let map;
46
+ try {
47
+ const json = binary.runAnalyzer(['repo-intel', 'init', cwd]);
48
+ map = JSON.parse(json);
49
+ } catch (err) {
50
+ return {
51
+ available: false,
52
+ error: `Git analysis failed: ${err.message}`
53
+ };
54
+ }
55
+
56
+ // Extract metrics from the map
57
+ const fileActivity = map.fileActivity || {};
58
+ const contributors = map.contributors || {};
59
+ const aiAttribution = map.aiAttribution || {};
60
+ const conventions = map.conventions || {};
61
+ const releases = map.releases || {};
62
+
63
+ // Hotspots: sort files by change count
64
+ const hotspots = Object.entries(fileActivity)
65
+ .map(([path, activity]) => ({
66
+ path,
67
+ changes: activity.totalChanges || 0,
68
+ recentChanges: activity.recentChanges || 0,
69
+ authors: activity.authors ? Object.keys(activity.authors).length : 0,
70
+ lastChanged: activity.lastChanged || null
71
+ }))
72
+ .sort((a, b) => b.changes - a.changes)
73
+ .slice(0, opts.top);
74
+
75
+ // Contributors summary
76
+ const humans = contributors.humans || {};
77
+ const humanList = Object.entries(humans)
78
+ .map(([name, data]) => ({
79
+ name,
80
+ commits: data.commitCount || 0,
81
+ firstSeen: data.firstSeen || null,
82
+ lastSeen: data.lastSeen || null
83
+ }))
84
+ .sort((a, b) => b.commits - a.commits);
85
+
86
+ // Bus factor: people covering 80% of commits
87
+ const totalCommits = humanList.reduce((sum, c) => sum + c.commits, 0);
88
+ let cumulative = 0;
89
+ let busFactor = 0;
90
+ for (const contributor of humanList) {
91
+ cumulative += contributor.commits;
92
+ busFactor++;
93
+ if (cumulative >= totalCommits * 0.8) break;
94
+ }
95
+
96
+ // AI ratio
97
+ const aiTotal = (aiAttribution.attributed || 0) + (aiAttribution.heuristic || 0);
98
+ const allCommits = map.git?.totalCommitsAnalyzed || totalCommits;
99
+ const aiRatio = allCommits > 0 ? aiTotal / allCommits : 0;
100
+
101
+ return {
102
+ available: true,
103
+ health: {
104
+ active: humanList.length > 0,
105
+ busFactor,
106
+ aiRatio: Math.round(aiRatio * 100) / 100,
107
+ totalCommits: allCommits,
108
+ totalContributors: humanList.length
109
+ },
110
+ hotspots,
111
+ contributors: humanList.slice(0, 10),
112
+ aiAttribution: {
113
+ ratio: Math.round(aiRatio * 100) / 100,
114
+ attributed: aiAttribution.attributed || 0,
115
+ heuristic: aiAttribution.heuristic || 0,
116
+ none: aiAttribution.none || 0,
117
+ confidence: aiAttribution.confidence || 'low',
118
+ tools: aiAttribution.tools || {}
119
+ },
120
+ busFactor,
121
+ conventions: {
122
+ style: conventions.style || null,
123
+ prefixes: conventions.prefixes || {},
124
+ usesScopes: conventions.usesScopes || false
125
+ },
126
+ releaseInfo: {
127
+ tagCount: releases.tags ? releases.tags.length : 0,
128
+ lastRelease: releases.tags && releases.tags.length > 0
129
+ ? releases.tags[releases.tags.length - 1]
130
+ : null,
131
+ cadence: releases.cadence || null
132
+ }
133
+ };
134
+ }
135
+
136
+ module.exports = {
137
+ collectGitData,
138
+ DEFAULT_OPTIONS
139
+ };
@@ -13,6 +13,7 @@ const github = require('./github');
13
13
  const documentation = require('./documentation');
14
14
  const codebase = require('./codebase');
15
15
  const docsPatterns = require('./docs-patterns');
16
+ const git = require('./git');
16
17
 
17
18
  const DEFAULT_OPTIONS = {
18
19
  collectors: ['github', 'docs', 'code'],
@@ -50,7 +51,8 @@ function collect(options = {}) {
50
51
  github: null,
51
52
  docs: null,
52
53
  code: null,
53
- docsPatterns: null
54
+ docsPatterns: null,
55
+ git: null
54
56
  };
55
57
 
56
58
  // Collect from each enabled collector
@@ -70,6 +72,10 @@ function collect(options = {}) {
70
72
  data.docsPatterns = docsPatterns.collect(opts);
71
73
  }
72
74
 
75
+ if (collectors.includes('git')) {
76
+ data.git = git.collectGitData(opts);
77
+ }
78
+
73
79
  return data;
74
80
  }
75
81
 
@@ -103,6 +109,7 @@ module.exports = {
103
109
  documentation,
104
110
  codebase,
105
111
  docsPatterns,
112
+ git,
106
113
 
107
114
  // Re-export commonly used functions for convenience
108
115
  scanGitHubState: github.scanGitHubState,
@@ -121,6 +128,8 @@ module.exports = {
121
128
  isInternalExport: docsPatterns.isInternalExport,
122
129
  isEntryPoint: docsPatterns.isEntryPoint,
123
130
 
131
+ collectGitData: git.collectGitData,
132
+
124
133
  // Constants
125
134
  DEFAULT_OPTIONS
126
135
  };
@@ -5,8 +5,6 @@
5
5
  * - Claude Code (Anthropic)
6
6
  * - OpenCode (multi-model)
7
7
  * - Codex CLI (OpenAI)
8
- * - Cursor (AI-first editor)
9
- * - Kiro (AWS agentic IDE)
10
8
  *
11
9
  * Based on research from official documentation:
12
10
  * - Anthropic Claude 4 Best Practices
@@ -46,9 +44,7 @@ function compareSemver(a, b) {
46
44
  const PLATFORMS = {
47
45
  CLAUDE_CODE: 'claude-code',
48
46
  OPENCODE: 'opencode',
49
- CODEX_CLI: 'codex-cli',
50
- CURSOR: 'cursor',
51
- KIRO: 'kiro'
47
+ CODEX_CLI: 'codex-cli'
52
48
  };
53
49
 
54
50
  /**
@@ -58,9 +54,7 @@ const PLATFORMS = {
58
54
  const STATE_DIRS = {
59
55
  [PLATFORMS.CLAUDE_CODE]: '.claude',
60
56
  [PLATFORMS.OPENCODE]: '.opencode',
61
- [PLATFORMS.CODEX_CLI]: '.codex',
62
- [PLATFORMS.CURSOR]: '.cursor',
63
- [PLATFORMS.KIRO]: '.kiro'
57
+ [PLATFORMS.CODEX_CLI]: '.codex'
64
58
  };
65
59
 
66
60
  /**
@@ -82,8 +76,6 @@ function detectPlatform() {
82
76
  const stateDir = process.env.AI_STATE_DIR;
83
77
  if (stateDir === '.opencode') return PLATFORMS.OPENCODE;
84
78
  if (stateDir === '.codex') return PLATFORMS.CODEX_CLI;
85
- if (stateDir === '.cursor') return PLATFORMS.CURSOR;
86
- if (stateDir === '.kiro') return PLATFORMS.KIRO;
87
79
  return PLATFORMS.CLAUDE_CODE;
88
80
  }
89
81
 
@@ -491,9 +483,7 @@ enabled = true
491
483
  const INSTRUCTION_FILES = {
492
484
  [PLATFORMS.CLAUDE_CODE]: ['CLAUDE.md', '.claude/CLAUDE.md'],
493
485
  [PLATFORMS.OPENCODE]: ['AGENTS.md', 'CLAUDE.md'],
494
- [PLATFORMS.CODEX_CLI]: ['AGENTS.md', 'AGENTS.override.md'],
495
- [PLATFORMS.CURSOR]: ['.cursor/rules/*.mdc'],
496
- [PLATFORMS.KIRO]: ['AGENTS.md', '.kiro/steering/*.md']
486
+ [PLATFORMS.CODEX_CLI]: ['AGENTS.md', 'AGENTS.override.md']
497
487
  };
498
488
 
499
489
  /**