opencode-swarm 6.40.4 → 6.40.6

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/cli/index.js CHANGED
@@ -32825,6 +32825,7 @@ async function handleDarkMatterCommand(directory, args) {
32825
32825
  import { execSync } from "child_process";
32826
32826
  import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
32827
32827
  import path12 from "path";
32828
+ import { fileURLToPath } from "url";
32828
32829
  init_manager();
32829
32830
  init_utils2();
32830
32831
  init_manager2();
@@ -33165,8 +33166,9 @@ async function checkGrammarWasmFiles() {
33165
33166
  "tree-sitter-swift.wasm",
33166
33167
  "tree-sitter-dart.wasm"
33167
33168
  ];
33168
- const isDev = import.meta.dir.includes("src/services") || import.meta.dir.includes("src\\services");
33169
- const grammarDir = isDev ? path12.join(import.meta.dir, "../../dist/lang/grammars/") : path12.join(import.meta.dir, "../lang/grammars/");
33169
+ const thisDir = path12.dirname(fileURLToPath(import.meta.url));
33170
+ const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
33171
+ const grammarDir = isSource ? path12.join(thisDir, "..", "lang", "grammars") : path12.join(thisDir, "lang", "grammars");
33170
33172
  const missing = [];
33171
33173
  for (const file3 of grammarFiles) {
33172
33174
  if (!existsSync4(path12.join(grammarDir, file3))) {
package/dist/index.js CHANGED
@@ -40679,12 +40679,24 @@ ${JSON.stringify(symbolNames, null, 2)}`);
40679
40679
  });
40680
40680
 
40681
40681
  // src/lang/runtime.ts
40682
- import { fileURLToPath } from "url";
40682
+ import * as path47 from "path";
40683
+ import { fileURLToPath as fileURLToPath2 } from "url";
40683
40684
  async function initTreeSitter() {
40684
40685
  if (treeSitterInitialized) {
40685
40686
  return;
40686
40687
  }
40687
- await Parser.init();
40688
+ const thisDir = path47.dirname(fileURLToPath2(import.meta.url));
40689
+ const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/lang");
40690
+ if (isSource) {
40691
+ await Parser.init();
40692
+ } else {
40693
+ const grammarsDir = getGrammarsDirAbsolute();
40694
+ await Parser.init({
40695
+ locateFile(scriptName) {
40696
+ return path47.join(grammarsDir, scriptName);
40697
+ }
40698
+ });
40699
+ }
40688
40700
  treeSitterInitialized = true;
40689
40701
  }
40690
40702
  function sanitizeLanguageId(languageId) {
@@ -40701,12 +40713,10 @@ function getWasmFileName(languageId) {
40701
40713
  }
40702
40714
  return `tree-sitter-${sanitized}.wasm`;
40703
40715
  }
40704
- function getGrammarsPath() {
40705
- const isProduction = !import.meta.url.includes("src/");
40706
- if (isProduction) {
40707
- return "./lang/grammars/";
40708
- }
40709
- return "../../dist/lang/grammars/";
40716
+ function getGrammarsDirAbsolute() {
40717
+ const thisDir = path47.dirname(fileURLToPath2(import.meta.url));
40718
+ const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/lang");
40719
+ return isSource ? path47.join(thisDir, "grammars") : path47.join(thisDir, "lang", "grammars");
40710
40720
  }
40711
40721
  async function loadGrammar(languageId) {
40712
40722
  if (typeof languageId !== "string" || languageId.length > 100) {
@@ -40722,8 +40732,7 @@ async function loadGrammar(languageId) {
40722
40732
  await initTreeSitter();
40723
40733
  const parser = new Parser;
40724
40734
  const wasmFileName = getWasmFileName(normalizedId);
40725
- const grammarsPath = getGrammarsPath();
40726
- const wasmPath = fileURLToPath(new URL(`${grammarsPath}${wasmFileName}`, import.meta.url));
40735
+ const wasmPath = path47.join(getGrammarsDirAbsolute(), wasmFileName);
40727
40736
  const { existsSync: existsSync29 } = await import("fs");
40728
40737
  if (!existsSync29(wasmPath)) {
40729
40738
  throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
@@ -40766,7 +40775,7 @@ var init_runtime = __esm(() => {
40766
40775
  });
40767
40776
 
40768
40777
  // src/index.ts
40769
- import * as path62 from "path";
40778
+ import * as path63 from "path";
40770
40779
 
40771
40780
  // src/agents/index.ts
40772
40781
  init_config();
@@ -42001,8 +42010,9 @@ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
42001
42010
  - quality_budget (maintainability metrics)
42002
42011
  \u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
42003
42012
  \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.
42004
- \u2192 If gates_passed === true: proceed to {{AGENT_PREFIX}}reviewer.
42005
- \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | FAIL \u2014 [gate]: [details]]"
42013
+ \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.
42014
+ \u2192 If gates_passed === true (no sast_preexisting_findings): proceed to {{AGENT_PREFIX}}reviewer.
42015
+ \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]]"
42006
42016
 
42007
42017
  \u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
42008
42018
  pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
@@ -46948,6 +46958,7 @@ init_manager2();
46948
46958
  import { execSync } from "child_process";
46949
46959
  import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
46950
46960
  import path18 from "path";
46961
+ import { fileURLToPath } from "url";
46951
46962
  function validateTaskDag(plan) {
46952
46963
  const allTaskIds = new Set;
46953
46964
  for (const phase of plan.phases) {
@@ -47285,8 +47296,9 @@ async function checkGrammarWasmFiles() {
47285
47296
  "tree-sitter-swift.wasm",
47286
47297
  "tree-sitter-dart.wasm"
47287
47298
  ];
47288
- const isDev = import.meta.dir.includes("src/services") || import.meta.dir.includes("src\\services");
47289
- const grammarDir = isDev ? path18.join(import.meta.dir, "../../dist/lang/grammars/") : path18.join(import.meta.dir, "../lang/grammars/");
47299
+ const thisDir = path18.dirname(fileURLToPath(import.meta.url));
47300
+ const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
47301
+ const grammarDir = isSource ? path18.join(thisDir, "..", "lang", "grammars") : path18.join(thisDir, "lang", "grammars");
47290
47302
  const missing = [];
47291
47303
  for (const file3 of grammarFiles) {
47292
47304
  if (!existsSync8(path18.join(grammarDir, file3))) {
@@ -59020,20 +59032,20 @@ function validateBase(base) {
59020
59032
  function validatePaths(paths) {
59021
59033
  if (!paths)
59022
59034
  return null;
59023
- for (const path47 of paths) {
59024
- if (!path47 || path47.length === 0) {
59035
+ for (const path48 of paths) {
59036
+ if (!path48 || path48.length === 0) {
59025
59037
  return "empty path not allowed";
59026
59038
  }
59027
- if (path47.length > MAX_PATH_LENGTH) {
59039
+ if (path48.length > MAX_PATH_LENGTH) {
59028
59040
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
59029
59041
  }
59030
- if (SHELL_METACHARACTERS2.test(path47)) {
59042
+ if (SHELL_METACHARACTERS2.test(path48)) {
59031
59043
  return "path contains shell metacharacters";
59032
59044
  }
59033
- if (path47.startsWith("-")) {
59045
+ if (path48.startsWith("-")) {
59034
59046
  return 'path cannot start with "-" (option-like arguments not allowed)';
59035
59047
  }
59036
- if (CONTROL_CHAR_PATTERN2.test(path47)) {
59048
+ if (CONTROL_CHAR_PATTERN2.test(path48)) {
59037
59049
  return "path contains control characters";
59038
59050
  }
59039
59051
  }
@@ -59114,8 +59126,8 @@ var diff = createSwarmTool({
59114
59126
  if (parts2.length >= 3) {
59115
59127
  const additions = parseInt(parts2[0], 10) || 0;
59116
59128
  const deletions = parseInt(parts2[1], 10) || 0;
59117
- const path47 = parts2[2];
59118
- files.push({ path: path47, additions, deletions });
59129
+ const path48 = parts2[2];
59130
+ files.push({ path: path48, additions, deletions });
59119
59131
  }
59120
59132
  }
59121
59133
  const contractChanges = [];
@@ -59398,7 +59410,7 @@ Use these as DOMAIN values when delegating to @sme.`;
59398
59410
  init_dist();
59399
59411
  init_create_tool();
59400
59412
  import * as fs36 from "fs";
59401
- import * as path47 from "path";
59413
+ import * as path48 from "path";
59402
59414
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
59403
59415
  var MAX_EVIDENCE_FILES = 1000;
59404
59416
  var EVIDENCE_DIR2 = ".swarm/evidence";
@@ -59425,9 +59437,9 @@ function validateRequiredTypes(input) {
59425
59437
  return null;
59426
59438
  }
59427
59439
  function isPathWithinSwarm2(filePath, cwd) {
59428
- const normalizedCwd = path47.resolve(cwd);
59429
- const swarmPath = path47.join(normalizedCwd, ".swarm");
59430
- const normalizedPath = path47.resolve(filePath);
59440
+ const normalizedCwd = path48.resolve(cwd);
59441
+ const swarmPath = path48.join(normalizedCwd, ".swarm");
59442
+ const normalizedPath = path48.resolve(filePath);
59431
59443
  return normalizedPath.startsWith(swarmPath);
59432
59444
  }
59433
59445
  function parseCompletedTasks(planContent) {
@@ -59457,10 +59469,10 @@ function readEvidenceFiles(evidenceDir, _cwd) {
59457
59469
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
59458
59470
  continue;
59459
59471
  }
59460
- const filePath = path47.join(evidenceDir, filename);
59472
+ const filePath = path48.join(evidenceDir, filename);
59461
59473
  try {
59462
- const resolvedPath = path47.resolve(filePath);
59463
- const evidenceDirResolved = path47.resolve(evidenceDir);
59474
+ const resolvedPath = path48.resolve(filePath);
59475
+ const evidenceDirResolved = path48.resolve(evidenceDir);
59464
59476
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
59465
59477
  continue;
59466
59478
  }
@@ -59578,7 +59590,7 @@ var evidence_check = createSwarmTool({
59578
59590
  return JSON.stringify(errorResult, null, 2);
59579
59591
  }
59580
59592
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
59581
- const planPath = path47.join(cwd, PLAN_FILE);
59593
+ const planPath = path48.join(cwd, PLAN_FILE);
59582
59594
  if (!isPathWithinSwarm2(planPath, cwd)) {
59583
59595
  const errorResult = {
59584
59596
  error: "plan file path validation failed",
@@ -59610,7 +59622,7 @@ var evidence_check = createSwarmTool({
59610
59622
  };
59611
59623
  return JSON.stringify(result2, null, 2);
59612
59624
  }
59613
- const evidenceDir = path47.join(cwd, EVIDENCE_DIR2);
59625
+ const evidenceDir = path48.join(cwd, EVIDENCE_DIR2);
59614
59626
  const evidence = readEvidenceFiles(evidenceDir, cwd);
59615
59627
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
59616
59628
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -59628,7 +59640,7 @@ var evidence_check = createSwarmTool({
59628
59640
  init_tool();
59629
59641
  init_create_tool();
59630
59642
  import * as fs37 from "fs";
59631
- import * as path48 from "path";
59643
+ import * as path49 from "path";
59632
59644
  var EXT_MAP = {
59633
59645
  python: ".py",
59634
59646
  py: ".py",
@@ -59709,12 +59721,12 @@ var extract_code_blocks = createSwarmTool({
59709
59721
  if (prefix) {
59710
59722
  filename = `${prefix}_${filename}`;
59711
59723
  }
59712
- let filepath = path48.join(targetDir, filename);
59713
- const base = path48.basename(filepath, path48.extname(filepath));
59714
- const ext = path48.extname(filepath);
59724
+ let filepath = path49.join(targetDir, filename);
59725
+ const base = path49.basename(filepath, path49.extname(filepath));
59726
+ const ext = path49.extname(filepath);
59715
59727
  let counter = 1;
59716
59728
  while (fs37.existsSync(filepath)) {
59717
- filepath = path48.join(targetDir, `${base}_${counter}${ext}`);
59729
+ filepath = path49.join(targetDir, `${base}_${counter}${ext}`);
59718
59730
  counter++;
59719
59731
  }
59720
59732
  try {
@@ -59835,7 +59847,7 @@ var gitingest = createSwarmTool({
59835
59847
  init_dist();
59836
59848
  init_create_tool();
59837
59849
  import * as fs38 from "fs";
59838
- import * as path49 from "path";
59850
+ import * as path50 from "path";
59839
59851
  var MAX_FILE_PATH_LENGTH2 = 500;
59840
59852
  var MAX_SYMBOL_LENGTH = 256;
59841
59853
  var MAX_FILE_SIZE_BYTES5 = 1024 * 1024;
@@ -59883,7 +59895,7 @@ function validateSymbolInput(symbol3) {
59883
59895
  return null;
59884
59896
  }
59885
59897
  function isBinaryFile2(filePath, buffer) {
59886
- const ext = path49.extname(filePath).toLowerCase();
59898
+ const ext = path50.extname(filePath).toLowerCase();
59887
59899
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
59888
59900
  return false;
59889
59901
  }
@@ -59907,15 +59919,15 @@ function parseImports(content, targetFile, targetSymbol) {
59907
59919
  const imports = [];
59908
59920
  let _resolvedTarget;
59909
59921
  try {
59910
- _resolvedTarget = path49.resolve(targetFile);
59922
+ _resolvedTarget = path50.resolve(targetFile);
59911
59923
  } catch {
59912
59924
  _resolvedTarget = targetFile;
59913
59925
  }
59914
- const targetBasename = path49.basename(targetFile, path49.extname(targetFile));
59926
+ const targetBasename = path50.basename(targetFile, path50.extname(targetFile));
59915
59927
  const targetWithExt = targetFile;
59916
59928
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
59917
- const normalizedTargetWithExt = path49.normalize(targetWithExt).replace(/\\/g, "/");
59918
- const normalizedTargetWithoutExt = path49.normalize(targetWithoutExt).replace(/\\/g, "/");
59929
+ const normalizedTargetWithExt = path50.normalize(targetWithExt).replace(/\\/g, "/");
59930
+ const normalizedTargetWithoutExt = path50.normalize(targetWithoutExt).replace(/\\/g, "/");
59919
59931
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
59920
59932
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
59921
59933
  const modulePath = match[1] || match[2] || match[3];
@@ -59938,9 +59950,9 @@ function parseImports(content, targetFile, targetSymbol) {
59938
59950
  }
59939
59951
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
59940
59952
  let isMatch = false;
59941
- const _targetDir = path49.dirname(targetFile);
59942
- const targetExt = path49.extname(targetFile);
59943
- const targetBasenameNoExt = path49.basename(targetFile, targetExt);
59953
+ const _targetDir = path50.dirname(targetFile);
59954
+ const targetExt = path50.extname(targetFile);
59955
+ const targetBasenameNoExt = path50.basename(targetFile, targetExt);
59944
59956
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
59945
59957
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
59946
59958
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -60008,10 +60020,10 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
60008
60020
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
60009
60021
  for (const entry of entries) {
60010
60022
  if (SKIP_DIRECTORIES3.has(entry)) {
60011
- stats.skippedDirs.push(path49.join(dir, entry));
60023
+ stats.skippedDirs.push(path50.join(dir, entry));
60012
60024
  continue;
60013
60025
  }
60014
- const fullPath = path49.join(dir, entry);
60026
+ const fullPath = path50.join(dir, entry);
60015
60027
  let stat2;
60016
60028
  try {
60017
60029
  stat2 = fs38.statSync(fullPath);
@@ -60025,7 +60037,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
60025
60037
  if (stat2.isDirectory()) {
60026
60038
  findSourceFiles(fullPath, files, stats);
60027
60039
  } else if (stat2.isFile()) {
60028
- const ext = path49.extname(fullPath).toLowerCase();
60040
+ const ext = path50.extname(fullPath).toLowerCase();
60029
60041
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
60030
60042
  files.push(fullPath);
60031
60043
  }
@@ -60082,7 +60094,7 @@ var imports = createSwarmTool({
60082
60094
  return JSON.stringify(errorResult, null, 2);
60083
60095
  }
60084
60096
  try {
60085
- const targetFile = path49.resolve(file3);
60097
+ const targetFile = path50.resolve(file3);
60086
60098
  if (!fs38.existsSync(targetFile)) {
60087
60099
  const errorResult = {
60088
60100
  error: `target file not found: ${file3}`,
@@ -60104,7 +60116,7 @@ var imports = createSwarmTool({
60104
60116
  };
60105
60117
  return JSON.stringify(errorResult, null, 2);
60106
60118
  }
60107
- const baseDir = path49.dirname(targetFile);
60119
+ const baseDir = path50.dirname(targetFile);
60108
60120
  const scanStats = {
60109
60121
  skippedDirs: [],
60110
60122
  skippedFiles: 0,
@@ -60714,7 +60726,7 @@ init_config();
60714
60726
  init_schema();
60715
60727
  init_manager();
60716
60728
  import * as fs39 from "fs";
60717
- import * as path50 from "path";
60729
+ import * as path51 from "path";
60718
60730
  init_utils2();
60719
60731
  init_telemetry();
60720
60732
  init_create_tool();
@@ -60934,7 +60946,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60934
60946
  safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
60935
60947
  }
60936
60948
  try {
60937
- const driftEvidencePath = path50.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
60949
+ const driftEvidencePath = path51.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
60938
60950
  let driftVerdictFound = false;
60939
60951
  let driftVerdictApproved = false;
60940
60952
  try {
@@ -60968,7 +60980,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60968
60980
  driftVerdictFound = false;
60969
60981
  }
60970
60982
  if (!driftVerdictFound) {
60971
- const specPath = path50.join(dir, ".swarm", "spec.md");
60983
+ const specPath = path51.join(dir, ".swarm", "spec.md");
60972
60984
  const specExists = fs39.existsSync(specPath);
60973
60985
  if (!specExists) {
60974
60986
  let incompleteTaskCount = 0;
@@ -61042,7 +61054,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
61042
61054
  };
61043
61055
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
61044
61056
  try {
61045
- const projectName = path50.basename(dir);
61057
+ const projectName = path51.basename(dir);
61046
61058
  const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
61047
61059
  if (curationResult) {
61048
61060
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -61252,7 +61264,7 @@ init_discovery();
61252
61264
  init_utils();
61253
61265
  init_create_tool();
61254
61266
  import * as fs40 from "fs";
61255
- import * as path51 from "path";
61267
+ import * as path52 from "path";
61256
61268
  var MAX_OUTPUT_BYTES5 = 52428800;
61257
61269
  var AUDIT_TIMEOUT_MS = 120000;
61258
61270
  function isValidEcosystem(value) {
@@ -61270,16 +61282,16 @@ function validateArgs3(args2) {
61270
61282
  function detectEcosystems(directory) {
61271
61283
  const ecosystems = [];
61272
61284
  const cwd = directory;
61273
- if (fs40.existsSync(path51.join(cwd, "package.json"))) {
61285
+ if (fs40.existsSync(path52.join(cwd, "package.json"))) {
61274
61286
  ecosystems.push("npm");
61275
61287
  }
61276
- if (fs40.existsSync(path51.join(cwd, "pyproject.toml")) || fs40.existsSync(path51.join(cwd, "requirements.txt"))) {
61288
+ if (fs40.existsSync(path52.join(cwd, "pyproject.toml")) || fs40.existsSync(path52.join(cwd, "requirements.txt"))) {
61277
61289
  ecosystems.push("pip");
61278
61290
  }
61279
- if (fs40.existsSync(path51.join(cwd, "Cargo.toml"))) {
61291
+ if (fs40.existsSync(path52.join(cwd, "Cargo.toml"))) {
61280
61292
  ecosystems.push("cargo");
61281
61293
  }
61282
- if (fs40.existsSync(path51.join(cwd, "go.mod"))) {
61294
+ if (fs40.existsSync(path52.join(cwd, "go.mod"))) {
61283
61295
  ecosystems.push("go");
61284
61296
  }
61285
61297
  try {
@@ -61288,10 +61300,10 @@ function detectEcosystems(directory) {
61288
61300
  ecosystems.push("dotnet");
61289
61301
  }
61290
61302
  } catch {}
61291
- if (fs40.existsSync(path51.join(cwd, "Gemfile")) || fs40.existsSync(path51.join(cwd, "Gemfile.lock"))) {
61303
+ if (fs40.existsSync(path52.join(cwd, "Gemfile")) || fs40.existsSync(path52.join(cwd, "Gemfile.lock"))) {
61292
61304
  ecosystems.push("ruby");
61293
61305
  }
61294
- if (fs40.existsSync(path51.join(cwd, "pubspec.yaml"))) {
61306
+ if (fs40.existsSync(path52.join(cwd, "pubspec.yaml"))) {
61295
61307
  ecosystems.push("dart");
61296
61308
  }
61297
61309
  return ecosystems;
@@ -62313,7 +62325,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
62313
62325
  // src/tools/pre-check-batch.ts
62314
62326
  init_dist();
62315
62327
  import * as fs42 from "fs";
62316
- import * as path53 from "path";
62328
+ import * as path54 from "path";
62317
62329
 
62318
62330
  // node_modules/yocto-queue/index.js
62319
62331
  class Node2 {
@@ -62588,7 +62600,7 @@ init_dist();
62588
62600
  init_manager();
62589
62601
  init_detector();
62590
62602
  import * as fs41 from "fs";
62591
- import * as path52 from "path";
62603
+ import * as path53 from "path";
62592
62604
  import { extname as extname10 } from "path";
62593
62605
 
62594
62606
  // src/sast/rules/c.ts
@@ -63554,7 +63566,7 @@ async function sastScan(input, directory, config3) {
63554
63566
  _filesSkipped++;
63555
63567
  continue;
63556
63568
  }
63557
- const resolvedPath = path52.isAbsolute(filePath) ? filePath : path52.resolve(directory, filePath);
63569
+ const resolvedPath = path53.isAbsolute(filePath) ? filePath : path53.resolve(directory, filePath);
63558
63570
  if (!fs41.existsSync(resolvedPath)) {
63559
63571
  _filesSkipped++;
63560
63572
  continue;
@@ -63753,18 +63765,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
63753
63765
  let resolved;
63754
63766
  const isWinAbs = isWindowsAbsolutePath(inputPath);
63755
63767
  if (isWinAbs) {
63756
- resolved = path53.win32.resolve(inputPath);
63757
- } else if (path53.isAbsolute(inputPath)) {
63758
- resolved = path53.resolve(inputPath);
63768
+ resolved = path54.win32.resolve(inputPath);
63769
+ } else if (path54.isAbsolute(inputPath)) {
63770
+ resolved = path54.resolve(inputPath);
63759
63771
  } else {
63760
- resolved = path53.resolve(baseDir, inputPath);
63772
+ resolved = path54.resolve(baseDir, inputPath);
63761
63773
  }
63762
- const workspaceResolved = path53.resolve(workspaceDir);
63774
+ const workspaceResolved = path54.resolve(workspaceDir);
63763
63775
  let relative6;
63764
63776
  if (isWinAbs) {
63765
- relative6 = path53.win32.relative(workspaceResolved, resolved);
63777
+ relative6 = path54.win32.relative(workspaceResolved, resolved);
63766
63778
  } else {
63767
- relative6 = path53.relative(workspaceResolved, resolved);
63779
+ relative6 = path54.relative(workspaceResolved, resolved);
63768
63780
  }
63769
63781
  if (relative6.startsWith("..")) {
63770
63782
  return "path traversal detected";
@@ -63825,13 +63837,13 @@ async function runLintWrapped(files, directory, _config) {
63825
63837
  }
63826
63838
  async function runLintOnFiles(linter, files, workspaceDir) {
63827
63839
  const isWindows = process.platform === "win32";
63828
- const binDir = path53.join(workspaceDir, "node_modules", ".bin");
63840
+ const binDir = path54.join(workspaceDir, "node_modules", ".bin");
63829
63841
  const validatedFiles = [];
63830
63842
  for (const file3 of files) {
63831
63843
  if (typeof file3 !== "string") {
63832
63844
  continue;
63833
63845
  }
63834
- const resolvedPath = path53.resolve(file3);
63846
+ const resolvedPath = path54.resolve(file3);
63835
63847
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
63836
63848
  if (validationError) {
63837
63849
  continue;
@@ -63849,10 +63861,10 @@ async function runLintOnFiles(linter, files, workspaceDir) {
63849
63861
  }
63850
63862
  let command;
63851
63863
  if (linter === "biome") {
63852
- const biomeBin = isWindows ? path53.join(binDir, "biome.EXE") : path53.join(binDir, "biome");
63864
+ const biomeBin = isWindows ? path54.join(binDir, "biome.EXE") : path54.join(binDir, "biome");
63853
63865
  command = [biomeBin, "check", ...validatedFiles];
63854
63866
  } else {
63855
- const eslintBin = isWindows ? path53.join(binDir, "eslint.cmd") : path53.join(binDir, "eslint");
63867
+ const eslintBin = isWindows ? path54.join(binDir, "eslint.cmd") : path54.join(binDir, "eslint");
63856
63868
  command = [eslintBin, ...validatedFiles];
63857
63869
  }
63858
63870
  try {
@@ -63989,7 +64001,7 @@ async function runSecretscanWithFiles(files, directory) {
63989
64001
  skippedFiles++;
63990
64002
  continue;
63991
64003
  }
63992
- const resolvedPath = path53.resolve(file3);
64004
+ const resolvedPath = path54.resolve(file3);
63993
64005
  const validationError = validatePath(resolvedPath, directory, directory);
63994
64006
  if (validationError) {
63995
64007
  skippedFiles++;
@@ -64007,7 +64019,7 @@ async function runSecretscanWithFiles(files, directory) {
64007
64019
  };
64008
64020
  }
64009
64021
  for (const file3 of validatedFiles) {
64010
- const ext = path53.extname(file3).toLowerCase();
64022
+ const ext = path54.extname(file3).toLowerCase();
64011
64023
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
64012
64024
  skippedFiles++;
64013
64025
  continue;
@@ -64125,6 +64137,99 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
64125
64137
  };
64126
64138
  }
64127
64139
  }
64140
+ var GATE_SEVERITIES = new Set(["high", "critical"]);
64141
+ async function runGitDiff(args2, directory) {
64142
+ try {
64143
+ const proc = Bun.spawn(["git", "diff", ...args2], {
64144
+ cwd: directory,
64145
+ stdout: "pipe",
64146
+ stderr: "pipe"
64147
+ });
64148
+ const [exitCode, stdout] = await Promise.all([
64149
+ proc.exited,
64150
+ new Response(proc.stdout).text()
64151
+ ]);
64152
+ if (exitCode !== 0)
64153
+ return null;
64154
+ const trimmed = stdout.trim();
64155
+ return trimmed.length > 0 ? trimmed : null;
64156
+ } catch {
64157
+ return null;
64158
+ }
64159
+ }
64160
+ function parseDiffLineRanges(diffOutput) {
64161
+ const result = new Map;
64162
+ let currentFile = null;
64163
+ for (const line of diffOutput.split(`
64164
+ `)) {
64165
+ if (line.startsWith("+++ b/")) {
64166
+ currentFile = line.slice(6).trim();
64167
+ if (!result.has(currentFile)) {
64168
+ result.set(currentFile, new Set);
64169
+ }
64170
+ continue;
64171
+ }
64172
+ if (line.startsWith("@@") && currentFile) {
64173
+ const match = line.match(/^@@ [^+]*\+(\d+)(?:,(\d+))? @@/);
64174
+ if (match) {
64175
+ const start2 = parseInt(match[1], 10);
64176
+ const count = match[2] !== undefined ? parseInt(match[2], 10) : 1;
64177
+ const lines = result.get(currentFile);
64178
+ for (let i2 = start2;i2 < start2 + count; i2++) {
64179
+ lines.add(i2);
64180
+ }
64181
+ }
64182
+ }
64183
+ }
64184
+ return result;
64185
+ }
64186
+ async function getChangedLineRanges(directory) {
64187
+ try {
64188
+ for (const baseBranch of ["origin/main", "origin/master", "main", "master"]) {
64189
+ const mergeBaseProc = Bun.spawn(["git", "merge-base", baseBranch, "HEAD"], { cwd: directory, stdout: "pipe", stderr: "pipe" });
64190
+ const [mbExit, mbOut] = await Promise.all([
64191
+ mergeBaseProc.exited,
64192
+ new Response(mergeBaseProc.stdout).text()
64193
+ ]);
64194
+ if (mbExit === 0 && mbOut.trim()) {
64195
+ const mergeBase = mbOut.trim();
64196
+ const diffOut = await runGitDiff(["-U0", `${mergeBase}..HEAD`], directory);
64197
+ if (diffOut) {
64198
+ return parseDiffLineRanges(diffOut);
64199
+ }
64200
+ }
64201
+ }
64202
+ const diffHead1 = await runGitDiff(["-U0", "HEAD~1"], directory);
64203
+ if (diffHead1) {
64204
+ return parseDiffLineRanges(diffHead1);
64205
+ }
64206
+ const diffHead = await runGitDiff(["-U0", "HEAD"], directory);
64207
+ if (diffHead) {
64208
+ return parseDiffLineRanges(diffHead);
64209
+ }
64210
+ return null;
64211
+ } catch {
64212
+ return null;
64213
+ }
64214
+ }
64215
+ function classifySastFindings(findings, changedLineRanges, directory) {
64216
+ if (!changedLineRanges || changedLineRanges.size === 0) {
64217
+ return { newFindings: findings, preexistingFindings: [] };
64218
+ }
64219
+ const newFindings = [];
64220
+ const preexistingFindings = [];
64221
+ for (const finding of findings) {
64222
+ const filePath = finding.location.file;
64223
+ const normalised = path54.relative(directory, filePath).replace(/\\/g, "/");
64224
+ const changedLines = changedLineRanges.get(normalised);
64225
+ if (changedLines && changedLines.has(finding.location.line)) {
64226
+ newFindings.push(finding);
64227
+ } else {
64228
+ preexistingFindings.push(finding);
64229
+ }
64230
+ }
64231
+ return { newFindings, preexistingFindings };
64232
+ }
64128
64233
  async function runPreCheckBatch(input, workspaceDir, contextDir) {
64129
64234
  const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
64130
64235
  const { files, directory, sast_threshold = "medium", config: config3 } = input;
@@ -64166,7 +64271,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
64166
64271
  warn(`pre_check_batch: Invalid file path: ${file3}`);
64167
64272
  continue;
64168
64273
  }
64169
- changedFiles.push(path53.resolve(directory, file3));
64274
+ changedFiles.push(path54.resolve(directory, file3));
64170
64275
  }
64171
64276
  if (changedFiles.length === 0) {
64172
64277
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -64233,10 +64338,24 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
64233
64338
  warn(`Failed to persist secretscan evidence: ${e instanceof Error ? e.message : String(e)}`);
64234
64339
  }
64235
64340
  }
64341
+ let sastPreexistingFindings;
64236
64342
  if (sastScanResult.ran && sastScanResult.result) {
64237
64343
  if (sastScanResult.result.verdict === "fail") {
64238
- gatesPassed = false;
64239
- warn("pre_check_batch: SAST scan found vulnerabilities - GATE FAILED");
64344
+ const gateFindings = sastScanResult.result.findings.filter((f) => GATE_SEVERITIES.has(f.severity));
64345
+ if (gateFindings.length > 0) {
64346
+ const changedLineRanges = await getChangedLineRanges(directory);
64347
+ const { newFindings, preexistingFindings } = classifySastFindings(gateFindings, changedLineRanges, directory);
64348
+ if (newFindings.length > 0) {
64349
+ gatesPassed = false;
64350
+ warn(`pre_check_batch: SAST scan found ${newFindings.length} new HIGH/CRITICAL finding(s) on changed lines - GATE FAILED`);
64351
+ } else if (preexistingFindings.length > 0) {
64352
+ sastPreexistingFindings = preexistingFindings;
64353
+ warn(`pre_check_batch: SAST scan found ${preexistingFindings.length} pre-existing HIGH/CRITICAL finding(s) on unchanged lines - passing to reviewer for triage`);
64354
+ }
64355
+ } else {
64356
+ gatesPassed = false;
64357
+ warn("pre_check_batch: SAST scan found vulnerabilities - GATE FAILED");
64358
+ }
64240
64359
  }
64241
64360
  } else if (sastScanResult.error) {
64242
64361
  gatesPassed = false;
@@ -64255,7 +64374,10 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
64255
64374
  secretscan: secretscanResult,
64256
64375
  sast_scan: sastScanResult,
64257
64376
  quality_budget: qualityBudgetResult,
64258
- total_duration_ms: Math.round(totalDuration)
64377
+ total_duration_ms: Math.round(totalDuration),
64378
+ ...sastPreexistingFindings && sastPreexistingFindings.length > 0 && {
64379
+ sast_preexisting_findings: sastPreexistingFindings
64380
+ }
64259
64381
  };
64260
64382
  const outputSize = JSON.stringify(result).length;
64261
64383
  if (outputSize > MAX_COMBINED_BYTES) {
@@ -64337,7 +64459,7 @@ var pre_check_batch = createSwarmTool({
64337
64459
  };
64338
64460
  return JSON.stringify(errorResult, null, 2);
64339
64461
  }
64340
- const resolvedDirectory = path53.resolve(typedArgs.directory);
64462
+ const resolvedDirectory = path54.resolve(typedArgs.directory);
64341
64463
  const workspaceAnchor = resolvedDirectory;
64342
64464
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
64343
64465
  if (dirError) {
@@ -64444,24 +64566,24 @@ ${paginatedContent}`;
64444
64566
  // src/tools/save-plan.ts
64445
64567
  init_tool();
64446
64568
  import * as fs44 from "fs";
64447
- import * as path55 from "path";
64569
+ import * as path56 from "path";
64448
64570
 
64449
64571
  // src/parallel/file-locks.ts
64450
64572
  import * as fs43 from "fs";
64451
- import * as path54 from "path";
64573
+ import * as path55 from "path";
64452
64574
  var LOCKS_DIR = ".swarm/locks";
64453
64575
  var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
64454
64576
  function getLockFilePath(directory, filePath) {
64455
- const normalized = path54.resolve(directory, filePath);
64456
- if (!normalized.startsWith(path54.resolve(directory))) {
64577
+ const normalized = path55.resolve(directory, filePath);
64578
+ if (!normalized.startsWith(path55.resolve(directory))) {
64457
64579
  throw new Error("Invalid file path: path traversal not allowed");
64458
64580
  }
64459
64581
  const hash3 = Buffer.from(normalized).toString("base64").replace(/[/+=]/g, "_");
64460
- return path54.join(directory, LOCKS_DIR, `${hash3}.lock`);
64582
+ return path55.join(directory, LOCKS_DIR, `${hash3}.lock`);
64461
64583
  }
64462
64584
  function tryAcquireLock(directory, filePath, agent, taskId) {
64463
64585
  const lockPath = getLockFilePath(directory, filePath);
64464
- const locksDir = path54.dirname(lockPath);
64586
+ const locksDir = path55.dirname(lockPath);
64465
64587
  if (!fs43.existsSync(locksDir)) {
64466
64588
  fs43.mkdirSync(locksDir, { recursive: true });
64467
64589
  }
@@ -64627,7 +64749,7 @@ async function executeSavePlan(args2, fallbackDir) {
64627
64749
  try {
64628
64750
  await savePlan(dir, plan);
64629
64751
  try {
64630
- const markerPath = path55.join(dir, ".swarm", ".plan-write-marker");
64752
+ const markerPath = path56.join(dir, ".swarm", ".plan-write-marker");
64631
64753
  const marker = JSON.stringify({
64632
64754
  source: "save_plan",
64633
64755
  timestamp: new Date().toISOString(),
@@ -64650,7 +64772,7 @@ async function executeSavePlan(args2, fallbackDir) {
64650
64772
  return {
64651
64773
  success: true,
64652
64774
  message: "Plan saved successfully",
64653
- plan_path: path55.join(dir, ".swarm", "plan.json"),
64775
+ plan_path: path56.join(dir, ".swarm", "plan.json"),
64654
64776
  phases_count: plan.phases.length,
64655
64777
  tasks_count: tasksCount,
64656
64778
  ...warnings.length > 0 ? { warnings } : {}
@@ -64693,7 +64815,7 @@ var save_plan = createSwarmTool({
64693
64815
  init_dist();
64694
64816
  init_manager();
64695
64817
  import * as fs45 from "fs";
64696
- import * as path56 from "path";
64818
+ import * as path57 from "path";
64697
64819
 
64698
64820
  // src/sbom/detectors/index.ts
64699
64821
  init_utils();
@@ -65543,7 +65665,7 @@ function findManifestFiles(rootDir) {
65543
65665
  try {
65544
65666
  const entries = fs45.readdirSync(dir, { withFileTypes: true });
65545
65667
  for (const entry of entries) {
65546
- const fullPath = path56.join(dir, entry.name);
65668
+ const fullPath = path57.join(dir, entry.name);
65547
65669
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
65548
65670
  continue;
65549
65671
  }
@@ -65552,7 +65674,7 @@ function findManifestFiles(rootDir) {
65552
65674
  } else if (entry.isFile()) {
65553
65675
  for (const pattern of patterns) {
65554
65676
  if (simpleGlobToRegex(pattern).test(entry.name)) {
65555
- manifestFiles.push(path56.relative(rootDir, fullPath));
65677
+ manifestFiles.push(path57.relative(rootDir, fullPath));
65556
65678
  break;
65557
65679
  }
65558
65680
  }
@@ -65570,11 +65692,11 @@ function findManifestFilesInDirs(directories, workingDir) {
65570
65692
  try {
65571
65693
  const entries = fs45.readdirSync(dir, { withFileTypes: true });
65572
65694
  for (const entry of entries) {
65573
- const fullPath = path56.join(dir, entry.name);
65695
+ const fullPath = path57.join(dir, entry.name);
65574
65696
  if (entry.isFile()) {
65575
65697
  for (const pattern of patterns) {
65576
65698
  if (simpleGlobToRegex(pattern).test(entry.name)) {
65577
- found.push(path56.relative(workingDir, fullPath));
65699
+ found.push(path57.relative(workingDir, fullPath));
65578
65700
  break;
65579
65701
  }
65580
65702
  }
@@ -65587,11 +65709,11 @@ function findManifestFilesInDirs(directories, workingDir) {
65587
65709
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
65588
65710
  const dirs = new Set;
65589
65711
  for (const file3 of changedFiles) {
65590
- let currentDir = path56.dirname(file3);
65712
+ let currentDir = path57.dirname(file3);
65591
65713
  while (true) {
65592
- if (currentDir && currentDir !== "." && currentDir !== path56.sep) {
65593
- dirs.add(path56.join(workingDir, currentDir));
65594
- const parent = path56.dirname(currentDir);
65714
+ if (currentDir && currentDir !== "." && currentDir !== path57.sep) {
65715
+ dirs.add(path57.join(workingDir, currentDir));
65716
+ const parent = path57.dirname(currentDir);
65595
65717
  if (parent === currentDir)
65596
65718
  break;
65597
65719
  currentDir = parent;
@@ -65675,7 +65797,7 @@ var sbom_generate = createSwarmTool({
65675
65797
  const changedFiles = obj.changed_files;
65676
65798
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
65677
65799
  const workingDir = directory;
65678
- const outputDir = path56.isAbsolute(relativeOutputDir) ? relativeOutputDir : path56.join(workingDir, relativeOutputDir);
65800
+ const outputDir = path57.isAbsolute(relativeOutputDir) ? relativeOutputDir : path57.join(workingDir, relativeOutputDir);
65679
65801
  let manifestFiles = [];
65680
65802
  if (scope === "all") {
65681
65803
  manifestFiles = findManifestFiles(workingDir);
@@ -65698,7 +65820,7 @@ var sbom_generate = createSwarmTool({
65698
65820
  const processedFiles = [];
65699
65821
  for (const manifestFile of manifestFiles) {
65700
65822
  try {
65701
- const fullPath = path56.isAbsolute(manifestFile) ? manifestFile : path56.join(workingDir, manifestFile);
65823
+ const fullPath = path57.isAbsolute(manifestFile) ? manifestFile : path57.join(workingDir, manifestFile);
65702
65824
  if (!fs45.existsSync(fullPath)) {
65703
65825
  continue;
65704
65826
  }
@@ -65715,7 +65837,7 @@ var sbom_generate = createSwarmTool({
65715
65837
  const bom = generateCycloneDX(allComponents);
65716
65838
  const bomJson = serializeCycloneDX(bom);
65717
65839
  const filename = generateSbomFilename();
65718
- const outputPath = path56.join(outputDir, filename);
65840
+ const outputPath = path57.join(outputDir, filename);
65719
65841
  fs45.writeFileSync(outputPath, bomJson, "utf-8");
65720
65842
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
65721
65843
  try {
@@ -65759,7 +65881,7 @@ var sbom_generate = createSwarmTool({
65759
65881
  init_dist();
65760
65882
  init_create_tool();
65761
65883
  import * as fs46 from "fs";
65762
- import * as path57 from "path";
65884
+ import * as path58 from "path";
65763
65885
  var SPEC_CANDIDATES = [
65764
65886
  "openapi.json",
65765
65887
  "openapi.yaml",
@@ -65791,12 +65913,12 @@ function normalizePath2(p) {
65791
65913
  }
65792
65914
  function discoverSpecFile(cwd, specFileArg) {
65793
65915
  if (specFileArg) {
65794
- const resolvedPath = path57.resolve(cwd, specFileArg);
65795
- const normalizedCwd = cwd.endsWith(path57.sep) ? cwd : cwd + path57.sep;
65916
+ const resolvedPath = path58.resolve(cwd, specFileArg);
65917
+ const normalizedCwd = cwd.endsWith(path58.sep) ? cwd : cwd + path58.sep;
65796
65918
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
65797
65919
  throw new Error("Invalid spec_file: path traversal detected");
65798
65920
  }
65799
- const ext = path57.extname(resolvedPath).toLowerCase();
65921
+ const ext = path58.extname(resolvedPath).toLowerCase();
65800
65922
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
65801
65923
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
65802
65924
  }
@@ -65810,7 +65932,7 @@ function discoverSpecFile(cwd, specFileArg) {
65810
65932
  return resolvedPath;
65811
65933
  }
65812
65934
  for (const candidate of SPEC_CANDIDATES) {
65813
- const candidatePath = path57.resolve(cwd, candidate);
65935
+ const candidatePath = path58.resolve(cwd, candidate);
65814
65936
  if (fs46.existsSync(candidatePath)) {
65815
65937
  const stats = fs46.statSync(candidatePath);
65816
65938
  if (stats.size <= MAX_SPEC_SIZE) {
@@ -65822,7 +65944,7 @@ function discoverSpecFile(cwd, specFileArg) {
65822
65944
  }
65823
65945
  function parseSpec(specFile) {
65824
65946
  const content = fs46.readFileSync(specFile, "utf-8");
65825
- const ext = path57.extname(specFile).toLowerCase();
65947
+ const ext = path58.extname(specFile).toLowerCase();
65826
65948
  if (ext === ".json") {
65827
65949
  return parseJsonSpec(content);
65828
65950
  }
@@ -65898,7 +66020,7 @@ function extractRoutes(cwd) {
65898
66020
  return;
65899
66021
  }
65900
66022
  for (const entry of entries) {
65901
- const fullPath = path57.join(dir, entry.name);
66023
+ const fullPath = path58.join(dir, entry.name);
65902
66024
  if (entry.isSymbolicLink()) {
65903
66025
  continue;
65904
66026
  }
@@ -65908,7 +66030,7 @@ function extractRoutes(cwd) {
65908
66030
  }
65909
66031
  walkDir(fullPath);
65910
66032
  } else if (entry.isFile()) {
65911
- const ext = path57.extname(entry.name).toLowerCase();
66033
+ const ext = path58.extname(entry.name).toLowerCase();
65912
66034
  const baseName = entry.name.toLowerCase();
65913
66035
  if (![".ts", ".js", ".mjs"].includes(ext)) {
65914
66036
  continue;
@@ -66078,7 +66200,7 @@ init_secretscan();
66078
66200
  init_tool();
66079
66201
  init_create_tool();
66080
66202
  import * as fs47 from "fs";
66081
- import * as path58 from "path";
66203
+ import * as path59 from "path";
66082
66204
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
66083
66205
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
66084
66206
  function containsWindowsAttacks(str) {
@@ -66095,11 +66217,11 @@ function containsWindowsAttacks(str) {
66095
66217
  }
66096
66218
  function isPathInWorkspace(filePath, workspace) {
66097
66219
  try {
66098
- const resolvedPath = path58.resolve(workspace, filePath);
66220
+ const resolvedPath = path59.resolve(workspace, filePath);
66099
66221
  const realWorkspace = fs47.realpathSync(workspace);
66100
66222
  const realResolvedPath = fs47.realpathSync(resolvedPath);
66101
- const relativePath = path58.relative(realWorkspace, realResolvedPath);
66102
- if (relativePath.startsWith("..") || path58.isAbsolute(relativePath)) {
66223
+ const relativePath = path59.relative(realWorkspace, realResolvedPath);
66224
+ if (relativePath.startsWith("..") || path59.isAbsolute(relativePath)) {
66103
66225
  return false;
66104
66226
  }
66105
66227
  return true;
@@ -66111,7 +66233,7 @@ function validatePathForRead(filePath, workspace) {
66111
66233
  return isPathInWorkspace(filePath, workspace);
66112
66234
  }
66113
66235
  function extractTSSymbols(filePath, cwd) {
66114
- const fullPath = path58.join(cwd, filePath);
66236
+ const fullPath = path59.join(cwd, filePath);
66115
66237
  if (!validatePathForRead(fullPath, cwd)) {
66116
66238
  return [];
66117
66239
  }
@@ -66263,7 +66385,7 @@ function extractTSSymbols(filePath, cwd) {
66263
66385
  });
66264
66386
  }
66265
66387
  function extractPythonSymbols(filePath, cwd) {
66266
- const fullPath = path58.join(cwd, filePath);
66388
+ const fullPath = path59.join(cwd, filePath);
66267
66389
  if (!validatePathForRead(fullPath, cwd)) {
66268
66390
  return [];
66269
66391
  }
@@ -66346,7 +66468,7 @@ var symbols = createSwarmTool({
66346
66468
  }, null, 2);
66347
66469
  }
66348
66470
  const cwd = directory;
66349
- const ext = path58.extname(file3);
66471
+ const ext = path59.extname(file3);
66350
66472
  if (containsControlChars(file3)) {
66351
66473
  return JSON.stringify({
66352
66474
  file: file3,
@@ -66418,7 +66540,7 @@ init_dist();
66418
66540
  init_utils();
66419
66541
  init_create_tool();
66420
66542
  import * as fs48 from "fs";
66421
- import * as path59 from "path";
66543
+ import * as path60 from "path";
66422
66544
  var MAX_TEXT_LENGTH = 200;
66423
66545
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
66424
66546
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -66483,9 +66605,9 @@ function validatePathsInput(paths, cwd) {
66483
66605
  return { error: "paths contains path traversal", resolvedPath: null };
66484
66606
  }
66485
66607
  try {
66486
- const resolvedPath = path59.resolve(paths);
66487
- const normalizedCwd = path59.resolve(cwd);
66488
- const normalizedResolved = path59.resolve(resolvedPath);
66608
+ const resolvedPath = path60.resolve(paths);
66609
+ const normalizedCwd = path60.resolve(cwd);
66610
+ const normalizedResolved = path60.resolve(resolvedPath);
66489
66611
  if (!normalizedResolved.startsWith(normalizedCwd)) {
66490
66612
  return {
66491
66613
  error: "paths must be within the current working directory",
@@ -66501,7 +66623,7 @@ function validatePathsInput(paths, cwd) {
66501
66623
  }
66502
66624
  }
66503
66625
  function isSupportedExtension(filePath) {
66504
- const ext = path59.extname(filePath).toLowerCase();
66626
+ const ext = path60.extname(filePath).toLowerCase();
66505
66627
  return SUPPORTED_EXTENSIONS2.has(ext);
66506
66628
  }
66507
66629
  function findSourceFiles2(dir, files = []) {
@@ -66516,7 +66638,7 @@ function findSourceFiles2(dir, files = []) {
66516
66638
  if (SKIP_DIRECTORIES4.has(entry)) {
66517
66639
  continue;
66518
66640
  }
66519
- const fullPath = path59.join(dir, entry);
66641
+ const fullPath = path60.join(dir, entry);
66520
66642
  let stat2;
66521
66643
  try {
66522
66644
  stat2 = fs48.statSync(fullPath);
@@ -66628,7 +66750,7 @@ var todo_extract = createSwarmTool({
66628
66750
  filesToScan.push(scanPath);
66629
66751
  } else {
66630
66752
  const errorResult = {
66631
- error: `unsupported file extension: ${path59.extname(scanPath)}`,
66753
+ error: `unsupported file extension: ${path60.extname(scanPath)}`,
66632
66754
  total: 0,
66633
66755
  byPriority: { high: 0, medium: 0, low: 0 },
66634
66756
  entries: []
@@ -66675,14 +66797,14 @@ init_tool();
66675
66797
  init_schema();
66676
66798
  init_gate_evidence();
66677
66799
  import * as fs50 from "fs";
66678
- import * as path61 from "path";
66800
+ import * as path62 from "path";
66679
66801
 
66680
66802
  // src/hooks/diff-scope.ts
66681
66803
  import * as fs49 from "fs";
66682
- import * as path60 from "path";
66804
+ import * as path61 from "path";
66683
66805
  function getDeclaredScope(taskId, directory) {
66684
66806
  try {
66685
- const planPath = path60.join(directory, ".swarm", "plan.json");
66807
+ const planPath = path61.join(directory, ".swarm", "plan.json");
66686
66808
  if (!fs49.existsSync(planPath))
66687
66809
  return null;
66688
66810
  const raw = fs49.readFileSync(planPath, "utf-8");
@@ -66798,7 +66920,7 @@ var TIER_3_PATTERNS = [
66798
66920
  ];
66799
66921
  function matchesTier3Pattern(files) {
66800
66922
  for (const file3 of files) {
66801
- const fileName = path61.basename(file3);
66923
+ const fileName = path62.basename(file3);
66802
66924
  for (const pattern of TIER_3_PATTERNS) {
66803
66925
  if (pattern.test(fileName)) {
66804
66926
  return true;
@@ -66812,7 +66934,7 @@ function checkReviewerGate(taskId, workingDirectory) {
66812
66934
  if (hasActiveTurboMode()) {
66813
66935
  const resolvedDir2 = workingDirectory;
66814
66936
  try {
66815
- const planPath = path61.join(resolvedDir2, ".swarm", "plan.json");
66937
+ const planPath = path62.join(resolvedDir2, ".swarm", "plan.json");
66816
66938
  const planRaw = fs50.readFileSync(planPath, "utf-8");
66817
66939
  const plan = JSON.parse(planRaw);
66818
66940
  for (const planPhase of plan.phases ?? []) {
@@ -66879,7 +67001,7 @@ function checkReviewerGate(taskId, workingDirectory) {
66879
67001
  }
66880
67002
  try {
66881
67003
  const resolvedDir2 = workingDirectory;
66882
- const planPath = path61.join(resolvedDir2, ".swarm", "plan.json");
67004
+ const planPath = path62.join(resolvedDir2, ".swarm", "plan.json");
66883
67005
  const planRaw = fs50.readFileSync(planPath, "utf-8");
66884
67006
  const plan = JSON.parse(planRaw);
66885
67007
  for (const planPhase of plan.phases ?? []) {
@@ -67062,8 +67184,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
67062
67184
  };
67063
67185
  }
67064
67186
  }
67065
- normalizedDir = path61.normalize(args2.working_directory);
67066
- const pathParts = normalizedDir.split(path61.sep);
67187
+ normalizedDir = path62.normalize(args2.working_directory);
67188
+ const pathParts = normalizedDir.split(path62.sep);
67067
67189
  if (pathParts.includes("..")) {
67068
67190
  return {
67069
67191
  success: false,
@@ -67073,10 +67195,10 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
67073
67195
  ]
67074
67196
  };
67075
67197
  }
67076
- const resolvedDir = path61.resolve(normalizedDir);
67198
+ const resolvedDir = path62.resolve(normalizedDir);
67077
67199
  try {
67078
67200
  const realPath = fs50.realpathSync(resolvedDir);
67079
- const planPath = path61.join(realPath, ".swarm", "plan.json");
67201
+ const planPath = path62.join(realPath, ".swarm", "plan.json");
67080
67202
  if (!fs50.existsSync(planPath)) {
67081
67203
  return {
67082
67204
  success: false,
@@ -67110,7 +67232,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
67110
67232
  recoverTaskStateFromDelegations(args2.task_id);
67111
67233
  let phaseRequiresReviewer = true;
67112
67234
  try {
67113
- const planPath = path61.join(directory, ".swarm", "plan.json");
67235
+ const planPath = path62.join(directory, ".swarm", "plan.json");
67114
67236
  const planRaw = fs50.readFileSync(planPath, "utf-8");
67115
67237
  const plan = JSON.parse(planRaw);
67116
67238
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
@@ -67318,7 +67440,7 @@ var OpenCodeSwarm = async (ctx) => {
67318
67440
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
67319
67441
  preflightTriggerManager = new PTM(automationConfig);
67320
67442
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
67321
- const swarmDir = path62.resolve(ctx.directory, ".swarm");
67443
+ const swarmDir = path63.resolve(ctx.directory, ".swarm");
67322
67444
  statusArtifact = new ASA(swarmDir);
67323
67445
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
67324
67446
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -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.4",
3
+ "version": "6.40.6",
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",