agentsys 5.4.1 → 5.6.4

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.
@@ -163,14 +163,11 @@ function discoverCommands(repoRoot) {
163
163
 
164
164
  const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md')).sort();
165
165
  for (const file of files) {
166
- const baseName = file.replace(/\.md$/, '');
167
- // Validate filename contains only safe characters (no path traversal)
168
- if (!/^[a-zA-Z0-9_-]+$/.test(baseName)) continue;
169
166
  const filePath = path.join(commandsDir, file);
170
167
  const content = fs.readFileSync(filePath, 'utf8');
171
168
  const frontmatter = parseFrontmatter(content);
172
169
  commands.push({
173
- name: baseName,
170
+ name: file.replace(/\.md$/, ''),
174
171
  plugin,
175
172
  file,
176
173
  frontmatter
@@ -340,48 +337,6 @@ function invalidateCache() {
340
337
  _cacheRoot = null;
341
338
  }
342
339
 
343
- /**
344
- * Build Cursor rule mappings from discovered commands.
345
- * Returns [ruleName, pluginName, sourceFile, description, type, globs] tuples.
346
- * Uses cursor-description frontmatter field, falls back to codex-description, then description.
347
- *
348
- * @param {string} [repoRoot] - Repository root path
349
- * @returns {Array<[string, string, string, string, string, string]>}
350
- */
351
- function getCursorRuleMappings(repoRoot) {
352
- const commands = discoverCommands(repoRoot);
353
- return commands.map(cmd => {
354
- const description = cmd.frontmatter['cursor-description'] ||
355
- cmd.frontmatter['codex-description'] ||
356
- cmd.frontmatter.description ||
357
- '';
358
- const type = cmd.frontmatter.type || 'command';
359
- const globs = cmd.frontmatter.globs || '';
360
- return [`agentsys-${cmd.plugin}-${cmd.name}`, cmd.plugin, cmd.file, description, type, globs];
361
- });
362
- }
363
-
364
- /**
365
- * Build Kiro steering mappings from discovered commands.
366
- * Returns [steeringName, pluginName, sourceFile, description] tuples.
367
- * Uses kiro-description frontmatter field, falls back to cursor-description,
368
- * codex-description, then description.
369
- *
370
- * @param {string} [repoRoot] - Repository root path
371
- * @returns {Array<[string, string, string, string]>}
372
- */
373
- function getKiroSteeringMappings(repoRoot) {
374
- const commands = discoverCommands(repoRoot);
375
- return commands.map(cmd => {
376
- const description = cmd.frontmatter['kiro-description'] ||
377
- cmd.frontmatter['cursor-description'] ||
378
- cmd.frontmatter['codex-description'] ||
379
- cmd.frontmatter.description ||
380
- '';
381
- return [cmd.name, cmd.plugin, cmd.file, description];
382
- });
383
- }
384
-
385
340
  module.exports = {
386
341
  parseFrontmatter,
387
342
  isValidPluginName,
@@ -392,8 +347,6 @@ module.exports = {
392
347
  discoverAll,
393
348
  getCommandMappings,
394
349
  getCodexSkillMappings,
395
- getCursorRuleMappings,
396
- getKiroSteeringMappings,
397
350
  getPluginPrefixRegex,
398
351
  invalidateCache
399
352
  };
package/lib/index.js CHANGED
@@ -29,6 +29,7 @@ const repoMap = require('./repo-map');
29
29
  const perf = require('./perf');
30
30
  const collectors = require('./collectors');
31
31
  const discoveryModule = require('./discovery');
32
+ const binary = require('./binary');
32
33
 
33
34
  /**
34
35
  * Platform detection and verification utilities
@@ -253,6 +254,7 @@ module.exports = {
253
254
  perf,
254
255
  collectors,
255
256
  discovery,
257
+ binary,
256
258
 
257
259
  // Direct module access for backward compatibility
258
260
  detectPlatform,
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  const { execSync, execFileSync } = require('child_process');
16
- const os = require('os');
17
16
  const path = require('path');
18
17
  const fs = require('fs');
19
18
  // Note: escapeDoubleQuotes no longer needed - using execFileSync with arg arrays
@@ -346,20 +345,16 @@ function runDuplicateDetection(repoPath, options = {}) {
346
345
  const minLines = options.minLines || 5;
347
346
  const minTokens = options.minTokens || 50;
348
347
 
349
- // Use temp directory for jscpd output to avoid platform-specific null device issues.
350
- // On Windows, NUL passed via execFileSync (no shell) is treated as a literal filename,
351
- // creating a mangled path in the working directory.
352
- let tempOutputDir;
353
348
  try {
354
- tempOutputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jscpd-'));
355
349
  // Run jscpd with JSON output
356
350
  // Use execFileSync with arg array to prevent command injection (no shell interpretation)
351
+ const outputPath = process.platform === 'win32' ? 'NUL' : '/dev/null';
357
352
  const args = [
358
353
  repoPath,
359
354
  '--min-lines', String(minLines),
360
355
  '--min-tokens', String(minTokens),
361
356
  '--reporters', 'json',
362
- '--output', tempOutputDir,
357
+ '--output', outputPath,
363
358
  '--silent'
364
359
  ];
365
360
 
@@ -398,10 +393,6 @@ function runDuplicateDetection(repoPath, options = {}) {
398
393
  } catch {
399
394
  // Tool execution failed
400
395
  return null;
401
- } finally {
402
- if (tempOutputDir) {
403
- try { fs.rmSync(tempOutputDir, { recursive: true, force: true }); } catch {}
404
- }
405
396
  }
406
397
  }
407
398
 
@@ -32,12 +32,10 @@ function isDirectory(targetPath) {
32
32
  * 1. AI_STATE_DIR env var (user override)
33
33
  * 2. OpenCode detection (OPENCODE_CONFIG env or .opencode/ exists)
34
34
  * 3. Codex detection (CODEX_HOME env or .codex/ exists)
35
- * 4. Kiro detection (.kiro/ exists)
36
- * 5. Cursor detection (.cursor/ exists)
37
- * 6. Default to .claude (Claude Code or unknown)
35
+ * 4. Default to .claude (Claude Code or unknown)
38
36
  *
39
37
  * @param {string} [basePath=process.cwd()] - Base path to check for project directories
40
- * @returns {string} State directory name (e.g., '.claude', '.opencode', '.codex', '.kiro')
38
+ * @returns {string} State directory name (e.g., '.claude', '.opencode', '.codex')
41
39
  */
42
40
  function getStateDir(basePath = process.cwd()) {
43
41
  // Check user override first
@@ -85,17 +83,6 @@ function getStateDir(basePath = process.cwd()) {
85
83
  // Ignore errors, continue detection
86
84
  }
87
85
 
88
- // Kiro detection
89
- try {
90
- const kiroPath = path.join(basePath, '.kiro');
91
- if (isDirectory(kiroPath)) {
92
- _cachedStateDirs.set(cacheKey, '.kiro');
93
- return '.kiro';
94
- }
95
- } catch {
96
- // Ignore errors, continue detection
97
- }
98
-
99
86
  // Default to Claude Code
100
87
  _cachedStateDirs.set(cacheKey, '.claude');
101
88
  return '.claude';
@@ -125,7 +112,6 @@ function getPlatformName(basePath = process.cwd()) {
125
112
  switch (stateDir) {
126
113
  case '.opencode': return 'opencode';
127
114
  case '.codex': return 'codex';
128
- case '.kiro': return 'kiro';
129
115
  case '.claude': return 'claude';
130
116
  default: return 'unknown';
131
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentsys",
3
- "version": "5.4.1",
3
+ "version": "5.6.4",
4
4
  "description": "A modular runtime and orchestration system for AI agents - works with Claude Code, OpenCode, and Codex CLI",
5
5
  "main": "lib/platform/detect-platform.js",
6
6
  "type": "commonjs",
@@ -120,11 +120,13 @@ function computeAdapters() {
120
120
  process.exit(1);
121
121
  }
122
122
 
123
- // --- Kiro adapters ---
123
+ // --- Kiro adapters (disabled - Kiro adapter generation removed) ---
124
124
  const KIRO_PLUGIN_ROOT_PLACEHOLDER = '{{PLUGIN_INSTALL_PATH}}';
125
125
 
126
126
  // Kiro steering files (from commands)
127
- const kiroSteeringMappings = discovery.getKiroSteeringMappings(ROOT_DIR);
127
+ const kiroSteeringMappings = typeof discovery.getKiroSteeringMappings === 'function'
128
+ ? discovery.getKiroSteeringMappings(ROOT_DIR)
129
+ : [];
128
130
  for (const [steeringName, plugin, sourceFile, description] of kiroSteeringMappings) {
129
131
  const srcPath = path.join(ROOT_DIR, 'plugins', plugin, 'commands', sourceFile);
130
132
  if (!fs.existsSync(srcPath)) continue;
@@ -53,12 +53,62 @@ const CATEGORY_MAP = {
53
53
  'sync-docs': 'Cleanup',
54
54
  'drift-detect': 'Analysis',
55
55
  'repo-map': 'Analysis',
56
- 'learn': 'Learning',
56
+ 'learn': 'AI Collaboration',
57
57
  'agnix': 'Linting',
58
- 'consult': 'Productivity',
59
- 'debate': 'Analysis'
58
+ 'consult': 'AI Collaboration',
59
+ 'debate': 'AI Collaboration',
60
+ 'skillers': 'AI Collaboration',
61
+ 'web-ctl': 'Web',
62
+ 'ship': 'Release',
63
+ 'git-map': 'Analysis',
64
+ 'onboard': 'Onboarding',
65
+ 'can-i-help': 'Onboarding',
66
+ 'audit-project': 'Code Review'
60
67
  };
61
68
 
69
+ // Static skill definitions for cross-repo plugins (not discoverable locally)
70
+ const STATIC_SKILLS = [
71
+ { plugin: 'next-task', name: 'orchestrate-review' },
72
+ { plugin: 'next-task', name: 'discover-tasks' },
73
+ { plugin: 'next-task', name: 'validate-delivery' },
74
+ { plugin: 'enhance', name: 'enhance-orchestrator' },
75
+ { plugin: 'enhance', name: 'enhance-plugins' },
76
+ { plugin: 'enhance', name: 'enhance-agent-prompts' },
77
+ { plugin: 'enhance', name: 'enhance-claude-memory' },
78
+ { plugin: 'enhance', name: 'enhance-docs' },
79
+ { plugin: 'enhance', name: 'enhance-prompts' },
80
+ { plugin: 'enhance', name: 'enhance-hooks' },
81
+ { plugin: 'enhance', name: 'enhance-skills' },
82
+ { plugin: 'enhance', name: 'enhance-cross-file' },
83
+ { plugin: 'perf', name: 'baseline' },
84
+ { plugin: 'perf', name: 'benchmark' },
85
+ { plugin: 'perf', name: 'profile' },
86
+ { plugin: 'perf', name: 'theory-tester' },
87
+ { plugin: 'perf', name: 'theory-gatherer' },
88
+ { plugin: 'perf', name: 'code-paths' },
89
+ { plugin: 'perf', name: 'investigation-logger' },
90
+ { plugin: 'perf', name: 'perf-analyzer' },
91
+ { plugin: 'deslop', name: 'deslop' },
92
+ { plugin: 'sync-docs', name: 'sync-docs' },
93
+ { plugin: 'drift-detect', name: 'drift-analysis' },
94
+ { plugin: 'repo-map', name: 'repo-mapping' },
95
+ { plugin: 'consult', name: 'consult' },
96
+ { plugin: 'debate', name: 'debate' },
97
+ { plugin: 'learn', name: 'learn' },
98
+ { plugin: 'web-ctl', name: 'web-auth' },
99
+ { plugin: 'web-ctl', name: 'web-browse' },
100
+ { plugin: 'ship', name: 'release' },
101
+ { plugin: 'skillers', name: 'skillers-compact' },
102
+ { plugin: 'skillers', name: 'recommend' },
103
+ { plugin: 'git-map', name: 'git-mapping' },
104
+ { plugin: 'onboard', name: 'onboard' },
105
+ { plugin: 'can-i-help', name: 'can-i-help' },
106
+ { plugin: 'audit-project', name: 'audit-project' },
107
+ { plugin: 'glidemq', name: 'glide-mq' },
108
+ { plugin: 'glidemq', name: 'glide-mq-migrate-bullmq' },
109
+ { plugin: 'glidemq', name: 'glide-mq-migrate-bee' }
110
+ ];
111
+
62
112
  // Purpose mapping for architecture table
63
113
  const PURPOSE_MAP = {
64
114
  'next-task': 'Master workflow orchestration',
@@ -73,7 +123,12 @@ const PURPOSE_MAP = {
73
123
  'learn': 'Topic research and learning guides',
74
124
  'agnix': 'Agent config linting',
75
125
  'consult': 'Cross-tool AI consultation',
76
- 'debate': 'Multi-perspective debate analysis'
126
+ 'debate': 'Multi-perspective debate analysis',
127
+ 'web-ctl': 'Browser automation for AI agents',
128
+ 'skillers': 'Workflow pattern learning',
129
+ 'git-map': 'Git history analysis',
130
+ 'onboard': 'Codebase onboarding',
131
+ 'can-i-help': 'Contributor guidance'
77
132
  };
78
133
 
79
134
  // ---------------------------------------------------------------------------
@@ -122,22 +177,32 @@ function generateCommandsTable(commands) {
122
177
  const COMMAND_ORDER = [
123
178
  'next-task', 'agnix', 'ship', 'deslop', 'perf',
124
179
  'drift-detect', 'audit-project', 'enhance',
125
- 'repo-map', 'sync-docs', 'learn'
180
+ 'repo-map', 'sync-docs', 'learn', 'consult',
181
+ 'debate', 'web-ctl', 'release', 'skillers',
182
+ 'git-map', 'onboard', 'can-i-help'
126
183
  ];
127
184
 
128
185
  // Command descriptions for the table (short, human-written summaries)
129
186
  const COMMAND_SUMMARIES = {
130
- 'next-task': 'Task \u2192 exploration \u2192 plan \u2192 implementation \u2192 review \u2192 ship',
131
- 'agnix': '**Lint agent configs** - 155 rules for Skills, Memory, Hooks, MCP across 10+ AI tools',
132
- 'ship': 'Branch \u2192 PR \u2192 CI \u2192 reviews addressed \u2192 merge \u2192 cleanup',
133
- 'deslop': '3-phase detection pipeline, certainty-graded findings',
134
- 'perf': '10-phase performance investigation with baselines and profiling',
135
- 'drift-detect': 'AST-based plan vs code analysis, finds what\'s documented but not built',
136
- 'audit-project': 'Multi-agent code review, iterates until issues resolved',
137
- 'enhance': 'Analyzes prompts, agents, plugins, docs, hooks, skills',
138
- 'repo-map': 'AST symbol and import mapping via ast-grep',
139
- 'sync-docs': 'Finds outdated references, stale examples, missing CHANGELOG entries',
140
- 'learn': 'Research any topic, gather online sources, create learning guide with RAG index'
187
+ 'next-task': 'Task workflow: discovery, implementation, PR, merge',
188
+ 'agnix': 'Lint agent configurations (342 rules)',
189
+ 'ship': 'PR creation, CI monitoring, merge',
190
+ 'deslop': 'Clean AI slop patterns',
191
+ 'perf': 'Performance investigation with baselines and profiling',
192
+ 'drift-detect': 'Compare plan vs implementation',
193
+ 'audit-project': 'Multi-agent iterative code review',
194
+ 'enhance': 'Plugin, agent, and prompt analyzers',
195
+ 'repo-map': 'AST-based repository map',
196
+ 'sync-docs': 'Sync documentation with code changes',
197
+ 'learn': 'Research topics, create learning guides',
198
+ 'consult': 'Cross-tool AI consultation',
199
+ 'debate': 'Structured debate between AI tools',
200
+ 'web-ctl': 'Browser automation for AI agents',
201
+ 'release': 'Versioned release with ecosystem detection',
202
+ 'skillers': 'Workflow pattern learning and automation',
203
+ 'git-map': 'Git history analysis: hotspots, coupling, ownership, bus factor',
204
+ 'onboard': 'Codebase orientation for newcomers',
205
+ 'can-i-help': 'Match contributor skills to project needs'
141
206
  };
142
207
 
143
208
  // Build lookup of discovered commands
@@ -146,12 +211,15 @@ function generateCommandsTable(commands) {
146
211
  if (!cmdMap[cmd.name]) cmdMap[cmd.name] = cmd;
147
212
  }
148
213
 
149
- // Emit in curated order, then any new commands not in the list
214
+ // Emit in curated order. Prefer curated summaries; fall back to discovered frontmatter.
150
215
  const emitted = new Set();
151
216
  for (const name of COMMAND_ORDER) {
152
- if (!cmdMap[name]) continue;
217
+ let summary = COMMAND_SUMMARIES[name] || '';
218
+ if (!summary && cmdMap[name]) {
219
+ summary = cmdMap[name].frontmatter.description || '';
220
+ }
221
+ if (!summary && !cmdMap[name]) continue;
153
222
  emitted.add(name);
154
- const summary = COMMAND_SUMMARIES[name] || cmdMap[name].frontmatter.description || '';
155
223
  lines.push(`| [\`/${name}\`](#${name}) | ${summary} |`);
156
224
  }
157
225
 
@@ -171,14 +239,16 @@ function generateCommandsTable(commands) {
171
239
  * Generate the skills table for README.md grouped by category.
172
240
  */
173
241
  function generateSkillsTable(skills) {
174
- const totalSkills = skills.length;
242
+ // Use static skills as fallback when discovery finds nothing (cross-repo plugins)
243
+ const effectiveSkills = skills.length > 0 ? skills : STATIC_SKILLS;
244
+ const totalSkills = effectiveSkills.length;
175
245
 
176
246
  // Group skills by category
177
247
  const groups = {};
178
- for (const skill of skills) {
248
+ for (const skill of effectiveSkills) {
179
249
  const category = CATEGORY_MAP[skill.plugin] || 'Other';
180
250
  if (!groups[category]) groups[category] = [];
181
- groups[category].push(`\`${skill.plugin}:${skill.name}\``);
251
+ groups[category].push(`\`${skill.name}\``);
182
252
  }
183
253
 
184
254
  // Sort skills within each group
@@ -188,8 +258,9 @@ function generateSkillsTable(skills) {
188
258
 
189
259
  // Defined category order
190
260
  const categoryOrder = [
191
- 'Performance', 'Enhancement', 'Workflow', 'Cleanup',
192
- 'Analysis', 'Productivity', 'Learning', 'Linting', 'Other'
261
+ 'Workflow', 'Enhancement', 'Performance', 'Cleanup',
262
+ 'Code Review', 'AI Collaboration', 'Onboarding',
263
+ 'Web', 'Release', 'Analysis', 'Linting', 'Other'
193
264
  ];
194
265
 
195
266
  const lines = [
@@ -318,26 +389,43 @@ function generateAgentCounts(agents, plugins) {
318
389
  /**
319
390
  * Update counts in site/content.json programmatically.
320
391
  */
392
+ // Static counts for cross-repo plugins not discoverable locally
393
+ const STATIC_PLUGIN_COUNT = 19;
394
+ const STATIC_AGENT_COUNT = 38;
395
+
321
396
  function updateSiteContent(plugins, agents, skills) {
322
397
  const contentPath = path.join(ROOT_DIR, 'site', 'content.json');
323
398
  if (!fs.existsSync(contentPath)) return null;
324
399
 
325
400
  const content = JSON.parse(fs.readFileSync(contentPath, 'utf8'));
326
- const totalAgents = agents.length + ROLE_BASED_AGENT_COUNT;
401
+
402
+ // Sync meta.version from package.json
403
+ const pkgPath = path.join(ROOT_DIR, 'package.json');
404
+ if (fs.existsSync(pkgPath)) {
405
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
406
+ if (content.meta && pkg.version) {
407
+ content.meta.version = pkg.version;
408
+ }
409
+ }
410
+
411
+ // Use static counts as fallback (cross-repo plugins not discoverable locally)
412
+ const effectivePlugins = plugins.length > 0 ? plugins.length : STATIC_PLUGIN_COUNT;
413
+ const effectiveAgents = agents.length > 0 ? agents.length + ROLE_BASED_AGENT_COUNT : STATIC_AGENT_COUNT;
414
+ const effectiveSkills = skills.length > 0 ? skills.length : STATIC_SKILLS.length;
327
415
 
328
416
  // Update stats array
329
417
  if (content.stats && Array.isArray(content.stats)) {
330
418
  for (const stat of content.stats) {
331
- if (stat.label === 'Plugins') stat.value = String(plugins.length);
332
- if (stat.label === 'Agents') stat.value = String(totalAgents);
333
- if (stat.label === 'Skills') stat.value = String(skills.length);
419
+ if (stat.label === 'Plugins') stat.value = String(effectivePlugins);
420
+ if (stat.label === 'Agents') stat.value = String(effectiveAgents);
421
+ if (stat.label === 'Skills') stat.value = String(effectiveSkills);
334
422
  }
335
423
  }
336
424
 
337
425
  // Update agents section
338
426
  if (content.agents) {
339
- content.agents.total = totalAgents;
340
- content.agents.file_based = agents.length;
427
+ content.agents.total = effectiveAgents;
428
+ content.agents.file_based = effectiveAgents - ROLE_BASED_AGENT_COUNT;
341
429
  content.agents.role_based = ROLE_BASED_AGENT_COUNT;
342
430
  }
343
431
 
@@ -556,5 +644,8 @@ module.exports = {
556
644
  updateSiteContent,
557
645
  CATEGORY_MAP,
558
646
  PURPOSE_MAP,
559
- ROLE_BASED_AGENT_COUNT
647
+ ROLE_BASED_AGENT_COUNT,
648
+ STATIC_SKILLS,
649
+ STATIC_PLUGIN_COUNT,
650
+ STATIC_AGENT_COUNT
560
651
  };
@@ -241,7 +241,7 @@ function formatCountMismatch(file, metric, expected, actual) {
241
241
  */
242
242
  function runValidation() {
243
243
  // When plugins/ doesn't exist, counts are not meaningful — return ok
244
- if (!fs.existsSync(path.join(REPO_ROOT, 'plugins'))) {
244
+ if (!fs.existsSync(path.join(REPO_ROOT, 'plugins')) || fs.readdirSync(path.join(REPO_ROOT, 'plugins')).filter(f => fs.statSync(path.join(REPO_ROOT, 'plugins', f)).isDirectory()).length === 0) {
245
245
  return {
246
246
  status: 'ok',
247
247
  message: 'plugins/ not present (extracted to standalone repos)',
@@ -251,11 +251,14 @@ function validateFeatureParity() {
251
251
  });
252
252
 
253
253
  // Check that all platforms document all required features
254
+ // Features documented in general docs (README.md) count for all platforms
255
+ const generalFeatures = featuresByPlatform.general || new Set();
256
+
254
257
  REQUIRED_FEATURES.forEach(feature => {
255
258
  Object.entries(featuresByPlatform).forEach(([platform, features]) => {
256
- if (platform === 'general') return; // General docs don't need to list all features
259
+ if (platform === 'general') return;
257
260
 
258
- if (!features.has(feature)) {
261
+ if (!features.has(feature) && !generalFeatures.has(feature)) {
259
262
  issues.push({
260
263
  platform,
261
264
  feature,