opencode-swarm 6.20.0 → 6.20.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/README.md +1 -1
- package/dist/cli/index.js +17 -6
- package/dist/hooks/delegation-gate.d.ts +10 -1
- package/dist/hooks/guardrails.d.ts +3 -3
- package/dist/index.js +160 -64
- package/dist/tools/lint.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -852,7 +852,7 @@ This release adds runtime detection hooks to catch and warn about architect work
|
|
|
852
852
|
- **Partial gate tracking**: Detects when QA gates are skipped
|
|
853
853
|
- **Self-fix detection**: Warns when an agent fixes its own gate failure (should delegate to fresh agent)
|
|
854
854
|
- **Batch detection**: Catches "implement X and add Y" batching in task requests
|
|
855
|
-
- **Zero-delegation detection**: Warns when tasks complete without any coder delegation
|
|
855
|
+
- **Zero-delegation detection**: Warns when tasks complete without any coder delegation; supports parsing delegation envelopes from JSON or KEY: VALUE text format for validation.
|
|
856
856
|
|
|
857
857
|
These hooks are advisory (warnings only) and help maintain workflow discipline during long sessions.
|
|
858
858
|
|
package/dist/cli/index.js
CHANGED
|
@@ -14186,6 +14186,8 @@ function log(message, data) {
|
|
|
14186
14186
|
}
|
|
14187
14187
|
}
|
|
14188
14188
|
function warn(message, data) {
|
|
14189
|
+
if (!DEBUG)
|
|
14190
|
+
return;
|
|
14189
14191
|
const timestamp = new Date().toISOString();
|
|
14190
14192
|
if (data !== undefined) {
|
|
14191
14193
|
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
|
|
@@ -32646,9 +32648,9 @@ function validateArgs(args) {
|
|
|
32646
32648
|
return false;
|
|
32647
32649
|
return true;
|
|
32648
32650
|
}
|
|
32649
|
-
function getLinterCommand(linter, mode) {
|
|
32651
|
+
function getLinterCommand(linter, mode, projectDir) {
|
|
32650
32652
|
const isWindows = process.platform === "win32";
|
|
32651
|
-
const binDir = path11.join(
|
|
32653
|
+
const binDir = path11.join(projectDir, "node_modules", ".bin");
|
|
32652
32654
|
const biomeBin = isWindows ? path11.join(binDir, "biome.EXE") : path11.join(binDir, "biome");
|
|
32653
32655
|
const eslintBin = isWindows ? path11.join(binDir, "eslint.cmd") : path11.join(binDir, "eslint");
|
|
32654
32656
|
switch (linter) {
|
|
@@ -32825,8 +32827,8 @@ async function detectAvailableLinter() {
|
|
|
32825
32827
|
} catch {}
|
|
32826
32828
|
return null;
|
|
32827
32829
|
}
|
|
32828
|
-
async function runLint(linter, mode) {
|
|
32829
|
-
const command = getLinterCommand(linter, mode);
|
|
32830
|
+
async function runLint(linter, mode, directory) {
|
|
32831
|
+
const command = getLinterCommand(linter, mode, directory);
|
|
32830
32832
|
const commandStr = command.join(" ");
|
|
32831
32833
|
if (commandStr.length > MAX_COMMAND_LENGTH) {
|
|
32832
32834
|
return {
|
|
@@ -32840,7 +32842,8 @@ async function runLint(linter, mode) {
|
|
|
32840
32842
|
try {
|
|
32841
32843
|
const proc = Bun.spawn(command, {
|
|
32842
32844
|
stdout: "pipe",
|
|
32843
|
-
stderr: "pipe"
|
|
32845
|
+
stderr: "pipe",
|
|
32846
|
+
cwd: directory
|
|
32844
32847
|
});
|
|
32845
32848
|
const [stdout, stderr] = await Promise.all([
|
|
32846
32849
|
new Response(proc.stdout).text(),
|
|
@@ -32954,11 +32957,19 @@ var lint = createSwarmTool({
|
|
|
32954
32957
|
};
|
|
32955
32958
|
return JSON.stringify(errorResult2, null, 2);
|
|
32956
32959
|
}
|
|
32960
|
+
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
32961
|
+
const errorResult2 = {
|
|
32962
|
+
success: false,
|
|
32963
|
+
mode: "check",
|
|
32964
|
+
error: "project directory is required but was not provided"
|
|
32965
|
+
};
|
|
32966
|
+
return JSON.stringify(errorResult2, null, 2);
|
|
32967
|
+
}
|
|
32957
32968
|
const { mode } = args;
|
|
32958
32969
|
const cwd = directory;
|
|
32959
32970
|
const linter = await detectAvailableLinter();
|
|
32960
32971
|
if (linter) {
|
|
32961
|
-
const result = await runLint(linter, mode);
|
|
32972
|
+
const result = await runLint(linter, mode, directory);
|
|
32962
32973
|
return JSON.stringify(result, null, 2);
|
|
32963
32974
|
}
|
|
32964
32975
|
const additionalLinter = detectAdditionalLinter(cwd);
|
|
@@ -5,13 +5,22 @@
|
|
|
5
5
|
* Uses experimental.chat.messages.transform to provide non-blocking guidance.
|
|
6
6
|
*/
|
|
7
7
|
import type { PluginConfig } from '../config';
|
|
8
|
-
import type { DelegationEnvelope } from '../types/delegation.js';
|
|
8
|
+
import type { DelegationEnvelope, EnvelopeValidationResult } from '../types/delegation.js';
|
|
9
9
|
/**
|
|
10
10
|
* Parses a string to extract a DelegationEnvelope.
|
|
11
11
|
* Returns null if no valid envelope is found.
|
|
12
12
|
* Never throws - all errors are caught and result in null.
|
|
13
13
|
*/
|
|
14
14
|
export declare function parseDelegationEnvelope(content: string): DelegationEnvelope | null;
|
|
15
|
+
interface ValidationContext {
|
|
16
|
+
planTasks: string[];
|
|
17
|
+
validAgents: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validates a DelegationEnvelope against the current plan and agent list.
|
|
21
|
+
* Returns { valid: true } on success, or { valid: false; reason: string } on failure.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateDelegationEnvelope(envelope: unknown, context: ValidationContext): EnvelopeValidationResult;
|
|
15
24
|
interface MessageInfo {
|
|
16
25
|
role: string;
|
|
17
26
|
agent?: string;
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
import { type GuardrailsConfig } from '../config/schema';
|
|
10
10
|
/**
|
|
11
11
|
* Creates guardrails hooks for circuit breaker protection
|
|
12
|
-
* @param
|
|
13
|
-
* @param config Guardrails configuration
|
|
12
|
+
* @param directoryOrConfig Working directory (from plugin init context) OR legacy config object for backward compatibility
|
|
13
|
+
* @param config Guardrails configuration (optional if first arg is config)
|
|
14
14
|
* @returns Tool before/after hooks and messages transform hook
|
|
15
15
|
*/
|
|
16
|
-
export declare function createGuardrailsHooks(
|
|
16
|
+
export declare function createGuardrailsHooks(directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig): {
|
|
17
17
|
toolBefore: (input: {
|
|
18
18
|
tool: string;
|
|
19
19
|
sessionID: string;
|
package/dist/index.js
CHANGED
|
@@ -14248,6 +14248,8 @@ function log(message, data) {
|
|
|
14248
14248
|
}
|
|
14249
14249
|
}
|
|
14250
14250
|
function warn(message, data) {
|
|
14251
|
+
if (!DEBUG)
|
|
14252
|
+
return;
|
|
14251
14253
|
const timestamp = new Date().toISOString();
|
|
14252
14254
|
if (data !== undefined) {
|
|
14253
14255
|
console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
|
|
@@ -32236,9 +32238,9 @@ function validateArgs(args2) {
|
|
|
32236
32238
|
return false;
|
|
32237
32239
|
return true;
|
|
32238
32240
|
}
|
|
32239
|
-
function getLinterCommand(linter, mode) {
|
|
32241
|
+
function getLinterCommand(linter, mode, projectDir) {
|
|
32240
32242
|
const isWindows = process.platform === "win32";
|
|
32241
|
-
const binDir = path18.join(
|
|
32243
|
+
const binDir = path18.join(projectDir, "node_modules", ".bin");
|
|
32242
32244
|
const biomeBin = isWindows ? path18.join(binDir, "biome.EXE") : path18.join(binDir, "biome");
|
|
32243
32245
|
const eslintBin = isWindows ? path18.join(binDir, "eslint.cmd") : path18.join(binDir, "eslint");
|
|
32244
32246
|
switch (linter) {
|
|
@@ -32415,8 +32417,8 @@ async function detectAvailableLinter() {
|
|
|
32415
32417
|
} catch {}
|
|
32416
32418
|
return null;
|
|
32417
32419
|
}
|
|
32418
|
-
async function runLint(linter, mode) {
|
|
32419
|
-
const command = getLinterCommand(linter, mode);
|
|
32420
|
+
async function runLint(linter, mode, directory) {
|
|
32421
|
+
const command = getLinterCommand(linter, mode, directory);
|
|
32420
32422
|
const commandStr = command.join(" ");
|
|
32421
32423
|
if (commandStr.length > MAX_COMMAND_LENGTH) {
|
|
32422
32424
|
return {
|
|
@@ -32430,7 +32432,8 @@ async function runLint(linter, mode) {
|
|
|
32430
32432
|
try {
|
|
32431
32433
|
const proc = Bun.spawn(command, {
|
|
32432
32434
|
stdout: "pipe",
|
|
32433
|
-
stderr: "pipe"
|
|
32435
|
+
stderr: "pipe",
|
|
32436
|
+
cwd: directory
|
|
32434
32437
|
});
|
|
32435
32438
|
const [stdout, stderr] = await Promise.all([
|
|
32436
32439
|
new Response(proc.stdout).text(),
|
|
@@ -32550,11 +32553,19 @@ var init_lint = __esm(() => {
|
|
|
32550
32553
|
};
|
|
32551
32554
|
return JSON.stringify(errorResult2, null, 2);
|
|
32552
32555
|
}
|
|
32556
|
+
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
32557
|
+
const errorResult2 = {
|
|
32558
|
+
success: false,
|
|
32559
|
+
mode: "check",
|
|
32560
|
+
error: "project directory is required but was not provided"
|
|
32561
|
+
};
|
|
32562
|
+
return JSON.stringify(errorResult2, null, 2);
|
|
32563
|
+
}
|
|
32553
32564
|
const { mode } = args2;
|
|
32554
32565
|
const cwd = directory;
|
|
32555
32566
|
const linter = await detectAvailableLinter();
|
|
32556
32567
|
if (linter) {
|
|
32557
|
-
const result = await runLint(linter, mode);
|
|
32568
|
+
const result = await runLint(linter, mode, directory);
|
|
32558
32569
|
return JSON.stringify(result, null, 2);
|
|
32559
32570
|
}
|
|
32560
32571
|
const additionalLinter = detectAdditionalLinter(cwd);
|
|
@@ -34357,10 +34368,10 @@ async function runVersionCheck(dir, _timeoutMs) {
|
|
|
34357
34368
|
};
|
|
34358
34369
|
}
|
|
34359
34370
|
}
|
|
34360
|
-
async function runLintCheck(
|
|
34371
|
+
async function runLintCheck(dir, linter, timeoutMs) {
|
|
34361
34372
|
const startTime = Date.now();
|
|
34362
34373
|
try {
|
|
34363
|
-
const lintPromise = runLint(linter, "check");
|
|
34374
|
+
const lintPromise = runLint(linter, "check", dir);
|
|
34364
34375
|
const timeoutPromise = new Promise((_, reject) => {
|
|
34365
34376
|
setTimeout(() => {
|
|
34366
34377
|
reject(new Error(`Lint check timed out after ${timeoutMs}ms`));
|
|
@@ -39092,6 +39103,8 @@ If you are about to edit a source file: STOP. You are violating protocol.
|
|
|
39092
39103
|
writeCount > 0 on source files from the Architect is equivalent to GATE_DELEGATION_BYPASS.
|
|
39093
39104
|
|
|
39094
39105
|
PLAN STATE PROTECTION
|
|
39106
|
+
WHY: plan.md is auto-regenerated by PlanSyncWorker from plan.json. Any direct write to plan.md will be silently overwritten within seconds. If you see plan.md reverting after your edit, this is the cause \u2014 the worker detected a plan.json change and regenerated plan.md from it.
|
|
39107
|
+
The correct tools: save_plan to create or restructure a plan (writes plan.json \u2192 triggers regeneration); update_task_status() for task completion status; phase_complete() for phase-level transitions.
|
|
39095
39108
|
.swarm/plan.md and .swarm/plan.json are READABLE but NOT DIRECTLY WRITABLE for state transitions.
|
|
39096
39109
|
Task-level status changes (marking individual tasks as "completed") must use update_task_status().
|
|
39097
39110
|
Phase-level completion (marking an entire phase as done) must use phase_complete().
|
|
@@ -44993,7 +45006,7 @@ async function handleKnowledgeListCommand(directory, _args) {
|
|
|
44993
45006
|
"|------|----------|------------|---------------------|"
|
|
44994
45007
|
];
|
|
44995
45008
|
for (const entry of entries) {
|
|
44996
|
-
const truncatedLesson = entry.lesson.length > 60 ? entry.lesson.slice(0, 57)
|
|
45009
|
+
const truncatedLesson = entry.lesson.length > 60 ? `${entry.lesson.slice(0, 57)}...` : entry.lesson;
|
|
44997
45010
|
const confidencePct = Math.round(entry.confidence * 100);
|
|
44998
45011
|
lines.push(`| ${entry.id.slice(0, 8)}... | ${entry.category} | ${confidencePct}% | ${truncatedLesson} |`);
|
|
44999
45012
|
}
|
|
@@ -45605,7 +45618,7 @@ async function handleRollbackCommand(directory, args2) {
|
|
|
45605
45618
|
`);
|
|
45606
45619
|
}
|
|
45607
45620
|
const targetPhase = parseInt(phaseArg, 10);
|
|
45608
|
-
if (isNaN(targetPhase) || targetPhase < 1) {
|
|
45621
|
+
if (Number.isNaN(targetPhase) || targetPhase < 1) {
|
|
45609
45622
|
return "Error: Phase number must be a positive integer.";
|
|
45610
45623
|
}
|
|
45611
45624
|
const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
|
|
@@ -45650,7 +45663,7 @@ async function handleRollbackCommand(directory, args2) {
|
|
|
45650
45663
|
timestamp: new Date().toISOString()
|
|
45651
45664
|
};
|
|
45652
45665
|
try {
|
|
45653
|
-
fs12.appendFileSync(eventsPath, JSON.stringify(rollbackEvent)
|
|
45666
|
+
fs12.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
|
|
45654
45667
|
`);
|
|
45655
45668
|
} catch (error93) {
|
|
45656
45669
|
console.error("Failed to write rollback event:", error93);
|
|
@@ -47493,14 +47506,24 @@ function getCurrentTaskId(sessionId) {
|
|
|
47493
47506
|
const session = swarmState.agentSessions.get(sessionId);
|
|
47494
47507
|
return session?.currentTaskId ?? `${sessionId}:unknown`;
|
|
47495
47508
|
}
|
|
47496
|
-
function createGuardrailsHooks(
|
|
47497
|
-
|
|
47509
|
+
function createGuardrailsHooks(directoryOrConfig, config3) {
|
|
47510
|
+
let directory;
|
|
47511
|
+
let guardrailsConfig;
|
|
47512
|
+
if (directoryOrConfig && typeof directoryOrConfig === "object" && "enabled" in directoryOrConfig) {
|
|
47513
|
+
directory = process.cwd();
|
|
47514
|
+
guardrailsConfig = directoryOrConfig;
|
|
47515
|
+
} else {
|
|
47516
|
+
directory = directoryOrConfig ?? process.cwd();
|
|
47517
|
+
guardrailsConfig = config3;
|
|
47518
|
+
}
|
|
47519
|
+
if (guardrailsConfig?.enabled === false) {
|
|
47498
47520
|
return {
|
|
47499
47521
|
toolBefore: async () => {},
|
|
47500
47522
|
toolAfter: async () => {},
|
|
47501
47523
|
messagesTransform: async () => {}
|
|
47502
47524
|
};
|
|
47503
47525
|
}
|
|
47526
|
+
const cfg = guardrailsConfig;
|
|
47504
47527
|
const inputArgsByCallID = new Map;
|
|
47505
47528
|
return {
|
|
47506
47529
|
toolBefore: async (input, output) => {
|
|
@@ -47508,6 +47531,13 @@ function createGuardrailsHooks(directory, config3) {
|
|
|
47508
47531
|
if (currentSession?.delegationActive) {} else if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
47509
47532
|
const args2 = output.args;
|
|
47510
47533
|
const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
|
|
47534
|
+
if (typeof targetPath === "string" && targetPath.length > 0) {
|
|
47535
|
+
const resolvedTarget = path25.resolve(directory, targetPath).toLowerCase();
|
|
47536
|
+
const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
|
|
47537
|
+
if (resolvedTarget === planMdPath) {
|
|
47538
|
+
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
47539
|
+
}
|
|
47540
|
+
}
|
|
47511
47541
|
if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
|
|
47512
47542
|
const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
|
|
47513
47543
|
if (typeof patchText === "string") {
|
|
@@ -47523,6 +47553,11 @@ function createGuardrailsHooks(directory, config3) {
|
|
|
47523
47553
|
paths.add(p);
|
|
47524
47554
|
}
|
|
47525
47555
|
for (const p of paths) {
|
|
47556
|
+
const resolvedP = path25.resolve(directory, p);
|
|
47557
|
+
const planMdPath = path25.resolve(directory, ".swarm", "plan.md");
|
|
47558
|
+
if (resolvedP.toLowerCase() === planMdPath.toLowerCase()) {
|
|
47559
|
+
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
47560
|
+
}
|
|
47526
47561
|
if (isOutsideSwarmDir(p, directory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
47527
47562
|
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
47528
47563
|
if (session2) {
|
|
@@ -47581,7 +47616,7 @@ function createGuardrailsHooks(directory, config3) {
|
|
|
47581
47616
|
if (resolvedName === ORCHESTRATOR_NAME) {
|
|
47582
47617
|
return;
|
|
47583
47618
|
}
|
|
47584
|
-
const agentConfig = resolveGuardrailsConfig(
|
|
47619
|
+
const agentConfig = resolveGuardrailsConfig(cfg, session.agentName);
|
|
47585
47620
|
if (agentConfig.max_duration_minutes === 0 && agentConfig.max_tool_calls === 0) {
|
|
47586
47621
|
return;
|
|
47587
47622
|
}
|
|
@@ -51240,7 +51275,7 @@ function validateExtensions(extensions) {
|
|
|
51240
51275
|
}
|
|
51241
51276
|
return { valid: true, value: extensions, error: null };
|
|
51242
51277
|
}
|
|
51243
|
-
async function getGitChurn(days) {
|
|
51278
|
+
async function getGitChurn(days, directory) {
|
|
51244
51279
|
const churnMap = new Map;
|
|
51245
51280
|
const proc = Bun.spawn([
|
|
51246
51281
|
"git",
|
|
@@ -51250,7 +51285,8 @@ async function getGitChurn(days) {
|
|
|
51250
51285
|
"--pretty=format:"
|
|
51251
51286
|
], {
|
|
51252
51287
|
stdout: "pipe",
|
|
51253
|
-
stderr: "pipe"
|
|
51288
|
+
stderr: "pipe",
|
|
51289
|
+
cwd: directory
|
|
51254
51290
|
});
|
|
51255
51291
|
const stdout = await new Response(proc.stdout).text();
|
|
51256
51292
|
await proc.exited;
|
|
@@ -51315,8 +51351,8 @@ function getComplexityForFile(filePath) {
|
|
|
51315
51351
|
return null;
|
|
51316
51352
|
}
|
|
51317
51353
|
}
|
|
51318
|
-
async function analyzeHotspots(days, topN, extensions) {
|
|
51319
|
-
const churnMap = await getGitChurn(days);
|
|
51354
|
+
async function analyzeHotspots(days, topN, extensions, directory) {
|
|
51355
|
+
const churnMap = await getGitChurn(days, directory);
|
|
51320
51356
|
const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
|
|
51321
51357
|
const filteredChurn = new Map;
|
|
51322
51358
|
for (const [file3, count] of churnMap) {
|
|
@@ -51326,7 +51362,7 @@ async function analyzeHotspots(days, topN, extensions) {
|
|
|
51326
51362
|
}
|
|
51327
51363
|
}
|
|
51328
51364
|
const hotspots = [];
|
|
51329
|
-
const cwd =
|
|
51365
|
+
const cwd = directory;
|
|
51330
51366
|
let analyzedFiles = 0;
|
|
51331
51367
|
for (const [file3, churnCount] of filteredChurn) {
|
|
51332
51368
|
let fullPath = file3;
|
|
@@ -51378,7 +51414,22 @@ var complexity_hotspots = createSwarmTool({
|
|
|
51378
51414
|
top_n: tool.schema.number().optional().describe("Number of top hotspots to return (default: 20, valid range: 1-100)"),
|
|
51379
51415
|
extensions: tool.schema.string().optional().describe('Comma-separated extensions to include (default: "ts,tsx,js,jsx,py,rs,ps1")')
|
|
51380
51416
|
},
|
|
51381
|
-
async execute(args2,
|
|
51417
|
+
async execute(args2, directory) {
|
|
51418
|
+
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
51419
|
+
const errorResult = {
|
|
51420
|
+
error: "project directory is required but was not provided",
|
|
51421
|
+
analyzedFiles: 0,
|
|
51422
|
+
period: "0 days",
|
|
51423
|
+
hotspots: [],
|
|
51424
|
+
summary: {
|
|
51425
|
+
fullGates: 0,
|
|
51426
|
+
securityReview: 0,
|
|
51427
|
+
enhancedReview: 0,
|
|
51428
|
+
standard: 0
|
|
51429
|
+
}
|
|
51430
|
+
};
|
|
51431
|
+
return JSON.stringify(errorResult, null, 2);
|
|
51432
|
+
}
|
|
51382
51433
|
let daysInput;
|
|
51383
51434
|
let topNInput;
|
|
51384
51435
|
let extensionsInput;
|
|
@@ -51442,7 +51493,7 @@ var complexity_hotspots = createSwarmTool({
|
|
|
51442
51493
|
const topN = topNValidation.value;
|
|
51443
51494
|
const extensions = extensionsValidation.value.split(",").map((e) => e.trim());
|
|
51444
51495
|
try {
|
|
51445
|
-
const result = await analyzeHotspots(days, topN, extensions);
|
|
51496
|
+
const result = await analyzeHotspots(days, topN, extensions, directory);
|
|
51446
51497
|
return JSON.stringify(result, null, 2);
|
|
51447
51498
|
} catch (e) {
|
|
51448
51499
|
const errorResult = {
|
|
@@ -51477,7 +51528,7 @@ var SAFE_REF_PATTERN = /^[a-zA-Z0-9._\-/~^@{}]+$/;
|
|
|
51477
51528
|
var MAX_REF_LENGTH = 256;
|
|
51478
51529
|
var MAX_PATH_LENGTH = 500;
|
|
51479
51530
|
var SHELL_METACHARACTERS2 = /[;|&$`(){}<>!'"]/;
|
|
51480
|
-
var CONTROL_CHAR_PATTERN2 =
|
|
51531
|
+
var CONTROL_CHAR_PATTERN2 = /[\u0000-\u001F\u007F]/;
|
|
51481
51532
|
function validateBase(base) {
|
|
51482
51533
|
if (base.length > MAX_REF_LENGTH) {
|
|
51483
51534
|
return `base ref exceeds maximum length of ${MAX_REF_LENGTH}`;
|
|
@@ -51515,8 +51566,17 @@ var diff = tool({
|
|
|
51515
51566
|
base: tool.schema.string().optional().describe('Base ref to diff against (default: HEAD). Use "staged" for staged changes, "unstaged" for working tree changes.'),
|
|
51516
51567
|
paths: tool.schema.array(tool.schema.string()).optional().describe("Optional file paths to restrict diff scope.")
|
|
51517
51568
|
},
|
|
51518
|
-
async execute(args2,
|
|
51569
|
+
async execute(args2, context) {
|
|
51519
51570
|
try {
|
|
51571
|
+
if (!context.directory || typeof context.directory !== "string" || context.directory.trim() === "") {
|
|
51572
|
+
const errorResult = {
|
|
51573
|
+
error: "project directory is required but was not provided",
|
|
51574
|
+
files: [],
|
|
51575
|
+
contractChanges: [],
|
|
51576
|
+
hasContractChanges: false
|
|
51577
|
+
};
|
|
51578
|
+
return JSON.stringify(errorResult, null, 2);
|
|
51579
|
+
}
|
|
51520
51580
|
const base = args2.base ?? "HEAD";
|
|
51521
51581
|
const baseValidationError = validateBase(base);
|
|
51522
51582
|
if (baseValidationError) {
|
|
@@ -51555,12 +51615,14 @@ var diff = tool({
|
|
|
51555
51615
|
const numstatOutput = execFileSync("git", numstatArgs, {
|
|
51556
51616
|
encoding: "utf-8",
|
|
51557
51617
|
timeout: DIFF_TIMEOUT_MS,
|
|
51558
|
-
maxBuffer: MAX_BUFFER_BYTES
|
|
51618
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
51619
|
+
cwd: context.directory
|
|
51559
51620
|
});
|
|
51560
51621
|
const fullDiffOutput = execFileSync("git", fullDiffArgs, {
|
|
51561
51622
|
encoding: "utf-8",
|
|
51562
51623
|
timeout: DIFF_TIMEOUT_MS,
|
|
51563
|
-
maxBuffer: MAX_BUFFER_BYTES
|
|
51624
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
51625
|
+
cwd: context.directory
|
|
51564
51626
|
});
|
|
51565
51627
|
const files = [];
|
|
51566
51628
|
const numstatLines = numstatOutput.split(`
|
|
@@ -51610,7 +51672,7 @@ var diff = tool({
|
|
|
51610
51672
|
return JSON.stringify(result, null, 2);
|
|
51611
51673
|
} catch (e) {
|
|
51612
51674
|
const errorResult = {
|
|
51613
|
-
error: e instanceof Error ? `git diff failed: ${e.
|
|
51675
|
+
error: e instanceof Error ? `git diff failed: ${e.message}` : "git diff failed: unknown error",
|
|
51614
51676
|
files: [],
|
|
51615
51677
|
contractChanges: [],
|
|
51616
51678
|
hasContractChanges: false
|
|
@@ -52906,9 +52968,9 @@ function validateArgs3(args2) {
|
|
|
52906
52968
|
}
|
|
52907
52969
|
return true;
|
|
52908
52970
|
}
|
|
52909
|
-
function detectEcosystems() {
|
|
52971
|
+
function detectEcosystems(directory) {
|
|
52910
52972
|
const ecosystems = [];
|
|
52911
|
-
const cwd =
|
|
52973
|
+
const cwd = directory;
|
|
52912
52974
|
if (fs23.existsSync(path34.join(cwd, "package.json"))) {
|
|
52913
52975
|
ecosystems.push("npm");
|
|
52914
52976
|
}
|
|
@@ -52935,13 +52997,13 @@ function detectEcosystems() {
|
|
|
52935
52997
|
}
|
|
52936
52998
|
return ecosystems;
|
|
52937
52999
|
}
|
|
52938
|
-
async function runNpmAudit() {
|
|
53000
|
+
async function runNpmAudit(directory) {
|
|
52939
53001
|
const command = ["npm", "audit", "--json"];
|
|
52940
53002
|
try {
|
|
52941
53003
|
const proc = Bun.spawn(command, {
|
|
52942
53004
|
stdout: "pipe",
|
|
52943
53005
|
stderr: "pipe",
|
|
52944
|
-
cwd:
|
|
53006
|
+
cwd: directory
|
|
52945
53007
|
});
|
|
52946
53008
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
52947
53009
|
const result = await Promise.race([
|
|
@@ -53058,13 +53120,13 @@ function mapNpmSeverity(severity) {
|
|
|
53058
53120
|
return "info";
|
|
53059
53121
|
}
|
|
53060
53122
|
}
|
|
53061
|
-
async function runPipAudit() {
|
|
53123
|
+
async function runPipAudit(directory) {
|
|
53062
53124
|
const command = ["pip-audit", "--format=json"];
|
|
53063
53125
|
try {
|
|
53064
53126
|
const proc = Bun.spawn(command, {
|
|
53065
53127
|
stdout: "pipe",
|
|
53066
53128
|
stderr: "pipe",
|
|
53067
|
-
cwd:
|
|
53129
|
+
cwd: directory
|
|
53068
53130
|
});
|
|
53069
53131
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53070
53132
|
const result = await Promise.race([
|
|
@@ -53189,13 +53251,13 @@ async function runPipAudit() {
|
|
|
53189
53251
|
};
|
|
53190
53252
|
}
|
|
53191
53253
|
}
|
|
53192
|
-
async function runCargoAudit() {
|
|
53254
|
+
async function runCargoAudit(directory) {
|
|
53193
53255
|
const command = ["cargo", "audit", "--json"];
|
|
53194
53256
|
try {
|
|
53195
53257
|
const proc = Bun.spawn(command, {
|
|
53196
53258
|
stdout: "pipe",
|
|
53197
53259
|
stderr: "pipe",
|
|
53198
|
-
cwd:
|
|
53260
|
+
cwd: directory
|
|
53199
53261
|
});
|
|
53200
53262
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53201
53263
|
const result = await Promise.race([
|
|
@@ -53303,7 +53365,7 @@ function mapCargoSeverity(cvss) {
|
|
|
53303
53365
|
return "moderate";
|
|
53304
53366
|
return "low";
|
|
53305
53367
|
}
|
|
53306
|
-
async function runGoAudit() {
|
|
53368
|
+
async function runGoAudit(directory) {
|
|
53307
53369
|
const command = ["govulncheck", "-json", "./..."];
|
|
53308
53370
|
if (!isCommandAvailable("govulncheck")) {
|
|
53309
53371
|
warn("[pkg-audit] govulncheck not found, skipping Go audit");
|
|
@@ -53322,7 +53384,7 @@ async function runGoAudit() {
|
|
|
53322
53384
|
const proc = Bun.spawn(command, {
|
|
53323
53385
|
stdout: "pipe",
|
|
53324
53386
|
stderr: "pipe",
|
|
53325
|
-
cwd:
|
|
53387
|
+
cwd: directory
|
|
53326
53388
|
});
|
|
53327
53389
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53328
53390
|
const result = await Promise.race([
|
|
@@ -53433,7 +53495,7 @@ async function runGoAudit() {
|
|
|
53433
53495
|
};
|
|
53434
53496
|
}
|
|
53435
53497
|
}
|
|
53436
|
-
async function runDotnetAudit() {
|
|
53498
|
+
async function runDotnetAudit(directory) {
|
|
53437
53499
|
const command = [
|
|
53438
53500
|
"dotnet",
|
|
53439
53501
|
"list",
|
|
@@ -53458,7 +53520,7 @@ async function runDotnetAudit() {
|
|
|
53458
53520
|
const proc = Bun.spawn(command, {
|
|
53459
53521
|
stdout: "pipe",
|
|
53460
53522
|
stderr: "pipe",
|
|
53461
|
-
cwd:
|
|
53523
|
+
cwd: directory
|
|
53462
53524
|
});
|
|
53463
53525
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53464
53526
|
const result = await Promise.race([
|
|
@@ -53557,7 +53619,7 @@ function mapDotnetSeverity(severity) {
|
|
|
53557
53619
|
return "info";
|
|
53558
53620
|
}
|
|
53559
53621
|
}
|
|
53560
|
-
async function runBundleAudit() {
|
|
53622
|
+
async function runBundleAudit(directory) {
|
|
53561
53623
|
const useBundleExec = !isCommandAvailable("bundle-audit") && isCommandAvailable("bundle");
|
|
53562
53624
|
if (!isCommandAvailable("bundle-audit") && !isCommandAvailable("bundle")) {
|
|
53563
53625
|
warn("[pkg-audit] bundle-audit not found, skipping Ruby audit");
|
|
@@ -53577,7 +53639,7 @@ async function runBundleAudit() {
|
|
|
53577
53639
|
const proc = Bun.spawn(command, {
|
|
53578
53640
|
stdout: "pipe",
|
|
53579
53641
|
stderr: "pipe",
|
|
53580
|
-
cwd:
|
|
53642
|
+
cwd: directory
|
|
53581
53643
|
});
|
|
53582
53644
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53583
53645
|
const result = await Promise.race([
|
|
@@ -53704,7 +53766,7 @@ function mapBundleSeverity(adv) {
|
|
|
53704
53766
|
return "moderate";
|
|
53705
53767
|
return "low";
|
|
53706
53768
|
}
|
|
53707
|
-
async function runDartAudit() {
|
|
53769
|
+
async function runDartAudit(directory) {
|
|
53708
53770
|
const dartBin = isCommandAvailable("dart") ? "dart" : isCommandAvailable("flutter") ? "flutter" : null;
|
|
53709
53771
|
if (!dartBin) {
|
|
53710
53772
|
warn("[pkg-audit] dart/flutter not found, skipping Dart audit");
|
|
@@ -53724,7 +53786,7 @@ async function runDartAudit() {
|
|
|
53724
53786
|
const proc = Bun.spawn(command, {
|
|
53725
53787
|
stdout: "pipe",
|
|
53726
53788
|
stderr: "pipe",
|
|
53727
|
-
cwd:
|
|
53789
|
+
cwd: directory
|
|
53728
53790
|
});
|
|
53729
53791
|
const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
|
|
53730
53792
|
const result = await Promise.race([
|
|
@@ -53823,8 +53885,8 @@ async function runDartAudit() {
|
|
|
53823
53885
|
};
|
|
53824
53886
|
}
|
|
53825
53887
|
}
|
|
53826
|
-
async function runAutoAudit() {
|
|
53827
|
-
const ecosystems = detectEcosystems();
|
|
53888
|
+
async function runAutoAudit(directory) {
|
|
53889
|
+
const ecosystems = detectEcosystems(directory);
|
|
53828
53890
|
if (ecosystems.length === 0) {
|
|
53829
53891
|
return {
|
|
53830
53892
|
ecosystems: [],
|
|
@@ -53839,25 +53901,25 @@ async function runAutoAudit() {
|
|
|
53839
53901
|
for (const eco of ecosystems) {
|
|
53840
53902
|
switch (eco) {
|
|
53841
53903
|
case "npm":
|
|
53842
|
-
results.push(await runNpmAudit());
|
|
53904
|
+
results.push(await runNpmAudit(directory));
|
|
53843
53905
|
break;
|
|
53844
53906
|
case "pip":
|
|
53845
|
-
results.push(await runPipAudit());
|
|
53907
|
+
results.push(await runPipAudit(directory));
|
|
53846
53908
|
break;
|
|
53847
53909
|
case "cargo":
|
|
53848
|
-
results.push(await runCargoAudit());
|
|
53910
|
+
results.push(await runCargoAudit(directory));
|
|
53849
53911
|
break;
|
|
53850
53912
|
case "go":
|
|
53851
|
-
results.push(await runGoAudit());
|
|
53913
|
+
results.push(await runGoAudit(directory));
|
|
53852
53914
|
break;
|
|
53853
53915
|
case "dotnet":
|
|
53854
|
-
results.push(await runDotnetAudit());
|
|
53916
|
+
results.push(await runDotnetAudit(directory));
|
|
53855
53917
|
break;
|
|
53856
53918
|
case "ruby":
|
|
53857
|
-
results.push(await runBundleAudit());
|
|
53919
|
+
results.push(await runBundleAudit(directory));
|
|
53858
53920
|
break;
|
|
53859
53921
|
case "dart":
|
|
53860
|
-
results.push(await runDartAudit());
|
|
53922
|
+
results.push(await runDartAudit(directory));
|
|
53861
53923
|
break;
|
|
53862
53924
|
}
|
|
53863
53925
|
}
|
|
@@ -53883,40 +53945,46 @@ var pkg_audit = createSwarmTool({
|
|
|
53883
53945
|
args: {
|
|
53884
53946
|
ecosystem: tool.schema.enum(["auto", "npm", "pip", "cargo", "go", "dotnet", "ruby", "dart"]).default("auto").describe('Package ecosystem to audit: "auto" (detect from project files), "npm", "pip", "cargo", "go" (govulncheck), "dotnet" (dotnet list package), "ruby" (bundle-audit), or "dart" (dart pub outdated)')
|
|
53885
53947
|
},
|
|
53886
|
-
async execute(args2,
|
|
53948
|
+
async execute(args2, directory) {
|
|
53887
53949
|
if (!validateArgs3(args2)) {
|
|
53888
53950
|
const errorResult = {
|
|
53889
53951
|
error: 'Invalid arguments: ecosystem must be "auto", "npm", "pip", "cargo", "go", "dotnet", "ruby", or "dart"'
|
|
53890
53952
|
};
|
|
53891
53953
|
return JSON.stringify(errorResult, null, 2);
|
|
53892
53954
|
}
|
|
53955
|
+
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
53956
|
+
const errorResult = {
|
|
53957
|
+
error: "project directory is required but was not provided"
|
|
53958
|
+
};
|
|
53959
|
+
return JSON.stringify(errorResult, null, 2);
|
|
53960
|
+
}
|
|
53893
53961
|
const obj = args2;
|
|
53894
53962
|
const ecosystem = obj.ecosystem || "auto";
|
|
53895
53963
|
let result;
|
|
53896
53964
|
switch (ecosystem) {
|
|
53897
53965
|
case "auto":
|
|
53898
|
-
result = await runAutoAudit();
|
|
53966
|
+
result = await runAutoAudit(directory);
|
|
53899
53967
|
break;
|
|
53900
53968
|
case "npm":
|
|
53901
|
-
result = await runNpmAudit();
|
|
53969
|
+
result = await runNpmAudit(directory);
|
|
53902
53970
|
break;
|
|
53903
53971
|
case "pip":
|
|
53904
|
-
result = await runPipAudit();
|
|
53972
|
+
result = await runPipAudit(directory);
|
|
53905
53973
|
break;
|
|
53906
53974
|
case "cargo":
|
|
53907
|
-
result = await runCargoAudit();
|
|
53975
|
+
result = await runCargoAudit(directory);
|
|
53908
53976
|
break;
|
|
53909
53977
|
case "go":
|
|
53910
|
-
result = await runGoAudit();
|
|
53978
|
+
result = await runGoAudit(directory);
|
|
53911
53979
|
break;
|
|
53912
53980
|
case "dotnet":
|
|
53913
|
-
result = await runDotnetAudit();
|
|
53981
|
+
result = await runDotnetAudit(directory);
|
|
53914
53982
|
break;
|
|
53915
53983
|
case "ruby":
|
|
53916
|
-
result = await runBundleAudit();
|
|
53984
|
+
result = await runBundleAudit(directory);
|
|
53917
53985
|
break;
|
|
53918
53986
|
case "dart":
|
|
53919
|
-
result = await runDartAudit();
|
|
53987
|
+
result = await runDartAudit(directory);
|
|
53920
53988
|
break;
|
|
53921
53989
|
}
|
|
53922
53990
|
return JSON.stringify(result, null, 2);
|
|
@@ -56088,7 +56156,7 @@ async function runLintWrapped(files, directory, _config) {
|
|
|
56088
56156
|
duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
|
|
56089
56157
|
};
|
|
56090
56158
|
}
|
|
56091
|
-
const result = await runWithTimeout(runLint(linter, "check"), TOOL_TIMEOUT_MS);
|
|
56159
|
+
const result = await runWithTimeout(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
|
|
56092
56160
|
return {
|
|
56093
56161
|
ran: true,
|
|
56094
56162
|
result,
|
|
@@ -56104,7 +56172,7 @@ async function runLintWrapped(files, directory, _config) {
|
|
|
56104
56172
|
}
|
|
56105
56173
|
async function runLintOnFiles(linter, files, workspaceDir) {
|
|
56106
56174
|
const isWindows = process.platform === "win32";
|
|
56107
|
-
const binDir = path37.join(
|
|
56175
|
+
const binDir = path37.join(workspaceDir, "node_modules", ".bin");
|
|
56108
56176
|
const validatedFiles = [];
|
|
56109
56177
|
for (const file3 of files) {
|
|
56110
56178
|
if (typeof file3 !== "string") {
|
|
@@ -56137,7 +56205,8 @@ async function runLintOnFiles(linter, files, workspaceDir) {
|
|
|
56137
56205
|
try {
|
|
56138
56206
|
const proc = Bun.spawn(command, {
|
|
56139
56207
|
stdout: "pipe",
|
|
56140
|
-
stderr: "pipe"
|
|
56208
|
+
stderr: "pipe",
|
|
56209
|
+
cwd: workspaceDir
|
|
56141
56210
|
});
|
|
56142
56211
|
const [stdout, stderr] = await Promise.all([
|
|
56143
56212
|
new Response(proc.stdout).text(),
|
|
@@ -56528,7 +56597,7 @@ var pre_check_batch = createSwarmTool({
|
|
|
56528
56597
|
directory: tool.schema.string().describe('Directory to run checks in (e.g., "." or "./src")'),
|
|
56529
56598
|
sast_threshold: tool.schema.enum(["low", "medium", "high", "critical"]).optional().describe("Minimum severity for SAST findings to cause failure (default: medium)")
|
|
56530
56599
|
},
|
|
56531
|
-
async execute(args2,
|
|
56600
|
+
async execute(args2, directory) {
|
|
56532
56601
|
if (!args2 || typeof args2 !== "object") {
|
|
56533
56602
|
const errorResult = {
|
|
56534
56603
|
gates_passed: false,
|
|
@@ -56544,6 +56613,33 @@ var pre_check_batch = createSwarmTool({
|
|
|
56544
56613
|
};
|
|
56545
56614
|
return JSON.stringify(errorResult, null, 2);
|
|
56546
56615
|
}
|
|
56616
|
+
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
56617
|
+
const errorResult = {
|
|
56618
|
+
gates_passed: false,
|
|
56619
|
+
lint: {
|
|
56620
|
+
ran: false,
|
|
56621
|
+
error: "project directory is required but was not provided",
|
|
56622
|
+
duration_ms: 0
|
|
56623
|
+
},
|
|
56624
|
+
secretscan: {
|
|
56625
|
+
ran: false,
|
|
56626
|
+
error: "project directory is required but was not provided",
|
|
56627
|
+
duration_ms: 0
|
|
56628
|
+
},
|
|
56629
|
+
sast_scan: {
|
|
56630
|
+
ran: false,
|
|
56631
|
+
error: "project directory is required but was not provided",
|
|
56632
|
+
duration_ms: 0
|
|
56633
|
+
},
|
|
56634
|
+
quality_budget: {
|
|
56635
|
+
ran: false,
|
|
56636
|
+
error: "project directory is required but was not provided",
|
|
56637
|
+
duration_ms: 0
|
|
56638
|
+
},
|
|
56639
|
+
total_duration_ms: 0
|
|
56640
|
+
};
|
|
56641
|
+
return JSON.stringify(errorResult, null, 2);
|
|
56642
|
+
}
|
|
56547
56643
|
const typedArgs = args2;
|
|
56548
56644
|
if (!typedArgs.directory) {
|
|
56549
56645
|
const errorResult = {
|
package/dist/tools/lint.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare function containsControlChars(str: string): boolean;
|
|
|
29
29
|
export declare function validateArgs(args: unknown): args is {
|
|
30
30
|
mode: 'fix' | 'check';
|
|
31
31
|
};
|
|
32
|
-
export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check'): string[];
|
|
32
|
+
export declare function getLinterCommand(linter: SupportedLinter, mode: 'fix' | 'check', projectDir: string): string[];
|
|
33
33
|
/**
|
|
34
34
|
* Build the shell command for an additional (non-JS/TS) linter.
|
|
35
35
|
* cppcheck has no --fix mode; csharp and some others behave differently.
|
|
@@ -41,7 +41,7 @@ export declare function getAdditionalLinterCommand(linter: AdditionalLinter, mod
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function detectAdditionalLinter(cwd: string): 'ruff' | 'clippy' | 'golangci-lint' | 'checkstyle' | 'ktlint' | 'dotnet-format' | 'cppcheck' | 'swiftlint' | 'dart-analyze' | 'rubocop' | null;
|
|
43
43
|
export declare function detectAvailableLinter(): Promise<SupportedLinter | null>;
|
|
44
|
-
export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check'): Promise<LintResult>;
|
|
44
|
+
export declare function runLint(linter: SupportedLinter, mode: 'fix' | 'check', directory: string): Promise<LintResult>;
|
|
45
45
|
/**
|
|
46
46
|
* Run an additional (non-JS/TS) linter.
|
|
47
47
|
* Follows the same structure as runLint() but uses getAdditionalLinterCommand().
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.20.
|
|
3
|
+
"version": "6.20.2",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|