agentsys 5.0.0 → 5.0.2

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 (202) hide show
  1. package/.claude-plugin/marketplace.json +13 -13
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +19 -2
  4. package/README.md +1 -0
  5. package/adapters/README.md +1 -1
  6. package/adapters/codex/skills/consult/SKILL.md +3 -2
  7. package/adapters/codex/skills/next-task/SKILL.md +8 -8
  8. package/adapters/opencode/agents/consult-agent.md +1 -1
  9. package/adapters/opencode/agents/delivery-validator.md +1 -1
  10. package/adapters/opencode/agents/implementation-agent.md +1 -1
  11. package/adapters/opencode/agents/worktree-manager.md +3 -3
  12. package/adapters/opencode/commands/consult.md +3 -2
  13. package/adapters/opencode/commands/next-task.md +7 -7
  14. package/adapters/opencode/skills/agnix/SKILL.md +1 -1
  15. package/adapters/opencode/skills/consult/SKILL.md +16 -4
  16. package/adapters/opencode/skills/deslop/SKILL.md +1 -1
  17. package/adapters/opencode/skills/discover-tasks/SKILL.md +2 -2
  18. package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -1
  19. package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -1
  20. package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -1
  21. package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -1
  22. package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -1
  23. package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -1
  24. package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -1
  25. package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -1
  26. package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -1
  27. package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -1
  28. package/adapters/opencode/skills/learn/SKILL.md +1 -1
  29. package/adapters/opencode/skills/orchestrate-review/SKILL.md +1 -1
  30. package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -1
  31. package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -1
  32. package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -1
  33. package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -1
  34. package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -1
  35. package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -1
  36. package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -1
  37. package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -1
  38. package/adapters/opencode/skills/sync-docs/SKILL.md +1 -1
  39. package/adapters/opencode/skills/validate-delivery/SKILL.md +2 -2
  40. package/bin/cli.js +42 -8
  41. package/bin/dev-cli.js +16 -6
  42. package/lib/collectors/github.js +76 -12
  43. package/lib/perf/benchmark-runner.js +11 -6
  44. package/lib/perf/investigation-state.js +12 -13
  45. package/lib/perf/profiling-runner.js +23 -4
  46. package/lib/repo-map/concurrency.js +29 -0
  47. package/lib/repo-map/runner.js +218 -19
  48. package/lib/repo-map/updater.js +115 -27
  49. package/lib/state/workflow-state.js +31 -30
  50. package/lib/utils/command-parser.js +0 -0
  51. package/lib/utils/state-helpers.js +61 -0
  52. package/package.json +2 -1
  53. package/plugins/agnix/.claude-plugin/plugin.json +1 -1
  54. package/plugins/agnix/skills/agnix/SKILL.md +1 -1
  55. package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
  56. package/plugins/audit-project/lib/collectors/github.js +76 -12
  57. package/plugins/audit-project/lib/perf/benchmark-runner.js +11 -6
  58. package/plugins/audit-project/lib/perf/investigation-state.js +12 -13
  59. package/plugins/audit-project/lib/perf/profiling-runner.js +23 -4
  60. package/plugins/audit-project/lib/repo-map/concurrency.js +29 -0
  61. package/plugins/audit-project/lib/repo-map/runner.js +218 -19
  62. package/plugins/audit-project/lib/repo-map/updater.js +115 -27
  63. package/plugins/audit-project/lib/state/workflow-state.js +31 -30
  64. package/plugins/audit-project/lib/utils/command-parser.js +0 -0
  65. package/plugins/audit-project/lib/utils/state-helpers.js +61 -0
  66. package/plugins/consult/.claude-plugin/plugin.json +1 -1
  67. package/plugins/consult/agents/consult-agent.md +1 -1
  68. package/plugins/consult/commands/consult.md +3 -2
  69. package/plugins/consult/skills/consult/SKILL.md +16 -4
  70. package/plugins/deslop/.claude-plugin/plugin.json +1 -1
  71. package/plugins/deslop/lib/collectors/github.js +76 -12
  72. package/plugins/deslop/lib/perf/benchmark-runner.js +11 -6
  73. package/plugins/deslop/lib/perf/investigation-state.js +12 -13
  74. package/plugins/deslop/lib/perf/profiling-runner.js +23 -4
  75. package/plugins/deslop/lib/repo-map/concurrency.js +29 -0
  76. package/plugins/deslop/lib/repo-map/runner.js +218 -19
  77. package/plugins/deslop/lib/repo-map/updater.js +115 -27
  78. package/plugins/deslop/lib/state/workflow-state.js +31 -30
  79. package/plugins/deslop/lib/utils/command-parser.js +0 -0
  80. package/plugins/deslop/lib/utils/state-helpers.js +61 -0
  81. package/plugins/deslop/skills/deslop/SKILL.md +1 -1
  82. package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
  83. package/plugins/drift-detect/lib/collectors/github.js +76 -12
  84. package/plugins/drift-detect/lib/perf/benchmark-runner.js +11 -6
  85. package/plugins/drift-detect/lib/perf/investigation-state.js +12 -13
  86. package/plugins/drift-detect/lib/perf/profiling-runner.js +23 -4
  87. package/plugins/drift-detect/lib/repo-map/concurrency.js +29 -0
  88. package/plugins/drift-detect/lib/repo-map/runner.js +218 -19
  89. package/plugins/drift-detect/lib/repo-map/updater.js +115 -27
  90. package/plugins/drift-detect/lib/state/workflow-state.js +31 -30
  91. package/plugins/drift-detect/lib/utils/command-parser.js +0 -0
  92. package/plugins/drift-detect/lib/utils/state-helpers.js +61 -0
  93. package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
  94. package/plugins/enhance/.claude-plugin/plugin.json +1 -1
  95. package/plugins/enhance/lib/collectors/github.js +76 -12
  96. package/plugins/enhance/lib/perf/benchmark-runner.js +11 -6
  97. package/plugins/enhance/lib/perf/investigation-state.js +12 -13
  98. package/plugins/enhance/lib/perf/profiling-runner.js +23 -4
  99. package/plugins/enhance/lib/repo-map/concurrency.js +29 -0
  100. package/plugins/enhance/lib/repo-map/runner.js +218 -19
  101. package/plugins/enhance/lib/repo-map/updater.js +115 -27
  102. package/plugins/enhance/lib/state/workflow-state.js +31 -30
  103. package/plugins/enhance/lib/utils/command-parser.js +0 -0
  104. package/plugins/enhance/lib/utils/state-helpers.js +61 -0
  105. package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
  106. package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
  107. package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
  108. package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
  109. package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
  110. package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
  111. package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
  112. package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
  113. package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
  114. package/plugins/learn/.claude-plugin/plugin.json +1 -1
  115. package/plugins/learn/lib/collectors/github.js +76 -12
  116. package/plugins/learn/lib/perf/benchmark-runner.js +11 -6
  117. package/plugins/learn/lib/perf/investigation-state.js +12 -13
  118. package/plugins/learn/lib/perf/profiling-runner.js +23 -4
  119. package/plugins/learn/lib/repo-map/concurrency.js +29 -0
  120. package/plugins/learn/lib/repo-map/runner.js +218 -19
  121. package/plugins/learn/lib/repo-map/updater.js +115 -27
  122. package/plugins/learn/lib/state/workflow-state.js +31 -30
  123. package/plugins/learn/lib/utils/command-parser.js +0 -0
  124. package/plugins/learn/lib/utils/state-helpers.js +61 -0
  125. package/plugins/learn/skills/learn/SKILL.md +1 -1
  126. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  127. package/plugins/next-task/agents/delivery-validator.md +1 -1
  128. package/plugins/next-task/agents/implementation-agent.md +2 -2
  129. package/plugins/next-task/agents/worktree-manager.md +3 -3
  130. package/plugins/next-task/commands/next-task.md +8 -8
  131. package/plugins/next-task/hooks/hooks.json +1 -1
  132. package/plugins/next-task/lib/collectors/github.js +76 -12
  133. package/plugins/next-task/lib/perf/benchmark-runner.js +11 -6
  134. package/plugins/next-task/lib/perf/investigation-state.js +12 -13
  135. package/plugins/next-task/lib/perf/profiling-runner.js +23 -4
  136. package/plugins/next-task/lib/repo-map/concurrency.js +29 -0
  137. package/plugins/next-task/lib/repo-map/runner.js +218 -19
  138. package/plugins/next-task/lib/repo-map/updater.js +115 -27
  139. package/plugins/next-task/lib/state/workflow-state.js +31 -30
  140. package/plugins/next-task/lib/utils/command-parser.js +0 -0
  141. package/plugins/next-task/lib/utils/state-helpers.js +61 -0
  142. package/plugins/next-task/skills/discover-tasks/SKILL.md +2 -2
  143. package/plugins/next-task/skills/orchestrate-review/SKILL.md +1 -1
  144. package/plugins/next-task/skills/validate-delivery/SKILL.md +2 -2
  145. package/plugins/perf/.claude-plugin/plugin.json +1 -1
  146. package/plugins/perf/lib/collectors/github.js +76 -12
  147. package/plugins/perf/lib/perf/benchmark-runner.js +11 -6
  148. package/plugins/perf/lib/perf/investigation-state.js +12 -13
  149. package/plugins/perf/lib/perf/profiling-runner.js +23 -4
  150. package/plugins/perf/lib/repo-map/concurrency.js +29 -0
  151. package/plugins/perf/lib/repo-map/runner.js +218 -19
  152. package/plugins/perf/lib/repo-map/updater.js +115 -27
  153. package/plugins/perf/lib/state/workflow-state.js +31 -30
  154. package/plugins/perf/lib/utils/command-parser.js +0 -0
  155. package/plugins/perf/lib/utils/state-helpers.js +61 -0
  156. package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
  157. package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
  158. package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
  159. package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
  160. package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
  161. package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
  162. package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
  163. package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
  164. package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
  165. package/plugins/repo-map/lib/collectors/github.js +76 -12
  166. package/plugins/repo-map/lib/perf/benchmark-runner.js +11 -6
  167. package/plugins/repo-map/lib/perf/investigation-state.js +12 -13
  168. package/plugins/repo-map/lib/perf/profiling-runner.js +23 -4
  169. package/plugins/repo-map/lib/repo-map/concurrency.js +29 -0
  170. package/plugins/repo-map/lib/repo-map/runner.js +218 -19
  171. package/plugins/repo-map/lib/repo-map/updater.js +115 -27
  172. package/plugins/repo-map/lib/state/workflow-state.js +31 -30
  173. package/plugins/repo-map/lib/utils/command-parser.js +0 -0
  174. package/plugins/repo-map/lib/utils/state-helpers.js +61 -0
  175. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  176. package/plugins/ship/lib/collectors/github.js +76 -12
  177. package/plugins/ship/lib/perf/benchmark-runner.js +11 -6
  178. package/plugins/ship/lib/perf/investigation-state.js +12 -13
  179. package/plugins/ship/lib/perf/profiling-runner.js +23 -4
  180. package/plugins/ship/lib/repo-map/concurrency.js +29 -0
  181. package/plugins/ship/lib/repo-map/runner.js +218 -19
  182. package/plugins/ship/lib/repo-map/updater.js +115 -27
  183. package/plugins/ship/lib/state/workflow-state.js +31 -30
  184. package/plugins/ship/lib/utils/command-parser.js +0 -0
  185. package/plugins/ship/lib/utils/state-helpers.js +61 -0
  186. package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
  187. package/plugins/sync-docs/lib/collectors/github.js +76 -12
  188. package/plugins/sync-docs/lib/perf/benchmark-runner.js +11 -6
  189. package/plugins/sync-docs/lib/perf/investigation-state.js +12 -13
  190. package/plugins/sync-docs/lib/perf/profiling-runner.js +23 -4
  191. package/plugins/sync-docs/lib/repo-map/concurrency.js +29 -0
  192. package/plugins/sync-docs/lib/repo-map/runner.js +218 -19
  193. package/plugins/sync-docs/lib/repo-map/updater.js +115 -27
  194. package/plugins/sync-docs/lib/state/workflow-state.js +31 -30
  195. package/plugins/sync-docs/lib/utils/command-parser.js +0 -0
  196. package/plugins/sync-docs/lib/utils/state-helpers.js +61 -0
  197. package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
  198. package/scripts/bump-version.js +4 -1
  199. package/scripts/dev-install.js +9 -0
  200. package/scripts/validate-opencode-install.js +17 -4
  201. package/site/content.json +1 -1
  202. package/site/index.html +1 -1
