opencode-swarm 6.40.3 → 6.40.5
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/dist/index.js +121 -6
- package/dist/state.d.ts +1 -1
- package/dist/tools/pre-check-batch.d.ts +26 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41191,7 +41191,11 @@ function applyRehydrationCache(session) {
|
|
|
41191
41191
|
const evidence = evidenceMap.get(taskId);
|
|
41192
41192
|
if (evidence) {
|
|
41193
41193
|
const derivedState = evidenceToWorkflowState(evidence);
|
|
41194
|
-
|
|
41194
|
+
const existingIndex = existingState ? STATE_ORDER.indexOf(existingState) : -1;
|
|
41195
|
+
const derivedIndex = STATE_ORDER.indexOf(derivedState);
|
|
41196
|
+
if (derivedIndex > existingIndex) {
|
|
41197
|
+
session.taskWorkflowStates.set(taskId, derivedState);
|
|
41198
|
+
}
|
|
41195
41199
|
} else {
|
|
41196
41200
|
const existingIndex = existingState ? STATE_ORDER.indexOf(existingState) : -1;
|
|
41197
41201
|
const derivedIndex = STATE_ORDER.indexOf(planState);
|
|
@@ -41997,8 +42001,9 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
|
|
|
41997
42001
|
- quality_budget (maintainability metrics)
|
|
41998
42002
|
\u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
|
|
41999
42003
|
\u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to {{AGENT_PREFIX}}coder with specific tool failures. Do NOT call {{AGENT_PREFIX}}reviewer.
|
|
42000
|
-
\u2192 If gates_passed === true: proceed to {{AGENT_PREFIX}}reviewer.
|
|
42001
|
-
\u2192
|
|
42004
|
+
\u2192 If gates_passed === true AND sast_preexisting_findings is present: proceed to {{AGENT_PREFIX}}reviewer. Include the pre-existing SAST findings in the reviewer delegation context with instruction: "SAST TRIAGE REQUIRED: The following HIGH/CRITICAL SAST findings exist on unchanged lines in this changeset. Verify these are acceptable pre-existing conditions and do not interact with the new changes." Do NOT return to coder for pre-existing findings on unchanged code.
|
|
42005
|
+
\u2192 If gates_passed === true (no sast_preexisting_findings): proceed to {{AGENT_PREFIX}}reviewer.
|
|
42006
|
+
\u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | PASS \u2014 pre-existing SAST findings on unchanged lines (N findings, reviewer triage) | FAIL \u2014 [gate]: [details]]"
|
|
42002
42007
|
|
|
42003
42008
|
\u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
|
|
42004
42009
|
pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
|
|
@@ -64121,6 +64126,99 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
|
|
|
64121
64126
|
};
|
|
64122
64127
|
}
|
|
64123
64128
|
}
|
|
64129
|
+
var GATE_SEVERITIES = new Set(["high", "critical"]);
|
|
64130
|
+
async function runGitDiff(args2, directory) {
|
|
64131
|
+
try {
|
|
64132
|
+
const proc = Bun.spawn(["git", "diff", ...args2], {
|
|
64133
|
+
cwd: directory,
|
|
64134
|
+
stdout: "pipe",
|
|
64135
|
+
stderr: "pipe"
|
|
64136
|
+
});
|
|
64137
|
+
const [exitCode, stdout] = await Promise.all([
|
|
64138
|
+
proc.exited,
|
|
64139
|
+
new Response(proc.stdout).text()
|
|
64140
|
+
]);
|
|
64141
|
+
if (exitCode !== 0)
|
|
64142
|
+
return null;
|
|
64143
|
+
const trimmed = stdout.trim();
|
|
64144
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
64145
|
+
} catch {
|
|
64146
|
+
return null;
|
|
64147
|
+
}
|
|
64148
|
+
}
|
|
64149
|
+
function parseDiffLineRanges(diffOutput) {
|
|
64150
|
+
const result = new Map;
|
|
64151
|
+
let currentFile = null;
|
|
64152
|
+
for (const line of diffOutput.split(`
|
|
64153
|
+
`)) {
|
|
64154
|
+
if (line.startsWith("+++ b/")) {
|
|
64155
|
+
currentFile = line.slice(6).trim();
|
|
64156
|
+
if (!result.has(currentFile)) {
|
|
64157
|
+
result.set(currentFile, new Set);
|
|
64158
|
+
}
|
|
64159
|
+
continue;
|
|
64160
|
+
}
|
|
64161
|
+
if (line.startsWith("@@") && currentFile) {
|
|
64162
|
+
const match = line.match(/^@@ [^+]*\+(\d+)(?:,(\d+))? @@/);
|
|
64163
|
+
if (match) {
|
|
64164
|
+
const start2 = parseInt(match[1], 10);
|
|
64165
|
+
const count = match[2] !== undefined ? parseInt(match[2], 10) : 1;
|
|
64166
|
+
const lines = result.get(currentFile);
|
|
64167
|
+
for (let i2 = start2;i2 < start2 + count; i2++) {
|
|
64168
|
+
lines.add(i2);
|
|
64169
|
+
}
|
|
64170
|
+
}
|
|
64171
|
+
}
|
|
64172
|
+
}
|
|
64173
|
+
return result;
|
|
64174
|
+
}
|
|
64175
|
+
async function getChangedLineRanges(directory) {
|
|
64176
|
+
try {
|
|
64177
|
+
for (const baseBranch of ["origin/main", "origin/master", "main", "master"]) {
|
|
64178
|
+
const mergeBaseProc = Bun.spawn(["git", "merge-base", baseBranch, "HEAD"], { cwd: directory, stdout: "pipe", stderr: "pipe" });
|
|
64179
|
+
const [mbExit, mbOut] = await Promise.all([
|
|
64180
|
+
mergeBaseProc.exited,
|
|
64181
|
+
new Response(mergeBaseProc.stdout).text()
|
|
64182
|
+
]);
|
|
64183
|
+
if (mbExit === 0 && mbOut.trim()) {
|
|
64184
|
+
const mergeBase = mbOut.trim();
|
|
64185
|
+
const diffOut = await runGitDiff(["-U0", `${mergeBase}..HEAD`], directory);
|
|
64186
|
+
if (diffOut) {
|
|
64187
|
+
return parseDiffLineRanges(diffOut);
|
|
64188
|
+
}
|
|
64189
|
+
}
|
|
64190
|
+
}
|
|
64191
|
+
const diffHead1 = await runGitDiff(["-U0", "HEAD~1"], directory);
|
|
64192
|
+
if (diffHead1) {
|
|
64193
|
+
return parseDiffLineRanges(diffHead1);
|
|
64194
|
+
}
|
|
64195
|
+
const diffHead = await runGitDiff(["-U0", "HEAD"], directory);
|
|
64196
|
+
if (diffHead) {
|
|
64197
|
+
return parseDiffLineRanges(diffHead);
|
|
64198
|
+
}
|
|
64199
|
+
return null;
|
|
64200
|
+
} catch {
|
|
64201
|
+
return null;
|
|
64202
|
+
}
|
|
64203
|
+
}
|
|
64204
|
+
function classifySastFindings(findings, changedLineRanges, directory) {
|
|
64205
|
+
if (!changedLineRanges || changedLineRanges.size === 0) {
|
|
64206
|
+
return { newFindings: findings, preexistingFindings: [] };
|
|
64207
|
+
}
|
|
64208
|
+
const newFindings = [];
|
|
64209
|
+
const preexistingFindings = [];
|
|
64210
|
+
for (const finding of findings) {
|
|
64211
|
+
const filePath = finding.location.file;
|
|
64212
|
+
const normalised = path53.relative(directory, filePath).replace(/\\/g, "/");
|
|
64213
|
+
const changedLines = changedLineRanges.get(normalised);
|
|
64214
|
+
if (changedLines && changedLines.has(finding.location.line)) {
|
|
64215
|
+
newFindings.push(finding);
|
|
64216
|
+
} else {
|
|
64217
|
+
preexistingFindings.push(finding);
|
|
64218
|
+
}
|
|
64219
|
+
}
|
|
64220
|
+
return { newFindings, preexistingFindings };
|
|
64221
|
+
}
|
|
64124
64222
|
async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
64125
64223
|
const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
|
|
64126
64224
|
const { files, directory, sast_threshold = "medium", config: config3 } = input;
|
|
@@ -64229,10 +64327,24 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
|
64229
64327
|
warn(`Failed to persist secretscan evidence: ${e instanceof Error ? e.message : String(e)}`);
|
|
64230
64328
|
}
|
|
64231
64329
|
}
|
|
64330
|
+
let sastPreexistingFindings;
|
|
64232
64331
|
if (sastScanResult.ran && sastScanResult.result) {
|
|
64233
64332
|
if (sastScanResult.result.verdict === "fail") {
|
|
64234
|
-
|
|
64235
|
-
|
|
64333
|
+
const gateFindings = sastScanResult.result.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
|
|
64334
|
+
if (gateFindings.length > 0) {
|
|
64335
|
+
const changedLineRanges = await getChangedLineRanges(directory);
|
|
64336
|
+
const { newFindings, preexistingFindings } = classifySastFindings(gateFindings, changedLineRanges, directory);
|
|
64337
|
+
if (newFindings.length > 0) {
|
|
64338
|
+
gatesPassed = false;
|
|
64339
|
+
warn(`pre_check_batch: SAST scan found ${newFindings.length} new HIGH/CRITICAL finding(s) on changed lines - GATE FAILED`);
|
|
64340
|
+
} else if (preexistingFindings.length > 0) {
|
|
64341
|
+
sastPreexistingFindings = preexistingFindings;
|
|
64342
|
+
warn(`pre_check_batch: SAST scan found ${preexistingFindings.length} pre-existing HIGH/CRITICAL finding(s) on unchanged lines - passing to reviewer for triage`);
|
|
64343
|
+
}
|
|
64344
|
+
} else {
|
|
64345
|
+
gatesPassed = false;
|
|
64346
|
+
warn("pre_check_batch: SAST scan found vulnerabilities - GATE FAILED");
|
|
64347
|
+
}
|
|
64236
64348
|
}
|
|
64237
64349
|
} else if (sastScanResult.error) {
|
|
64238
64350
|
gatesPassed = false;
|
|
@@ -64251,7 +64363,10 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
|
|
|
64251
64363
|
secretscan: secretscanResult,
|
|
64252
64364
|
sast_scan: sastScanResult,
|
|
64253
64365
|
quality_budget: qualityBudgetResult,
|
|
64254
|
-
total_duration_ms: Math.round(totalDuration)
|
|
64366
|
+
total_duration_ms: Math.round(totalDuration),
|
|
64367
|
+
...sastPreexistingFindings && sastPreexistingFindings.length > 0 && {
|
|
64368
|
+
sast_preexisting_findings: sastPreexistingFindings
|
|
64369
|
+
}
|
|
64255
64370
|
};
|
|
64256
64371
|
const outputSize = JSON.stringify(result).length;
|
|
64257
64372
|
if (outputSize > MAX_COMBINED_BYTES) {
|
package/dist/state.d.ts
CHANGED
|
@@ -319,7 +319,7 @@ export declare function buildRehydrationCache(directory: string): Promise<void>;
|
|
|
319
319
|
/**
|
|
320
320
|
* Synchronously applies the cached plan+evidence data to a session.
|
|
321
321
|
* Merge rules:
|
|
322
|
-
* - evidence-derived state:
|
|
322
|
+
* - evidence-derived state: only applied if it advances past existing state
|
|
323
323
|
* - plan-only derived state: only applied if it advances past existing state
|
|
324
324
|
* No-op when the cache has not been built yet.
|
|
325
325
|
*/
|
|
@@ -7,7 +7,7 @@ import { tool } from '@opencode-ai/plugin';
|
|
|
7
7
|
import type { PluginConfig } from '../config';
|
|
8
8
|
import type { LintResult } from './lint';
|
|
9
9
|
import type { QualityBudgetResult } from './quality-budget';
|
|
10
|
-
import type { SastScanResult } from './sast-scan';
|
|
10
|
+
import type { SastScanFinding, SastScanResult } from './sast-scan';
|
|
11
11
|
import type { SecretscanErrorResult, SecretscanResult } from './secretscan';
|
|
12
12
|
export interface PreCheckBatchInput {
|
|
13
13
|
/** List of specific files to check (optional) */
|
|
@@ -42,7 +42,32 @@ export interface PreCheckBatchResult {
|
|
|
42
42
|
quality_budget: ToolResult<QualityBudgetResult>;
|
|
43
43
|
/** Total duration in milliseconds */
|
|
44
44
|
total_duration_ms: number;
|
|
45
|
+
/** Pre-existing SAST findings on unchanged lines, requiring reviewer triage */
|
|
46
|
+
sast_preexisting_findings?: SastScanFinding[];
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse unified diff output (with -U0) to extract added/modified line numbers per file.
|
|
50
|
+
* Returns a Map from normalised file path → Set of changed line numbers.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseDiffLineRanges(diffOutput: string): Map<string, Set<number>>;
|
|
53
|
+
/**
|
|
54
|
+
* Get changed line ranges for the current branch vs its base.
|
|
55
|
+
* Tries three strategies in order:
|
|
56
|
+
* 1. merge-base diff against main/master (captures all branch changes, works after commit)
|
|
57
|
+
* 2. HEAD~1 (single-commit diff, works after commit)
|
|
58
|
+
* 3. HEAD (unstaged/staged changes, works before commit)
|
|
59
|
+
* Returns null if git is unavailable or no changes found.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getChangedLineRanges(directory: string): Promise<Map<string, Set<number>> | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Classify SAST findings as "new" (on changed lines) or "pre-existing" (unchanged lines).
|
|
64
|
+
* A finding is "new" if its file+line intersects the changed line ranges from git diff.
|
|
65
|
+
* If line ranges cannot be determined (git unavailable), all findings are treated as new (fail-closed).
|
|
66
|
+
*/
|
|
67
|
+
export declare function classifySastFindings(findings: SastScanFinding[], changedLineRanges: Map<string, Set<number>> | null, directory: string): {
|
|
68
|
+
newFindings: SastScanFinding[];
|
|
69
|
+
preexistingFindings: SastScanFinding[];
|
|
70
|
+
};
|
|
46
71
|
/**
|
|
47
72
|
* Run all 4 pre-check tools in parallel with concurrency limit
|
|
48
73
|
* @param input - The pre-check batch input
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.40.
|
|
3
|
+
"version": "6.40.5",
|
|
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",
|