opencode-swarm 6.41.0 → 6.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -32643,7 +32643,7 @@ async function executeWriteRetro(args, directory) {
32643
32643
  var write_retro = createSwarmTool({
32644
32644
  description: "Write a retrospective evidence bundle for a completed phase. " + "Accepts flat retro fields and writes a correctly-wrapped EvidenceBundle to " + ".swarm/evidence/retro-{phase}/evidence.json. " + "Use this instead of manually writing retro JSON to avoid schema validation failures in phase_complete.",
32645
32645
  args: {
32646
- phase: tool.schema.number().int().positive().max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
32646
+ phase: tool.schema.number().int().min(1).max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
32647
32647
  summary: tool.schema.string().describe("Human-readable summary of the phase"),
32648
32648
  task_count: tool.schema.number().int().min(1).max(9999).describe("Count of tasks completed in this phase"),
32649
32649
  task_complexity: tool.schema.enum(["trivial", "simple", "moderate", "complex"]).describe("Complexity level of the completed tasks"),
@@ -106,7 +106,7 @@ export declare function validateAndRecordAttestation(dir: string, findingId: str
106
106
  /**
107
107
  * Checks whether the given agent is authorised to write to the given file path.
108
108
  */
109
- export declare function checkFileAuthority(agentName: string, filePath: string, _cwd: string): {
109
+ export declare function checkFileAuthority(agentName: string, filePath: string, cwd: string): {
110
110
  allowed: true;
111
111
  } | {
112
112
  allowed: false;
package/dist/index.js CHANGED
@@ -41431,6 +41431,9 @@ function isValidTaskId(taskId) {
41431
41431
  if (taskId === null || taskId === undefined) {
41432
41432
  return false;
41433
41433
  }
41434
+ if (typeof taskId !== "string") {
41435
+ return false;
41436
+ }
41434
41437
  const trimmed = taskId.trim();
41435
41438
  return trimmed.length > 0;
41436
41439
  }
@@ -47071,7 +47074,7 @@ async function executeWriteRetro(args2, directory) {
47071
47074
  var write_retro = createSwarmTool({
47072
47075
  description: "Write a retrospective evidence bundle for a completed phase. " + "Accepts flat retro fields and writes a correctly-wrapped EvidenceBundle to " + ".swarm/evidence/retro-{phase}/evidence.json. " + "Use this instead of manually writing retro JSON to avoid schema validation failures in phase_complete.",
47073
47076
  args: {
47074
- phase: tool.schema.number().int().positive().max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
47077
+ phase: tool.schema.number().int().min(1).max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
47075
47078
  summary: tool.schema.string().describe("Human-readable summary of the phase"),
47076
47079
  task_count: tool.schema.number().int().min(1).max(9999).describe("Count of tasks completed in this phase"),
47077
47080
  task_complexity: tool.schema.enum(["trivial", "simple", "moderate", "complex"]).describe("Complexity level of the completed tasks"),
@@ -52386,10 +52389,11 @@ function getCurrentTaskId(sessionId) {
52386
52389
  const session = swarmState.agentSessions.get(sessionId);
52387
52390
  return session?.currentTaskId ?? `${sessionId}:unknown`;
52388
52391
  }
52389
- function isInDeclaredScope(filePath, scopeEntries) {
52390
- const resolvedFile = path32.resolve(filePath);
52392
+ function isInDeclaredScope(filePath, scopeEntries, cwd) {
52393
+ const dir = cwd ?? process.cwd();
52394
+ const resolvedFile = path32.resolve(dir, filePath);
52391
52395
  return scopeEntries.some((scope) => {
52392
- const resolvedScope = path32.resolve(scope);
52396
+ const resolvedScope = path32.resolve(dir, scope);
52393
52397
  if (resolvedFile === resolvedScope)
52394
52398
  return true;
52395
52399
  const rel = path32.relative(resolvedScope, resolvedFile);
@@ -52847,7 +52851,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
52847
52851
  }
52848
52852
  session.partialGateWarningsIssuedForTask?.delete(session.currentTaskId);
52849
52853
  if (session.declaredCoderScope !== null) {
52850
- const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope));
52854
+ const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope, directory));
52851
52855
  if (undeclaredFiles.length >= 1) {
52852
52856
  const safeTaskId = String(session.currentTaskId ?? "").replace(/[\r\n\t]/g, "_");
52853
52857
  session.lastScopeViolation = `Scope violation for task ${safeTaskId}: ` + `${undeclaredFiles.length} undeclared files modified: ` + undeclaredFiles.join(", ");
@@ -53290,9 +53294,11 @@ var AGENT_AUTHORITY_RULES = {
53290
53294
  blockedZones: ["generated"]
53291
53295
  }
53292
53296
  };
53293
- function checkFileAuthority(agentName, filePath, _cwd) {
53297
+ function checkFileAuthority(agentName, filePath, cwd) {
53294
53298
  const normalizedAgent = agentName.toLowerCase();
53295
- const normalizedPath = filePath.replace(/\\/g, "/");
53299
+ const dir = cwd || process.cwd();
53300
+ const resolved = path32.resolve(dir, filePath);
53301
+ const normalizedPath = path32.relative(dir, resolved).replace(/\\/g, "/");
53296
53302
  const rules = AGENT_AUTHORITY_RULES[normalizedAgent];
53297
53303
  if (!rules) {
53298
53304
  return { allowed: false, reason: `Unknown agent: ${agentName}` };
@@ -57670,6 +57676,19 @@ function parseFilePaths(description, filesTouched) {
57670
57676
  }
57671
57677
  return filePaths;
57672
57678
  }
57679
+ function buildVerifySummary(tasksChecked, tasksSkipped, tasksBlocked) {
57680
+ if (tasksBlocked > 0) {
57681
+ return `Blocked: ${tasksBlocked} task(s) with missing identifiers`;
57682
+ }
57683
+ const verified = tasksChecked - tasksSkipped;
57684
+ if (tasksSkipped === tasksChecked) {
57685
+ return `All ${tasksChecked} completed task(s) skipped \u2014 research/inventory tasks`;
57686
+ }
57687
+ if (tasksSkipped > 0) {
57688
+ return `${verified} task(s) verified, ${tasksSkipped} skipped (research tasks)`;
57689
+ }
57690
+ return `All ${tasksChecked} completed tasks verified successfully`;
57691
+ }
57673
57692
  async function executeCompletionVerify(args2, directory) {
57674
57693
  const phase = Number(args2.phase);
57675
57694
  if (Number.isNaN(phase) || phase < 1 || !Number.isFinite(phase)) {
@@ -57731,7 +57750,7 @@ async function executeCompletionVerify(args2, directory) {
57731
57750
  return JSON.stringify(result2, null, 2);
57732
57751
  }
57733
57752
  let tasksChecked = 0;
57734
- const tasksSkipped = 0;
57753
+ let tasksSkipped = 0;
57735
57754
  let tasksBlocked = 0;
57736
57755
  const blockedTasks = [];
57737
57756
  for (const task of targetPhase.tasks) {
@@ -57742,13 +57761,7 @@ async function executeCompletionVerify(args2, directory) {
57742
57761
  const fileTargets = parseFilePaths(task.description, task.files_touched);
57743
57762
  const identifiers = parseIdentifiers(task.description);
57744
57763
  if (fileTargets.length === 0) {
57745
- blockedTasks.push({
57746
- task_id: task.id,
57747
- identifier: "",
57748
- file_path: "",
57749
- reason: "No file targets \u2014 cannot verify completion without files_touched"
57750
- });
57751
- tasksBlocked++;
57764
+ tasksSkipped++;
57752
57765
  continue;
57753
57766
  }
57754
57767
  if (identifiers.length === 0) {
@@ -57766,6 +57779,19 @@ async function executeCompletionVerify(args2, directory) {
57766
57779
  for (const filePath of fileTargets) {
57767
57780
  const normalizedPath = filePath.replace(/\\/g, "/");
57768
57781
  const resolvedPath = path45.resolve(directory, normalizedPath);
57782
+ const projectRoot = path45.resolve(directory);
57783
+ const relative6 = path45.relative(projectRoot, resolvedPath);
57784
+ const withinProject = relative6 === "" || !relative6.startsWith("..") && !path45.isAbsolute(relative6);
57785
+ if (!withinProject) {
57786
+ blockedTasks.push({
57787
+ task_id: task.id,
57788
+ identifier: "",
57789
+ file_path: filePath,
57790
+ reason: `File path '${filePath}' escapes the project directory \u2014 cannot verify completion`
57791
+ });
57792
+ hasFileReadFailure = true;
57793
+ continue;
57794
+ }
57769
57795
  let fileContent;
57770
57796
  try {
57771
57797
  fileContent = fs33.readFileSync(resolvedPath, "utf-8");
@@ -57824,7 +57850,7 @@ async function executeCompletionVerify(args2, directory) {
57824
57850
  timestamp: now,
57825
57851
  agent: "completion_verify",
57826
57852
  verdict: tasksBlocked === 0 ? "pass" : "fail",
57827
- summary: tasksBlocked === 0 ? `All ${tasksChecked} completed tasks verified successfully` : `Blocked: ${tasksBlocked} task(s) with missing identifiers`,
57853
+ summary: buildVerifySummary(tasksChecked, tasksSkipped, tasksBlocked),
57828
57854
  phase,
57829
57855
  tasks_checked: tasksChecked,
57830
57856
  tasks_skipped: tasksSkipped,
@@ -58966,7 +58992,29 @@ async function executeDeclareScope(args2, fallbackDir) {
58966
58992
  };
58967
58993
  }
58968
58994
  }
58969
- const mergedFiles = [...args2.files, ...args2.whitelist ?? []];
58995
+ const rawMergedFiles = [...args2.files, ...args2.whitelist ?? []];
58996
+ const warnings = [];
58997
+ const normalizeErrors = [];
58998
+ const dir = normalizedDir || fallbackDir || process.cwd();
58999
+ const mergedFiles = rawMergedFiles.map((file3) => {
59000
+ if (path48.isAbsolute(file3)) {
59001
+ const relativePath = path48.relative(dir, file3).replace(/\\/g, "/");
59002
+ if (relativePath.startsWith("..")) {
59003
+ normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
59004
+ return file3;
59005
+ }
59006
+ warnings.push(`Absolute path normalized to relative: '${relativePath}' (was '${file3}')`);
59007
+ return relativePath;
59008
+ }
59009
+ return file3;
59010
+ });
59011
+ if (normalizeErrors.length > 0) {
59012
+ return {
59013
+ success: false,
59014
+ message: "Validation failed",
59015
+ errors: normalizeErrors
59016
+ };
59017
+ }
58970
59018
  for (const [_sessionId, session] of swarmState.agentSessions) {
58971
59019
  session.declaredCoderScope = mergedFiles;
58972
59020
  session.lastScopeViolation = null;
@@ -58975,7 +59023,8 @@ async function executeDeclareScope(args2, fallbackDir) {
58975
59023
  success: true,
58976
59024
  message: "Scope declared successfully",
58977
59025
  taskId: args2.taskId,
58978
- fileCount: mergedFiles.length
59026
+ fileCount: mergedFiles.length,
59027
+ ...warnings.length > 0 ? { warnings } : {}
58979
59028
  };
58980
59029
  }
58981
59030
  var declare_scope = createSwarmTool({
@@ -61480,7 +61529,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
61480
61529
  var phase_complete = createSwarmTool({
61481
61530
  description: "Mark a phase as complete and track which agents were dispatched. Used for phase completion gating and tracking. Accepts phase number and optional summary. Returns list of agents that were dispatched.",
61482
61531
  args: {
61483
- phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
61532
+ phase: tool.schema.number().int().min(1).describe("The phase number being completed \u2014 a positive integer (e.g., 1, 2, 3)"),
61484
61533
  summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
61485
61534
  sessionID: tool.schema.string().optional().describe("Session ID for tracking state (auto-provided by plugin context)")
61486
61535
  },
@@ -64018,13 +64067,13 @@ function validatePath(inputPath, baseDir, workspaceDir) {
64018
64067
  resolved = path56.resolve(baseDir, inputPath);
64019
64068
  }
64020
64069
  const workspaceResolved = path56.resolve(workspaceDir);
64021
- let relative6;
64070
+ let relative8;
64022
64071
  if (isWinAbs) {
64023
- relative6 = path56.win32.relative(workspaceResolved, resolved);
64072
+ relative8 = path56.win32.relative(workspaceResolved, resolved);
64024
64073
  } else {
64025
- relative6 = path56.relative(workspaceResolved, resolved);
64074
+ relative8 = path56.relative(workspaceResolved, resolved);
64026
64075
  }
64027
- if (relative6.startsWith("..")) {
64076
+ if (relative8.startsWith("..")) {
64028
64077
  return "path traversal detected";
64029
64078
  }
64030
64079
  return null;
@@ -64933,7 +64982,7 @@ async function executeSavePlan(args2, fallbackDir) {
64933
64982
  success: false,
64934
64983
  message: "Plan rejected: invalid phase or task IDs",
64935
64984
  errors: validationErrors,
64936
- recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
64985
+ recovery_guidance: "Phase IDs must be positive integers: 1, 2, 3 (not 0, -1, or decimals). " + 'Task IDs must use N.M format: "1.1", "2.3", "3.1". ' + "Call save_plan again with corrected ids. " + "Never write .swarm/plan.json or .swarm/plan.md directly."
64937
64986
  };
64938
64987
  }
64939
64988
  const placeholderIssues = detectPlaceholderContent(args2);
@@ -65046,7 +65095,7 @@ var save_plan = createSwarmTool({
65046
65095
  title: tool.schema.string().min(1).describe("Plan title \u2014 the REAL project name from the spec. NOT a placeholder like [Project]."),
65047
65096
  swarm_id: tool.schema.string().min(1).describe('Swarm identifier (e.g. "mega")'),
65048
65097
  phases: tool.schema.array(tool.schema.object({
65049
- id: tool.schema.number().int().positive().describe("Phase number, starting at 1"),
65098
+ id: tool.schema.number().int().min(1).describe("Phase number \u2014 a positive integer starting at 1. Use 1, 2, 3, etc."),
65050
65099
  name: tool.schema.string().min(1).describe("Descriptive phase name derived from the spec"),
65051
65100
  tasks: tool.schema.array(tool.schema.object({
65052
65101
  id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, 'Task ID must be in N.M format, e.g. "1.1"').describe('Task ID in N.M format, e.g. "1.1", "2.3"'),
@@ -67629,7 +67678,7 @@ async function executeWriteDriftEvidence(args2, directory) {
67629
67678
  var write_drift_evidence = createSwarmTool({
67630
67679
  description: "Write drift verification evidence for a completed phase. " + "Normalizes verdict (APPROVED->approved, NEEDS_REVISION->rejected) and writes " + "a gate-contract formatted EvidenceBundle to .swarm/evidence/{phase}/drift-verifier.json. " + "Use this after critic_drift_verifier delegation to persist the verification result.",
67631
67680
  args: {
67632
- phase: tool.schema.number().int().positive().describe("The phase number for the drift verification"),
67681
+ phase: tool.schema.number().int().min(1).describe("The phase number for the drift verification (e.g., 1, 2, 3)"),
67633
67682
  verdict: tool.schema.enum(["APPROVED", "NEEDS_REVISION"]).describe("Verdict of the drift verification: 'APPROVED' or 'NEEDS_REVISION'"),
67634
67683
  summary: tool.schema.string().describe("Human-readable summary of the drift verification")
67635
67684
  },
@@ -22,6 +22,7 @@ export interface DeclareScopeResult {
22
22
  taskId?: string;
23
23
  fileCount?: number;
24
24
  errors?: string[];
25
+ warnings?: string[];
25
26
  }
26
27
  /**
27
28
  * Validate that taskId matches the required format (N.M or N.M.P).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.41.0",
3
+ "version": "6.41.2",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",