opencode-swarm 6.22.20 → 6.23.0

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
@@ -33333,8 +33333,9 @@ function isHighEntropyString(str) {
33333
33333
  function containsPathTraversal(str) {
33334
33334
  if (/\.\.[/\\]/.test(str))
33335
33335
  return true;
33336
- const normalized = path12.normalize(str);
33337
- if (/\.\.[/\\]/.test(normalized))
33336
+ if (/[/\\]\.\.$/.test(str) || str === "..")
33337
+ return true;
33338
+ if (/\.\.[/\\]/.test(path12.normalize(str.replace(/\*/g, "x"))))
33338
33339
  return true;
33339
33340
  if (str.includes("%2e%2e") || str.includes("%2E%2E"))
33340
33341
  return true;
@@ -33342,6 +33343,58 @@ function containsPathTraversal(str) {
33342
33343
  return true;
33343
33344
  return false;
33344
33345
  }
33346
+ function validateExcludePattern(exc) {
33347
+ if (exc.length === 0)
33348
+ return null;
33349
+ if (exc.length > MAX_FILE_PATH_LENGTH) {
33350
+ return `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`;
33351
+ }
33352
+ if (containsControlChars(exc)) {
33353
+ return "invalid exclude path: contains path traversal or control characters";
33354
+ }
33355
+ if (containsPathTraversal(exc)) {
33356
+ return "invalid exclude path: contains path traversal or control characters";
33357
+ }
33358
+ if (exc.startsWith("!")) {
33359
+ return "invalid exclude path: negation patterns are not supported";
33360
+ }
33361
+ if (exc.startsWith("/") || exc.startsWith("\\")) {
33362
+ return "invalid exclude path: absolute paths are not supported";
33363
+ }
33364
+ return null;
33365
+ }
33366
+ function isGlobOrPathPattern(pattern) {
33367
+ return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
33368
+ }
33369
+ function loadSecretScanIgnore(scanDir) {
33370
+ const ignorePath = path12.join(scanDir, ".secretscanignore");
33371
+ try {
33372
+ if (!fs4.existsSync(ignorePath))
33373
+ return [];
33374
+ const content = fs4.readFileSync(ignorePath, "utf8");
33375
+ const patterns = [];
33376
+ for (const rawLine of content.split(/\r?\n/)) {
33377
+ const line = rawLine.trim();
33378
+ if (!line || line.startsWith("#"))
33379
+ continue;
33380
+ if (validateExcludePattern(line) === null) {
33381
+ patterns.push(line);
33382
+ }
33383
+ }
33384
+ return patterns;
33385
+ } catch {
33386
+ return [];
33387
+ }
33388
+ }
33389
+ function isExcluded(entry, relPath, exactNames, globPatterns) {
33390
+ if (exactNames.has(entry))
33391
+ return true;
33392
+ for (const pattern of globPatterns) {
33393
+ if (path12.matchesGlob(relPath, pattern))
33394
+ return true;
33395
+ }
33396
+ return false;
33397
+ }
33345
33398
  function containsControlChars(str) {
33346
33399
  return /[\0\r]/.test(str);
33347
33400
  }
@@ -33502,7 +33555,7 @@ function isPathWithinScope(realPath, scanDir) {
33502
33555
  const resolvedRealPath = path12.resolve(realPath);
33503
33556
  return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path12.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
33504
33557
  }
33505
- function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33558
+ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
33506
33559
  skippedDirs: 0,
33507
33560
  skippedFiles: 0,
33508
33561
  fileErrors: 0,
@@ -33526,11 +33579,12 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33526
33579
  return a.localeCompare(b);
33527
33580
  });