@@ -2,7 +2,7 @@
2
2
  ---
3
3
  name: perf-theory-gatherer
4
4
  description: "Use when generating performance hypotheses backed by git history and code evidence."
5
- version: 5.0.0
5
+ version: 5.0.2
6
6
  ---
7
7
 
8
8
  # perf-theory-gatherer
@@ -2,7 +2,7 @@
2
2
  ---
3
3
  name: perf-theory-tester
4
4
  description: "Use when running controlled perf experiments to validate hypotheses."
5
- version: 5.0.0
5
+ version: 5.0.2
6
6
  ---
7
7
 
8
8
  # perf-theory-tester
@@ -2,7 +2,7 @@
2
2
  ---
3
3
  name: sync-docs
4
4
  description: "Sync documentation with code. Use when user asks to update docs, check docs, fix stale documentation, update changelog, or after code changes."
5
- version: 5.0.0
5
+ version: 5.0.2
6
6
  argument-hint: "[report|apply] [--scope=all|recent|before-pr] [--include-undocumented]"
7
7
  allowed-tools: Bash(git:*), Read, Grep, Glob
8
8
  ---
@@ -1,8 +1,8 @@
1
1
  <!-- AUTO-GENERATED by scripts/gen-adapters.js - DO NOT EDIT -->
