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.
- package/.claude-plugin/marketplace.json +13 -13
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -2
- package/README.md +1 -0
- package/adapters/README.md +1 -1
- package/adapters/codex/skills/consult/SKILL.md +3 -2
- package/adapters/codex/skills/next-task/SKILL.md +8 -8
- package/adapters/opencode/agents/consult-agent.md +1 -1
- package/adapters/opencode/agents/delivery-validator.md +1 -1
- package/adapters/opencode/agents/implementation-agent.md +1 -1
- package/adapters/opencode/agents/worktree-manager.md +3 -3
- package/adapters/opencode/commands/consult.md +3 -2
- package/adapters/opencode/commands/next-task.md +7 -7
- package/adapters/opencode/skills/agnix/SKILL.md +1 -1
- package/adapters/opencode/skills/consult/SKILL.md +16 -4
- package/adapters/opencode/skills/deslop/SKILL.md +1 -1
- package/adapters/opencode/skills/discover-tasks/SKILL.md +2 -2
- package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -1
- package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -1
- package/adapters/opencode/skills/learn/SKILL.md +1 -1
- package/adapters/opencode/skills/orchestrate-review/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -1
- package/adapters/opencode/skills/sync-docs/SKILL.md +1 -1
- package/adapters/opencode/skills/validate-delivery/SKILL.md +2 -2
- package/bin/cli.js +42 -8
- package/bin/dev-cli.js +16 -6
- package/lib/collectors/github.js +76 -12
- package/lib/perf/benchmark-runner.js +11 -6
- package/lib/perf/investigation-state.js +12 -13
- package/lib/perf/profiling-runner.js +23 -4
- package/lib/repo-map/concurrency.js +29 -0
- package/lib/repo-map/runner.js +218 -19
- package/lib/repo-map/updater.js +115 -27
- package/lib/state/workflow-state.js +31 -30
- package/lib/utils/command-parser.js +0 -0
- package/lib/utils/state-helpers.js +61 -0
- package/package.json +2 -1
- package/plugins/agnix/.claude-plugin/plugin.json +1 -1
- package/plugins/agnix/skills/agnix/SKILL.md +1 -1
- package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
- package/plugins/audit-project/lib/collectors/github.js +76 -12
- package/plugins/audit-project/lib/perf/benchmark-runner.js +11 -6
- package/plugins/audit-project/lib/perf/investigation-state.js +12 -13
- package/plugins/audit-project/lib/perf/profiling-runner.js +23 -4
- package/plugins/audit-project/lib/repo-map/concurrency.js +29 -0
- package/plugins/audit-project/lib/repo-map/runner.js +218 -19
- package/plugins/audit-project/lib/repo-map/updater.js +115 -27
- package/plugins/audit-project/lib/state/workflow-state.js +31 -30
- package/plugins/audit-project/lib/utils/command-parser.js +0 -0
- package/plugins/audit-project/lib/utils/state-helpers.js +61 -0
- package/plugins/consult/.claude-plugin/plugin.json +1 -1
- package/plugins/consult/agents/consult-agent.md +1 -1
- package/plugins/consult/commands/consult.md +3 -2
- package/plugins/consult/skills/consult/SKILL.md +16 -4
- package/plugins/deslop/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop/lib/collectors/github.js +76 -12
- package/plugins/deslop/lib/perf/benchmark-runner.js +11 -6
- package/plugins/deslop/lib/perf/investigation-state.js +12 -13
- package/plugins/deslop/lib/perf/profiling-runner.js +23 -4
- package/plugins/deslop/lib/repo-map/concurrency.js +29 -0
- package/plugins/deslop/lib/repo-map/runner.js +218 -19
- package/plugins/deslop/lib/repo-map/updater.js +115 -27
- package/plugins/deslop/lib/state/workflow-state.js +31 -30
- package/plugins/deslop/lib/utils/command-parser.js +0 -0
- package/plugins/deslop/lib/utils/state-helpers.js +61 -0
- package/plugins/deslop/skills/deslop/SKILL.md +1 -1
- package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
- package/plugins/drift-detect/lib/collectors/github.js +76 -12
- package/plugins/drift-detect/lib/perf/benchmark-runner.js +11 -6
- package/plugins/drift-detect/lib/perf/investigation-state.js +12 -13
- package/plugins/drift-detect/lib/perf/profiling-runner.js +23 -4
- package/plugins/drift-detect/lib/repo-map/concurrency.js +29 -0
- package/plugins/drift-detect/lib/repo-map/runner.js +218 -19
- package/plugins/drift-detect/lib/repo-map/updater.js +115 -27
- package/plugins/drift-detect/lib/state/workflow-state.js +31 -30
- package/plugins/drift-detect/lib/utils/command-parser.js +0 -0
- package/plugins/drift-detect/lib/utils/state-helpers.js +61 -0
- package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
- package/plugins/enhance/.claude-plugin/plugin.json +1 -1
- package/plugins/enhance/lib/collectors/github.js +76 -12
- package/plugins/enhance/lib/perf/benchmark-runner.js +11 -6
- package/plugins/enhance/lib/perf/investigation-state.js +12 -13
- package/plugins/enhance/lib/perf/profiling-runner.js +23 -4
- package/plugins/enhance/lib/repo-map/concurrency.js +29 -0
- package/plugins/enhance/lib/repo-map/runner.js +218 -19
- package/plugins/enhance/lib/repo-map/updater.js +115 -27
- package/plugins/enhance/lib/state/workflow-state.js +31 -30
- package/plugins/enhance/lib/utils/command-parser.js +0 -0
- package/plugins/enhance/lib/utils/state-helpers.js +61 -0
- package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
- package/plugins/learn/.claude-plugin/plugin.json +1 -1
- package/plugins/learn/lib/collectors/github.js +76 -12
- package/plugins/learn/lib/perf/benchmark-runner.js +11 -6
- package/plugins/learn/lib/perf/investigation-state.js +12 -13
- package/plugins/learn/lib/perf/profiling-runner.js +23 -4
- package/plugins/learn/lib/repo-map/concurrency.js +29 -0
- package/plugins/learn/lib/repo-map/runner.js +218 -19
- package/plugins/learn/lib/repo-map/updater.js +115 -27
- package/plugins/learn/lib/state/workflow-state.js +31 -30
- package/plugins/learn/lib/utils/command-parser.js +0 -0
- package/plugins/learn/lib/utils/state-helpers.js +61 -0
- package/plugins/learn/skills/learn/SKILL.md +1 -1
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/delivery-validator.md +1 -1
- package/plugins/next-task/agents/implementation-agent.md +2 -2
- package/plugins/next-task/agents/worktree-manager.md +3 -3
- package/plugins/next-task/commands/next-task.md +8 -8
- package/plugins/next-task/hooks/hooks.json +1 -1
- package/plugins/next-task/lib/collectors/github.js +76 -12
- package/plugins/next-task/lib/perf/benchmark-runner.js +11 -6
- package/plugins/next-task/lib/perf/investigation-state.js +12 -13
- package/plugins/next-task/lib/perf/profiling-runner.js +23 -4
- package/plugins/next-task/lib/repo-map/concurrency.js +29 -0
- package/plugins/next-task/lib/repo-map/runner.js +218 -19
- package/plugins/next-task/lib/repo-map/updater.js +115 -27
- package/plugins/next-task/lib/state/workflow-state.js +31 -30
- package/plugins/next-task/lib/utils/command-parser.js +0 -0
- package/plugins/next-task/lib/utils/state-helpers.js +61 -0
- package/plugins/next-task/skills/discover-tasks/SKILL.md +2 -2
- package/plugins/next-task/skills/orchestrate-review/SKILL.md +1 -1
- package/plugins/next-task/skills/validate-delivery/SKILL.md +2 -2
- package/plugins/perf/.claude-plugin/plugin.json +1 -1
- package/plugins/perf/lib/collectors/github.js +76 -12
- package/plugins/perf/lib/perf/benchmark-runner.js +11 -6
- package/plugins/perf/lib/perf/investigation-state.js +12 -13
- package/plugins/perf/lib/perf/profiling-runner.js +23 -4
- package/plugins/perf/lib/repo-map/concurrency.js +29 -0
- package/plugins/perf/lib/repo-map/runner.js +218 -19
- package/plugins/perf/lib/repo-map/updater.js +115 -27
- package/plugins/perf/lib/state/workflow-state.js +31 -30
- package/plugins/perf/lib/utils/command-parser.js +0 -0
- package/plugins/perf/lib/utils/state-helpers.js +61 -0
- package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
- package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
- package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
- package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
- package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
- package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
- package/plugins/repo-map/lib/collectors/github.js +76 -12
- package/plugins/repo-map/lib/perf/benchmark-runner.js +11 -6
- package/plugins/repo-map/lib/perf/investigation-state.js +12 -13
- package/plugins/repo-map/lib/perf/profiling-runner.js +23 -4
- package/plugins/repo-map/lib/repo-map/concurrency.js +29 -0
- package/plugins/repo-map/lib/repo-map/runner.js +218 -19
- package/plugins/repo-map/lib/repo-map/updater.js +115 -27
- package/plugins/repo-map/lib/state/workflow-state.js +31 -30
- package/plugins/repo-map/lib/utils/command-parser.js +0 -0
- package/plugins/repo-map/lib/utils/state-helpers.js +61 -0
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/collectors/github.js +76 -12
- package/plugins/ship/lib/perf/benchmark-runner.js +11 -6
- package/plugins/ship/lib/perf/investigation-state.js +12 -13
- package/plugins/ship/lib/perf/profiling-runner.js +23 -4
- package/plugins/ship/lib/repo-map/concurrency.js +29 -0
- package/plugins/ship/lib/repo-map/runner.js +218 -19
- package/plugins/ship/lib/repo-map/updater.js +115 -27
- package/plugins/ship/lib/state/workflow-state.js +31 -30
- package/plugins/ship/lib/utils/command-parser.js +0 -0
- package/plugins/ship/lib/utils/state-helpers.js +61 -0
- package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/sync-docs/lib/collectors/github.js +76 -12
- package/plugins/sync-docs/lib/perf/benchmark-runner.js +11 -6
- package/plugins/sync-docs/lib/perf/investigation-state.js +12 -13
- package/plugins/sync-docs/lib/perf/profiling-runner.js +23 -4
- package/plugins/sync-docs/lib/repo-map/concurrency.js +29 -0
- package/plugins/sync-docs/lib/repo-map/runner.js +218 -19
- package/plugins/sync-docs/lib/repo-map/updater.js +115 -27
- package/plugins/sync-docs/lib/state/workflow-state.js +31 -30
- package/plugins/sync-docs/lib/utils/command-parser.js +0 -0
- package/plugins/sync-docs/lib/utils/state-helpers.js +61 -0
- package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
- package/scripts/bump-version.js +4 -1
- package/scripts/dev-install.js +9 -0
- package/scripts/validate-opencode-install.js +17 -4
- package/site/content.json +1 -1
- package/site/index.html +1 -1
|
@@ -16,11 +16,11 @@ const path = require('path');
|
|
|
16
16
|
const crypto = require('crypto');
|
|
17
17
|
const { getStateDir } = require('../platform/state-dir');
|
|
18
18
|
const { writeJsonAtomic } = require('../utils/atomic-write');
|
|
19
|
+
const { isPlainObject, updatesApplied, sleepForRetry } = require('../utils/state-helpers');
|
|
19
20
|
|
|
20
21
|
// File paths
|
|
21
22
|
const TASKS_FILE = 'tasks.json';
|
|
22
23
|
const FLOW_FILE = 'flow.json';
|
|
23
|
-
|
|
24
24
|
/**
|
|
25
25
|
* Validate and resolve path to prevent path traversal attacks
|
|
26
26
|
* @param {string} basePath - Base directory path
|
|
@@ -223,7 +223,7 @@ function writeFlow(flow, worktreePath = process.cwd()) {
|
|
|
223
223
|
* Uses optimistic locking with version check and retry
|
|
224
224
|
*/
|
|
225
225
|
function updateFlow(updates, worktreePath = process.cwd()) {
|
|
226
|
-
const MAX_RETRIES =
|
|
226
|
+
const MAX_RETRIES = 5;
|
|
227
227
|
|
|
228
228
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
229
229
|
const flow = readFlow(worktreePath) || {};
|
|
@@ -238,10 +238,7 @@ function updateFlow(updates, worktreePath = process.cwd()) {
|
|
|
238
238
|
flow[key] = null;
|
|
239
239
|
}
|
|
240
240
|
// Deep merge if both source and target are non-null objects
|
|
241
|
-
else if (
|
|
242
|
-
value && typeof value === 'object' && !Array.isArray(value) &&
|
|
243
|
-
flow[key] && typeof flow[key] === 'object' && !Array.isArray(flow[key])
|
|
244
|
-
) {
|
|
241
|
+
else if (isPlainObject(value) && isPlainObject(flow[key])) {
|
|
245
242
|
flow[key] = { ...flow[key], ...value };
|
|
246
243
|
}
|
|
247
244
|
// Otherwise direct assignment
|
|
@@ -258,24 +255,26 @@ function updateFlow(updates, worktreePath = process.cwd()) {
|
|
|
258
255
|
|
|
259
256
|
// Re-read to verify our write succeeded
|
|
260
257
|
const afterWrite = readFlow(worktreePath);
|
|
261
|
-
if (afterWrite && afterWrite._version
|
|
258
|
+
if (afterWrite && afterWrite._version >= initialVersion + 1 && updatesApplied(afterWrite, updates)) {
|
|
262
259
|
return true; // Success
|
|
263
260
|
}
|
|
264
261
|
|
|
265
|
-
// Version conflict - retry after brief delay
|
|
262
|
+
// Version conflict or overwrite - retry after brief delay
|
|
266
263
|
if (attempt < MAX_RETRIES - 1) {
|
|
267
|
-
// Small random delay to reduce collision probability
|
|
268
264
|
const delay = Math.floor(Math.random() * 50) + 10;
|
|
269
|
-
|
|
270
|
-
while (Date.now() - start < delay) {
|
|
271
|
-
// Busy wait (synchronous delay)
|
|
272
|
-
}
|
|
265
|
+
sleepForRetry(delay);
|
|
273
266
|
}
|
|
274
267
|
}
|
|
275
268
|
|
|
276
|
-
// All retries exhausted
|
|
277
|
-
|
|
278
|
-
|
|
269
|
+
// All retries exhausted. One final read can detect if another writer
|
|
270
|
+
// applied the same updates while we were retrying.
|
|
271
|
+
const latest = readFlow(worktreePath);
|
|
272
|
+
if (latest && updatesApplied(latest, updates)) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
console.error('[ERROR] updateFlow: failed to apply updates after max retries');
|
|
277
|
+
return false;
|
|
279
278
|
}
|
|
280
279
|
|
|
281
280
|
/**
|
|
@@ -420,8 +419,8 @@ function completePhase(result = null, worktreePath = process.cwd()) {
|
|
|
420
419
|
}
|
|
421
420
|
}
|
|
422
421
|
|
|
423
|
-
updateFlow(updates, worktreePath);
|
|
424
|
-
return readFlow(worktreePath);
|
|
422
|
+
const updated = updateFlow(updates, worktreePath);
|
|
423
|
+
return updated ? readFlow(worktreePath) : null;
|
|
425
424
|
}
|
|
426
425
|
|
|
427
426
|
/**
|
|
@@ -454,16 +453,17 @@ function failWorkflow(error, worktreePath = process.cwd()) {
|
|
|
454
453
|
function completeWorkflow(worktreePath = process.cwd()) {
|
|
455
454
|
const flow = readFlow(worktreePath);
|
|
456
455
|
|
|
457
|
-
|
|
458
|
-
if (flow && flow.projectPath) {
|
|
459
|
-
clearActiveTask(flow.projectPath);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return updateFlow({
|
|
456
|
+
const updated = updateFlow({
|
|
463
457
|
phase: 'complete',
|
|
464
458
|
status: 'completed',
|
|
465
459
|
completedAt: new Date().toISOString()
|
|
466
460
|
}, worktreePath);
|
|
461
|
+
|
|
462
|
+
if (updated && flow && flow.projectPath) {
|
|
463
|
+
clearActiveTask(flow.projectPath);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return updated;
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
/**
|
|
@@ -473,16 +473,17 @@ function completeWorkflow(worktreePath = process.cwd()) {
|
|
|
473
473
|
function abortWorkflow(reason, worktreePath = process.cwd()) {
|
|
474
474
|
const flow = readFlow(worktreePath);
|
|
475
475
|
|
|
476
|
-
|
|
477
|
-
if (flow && flow.projectPath) {
|
|
478
|
-
clearActiveTask(flow.projectPath);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return updateFlow({
|
|
476
|
+
const updated = updateFlow({
|
|
482
477
|
status: 'aborted',
|
|
483
478
|
abortReason: reason,
|
|
484
479
|
abortedAt: new Date().toISOString()
|
|
485
480
|
}, worktreePath);
|
|
481
|
+
|
|
482
|
+
if (updated && flow && flow.projectPath) {
|
|
483
|
+
clearActiveTask(flow.projectPath);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return updated;
|
|
486
487
|
}
|
|
487
488
|
|
|
488
489
|
// =============================================================================
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isDeepStrictEqual } = require('util');
|
|
4
|
+
|
|
5
|
+
const RETRY_SLEEP_STATE = typeof SharedArrayBuffer === 'function' && typeof Atomics === 'object' && typeof Atomics.wait === 'function'
|
|
6
|
+
? new Int32Array(new SharedArrayBuffer(4))
|
|
7
|
+
: null;
|
|
8
|
+
|
|
9
|
+
function isPlainObject(value) {
|
|
10
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function hasUpdatedSubset(target, subset) {
|
|
14
|
+
if (!isPlainObject(subset)) {
|
|
15
|
+
return isDeepStrictEqual(target, subset);
|
|
16
|
+
}
|
|
17
|
+
if (!isPlainObject(target)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const [key, value] of Object.entries(subset)) {
|
|
22
|
+
if (!hasUpdatedSubset(target[key], value)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function updatesApplied(state, updates) {
|
|
30
|
+
if (!state) return false;
|
|
31
|
+
|
|
32
|
+
for (const [key, value] of Object.entries(updates || {})) {
|
|
33
|
+
if (key === '_version') continue;
|
|
34
|
+
if (!hasUpdatedSubset(state[key], value)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sleepForRetry(ms) {
|
|
43
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const delayMs = Math.floor(ms);
|
|
48
|
+
if (RETRY_SLEEP_STATE) {
|
|
49
|
+
try {
|
|
50
|
+
Atomics.wait(RETRY_SLEEP_STATE, 0, 0, delayMs);
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore environments where Atomics.wait exists but cannot be used.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
isPlainObject,
|
|
59
|
+
updatesApplied,
|
|
60
|
+
sleepForRetry
|
|
61
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsys",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
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",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"node": ">=18.0.0"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
+
"agentsys": "^5.0.0",
|
|
84
85
|
"js-yaml": "^4.1.1"
|
|
85
86
|
},
|
|
86
87
|
"devDependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agnix
|
|
3
3
|
description: "Use when user asks to 'lint agent configs', 'validate skills', 'check CLAUDE.md', 'validate hooks', 'lint MCP'. Validates agent configuration files against 155 rules across 10+ AI tools."
|
|
4
|
-
version: 5.0.
|
|
4
|
+
version: 5.0.2
|
|
5
5
|
argument-hint: "[path] [--fix] [--strict] [--target=claude-code|cursor|codex]"
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
285
|
+
const milestonesResult = execGhWithResult([
|
|
242
286
|
'api', 'repos/{owner}/{repo}/milestones',
|
|
243
|
-
'--
|
|
287
|
+
'--paginate',
|
|
288
|
+
'--slurp'
|
|
244
289
|
], opts);
|
|
245
290
|
|
|
246
|
-
if (
|
|
247
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
81
|
-
const stdout = error.stdout ? error.stdout
|
|
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}): ${
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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('[
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|