33528
33581
  for (const entry of entries) {
33529
- if (excludeDirs.has(entry)) {
33582
+ const fullPath = path12.join(dir, entry);
33583
+ const relPath = path12.relative(scanDir, fullPath).replace(/\\/g, "/");
33584
+ if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
33530
33585
  stats.skippedDirs++;
33531
33586
  continue;
33532
33587
  }
33533
- const fullPath = path12.join(dir, entry);
33534
33588
  let lstat;
33535
33589
  try {
33536
33590
  lstat = fs4.lstatSync(fullPath);
@@ -33558,7 +33612,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33558
33612
  stats.symlinkSkipped++;
33559
33613
  continue;
33560
33614
  }
33561
- const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
33615
+ const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
33562
33616
  files.push(...subFiles);
33563
33617
  } else if (lstat.isFile()) {
33564
33618
  const ext = path12.extname(fullPath).toLowerCase();
@@ -33572,10 +33626,10 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33572
33626
  return files;
33573
33627
  }
33574
33628
  var secretscan = tool({
33575
- description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default.",
33629
+ description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default. Supports glob patterns (e.g. **/.svelte-kit/**, **/*.test.ts) and reads .secretscanignore at the scan root.",
33576
33630
  args: {
33577
33631
  directory: tool.schema.string().describe('Directory to scan for secrets (e.g., "." or "./src")'),
33578
- exclude: tool.schema.array(tool.schema.string()).optional().describe("Additional directories to exclude (added to default exclusions like node_modules, .git, dist)")
33632
+ exclude: tool.schema.array(tool.schema.string()).optional().describe("Patterns to exclude: plain directory names (e.g. node_modules), relative paths, or globs (e.g. **/.svelte-kit/**, **/*.test.ts). Added to default exclusions.")
33579
33633
  },
33580
33634
  async execute(args, _context) {
33581
33635
  let directory;
@@ -33611,20 +33665,10 @@ var secretscan = tool({
33611
33665
  }
33612
33666
  if (exclude) {
33613
33667
  for (const exc of exclude) {
33614
- if (exc.length > MAX_FILE_PATH_LENGTH) {
33615
- const errorResult = {
33616
- error: `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`,
33617
- scan_dir: directory,
33618
- findings: [],
33619
- count: 0,
33620
- files_scanned: 0,
33621
- skipped_files: 0
33622
- };
33623
- return JSON.stringify(errorResult, null, 2);
33624
- }
33625
- if (containsPathTraversal(exc) || containsControlChars(exc)) {
33668
+ const err = validateExcludePattern(exc);
33669
+ if (err) {
33626
33670
  const errorResult = {
33627
- error: `invalid exclude path: contains path traversal or control characters`,
33671
+ error: err,
33628
33672
  scan_dir: directory,
33629
33673
  findings: [],
33630
33674
  count: 0,
@@ -33660,10 +33704,20 @@ var secretscan = tool({
33660
33704
  };
33661
33705
  return JSON.stringify(errorResult, null, 2);
33662
33706
  }
33663
- const excludeDirs = new Set(DEFAULT_EXCLUDE_DIRS);
33664
- if (exclude) {
33665
- for (const exc of exclude) {
33666
- excludeDirs.add(exc);
33707
+ const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
33708
+ const excludeGlobs = [];
33709
+ const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
33710
+ const allUserPatterns = [
33711
+ ...exclude ?? [],
33712
+ ...ignoreFilePatterns
33713
+ ];
33714
+ for (const exc of allUserPatterns) {
33715
+ if (exc.length === 0)
33716
+ continue;
33717
+ if (isGlobOrPathPattern(exc)) {
33718
+ excludeGlobs.push(exc);
33719
+ } else {
33720
+ excludeExact.add(exc);
33667
33721
  }
33668
33722
  }
33669
33723
  const stats = {
@@ -33673,7 +33727,7 @@ var secretscan = tool({
33673
33727
  symlinkSkipped: 0
33674
33728
  };
33675
33729
  const visited = new Set;
33676
- const files = findScannableFiles(scanDir, excludeDirs, scanDir, visited, stats);
33730
+ const files = findScannableFiles(scanDir, excludeExact, excludeGlobs, scanDir, visited, stats);
33677
33731
  files.sort((a, b) => {
33678
33732
  const aLower = a.toLowerCase();
33679
33733
  const bLower = b.toLowerCase();
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js CHANGED
@@ -15792,6 +15792,18 @@ async function updateTaskStatus(directory, taskId, status) {
15792
15792
  throw new Error(`Plan not found in directory: ${directory}`);
15793
15793
  }
15794
15794
  let taskFound = false;
15795
+ const derivePhaseStatusFromTasks = (tasks) => {
15796
+ if (tasks.length > 0 && tasks.every((task) => task.status === "completed")) {
15797
+ return "complete";
15798
+ }
15799
+ if (tasks.some((task) => task.status === "in_progress")) {
15800
+ return "in_progress";
15801
+ }
15802
+ if (tasks.some((task) => task.status === "blocked")) {
15803
+ return "blocked";
15804
+ }
15805
+ return "pending";
15806
+ };
15795
15807
  const updatedPhases = plan.phases.map((phase) => {
15796
15808
  const updatedTasks = phase.tasks.map((task) => {
15797
15809
  if (task.id === taskId) {
@@ -15800,7 +15812,11 @@ async function updateTaskStatus(directory, taskId, status) {
15800
15812
  }
15801
15813
  return task;
15802
15814
  });
15803
- return { ...phase, tasks: updatedTasks };
15815
+ return {
15816
+ ...phase,
15817
+ status: derivePhaseStatusFromTasks(updatedTasks),
15818
+ tasks: updatedTasks
15819
+ };
15804
15820
  });
15805
15821
  if (!taskFound) {
15806
15822
  throw new Error(`Task not found: ${taskId}`);
@@ -33558,8 +33574,9 @@ function isHighEntropyString(str) {
33558
33574
  function containsPathTraversal(str) {
33559
33575
  if (/\.\.[/\\]/.test(str))
33560
33576
  return true;
33561
- const normalized = path21.normalize(str);
33562
- if (/\.\.[/\\]/.test(normalized))
33577
+ if (/[/\\]\.\.$/.test(str) || str === "..")
33578
+ return true;
33579
+ if (/\.\.[/\\]/.test(path21.normalize(str.replace(/\*/g, "x"))))
33563
33580
  return true;
33564
33581
  if (str.includes("%2e%2e") || str.includes("%2E%2E"))
33565
33582
  return true;
@@ -33567,6 +33584,58 @@ function containsPathTraversal(str) {
33567
33584
  return true;
33568
33585
  return false;
33569
33586
  }
33587
+ function validateExcludePattern(exc) {
33588
+ if (exc.length === 0)
33589
+ return null;
33590
+ if (exc.length > MAX_FILE_PATH_LENGTH) {
33591
+ return `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`;
33592
+ }
33593
+ if (containsControlChars(exc)) {
33594
+ return "invalid exclude path: contains path traversal or control characters";
33595
+ }
33596
+ if (containsPathTraversal(exc)) {
33597
+ return "invalid exclude path: contains path traversal or control characters";
33598
+ }
33599
+ if (exc.startsWith("!")) {
33600
+ return "invalid exclude path: negation patterns are not supported";
33601
+ }
33602
+ if (exc.startsWith("/") || exc.startsWith("\\")) {
33603
+ return "invalid exclude path: absolute paths are not supported";
33604
+ }
33605
+ return null;
33606
+ }
33607
+ function isGlobOrPathPattern(pattern) {
33608
+ return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
33609
+ }
33610
+ function loadSecretScanIgnore(scanDir) {
33611
+ const ignorePath = path21.join(scanDir, ".secretscanignore");
33612
+ try {
33613
+ if (!fs9.existsSync(ignorePath))
33614
+ return [];
33615
+ const content = fs9.readFileSync(ignorePath, "utf8");
33616
+ const patterns = [];
33617
+ for (const rawLine of content.split(/\r?\n/)) {
33618
+ const line = rawLine.trim();
33619
+ if (!line || line.startsWith("#"))
33620
+ continue;
33621
+ if (validateExcludePattern(line) === null) {
33622
+ patterns.push(line);
33623
+ }
33624
+ }
33625
+ return patterns;
33626
+ } catch {
33627
+ return [];
33628
+ }
33629
+ }
33630
+ function isExcluded(entry, relPath, exactNames, globPatterns) {
33631
+ if (exactNames.has(entry))
33632
+ return true;
33633
+ for (const pattern of globPatterns) {
33634
+ if (path21.matchesGlob(relPath, pattern))
33635
+ return true;
33636
+ }
33637
+ return false;
33638
+ }
33570
33639
  function containsControlChars(str) {
33571
33640
  return /[\0\r]/.test(str);
33572
33641
  }
@@ -33726,7 +33795,7 @@ function isPathWithinScope(realPath, scanDir) {
33726
33795
  const resolvedRealPath = path21.resolve(realPath);
33727
33796
  return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path21.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
33728
33797
  }
33729
- function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33798
+ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
33730
33799
  skippedDirs: 0,
33731
33800
  skippedFiles: 0,
33732
33801
  fileErrors: 0,
@@ -33750,11 +33819,12 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33750
33819
  return a.localeCompare(b);
33751
33820
  });
33752
33821
  for (const entry of entries) {
33753
- if (excludeDirs.has(entry)) {
33822
+ const fullPath = path21.join(dir, entry);
33823
+ const relPath = path21.relative(scanDir, fullPath).replace(/\\/g, "/");
33824
+ if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
33754
33825
  stats.skippedDirs++;
33755
33826
  continue;
33756
33827
  }
33757
- const fullPath = path21.join(dir, entry);
33758
33828
  let lstat;
33759
33829
  try {
33760
33830
  lstat = fs9.lstatSync(fullPath);
@@ -33782,7 +33852,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
33782
33852
  stats.symlinkSkipped++;
33783
33853
  continue;
33784
33854
  }
33785
- const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
33855
+ const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
33786
33856
  files.push(...subFiles);
33787
33857
  } else if (lstat.isFile()) {
33788
33858
  const ext = path21.extname(fullPath).toLowerCase();
@@ -33987,10 +34057,10 @@ var init_secretscan = __esm(() => {
33987
34057
  ];
33988
34058
  O_NOFOLLOW = process.platform !== "win32" ? fs9.constants.O_NOFOLLOW : undefined;
33989
34059
  secretscan = tool({
33990
- description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default.",
34060
+ description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default. Supports glob patterns (e.g. **/.svelte-kit/**, **/*.test.ts) and reads .secretscanignore at the scan root.",
33991
34061
  args: {
33992
34062
  directory: tool.schema.string().describe('Directory to scan for secrets (e.g., "." or "./src")'),
33993
- exclude: tool.schema.array(tool.schema.string()).optional().describe("Additional directories to exclude (added to default exclusions like node_modules, .git, dist)")
34063
+ exclude: tool.schema.array(tool.schema.string()).optional().describe("Patterns to exclude: plain directory names (e.g. node_modules), relative paths, or globs (e.g. **/.svelte-kit/**, **/*.test.ts). Added to default exclusions.")
33994
34064
  },
33995
34065
  async execute(args2, _context) {
33996
34066
  let directory;
@@ -34026,20 +34096,10 @@ var init_secretscan = __esm(() => {
34026
34096
  }
34027
34097
  if (exclude) {
34028
34098
  for (const exc of exclude) {
34029
- if (exc.length > MAX_FILE_PATH_LENGTH) {
34030
- const errorResult = {
34031
- error: `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`,
34032
- scan_dir: directory,
34033
- findings: [],
34034
- count: 0,
34035
- files_scanned: 0,
34036
- skipped_files: 0
34037
- };
34038
- return JSON.stringify(errorResult, null, 2);
34039
- }
34040
- if (containsPathTraversal(exc) || containsControlChars(exc)) {
34099
+ const err2 = validateExcludePattern(exc);
34100
+ if (err2) {
34041
34101
  const errorResult = {
34042
- error: `invalid exclude path: contains path traversal or control characters`,
34102
+ error: err2,
34043
34103
  scan_dir: directory,
34044
34104
  findings: [],
34045
34105
  count: 0,
@@ -34075,10 +34135,20 @@ var init_secretscan = __esm(() => {
34075
34135
  };
34076
34136
  return JSON.stringify(errorResult, null, 2);
34077
34137
  }
34078
- const excludeDirs = new Set(DEFAULT_EXCLUDE_DIRS);
34079
- if (exclude) {
34080
- for (const exc of exclude) {
34081
- excludeDirs.add(exc);
34138
+ const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
34139
+ const excludeGlobs = [];
34140
+ const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
34141
+ const allUserPatterns = [
34142
+ ...exclude ?? [],
34143
+ ...ignoreFilePatterns
34144
+ ];
34145
+ for (const exc of allUserPatterns) {
34146
+ if (exc.length === 0)
34147
+ continue;
34148
+ if (isGlobOrPathPattern(exc)) {
34149
+ excludeGlobs.push(exc);
34150
+ } else {
34151
+ excludeExact.add(exc);
34082
34152
  }
34083
34153
  }
34084
34154
  const stats = {
@@ -34088,7 +34158,7 @@ var init_secretscan = __esm(() => {
34088
34158
  symlinkSkipped: 0
34089
34159
  };
34090
34160
  const visited = new Set;
34091
- const files = findScannableFiles(scanDir, excludeDirs, scanDir, visited, stats);
34161
+ const files = findScannableFiles(scanDir, excludeExact, excludeGlobs, scanDir, visited, stats);
34092
34162
  files.sort((a, b) => {
34093
34163
  const aLower = a.toLowerCase();
34094
34164
  const bLower = b.toLowerCase();
@@ -47932,8 +48002,8 @@ function isOutsideSwarmDir(filePath, directory) {
47932
48002
  return false;
47933
48003
  const swarmDir = path26.resolve(directory, ".swarm");
47934
48004
  const resolved = path26.resolve(directory, filePath);
47935
- const relative3 = path26.relative(swarmDir, resolved);
47936
- return relative3.startsWith("..") || path26.isAbsolute(relative3);
48005
+ const relative4 = path26.relative(swarmDir, resolved);
48006
+ return relative4.startsWith("..") || path26.isAbsolute(relative4);
47937
48007
  }
47938
48008
  function isSourceCodePath(filePath) {
47939
48009
  if (!filePath)
@@ -48612,6 +48682,9 @@ function extractPlanTaskId(text) {
48612
48682
  }
48613
48683
  return null;
48614
48684
  }
48685
+ function getSeedTaskId(session) {
48686
+ return session.currentTaskId ?? session.lastCoderDelegationTaskId;
48687
+ }
48615
48688
  function createDelegationGateHook(config3) {
48616
48689
  const enabled = config3.hooks?.delegation_gate !== false;
48617
48690
  const delegationMaxChars = config3.hooks?.delegation_max_chars ?? 4000;
@@ -48669,6 +48742,10 @@ function createDelegationGateHook(config3) {
48669
48742
  if (!otherSession.taskWorkflowStates)
48670
48743
  continue;
48671
48744
  if (targetAgent === "reviewer") {
48745
+ const seedTaskId = getSeedTaskId(session);
48746
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
48747
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
48748
+ }
48672
48749
  for (const [taskId, state] of otherSession.taskWorkflowStates) {
48673
48750
  if (state === "coder_delegated" || state === "pre_check_passed") {
48674
48751
  try {
@@ -48680,6 +48757,10 @@ function createDelegationGateHook(config3) {
48680
48757
  }
48681
48758
  }
48682
48759
  if (targetAgent === "test_engineer") {
48760
+ const seedTaskId = getSeedTaskId(session);
48761
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
48762
+ otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
48763
+ }
48683
48764
  for (const [taskId, state] of otherSession.taskWorkflowStates) {
48684
48765
  if (state === "reviewer_run") {
48685
48766
  try {
@@ -48750,6 +48831,10 @@ function createDelegationGateHook(config3) {
48750
48831
  continue;
48751
48832
  if (!otherSession.taskWorkflowStates)
48752
48833
  continue;
48834
+ const seedTaskId = getSeedTaskId(session);
48835
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
48836
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
48837
+ }
48753
48838
  for (const [taskId, state] of otherSession.taskWorkflowStates) {
48754
48839
  if (state === "coder_delegated" || state === "pre_check_passed") {
48755
48840
  try {
@@ -48767,6 +48852,10 @@ function createDelegationGateHook(config3) {
48767
48852
  continue;
48768
48853
  if (!otherSession.taskWorkflowStates)
48769
48854
  continue;
48855
+ const seedTaskId = getSeedTaskId(session);
48856
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
48857
+ otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
48858
+ }
48770
48859
  for (const [taskId, state] of otherSession.taskWorkflowStates) {
48771
48860
  if (state === "reviewer_run") {
48772
48861
  try {
@@ -58050,13 +58139,13 @@ function validatePath(inputPath, baseDir, workspaceDir) {
58050
58139
  resolved = path41.resolve(baseDir, inputPath);
58051
58140
  }
58052
58141
  const workspaceResolved = path41.resolve(workspaceDir);
58053
- let relative4;
58142
+ let relative5;
58054
58143
  if (isWinAbs) {
58055
- relative4 = path41.win32.relative(workspaceResolved, resolved);
58144
+ relative5 = path41.win32.relative(workspaceResolved, resolved);
58056
58145
  } else {
58057
- relative4 = path41.relative(workspaceResolved, resolved);
58146
+ relative5 = path41.relative(workspaceResolved, resolved);
58058
58147
  }
58059
- if (relative4.startsWith("..")) {
58148
+ if (relative5.startsWith("..")) {
58060
58149
  return "path traversal detected";
58061
58150
  }
58062
58151
  return null;
@@ -60915,7 +61004,9 @@ function checkReviewerGate(taskId, workingDirectory) {
60915
61004
  stateEntries.push(`${sessionId}: ${state}`);
60916
61005
  }
60917
61006
  const allIdle = stateEntries.length > 0 && stateEntries.every((e) => e.endsWith(": idle"));
60918
- if (allIdle) {}
61007
+ if (allIdle) {
61008
+ return { blocked: false, reason: "" };
61009
+ }
60919
61010
  try {
60920
61011
  const resolvedDir = workingDirectory ?? process.cwd();
60921
61012
  const planPath = path47.join(resolvedDir, ".swarm", "plan.json");
@@ -60932,7 +61023,7 @@ function checkReviewerGate(taskId, workingDirectory) {
60932
61023
  const currentStateStr = stateEntries.length > 0 ? stateEntries.join(", ") : "no active sessions";
60933
61024
  return {
60934
61025
  blocked: true,
60935
- reason: `Task ${taskId} has not passed QA gates. Current state: [${currentStateStr}]. Required state: tests_run or complete. Do not write directly to plan files \u2014 use update_task_status after running the reviewer and test_engineer agents.`
61026
+ reason: `Task ${taskId} has not passed QA gates. Current state by session: [${currentStateStr}]. Missing required state: tests_run or complete in at least one valid session. Do not write directly to plan files \u2014 use update_task_status after running the reviewer and test_engineer agents.`
60936
61027
  };
60937
61028
  } catch {
60938
61029
  return { blocked: false, reason: "" };
@@ -61032,6 +61123,19 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
61032
61123
  }
61033
61124
  try {
61034
61125
  const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
61126
+ if (args2.status === "completed") {
61127
+ for (const [_sessionId, session] of swarmState.agentSessions) {
61128
+ if (!(session.taskWorkflowStates instanceof Map)) {
61129
+ continue;
61130
+ }
61131
+ const currentState = getTaskState(session, args2.task_id);
61132
+ if (currentState === "tests_run") {
61133
+ try {
61134
+ advanceTaskState(session, args2.task_id, "complete");
61135
+ } catch {}
61136
+ }
61137
+ }
61138
+ }
61035
61139
  return {
61036
61140
  success: true,
61037
61141
  message: "Task status updated successfully",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.22.20",
3
+ "version": "6.23.0",
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",