2
2
  ---
3
3
  name: validate-delivery
4
- description: "Use when validating task completion before shipping. Runs tests, build, and requirement checks. Returns pass/fail with fix instructions."
5
- version: 5.0.0
4
+ description: "Use when user asks to \"validate delivery\", \"check readiness\", or \"verify completion\". Runs tests, build, and requirement checks with pass/fail instructions."
5
+ version: 5.0.2
6
6
  ---
7
7
 
8
8
  # validate-delivery
package/bin/cli.js CHANGED
@@ -214,10 +214,17 @@ function installForClaude() {
214
214
 
215
215
  // Discover plugins from filesystem convention
216
216
  const plugins = discovery.discoverPlugins(PACKAGE_DIR);
217
+ const failedPlugins = [];
217
218
  for (const plugin of plugins) {
218
219
  // Validate plugin name before shell use (prevents injection)
219
220
  if (!/^[a-z0-9][a-z0-9-]*$/.test(plugin)) continue;
220
221
  console.log(` Installing ${plugin}...`);
222
+ // Remove pre-rename plugin ID to prevent dual loading on upgrade
223
+ try {
224
+ execSync(`claude plugin uninstall ${plugin}@awesome-slash`, { stdio: 'pipe' });
225
+ } catch {
226
+ // Not installed under old name
227
+ }
221
228
  try {
222
229
  // Try install first
223
230
  execSync(`claude plugin install ${plugin}@agentsys`, { stdio: 'pipe' });
@@ -226,11 +233,17 @@ function installForClaude() {
226
233
  try {
227
234
  execSync(`claude plugin update ${plugin}@agentsys`, { stdio: 'pipe' });
228
235
  } catch {
229
- // Ignore if update also fails
236
+ failedPlugins.push(plugin);
230
237
  }
231
238
  }
232
239
  }
233
240
 
241
+ if (failedPlugins.length > 0) {
242
+ console.log(`\n[ERROR] Failed to install/update ${failedPlugins.length} plugin(s): ${failedPlugins.join(', ')}`);
243
+ console.log('Retry with: /plugin install <plugin>@agentsys');
244
+ return false;
245
+ }
246
+
234
247
  console.log('\n[OK] Claude Code installation complete!\n');
235
248
  console.log('Commands: ' + plugins.map(p => '/' + p).join(', '));
236
249
  return true;
@@ -328,9 +341,11 @@ function installForOpenCode(installDir, options = {}) {
328
341
  // OpenCode global locations are under ~/.config/opencode (or $XDG_CONFIG_HOME/opencode).
329
342
  const commandsDir = path.join(opencodeConfigDir, 'commands');
330
343
  const pluginDir = path.join(opencodeConfigDir, 'plugins');
344
+ const agentsDir = path.join(opencodeConfigDir, 'agents');
331
345
 
332
346
  fs.mkdirSync(commandsDir, { recursive: true });
333
347
  fs.mkdirSync(pluginDir, { recursive: true });
348
+ fs.mkdirSync(agentsDir, { recursive: true });
334
349
 
335
350
  // Install native OpenCode plugin (auto-thinking, workflow enforcement, compaction)
336
351
  const pluginSrcDir = path.join(installDir, 'adapters', 'opencode-plugin');
@@ -384,10 +399,17 @@ function installForOpenCode(installDir, options = {}) {
384
399
  }
385
400
  }
386
401
 
402
+ // Remove legacy agent files from pre-rename installs.
403
+ const legacyAgentFiles = ['review.md', 'ship.md', 'workflow.md'];
404
+ for (const legacyFile of legacyAgentFiles) {
405
+ const legacyPath = path.join(agentsDir, legacyFile);
406
+ if (fs.existsSync(legacyPath)) {
407
+ fs.unlinkSync(legacyPath);
408
+ }
409
+ }
410
+
387
411
  // Install agents to global OpenCode location
388
412
  // OpenCode looks for agents in ~/.config/opencode/agents/ (global) or .opencode/agents/ (per-project)
389
- const agentsDir = path.join(opencodeConfigDir, 'agents');
390
- fs.mkdirSync(agentsDir, { recursive: true });
391
413
 
392
414
  console.log(' Installing agents for OpenCode...');
393
415
  const pluginDirs = discovery.discoverPlugins(installDir);
@@ -681,24 +703,36 @@ async function main() {
681
703
  }
682
704
 
683
705
  // Install for each platform
706
+ const failedPlatforms = [];
684
707
  for (const platform of selected) {
685
708
  switch (platform) {
686
709
  case 'claude':
687
- if (args.development) {
688
- installForClaudeDevelopment();
710
+ if (args.development && !installForClaudeDevelopment()) {
711
+ failedPlatforms.push('claude');
689
712
  } else {
690
- installForClaude();
713
+ if (!args.development && !installForClaude()) {
714
+ failedPlatforms.push('claude');
715
+ }
691
716
  }
692
717
  break;
693
718
  case 'opencode':
694
- installForOpenCode(installDir, { stripModels: args.stripModels });
719
+ if (!installForOpenCode(installDir, { stripModels: args.stripModels })) {
720
+ failedPlatforms.push('opencode');
721
+ }
695
722
  break;
696
723
  case 'codex':
697
- installForCodex(installDir);
724
+ if (!installForCodex(installDir)) {
725
+ failedPlatforms.push('codex');
726
+ }
698
727
  break;
699
728
  }
700
729
  }
701
730
 
731
+ if (failedPlatforms.length > 0) {
732
+ console.log(`\n[ERROR] Installation failed for: ${failedPlatforms.join(', ')}`);
733
+ process.exitCode = 1;
734
+ }
735
+
702
736
  console.log('─'.repeat(45));
703
737
  if (installDir) {
704
738
  console.log(`\nInstallation directory: ${installDir}`);
package/bin/dev-cli.js CHANGED
@@ -13,7 +13,8 @@
13
13
  */
14
14
 
15
15
  const path = require('path');
16
- const { execSync } = require('child_process');
16
+ const { execSync, spawnSync } = require('child_process');
17
+ const { resolveExecutableForPlatform } = require('../lib/utils/command-parser');
17
18
 
18
19
  const VERSION = require('../package.json').version;
19
20
  const ROOT_DIR = path.join(__dirname, '..');
@@ -293,13 +294,22 @@ const COMMANDS = {
293
294
  description: 'Run test suite',
294
295
  handler: (args) => {
295
296
  try {
296
- const cmd = ['npm', 'test'];
297
+ const cmdArgs = ['test'];
297
298
  if (args.length > 0) {
298
- cmd.push('--');
299
- cmd.push(...args);
299
+ cmdArgs.push('--');
300
+ cmdArgs.push(...args);
300
301
  }
301
- execSync(cmd.join(' '), { cwd: ROOT_DIR, stdio: 'inherit' });
302
- return 0;
302
+ const npmExecutable = resolveExecutableForPlatform('npm');
303
+ const result = spawnSync(npmExecutable, cmdArgs, {
304
+ cwd: ROOT_DIR,
305
+ stdio: 'inherit',
306
+ shell: false,
307
+ windowsHide: true
308
+ });
309
+ if (result.error) {
310
+ throw result.error;
311
+ }
312
+ return typeof result.status === 'number' ? result.status : 1;
303
313
  } catch (err) {
304
314
  return err.status || 1;
305
315
  }
@@ -14,6 +14,7 @@ const { execFileSync } = require('child_process');
14
14
  const DEFAULT_OPTIONS = {
15
15
  issueLimit: 100,
16
16
  prLimit: 50,
17
+ milestoneLimit: 100,
17
18
  timeout: 10000,
18
19
  cwd: process.cwd()
19
20
  };
@@ -25,16 +26,41 @@ const DEFAULT_OPTIONS = {
25
26
  * @returns {Object|null} Parsed JSON result or null
26
27
  */
27
28
  function execGh(args, options = {}) {
29
+ const result = execGhWithResult(args, options);
30
+ return result.ok ? result.data : null;
31
+ }
32
+
33
+ function execGhWithResult(args, options = {}) {
28
34
  try {
29
- const result = execFileSync('gh', args, {
35
+ const output = execFileSync('gh', args, {
30
36
  encoding: 'utf8',
31
37
  stdio: 'pipe',
32
38
  timeout: options.timeout || DEFAULT_OPTIONS.timeout,
33
39
  cwd: options.cwd || DEFAULT_OPTIONS.cwd
34
40
  });
35
- return JSON.parse(result);
36
- } catch {
37
- return null;
41
+
42
+ try {
43
+ return { ok: true, data: JSON.parse(output) };
44
+ } catch (error) {
45
+ return {
46
+ ok: false,
47
+ error: {
48
+ type: 'parse',
49
+ message: `Failed to parse gh output as JSON: ${error.message}`,
50
+ raw: output.slice(0, 500)
51
+ }
52
+ };
53
+ }
54
+ } catch (error) {
55
+ return {
56
+ ok: false,
57
+ error: {
58
+ type: error.killed ? 'timeout' : 'process',
59
+ message: error.message,
60
+ exitCode: error.status ?? null,
61
+ stderr: error.stderr ? String(error.stderr).trim() : ''
62
+ }
63
+ };
38
64
  }
39
65
  }
40
66
 
@@ -192,10 +218,18 @@ function scanGitHubState(options = {}) {
192
218
 
193
219
  const result = {
194
220
  available: false,
221
+ partial: false,
222
+ errors: [],
195
223
  summary: { issueCount: 0, prCount: 0, milestoneCount: 0 },
196
224
  issues: [],
197
225
  prs: [],
198
226
  milestones: [],
227
+ overdueMilestones: [],
228
+ pagination: {
229
+ issues: { requestedLimit: opts.issueLimit, fetchedCount: 0, hasMore: false },
230
+ prs: { requestedLimit: opts.prLimit, fetchedCount: 0, hasMore: false },
231
+ milestones: { requestedLimit: opts.milestoneLimit, fetchedCount: 0, hasMore: false }
232
+ },
199
233
  categorized: { bugs: [], features: [], security: [], enhancements: [], other: [] },
200
234
  stale: [],
201
235
  themes: []
@@ -209,44 +243,74 @@ function scanGitHubState(options = {}) {
209
243
  result.available = true;
210
244
 
211
245
  // Fetch open issues
212
- const issues = execGh([
246
+ const issuesResult = execGhWithResult([
213
247
  'issue', 'list',
214
248
  '--state', 'open',
215
249
  '--json', 'number,title,labels,milestone,createdAt,updatedAt,body',
216
250
  '--limit', String(opts.issueLimit)
217
251
  ], opts);
218
252
 
219
- if (issues) {
253
+ if (issuesResult.ok && Array.isArray(issuesResult.data)) {
254
+ const issues = issuesResult.data;
220
255
  result.issues = issues.map(summarizeIssue);
221
256
  result.summary.issueCount = issues.length;
257
+ result.pagination.issues.fetchedCount = issues.length;
258
+ result.pagination.issues.hasMore = opts.issueLimit > 0 && issues.length >= opts.issueLimit;
222
259
  categorizeIssues(result, issues);
223
260
  findStaleItems(result, issues, 90);
224
261
  extractThemes(result, issues);
262
+ } else if (!issuesResult.ok) {
263
+ result.errors.push({ source: 'issues', ...issuesResult.error });
225
264
  }
226
265
 
227
266
  // Fetch open PRs with files changed
228
- const prs = execGh([
267
+ const prsResult = execGhWithResult([
229
268
  'pr', 'list',
230
269
  '--state', 'open',
231
270
  '--json', 'number,title,labels,isDraft,createdAt,updatedAt,body,files',
232
271
  '--limit', String(opts.prLimit)
233
272
  ], opts);
234
273
 
235
- if (prs) {
274
+ if (prsResult.ok && Array.isArray(prsResult.data)) {
275
+ const prs = prsResult.data;
236
276
  result.prs = prs.map(summarizePR);
237
277
  result.summary.prCount = prs.length;
278
+ result.pagination.prs.fetchedCount = prs.length;
279
+ result.pagination.prs.hasMore = opts.prLimit > 0 && prs.length >= opts.prLimit;
280
+ } else if (!prsResult.ok) {
281
+ result.errors.push({ source: 'prs', ...prsResult.error });
238
282
  }
239
283
 
240
284
  // Fetch milestones
241
- const milestones = execGh([
285
+ const milestonesResult = execGhWithResult([
242
286
  'api', 'repos/{owner}/{repo}/milestones',
243
- '--jq', '.[].{title,state,due_on,open_issues,closed_issues}'
287
+ '--paginate',
288
+ '--slurp'
244
289
  ], opts);
245
290
 
246
- if (milestones) {
247
- result.milestones = Array.isArray(milestones) ? milestones : [milestones];
291
+ if (milestonesResult.ok && Array.isArray(milestonesResult.data)) {
292
+ const pages = milestonesResult.data;
293
+ const allMilestones = pages.flatMap(page => Array.isArray(page) ? page : []);
294
+ const mappedMilestones = allMilestones.map((milestone) => ({
295
+ title: milestone.title,
296
+ state: milestone.state,
297
+ due_on: milestone.due_on,
298
+ open_issues: milestone.open_issues,
299
+ closed_issues: milestone.closed_issues
300
+ }));
301
+
302
+ result.pagination.milestones.fetchedCount = mappedMilestones.length;
303
+ result.pagination.milestones.hasMore = opts.milestoneLimit > 0 && mappedMilestones.length > opts.milestoneLimit;
304
+ result.milestones = mappedMilestones.slice(0, opts.milestoneLimit);
248
305
  result.summary.milestoneCount = result.milestones.length;
249
306
  findOverdueMilestones(result);
307
+ } else if (!milestonesResult.ok) {
308
+ result.errors.push({ source: 'milestones', ...milestonesResult.error });
309
+ }
310
+
311
+ result.partial = result.errors.length > 0;
312
+ if (result.partial && !result.error) {
313
+ result.error = 'Partial GitHub data collected';
250
314
  }
251
315
 
252
316
  return result;
@@ -4,8 +4,9 @@
4
4
  * @module lib/perf/benchmark-runner
5
5
  */
6
6
 
7
- const { execSync } = require('child_process');
7
+ const { execFileSync } = require('child_process');
8
8
  const { validateBaseline } = require('./schemas');
9
+ const { parseCommand, resolveExecutableForPlatform } = require('../utils/command-parser');
9
10
 
10
11
  const DEFAULT_MIN_DURATION = 60;
11
12
  const BINARY_SEARCH_MIN_DURATION = 30;
@@ -55,6 +56,8 @@ function runBenchmark(command, options = {}) {
55
56
  throw new Error('Benchmark command must be a non-empty string');
56
57
  }
57
58
 
59
+ const parsedCommand = parseCommand(command, 'Benchmark command');
60
+ const executable = resolveExecutableForPlatform(parsedCommand.executable);
58
61
  const normalized = normalizeBenchmarkOptions(options);
59
62
  const setDurationEnv = options.setDurationEnv !== false;
60
63
  const env = {
@@ -71,18 +74,20 @@ function runBenchmark(command, options = {}) {
71
74
  const start = Date.now();
72
75
  let output;
73
76
  try {
74
- output = execSync(command, {
77
+ output = execFileSync(executable, parsedCommand.args, {
75
78
  stdio: 'pipe',
76
79
  encoding: 'utf8',
77
- env
80
+ env,
81
+ windowsHide: true,
82
+ cwd: options.cwd || process.cwd()
78
83
  });
79
84
  } catch (error) {
80
- const stderr = error.stderr ? error.stderr.toString().trim() : '';
81
- const stdout = error.stdout ? error.stdout.toString().trim() : '';
85
+ const stderr = error.stderr ? String(error.stderr).trim() : '';
86
+ const stdout = error.stdout ? String(error.stdout).trim() : '';
82
87
  const exitCode = error.status ?? 'unknown';
83
88
  const details = stderr || stdout || error.message || 'No error details available';
84
89
  throw new Error(
85
- `Benchmark command failed (exit code ${exitCode}): ${command}\n` +
90
+ `Benchmark command failed (exit code ${exitCode}): ${parsedCommand.display}\n` +
86
91
  `Details: ${details}`
87
92
  );
88
93
  }
@@ -14,12 +14,12 @@ const crypto = require('crypto');
14
14
  const { getStateDir } = require('../platform/state-dir');
15
15
  const { validateInvestigationState, assertValid } = require('./schemas');
16
16
  const { writeJsonAtomic, writeFileAtomic } = require('../utils/atomic-write');
17
+ const { isPlainObject, updatesApplied, sleepForRetry } = require('../utils/state-helpers');
17
18
 
18
19
  const SCHEMA_VERSION = 1;
19
20
  const INVESTIGATION_FILE = 'investigation.json';
20
21
  const LOG_DIR = 'investigations';
21
22
  const BASELINE_DIR = 'baselines';
22
-
23
23
  const PHASES = [
24
24
  'setup',
25
25
  'baseline',
@@ -196,10 +196,12 @@ function writeInvestigation(state, basePath = process.cwd()) {
196
196
  * @returns {object|null}
197
197
  */
198
198
  function updateInvestigation(updates, basePath = process.cwd()) {
199
- const MAX_RETRIES = 3;
199
+ const MAX_RETRIES = 5;
200
+ let fallbackState = null;
200
201
 
201
202
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
202
203
  const current = readInvestigation(basePath) || {};
204
+ fallbackState = current;
203
205
  const initialVersion = current._version || 0;
204
206
  const nextState = { ...current };
205
207
 
@@ -209,10 +211,7 @@ function updateInvestigation(updates, basePath = process.cwd()) {
209
211
 
210
212
  if (value === null) {
211
213
  nextState[key] = null;
212
- } else if (
213
- value && typeof value === 'object' && !Array.isArray(value) &&
214
- nextState[key] && typeof nextState[key] === 'object' && !Array.isArray(nextState[key])
215
- ) {
214
+ } else if (isPlainObject(value) && isPlainObject(nextState[key])) {
216
215
  nextState[key] = { ...nextState[key], ...value };
217
216
  } else {
218
217
  nextState[key] = value;
@@ -226,23 +225,23 @@ function updateInvestigation(updates, basePath = process.cwd()) {
226
225
 
227
226
  // Re-read to verify our write succeeded
228
227
  const afterWrite = readInvestigation(basePath);
229
- if (afterWrite && afterWrite._version === initialVersion + 1) {
228
+ if (afterWrite) {
229
+ fallbackState = afterWrite;
230
+ }
231
+ if (afterWrite && afterWrite._version >= initialVersion + 1 && updatesApplied(afterWrite, updates)) {
230
232
  return afterWrite; // Success
231
233
  }
232
234
 
233
235
  // Version conflict - retry after brief delay
234
236
  if (attempt < MAX_RETRIES - 1) {
235
237
  const delay = Math.floor(Math.random() * 50) + 10;
236
- const start = Date.now();
237
- while (Date.now() - start < delay) {
238
- // Busy wait (synchronous delay)
239
- }
238
+ sleepForRetry(delay);
240
239
  }
241
240
  }
242
241
 
243
242
  // All retries exhausted
244
- console.error('[WARN] updateInvestigation: max retries exceeded, possible version conflict');
245
- return readInvestigation(basePath);
243
+ console.error('[ERROR] updateInvestigation: failed to apply updates after max retries');
244
+ return readInvestigation(basePath) || fallbackState || { ...updates };
246
245
  }
247
246
 
248
247
  /**
@@ -4,8 +4,9 @@
4
4
  * @module lib/perf/profiling-runner
5
5
  */
6
6
 
7
- const { execSync } = require('child_process');
7
+ const { execFileSync } = require('child_process');
8
8
  const profilers = require('./profilers');
9
+ const { parseCommand, resolveExecutableForPlatform } = require('../utils/command-parser');
9
10
 
10
11
  /**
11
12
  * Run a profiling command and return artifacts/hotspots metadata.
@@ -16,6 +17,9 @@ const profilers = require('./profilers');
16
17
  */
17
18
  function runProfiling(options = {}) {
18
19
  const repoPath = options.repoPath || process.cwd();
20
+ const timeoutMs = Number.isFinite(options.timeoutMs)
21
+ ? Math.max(1, Math.floor(options.timeoutMs))
22
+ : null;
19
23
  const profiler = profilers.selectProfiler(repoPath);
20
24
 
21
25
  if (!profiler || typeof profiler.buildCommand !== 'function') {
@@ -27,14 +31,29 @@ function runProfiling(options = {}) {
27
31
  output: options.output,
28
32
  ...(options.profileOptions || {})
29
33
  });
34
+ const parsedCommand = parseCommand(command, 'Profiling command');
35
+ const executable = resolveExecutableForPlatform(parsedCommand.executable);
30
36
  const env = {
31
37
  ...process.env,
32
38
  ...(options.env || {})
33
39
  };
34
40
  try {
35
- execSync(command, { stdio: 'pipe', env });
41
+ const execOptions = {
42
+ stdio: 'pipe',
43
+ env,
44
+ cwd: repoPath,
45
+ windowsHide: true
46
+ };
47
+ if (timeoutMs !== null) {
48
+ execOptions.timeout = timeoutMs;
49
+ }
50
+
51
+ execFileSync(executable, parsedCommand.args, execOptions);
36
52
  } catch (error) {
37
- return { ok: false, error: error.message };
53
+ const stderr = error.stderr ? String(error.stderr).trim() : '';
54
+ const stdout = error.stdout ? String(error.stdout).trim() : '';
55
+ const details = stderr || stdout || error.message;
56
+ return { ok: false, error: `Profiling command failed: ${details}` };
38
57
  }
39
58
 
40
59
  const parsed = typeof profiler.parseOutput === 'function'
@@ -43,7 +62,7 @@ function runProfiling(options = {}) {
43
62
 
44
63
  const result = {
45
64
  tool: profiler.id,
46
- command,
65
+ command: parsedCommand.display,
47
66
  hotspots: parsed.hotspots || [],
48
67
  artifacts: parsed.artifacts || []
49
68
  };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ async function runWithConcurrency(items, limit, worker) {
4
+ if (!Array.isArray(items) || items.length === 0) {
5
+ return [];
6
+ }
7
+
8
+ const maxConcurrency = Math.max(1, Math.min(items.length, Math.floor(limit) || 1));
9
+ const results = new Array(items.length);
10
+ let cursor = 0;
11
+
12
+ async function runWorker() {
13
+ while (true) {
14
+ const index = cursor;
15
+ cursor += 1;
16
+ if (index >= items.length) {
17
+ return;
18
+ }
19
+ results[index] = await worker(items[index], index);
20
+ }
21
+ }
22
+
23
+ await Promise.all(Array.from({ length: maxConcurrency }, () => runWorker()));
24
+ return results;
25
+ }
26
+
27
+ module.exports = {
28
+ runWithConcurrency
29
+ };