opencode-swarm 6.14.0 → 6.14.11

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli/index.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -1,27 +1,16 @@
1
1
  // @bun
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
2
  var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
16
- return to;
17
- };
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name2, newValue) {
5
+ this[name2] = __returnValue.bind(null, newValue);
6
+ }
18
7
  var __export = (target, all) => {
19
8
  for (var name2 in all)
20
9
  __defProp(target, name2, {
21
10
  get: all[name2],
22
11
  enumerable: true,
23
12
  configurable: true,
24
- set: (newValue) => all[name2] = () => newValue
13
+ set: __exportSetter.bind(all, name2)
25
14
  });
26
15
  };
27
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -14816,7 +14805,7 @@ function migrateLegacyPlan(planContent, swarmId) {
14816
14805
  }
14817
14806
  continue;
14818
14807
  }
14819
- const phaseMatch = trimmed.match(/^##\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
14808
+ const phaseMatch = trimmed.match(/^#{2,3}\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
14820
14809
  if (phaseMatch) {
14821
14810
  if (currentPhase !== null) {
14822
14811
  phases.push(currentPhase);
@@ -14884,12 +14873,87 @@ function migrateLegacyPlan(planContent, swarmId) {
14884
14873
  };
14885
14874
  currentPhase.tasks.push(task);
14886
14875
  }
14876
+ const numberedTaskMatch = trimmed.match(/^(\d+)\.\s+(.+?)(?:\s*\[(\w+)\])?$/);
14877
+ if (numberedTaskMatch && currentPhase !== null) {
14878
+ const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
14879
+ let description = numberedTaskMatch[2].trim();
14880
+ const sizeText = numberedTaskMatch[3]?.toLowerCase() || "small";
14881
+ const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
14882
+ const depends = [];
14883
+ if (dependsMatch) {
14884
+ const depsText = dependsMatch[1];
14885
+ depends.push(...depsText.split(",").map((d) => d.trim()));
14886
+ description = description.substring(0, dependsMatch.index).trim();
14887
+ }
14888
+ const sizeMap = {
14889
+ small: "small",
14890
+ medium: "medium",
14891
+ large: "large"
14892
+ };
14893
+ const task = {
14894
+ id: taskId,
14895
+ phase: currentPhase.id,
14896
+ status: "pending",
14897
+ size: sizeMap[sizeText] || "small",
14898
+ description,
14899
+ depends,
14900
+ acceptance: undefined,
14901
+ files_touched: [],
14902
+ evidence_path: undefined,
14903
+ blocked_reason: undefined
14904
+ };
14905
+ currentPhase.tasks.push(task);
14906
+ }
14907
+ const noPrefixTaskMatch = trimmed.match(/^-\s*\[([^\]]+)\]\s+(?!\d+\.\d+:)(.+?)(?:\s*\[(\w+)\])?(?:\s*-\s*(.+))?$/i);
14908
+ if (noPrefixTaskMatch && currentPhase !== null) {
14909
+ const checkbox = noPrefixTaskMatch[1].toLowerCase();
14910
+ const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
14911
+ let description = noPrefixTaskMatch[2].trim();
14912
+ const sizeText = noPrefixTaskMatch[3]?.toLowerCase() || "small";
14913
+ let blockedReason;
14914
+ const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
14915
+ const depends = [];
14916
+ if (dependsMatch) {
14917
+ const depsText = dependsMatch[1];
14918
+ depends.push(...depsText.split(",").map((d) => d.trim()));
14919
+ description = description.substring(0, dependsMatch.index).trim();
14920
+ }
14921
+ let status = "pending";
14922
+ if (checkbox === "x") {
14923
+ status = "completed";
14924
+ } else if (checkbox === "blocked") {
14925
+ status = "blocked";
14926
+ const blockedReasonMatch = noPrefixTaskMatch[4];
14927
+ if (blockedReasonMatch) {
14928
+ blockedReason = blockedReasonMatch.trim();
14929
+ }
14930
+ }
14931
+ const sizeMap = {
14932
+ small: "small",
14933
+ medium: "medium",
14934
+ large: "large"
14935
+ };
14936
+ const task = {
14937
+ id: taskId,
14938
+ phase: currentPhase.id,
14939
+ status,
14940
+ size: sizeMap[sizeText] || "small",
14941
+ description,
14942
+ depends,
14943
+ acceptance: undefined,
14944
+ files_touched: [],
14945
+ evidence_path: undefined,
14946
+ blocked_reason: blockedReason
14947
+ };
14948
+ currentPhase.tasks.push(task);
14949
+ }
14887
14950
  }
14888
14951
  if (currentPhase !== null) {
14889
14952
  phases.push(currentPhase);
14890
14953
  }
14891
14954
  let migrationStatus = "migrated";
14892
14955
  if (phases.length === 0) {
14956
+ console.warn(`migrateLegacyPlan: 0 phases parsed from ${lines.length} lines. First 3 lines: ${lines.slice(0, 3).join(" | ")}`);
14893
14957
  migrationStatus = "migration_failed";
14894
14958
  phases.push({
14895
14959
  id: 1,
@@ -31440,7 +31504,7 @@ var init_preflight_integration = __esm(() => {
31440
31504
  });
31441
31505
 
31442
31506
  // src/index.ts
31443
- import * as path31 from "path";
31507
+ import * as path32 from "path";
31444
31508
 
31445
31509
  // src/tools/tool-names.ts
31446
31510
  var TOOL_NAMES = [
@@ -31467,7 +31531,8 @@ var TOOL_NAMES = [
31467
31531
  "gitingest",
31468
31532
  "retrieve_summary",
31469
31533
  "extract_code_blocks",
31470
- "phase_complete"
31534
+ "phase_complete",
31535
+ "save_plan"
31471
31536
  ];
31472
31537
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
31473
31538
 
@@ -31500,6 +31565,7 @@ var AGENT_TOOL_MAP = {
31500
31565
  "pkg_audit",
31501
31566
  "pre_check_batch",
31502
31567
  "retrieve_summary",
31568
+ "save_plan",
31503
31569
  "schema_drift",
31504
31570
  "secretscan",
31505
31571
  "symbols",
@@ -32531,10 +32597,21 @@ This briefing is a HARD REQUIREMENT for ALL phases. Skipping it is a process vio
32531
32597
 
32532
32598
  ### MODE: PLAN
32533
32599
 
32534
- Create .swarm/plan.md
32535
- - Phases with discrete tasks
32536
- - Dependencies (depends: X.Y)
32537
- - Acceptance criteria per task
32600
+ Use the \`save_plan\` tool to create the implementation plan. Required parameters:
32601
+ - \`title\`: The real project name from the spec (NOT a placeholder like [Project])
32602
+ - \`swarm_id\`: The swarm identifier (e.g. "mega", "local", "paid")
32603
+ - \`phases\`: Array of phases, each with \`id\` (number), \`name\` (string), and \`tasks\` (array)
32604
+ - Each task needs: \`id\` (e.g. "1.1"), \`description\` (real content from spec \u2014 bracket placeholders like [task] will be REJECTED)
32605
+ - Optional task fields: \`size\` (small/medium/large), \`depends\` (array of task IDs), \`acceptance\` (string)
32606
+
32607
+ Example call:
32608
+ save_plan({ title: "My Real Project", swarm_id: "mega", phases: [{ id: 1, name: "Setup", tasks: [{ id: "1.1", description: "Install dependencies and configure TypeScript", size: "small" }] }] })
32609
+
32610
+ \u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
32611
+ TASK: Write the implementation plan to .swarm/plan.md
32612
+ FILE: .swarm/plan.md
32613
+ INPUT: [provide the complete plan content below]
32614
+ CONSTRAINT: Write EXACTLY the content provided. Do not modify, summarize, or interpret.
32538
32615
 
32539
32616
  TASK GRANULARITY RULES:
32540
32617
  - SMALL task: 1 file, 1 function/class/component, 1 logical concern. Delegate as-is.
@@ -32553,8 +32630,7 @@ PHASE COUNT GUIDANCE:
32553
32630
  - Rationale: Retrospectives at phase boundaries capture lessons that improve subsequent
32554
32631
  phases. A single-phase plan gets zero iterative learning benefit.
32555
32632
 
32556
- Create .swarm/context.md
32557
- - Decisions, patterns, SME cache, file map
32633
+ Also create .swarm/context.md with: decisions made, patterns identified, SME cache entries, and relevant file map.
32558
32634
 
32559
32635
  ### MODE: CRITIC-GATE
32560
32636
  Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation begins.
@@ -32765,19 +32841,21 @@ Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
32765
32841
 
32766
32842
  ## FILES
32767
32843
 
32844
+ \u26A0\uFE0F FILE FORMAT RULES: Every value in angle brackets below MUST be real content derived from the spec or codebase analysis. NEVER write literal bracket-placeholder text like "[task]", "[Project]", "[date]", "[reason]" \u2014 those are template slots in this example, NOT values to reproduce. Status tags like [COMPLETE], [IN PROGRESS], [BLOCKED], [SMALL], [MEDIUM], [LARGE], and checkboxes [x]/[ ] are valid format elements and must be reproduced exactly.
32845
+
32768
32846
  .swarm/plan.md:
32769
32847
  \`\`\`
32770
- # [Project]
32848
+ # <real project name derived from the spec>
32771
32849
  Swarm: {{SWARM_ID}}
32772
- Phase: [N] | Updated: [date]
32850
+ Phase: <current phase number> | Updated: <today's date in ISO format>
32773
32851
 
32774
- ## Phase 1 [COMPLETE]
32775
- - [x] 1.1: [task] [SMALL]
32852
+ ## Phase 1: <descriptive phase name> [COMPLETE]
32853
+ - [x] 1.1: <specific completed task description from spec> [SMALL]
32776
32854
 
32777
- ## Phase 2 [IN PROGRESS]
32778
- - [x] 2.1: [task] [MEDIUM]
32779
- - [ ] 2.2: [task] (depends: 2.1) \u2190 CURRENT
32780
- - [BLOCKED] 2.3: [task] - [reason]
32855
+ ## Phase 2: <descriptive phase name> [IN PROGRESS]
32856
+ - [x] 2.1: <specific task description from spec> [MEDIUM]
32857
+ - [ ] 2.2: <specific task description from spec> (depends: 2.1) \u2190 CURRENT
32858
+ - [BLOCKED] 2.3: <specific task description from spec> - <reason for blockage>
32781
32859
  \`\`\`
32782
32860
 
32783
32861
  .swarm/context.md:
@@ -32786,14 +32864,14 @@ Phase: [N] | Updated: [date]
32786
32864
  Swarm: {{SWARM_ID}}
32787
32865
 
32788
32866
  ## Decisions
32789
- - [decision]: [rationale]
32867
+ - <specific technical decision made>: <rationale for the decision>
32790
32868
 
32791
32869
  ## SME Cache
32792
- ### [domain]
32793
- - [guidance]
32870
+ ### <domain name e.g. security, cross-platform>
32871
+ - <specific guidance from the SME consultation>
32794
32872
 
32795
32873
  ## Patterns
32796
- - [pattern]: [usage]
32874
+ - <pattern name>: <how and when to use it in this codebase>
32797
32875
  \`\`\``;
32798
32876
  function createArchitectAgent(model, customPrompt, customAppendPrompt) {
32799
32877
  let prompt = ARCHITECT_PROMPT;
@@ -41017,10 +41095,108 @@ var phase_complete = tool({
41017
41095
  return executePhaseComplete(phaseCompleteArgs);
41018
41096
  }
41019
41097
  });
41098
+ // src/tools/save-plan.ts
41099
+ init_tool();
41100
+ init_manager2();
41101
+ import * as path23 from "path";
41102
+ function detectPlaceholderContent(args2) {
41103
+ const issues = [];
41104
+ const placeholderPattern = /^\[\w[\w\s]*\]$/;
41105
+ if (placeholderPattern.test(args2.title.trim())) {
41106
+ issues.push(`Plan title appears to be a template placeholder: "${args2.title}"`);
41107
+ }
41108
+ for (const phase of args2.phases) {
41109
+ if (placeholderPattern.test(phase.name.trim())) {
41110
+ issues.push(`Phase ${phase.id} name appears to be a template placeholder: "${phase.name}"`);
41111
+ }
41112
+ for (const task of phase.tasks) {
41113
+ if (placeholderPattern.test(task.description.trim())) {
41114
+ issues.push(`Task ${task.id} description appears to be a template placeholder: "${task.description}"`);
41115
+ }
41116
+ }
41117
+ }
41118
+ return issues;
41119
+ }
41120
+ async function executeSavePlan(args2) {
41121
+ const placeholderIssues = detectPlaceholderContent(args2);
41122
+ if (placeholderIssues.length > 0) {
41123
+ return {
41124
+ success: false,
41125
+ message: "Plan rejected: contains template placeholder content",
41126
+ errors: placeholderIssues
41127
+ };
41128
+ }
41129
+ const plan = {
41130
+ schema_version: "1.0.0",
41131
+ title: args2.title,
41132
+ swarm: args2.swarm_id,
41133
+ migration_status: "native",
41134
+ current_phase: args2.phases[0]?.id,
41135
+ phases: args2.phases.map((phase) => {
41136
+ return {
41137
+ id: phase.id,
41138
+ name: phase.name,
41139
+ status: "pending",
41140
+ tasks: phase.tasks.map((task) => {
41141
+ return {
41142
+ id: task.id,
41143
+ phase: phase.id,
41144
+ status: "pending",
41145
+ size: task.size ?? "small",
41146
+ description: task.description,
41147
+ depends: task.depends ?? [],
41148
+ acceptance: task.acceptance,
41149
+ files_touched: []
41150
+ };
41151
+ })
41152
+ };
41153
+ })
41154
+ };
41155
+ const tasksCount = plan.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
41156
+ const dir = args2.working_directory ?? process.cwd();
41157
+ try {
41158
+ await savePlan(dir, plan);
41159
+ return {
41160
+ success: true,
41161
+ message: "Plan saved successfully",
41162
+ plan_path: path23.join(dir, ".swarm", "plan.json"),
41163
+ phases_count: plan.phases.length,
41164
+ tasks_count: tasksCount
41165
+ };
41166
+ } catch (error93) {
41167
+ return {
41168
+ success: false,
41169
+ message: "Failed to save plan",
41170
+ errors: [String(error93)]
41171
+ };
41172
+ }
41173
+ }
41174
+ var save_plan = tool({
41175
+ description: "Save a structured implementation plan to .swarm/plan.json and .swarm/plan.md. " + "Task descriptions and phase names MUST contain real content from the spec \u2014 " + "bracket placeholders like [task] or [Project] will be rejected.",
41176
+ args: {
41177
+ title: tool.schema.string().min(1).describe("Plan title \u2014 the REAL project name from the spec. NOT a placeholder like [Project]."),
41178
+ swarm_id: tool.schema.string().min(1).describe('Swarm identifier (e.g. "mega")'),
41179
+ phases: tool.schema.array(tool.schema.object({
41180
+ id: tool.schema.number().int().positive().describe("Phase number, starting at 1"),
41181
+ name: tool.schema.string().min(1).describe("Descriptive phase name derived from the spec"),
41182
+ tasks: tool.schema.array(tool.schema.object({
41183
+ 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"'),
41184
+ description: tool.schema.string().min(1).describe("Specific task description from the spec. NOT a placeholder like [task]."),
41185
+ size: tool.schema.enum(["small", "medium", "large"]).optional().describe("Task size estimate (default: small)"),
41186
+ depends: tool.schema.array(tool.schema.string()).optional().describe('Task IDs this task depends on, e.g. ["1.1", "1.2"]'),
41187
+ acceptance: tool.schema.string().optional().describe("Acceptance criteria for this task")
41188
+ })).min(1).describe("Tasks in this phase")
41189
+ })).min(1).describe("Implementation phases"),
41190
+ working_directory: tool.schema.string().optional().describe("Working directory (defaults to process.cwd())")
41191
+ },
41192
+ execute: async (args2) => {
41193
+ return JSON.stringify(await executeSavePlan(args2), null, 2);
41194
+ }
41195
+ });
41020
41196
  // src/tools/pkg-audit.ts
41021
41197
  init_dist();
41022
41198
  import * as fs18 from "fs";
41023
- import * as path23 from "path";
41199
+ import * as path24 from "path";
41024
41200
  var MAX_OUTPUT_BYTES5 = 52428800;
41025
41201
  var AUDIT_TIMEOUT_MS = 120000;
41026
41202
  function isValidEcosystem(value) {
@@ -41038,13 +41214,13 @@ function validateArgs3(args2) {
41038
41214
  function detectEcosystems() {
41039
41215
  const ecosystems = [];
41040
41216
  const cwd = process.cwd();
41041
- if (fs18.existsSync(path23.join(cwd, "package.json"))) {
41217
+ if (fs18.existsSync(path24.join(cwd, "package.json"))) {
41042
41218
  ecosystems.push("npm");
41043
41219
  }
41044
- if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
41220
+ if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
41045
41221
  ecosystems.push("pip");
41046
41222
  }
41047
- if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
41223
+ if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
41048
41224
  ecosystems.push("cargo");
41049
41225
  }
41050
41226
  return ecosystems;
@@ -42952,11 +43128,11 @@ var Module2 = (() => {
42952
43128
  throw toThrow;
42953
43129
  }, "quit_");
42954
43130
  var scriptDirectory = "";
42955
- function locateFile(path24) {
43131
+ function locateFile(path25) {
42956
43132
  if (Module["locateFile"]) {
42957
- return Module["locateFile"](path24, scriptDirectory);
43133
+ return Module["locateFile"](path25, scriptDirectory);
42958
43134
  }
42959
- return scriptDirectory + path24;
43135
+ return scriptDirectory + path25;
42960
43136
  }
42961
43137
  __name(locateFile, "locateFile");
42962
43138
  var readAsync, readBinary;
@@ -44770,7 +44946,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
44770
44946
  ]);
44771
44947
  // src/tools/pre-check-batch.ts
44772
44948
  init_dist();
44773
- import * as path26 from "path";
44949
+ import * as path27 from "path";
44774
44950
 
44775
44951
  // node_modules/yocto-queue/index.js
44776
44952
  class Node2 {
@@ -44937,7 +45113,7 @@ init_manager();
44937
45113
 
44938
45114
  // src/quality/metrics.ts
44939
45115
  import * as fs19 from "fs";
44940
- import * as path24 from "path";
45116
+ import * as path25 from "path";
44941
45117
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
44942
45118
  var MIN_DUPLICATION_LINES = 10;
44943
45119
  function estimateCyclomaticComplexity(content) {
@@ -44989,7 +45165,7 @@ async function computeComplexityDelta(files, workingDir) {
44989
45165
  let totalComplexity = 0;
44990
45166
  const analyzedFiles = [];
44991
45167
  for (const file3 of files) {
44992
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45168
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
44993
45169
  if (!fs19.existsSync(fullPath)) {
44994
45170
  continue;
44995
45171
  }
@@ -45112,7 +45288,7 @@ function countGoExports(content) {
45112
45288
  function getExportCountForFile(filePath) {
45113
45289
  try {
45114
45290
  const content = fs19.readFileSync(filePath, "utf-8");
45115
- const ext = path24.extname(filePath).toLowerCase();
45291
+ const ext = path25.extname(filePath).toLowerCase();
45116
45292
  switch (ext) {
45117
45293
  case ".ts":
45118
45294
  case ".tsx":
@@ -45138,7 +45314,7 @@ async function computePublicApiDelta(files, workingDir) {
45138
45314
  let totalExports = 0;
45139
45315
  const analyzedFiles = [];
45140
45316
  for (const file3 of files) {
45141
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45317
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45142
45318
  if (!fs19.existsSync(fullPath)) {
45143
45319
  continue;
45144
45320
  }
@@ -45172,7 +45348,7 @@ async function computeDuplicationRatio(files, workingDir) {
45172
45348
  let duplicateLines = 0;
45173
45349
  const analyzedFiles = [];
45174
45350
  for (const file3 of files) {
45175
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45351
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45176
45352
  if (!fs19.existsSync(fullPath)) {
45177
45353
  continue;
45178
45354
  }
@@ -45205,8 +45381,8 @@ function countCodeLines(content) {
45205
45381
  return lines.length;
45206
45382
  }
45207
45383
  function isTestFile(filePath) {
45208
- const basename5 = path24.basename(filePath);
45209
- const _ext = path24.extname(filePath).toLowerCase();
45384
+ const basename5 = path25.basename(filePath);
45385
+ const _ext = path25.extname(filePath).toLowerCase();
45210
45386
  const testPatterns = [
45211
45387
  ".test.",
45212
45388
  ".spec.",
@@ -45248,7 +45424,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
45248
45424
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45249
45425
  let testLines = 0;
45250
45426
  let codeLines = 0;
45251
- const srcDir = path24.join(workingDir, "src");
45427
+ const srcDir = path25.join(workingDir, "src");
45252
45428
  if (fs19.existsSync(srcDir)) {
45253
45429
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
45254
45430
  codeLines += lines;
@@ -45256,14 +45432,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45256
45432
  }
45257
45433
  const possibleSrcDirs = ["lib", "app", "source", "core"];
45258
45434
  for (const dir of possibleSrcDirs) {
45259
- const dirPath = path24.join(workingDir, dir);
45435
+ const dirPath = path25.join(workingDir, dir);
45260
45436
  if (fs19.existsSync(dirPath)) {
45261
45437
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
45262
45438
  codeLines += lines;
45263
45439
  });
45264
45440
  }
45265
45441
  }
45266
- const testsDir = path24.join(workingDir, "tests");
45442
+ const testsDir = path25.join(workingDir, "tests");
45267
45443
  if (fs19.existsSync(testsDir)) {
45268
45444
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
45269
45445
  testLines += lines;
@@ -45271,7 +45447,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45271
45447
  }
45272
45448
  const possibleTestDirs = ["test", "__tests__", "specs"];
45273
45449
  for (const dir of possibleTestDirs) {
45274
- const dirPath = path24.join(workingDir, dir);
45450
+ const dirPath = path25.join(workingDir, dir);
45275
45451
  if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
45276
45452
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
45277
45453
  testLines += lines;
@@ -45286,7 +45462,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
45286
45462
  try {
45287
45463
  const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
45288
45464
  for (const entry of entries) {
45289
- const fullPath = path24.join(dirPath, entry.name);
45465
+ const fullPath = path25.join(dirPath, entry.name);
45290
45466
  if (entry.isDirectory()) {
45291
45467
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
45292
45468
  continue;
@@ -45294,7 +45470,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
45294
45470
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
45295
45471
  } else if (entry.isFile()) {
45296
45472
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
45297
- const ext = path24.extname(entry.name).toLowerCase();
45473
+ const ext = path25.extname(entry.name).toLowerCase();
45298
45474
  const validExts = [
45299
45475
  ".ts",
45300
45476
  ".tsx",
@@ -45549,7 +45725,7 @@ async function qualityBudget(input, directory) {
45549
45725
  // src/tools/sast-scan.ts
45550
45726
  init_manager();
45551
45727
  import * as fs20 from "fs";
45552
- import * as path25 from "path";
45728
+ import * as path26 from "path";
45553
45729
  import { extname as extname7 } from "path";
45554
45730
 
45555
45731
  // src/sast/rules/c.ts
@@ -46504,7 +46680,7 @@ async function sastScan(input, directory, config3) {
46504
46680
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
46505
46681
  const filesByLanguage = new Map;
46506
46682
  for (const filePath of changed_files) {
46507
- const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
46683
+ const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
46508
46684
  if (!fs20.existsSync(resolvedPath)) {
46509
46685
  _filesSkipped++;
46510
46686
  continue;
@@ -46613,10 +46789,10 @@ function validatePath(inputPath, baseDir) {
46613
46789
  if (!inputPath || inputPath.length === 0) {
46614
46790
  return "path is required";
46615
46791
  }
46616
- const resolved = path26.resolve(baseDir, inputPath);
46617
- const baseResolved = path26.resolve(baseDir);
46618
- const relative3 = path26.relative(baseResolved, resolved);
46619
- if (relative3.startsWith("..") || path26.isAbsolute(relative3)) {
46792
+ const resolved = path27.resolve(baseDir, inputPath);
46793
+ const baseResolved = path27.resolve(baseDir);
46794
+ const relative3 = path27.relative(baseResolved, resolved);
46795
+ if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
46620
46796
  return "path traversal detected";
46621
46797
  }
46622
46798
  return null;
@@ -46738,7 +46914,7 @@ async function runPreCheckBatch(input) {
46738
46914
  warn(`pre_check_batch: Invalid file path: ${file3}`);
46739
46915
  continue;
46740
46916
  }
46741
- changedFiles.push(path26.resolve(directory, file3));
46917
+ changedFiles.push(path27.resolve(directory, file3));
46742
46918
  }
46743
46919
  } else {
46744
46920
  changedFiles = [];
@@ -46929,7 +47105,7 @@ var retrieve_summary = tool({
46929
47105
  init_dist();
46930
47106
  init_manager();
46931
47107
  import * as fs21 from "fs";
46932
- import * as path27 from "path";
47108
+ import * as path28 from "path";
46933
47109
 
46934
47110
  // src/sbom/detectors/dart.ts
46935
47111
  function parsePubspecLock(content) {
@@ -47776,7 +47952,7 @@ function findManifestFiles(rootDir) {
47776
47952
  try {
47777
47953
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47778
47954
  for (const entry of entries) {
47779
- const fullPath = path27.join(dir, entry.name);
47955
+ const fullPath = path28.join(dir, entry.name);
47780
47956
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
47781
47957
  continue;
47782
47958
  }
@@ -47786,7 +47962,7 @@ function findManifestFiles(rootDir) {
47786
47962
  for (const pattern of patterns) {
47787
47963
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47788
47964
  if (new RegExp(regex, "i").test(entry.name)) {
47789
- manifestFiles.push(path27.relative(cwd, fullPath));
47965
+ manifestFiles.push(path28.relative(cwd, fullPath));
47790
47966
  break;
47791
47967
  }
47792
47968
  }
@@ -47805,12 +47981,12 @@ function findManifestFilesInDirs(directories, workingDir) {
47805
47981
  try {
47806
47982
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47807
47983
  for (const entry of entries) {
47808
- const fullPath = path27.join(dir, entry.name);
47984
+ const fullPath = path28.join(dir, entry.name);
47809
47985
  if (entry.isFile()) {
47810
47986
  for (const pattern of patterns) {
47811
47987
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47812
47988
  if (new RegExp(regex, "i").test(entry.name)) {
47813
- found.push(path27.relative(workingDir, fullPath));
47989
+ found.push(path28.relative(workingDir, fullPath));
47814
47990
  break;
47815
47991
  }
47816
47992
  }
@@ -47823,11 +47999,11 @@ function findManifestFilesInDirs(directories, workingDir) {
47823
47999
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
47824
48000
  const dirs = new Set;
47825
48001
  for (const file3 of changedFiles) {
47826
- let currentDir = path27.dirname(file3);
48002
+ let currentDir = path28.dirname(file3);
47827
48003
  while (true) {
47828
- if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
47829
- dirs.add(path27.join(workingDir, currentDir));
47830
- const parent = path27.dirname(currentDir);
48004
+ if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
48005
+ dirs.add(path28.join(workingDir, currentDir));
48006
+ const parent = path28.dirname(currentDir);
47831
48007
  if (parent === currentDir)
47832
48008
  break;
47833
48009
  currentDir = parent;
@@ -47934,7 +48110,7 @@ var sbom_generate = tool({
47934
48110
  const processedFiles = [];
47935
48111
  for (const manifestFile of manifestFiles) {
47936
48112
  try {
47937
- const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
48113
+ const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
47938
48114
  if (!fs21.existsSync(fullPath)) {
47939
48115
  continue;
47940
48116
  }
@@ -47951,7 +48127,7 @@ var sbom_generate = tool({
47951
48127
  const bom = generateCycloneDX(allComponents);
47952
48128
  const bomJson = serializeCycloneDX(bom);
47953
48129
  const filename = generateSbomFilename();
47954
- const outputPath = path27.join(outputDir, filename);
48130
+ const outputPath = path28.join(outputDir, filename);
47955
48131
  fs21.writeFileSync(outputPath, bomJson, "utf-8");
47956
48132
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
47957
48133
  try {
@@ -47994,7 +48170,7 @@ var sbom_generate = tool({
47994
48170
  // src/tools/schema-drift.ts
47995
48171
  init_dist();
47996
48172
  import * as fs22 from "fs";
47997
- import * as path28 from "path";
48173
+ import * as path29 from "path";
47998
48174
  var SPEC_CANDIDATES = [
47999
48175
  "openapi.json",
48000
48176
  "openapi.yaml",
@@ -48026,12 +48202,12 @@ function normalizePath(p) {
48026
48202
  }
48027
48203
  function discoverSpecFile(cwd, specFileArg) {
48028
48204
  if (specFileArg) {
48029
- const resolvedPath = path28.resolve(cwd, specFileArg);
48030
- const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
48205
+ const resolvedPath = path29.resolve(cwd, specFileArg);
48206
+ const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
48031
48207
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
48032
48208
  throw new Error("Invalid spec_file: path traversal detected");
48033
48209
  }
48034
- const ext = path28.extname(resolvedPath).toLowerCase();
48210
+ const ext = path29.extname(resolvedPath).toLowerCase();
48035
48211
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
48036
48212
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
48037
48213
  }
@@ -48045,7 +48221,7 @@ function discoverSpecFile(cwd, specFileArg) {
48045
48221
  return resolvedPath;
48046
48222
  }
48047
48223
  for (const candidate of SPEC_CANDIDATES) {
48048
- const candidatePath = path28.resolve(cwd, candidate);
48224
+ const candidatePath = path29.resolve(cwd, candidate);
48049
48225
  if (fs22.existsSync(candidatePath)) {
48050
48226
  const stats = fs22.statSync(candidatePath);
48051
48227
  if (stats.size <= MAX_SPEC_SIZE) {
@@ -48057,7 +48233,7 @@ function discoverSpecFile(cwd, specFileArg) {
48057
48233
  }
48058
48234
  function parseSpec(specFile) {
48059
48235
  const content = fs22.readFileSync(specFile, "utf-8");
48060
- const ext = path28.extname(specFile).toLowerCase();
48236
+ const ext = path29.extname(specFile).toLowerCase();
48061
48237
  if (ext === ".json") {
48062
48238
  return parseJsonSpec(content);
48063
48239
  }
@@ -48128,7 +48304,7 @@ function extractRoutes(cwd) {
48128
48304
  return;
48129
48305
  }
48130
48306
  for (const entry of entries) {
48131
- const fullPath = path28.join(dir, entry.name);
48307
+ const fullPath = path29.join(dir, entry.name);
48132
48308
  if (entry.isSymbolicLink()) {
48133
48309
  continue;
48134
48310
  }
@@ -48138,7 +48314,7 @@ function extractRoutes(cwd) {
48138
48314
  }
48139
48315
  walkDir(fullPath);
48140
48316
  } else if (entry.isFile()) {
48141
- const ext = path28.extname(entry.name).toLowerCase();
48317
+ const ext = path29.extname(entry.name).toLowerCase();
48142
48318
  const baseName = entry.name.toLowerCase();
48143
48319
  if (![".ts", ".js", ".mjs"].includes(ext)) {
48144
48320
  continue;
@@ -48307,7 +48483,7 @@ init_secretscan();
48307
48483
  // src/tools/symbols.ts
48308
48484
  init_tool();
48309
48485
  import * as fs23 from "fs";
48310
- import * as path29 from "path";
48486
+ import * as path30 from "path";
48311
48487
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
48312
48488
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
48313
48489
  function containsControlCharacters(str) {
@@ -48336,11 +48512,11 @@ function containsWindowsAttacks(str) {
48336
48512
  }
48337
48513
  function isPathInWorkspace(filePath, workspace) {
48338
48514
  try {
48339
- const resolvedPath = path29.resolve(workspace, filePath);
48515
+ const resolvedPath = path30.resolve(workspace, filePath);
48340
48516
  const realWorkspace = fs23.realpathSync(workspace);
48341
48517
  const realResolvedPath = fs23.realpathSync(resolvedPath);
48342
- const relativePath = path29.relative(realWorkspace, realResolvedPath);
48343
- if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
48518
+ const relativePath = path30.relative(realWorkspace, realResolvedPath);
48519
+ if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
48344
48520
  return false;
48345
48521
  }
48346
48522
  return true;
@@ -48352,7 +48528,7 @@ function validatePathForRead(filePath, workspace) {
48352
48528
  return isPathInWorkspace(filePath, workspace);
48353
48529
  }
48354
48530
  function extractTSSymbols(filePath, cwd) {
48355
- const fullPath = path29.join(cwd, filePath);
48531
+ const fullPath = path30.join(cwd, filePath);
48356
48532
  if (!validatePathForRead(fullPath, cwd)) {
48357
48533
  return [];
48358
48534
  }
@@ -48504,7 +48680,7 @@ function extractTSSymbols(filePath, cwd) {
48504
48680
  });
48505
48681
  }
48506
48682
  function extractPythonSymbols(filePath, cwd) {
48507
- const fullPath = path29.join(cwd, filePath);
48683
+ const fullPath = path30.join(cwd, filePath);
48508
48684
  if (!validatePathForRead(fullPath, cwd)) {
48509
48685
  return [];
48510
48686
  }
@@ -48586,7 +48762,7 @@ var symbols = tool({
48586
48762
  }, null, 2);
48587
48763
  }
48588
48764
  const cwd = process.cwd();
48589
- const ext = path29.extname(file3);
48765
+ const ext = path30.extname(file3);
48590
48766
  if (containsControlCharacters(file3)) {
48591
48767
  return JSON.stringify({
48592
48768
  file: file3,
@@ -48655,7 +48831,7 @@ init_test_runner();
48655
48831
  // src/tools/todo-extract.ts
48656
48832
  init_dist();
48657
48833
  import * as fs24 from "fs";
48658
- import * as path30 from "path";
48834
+ import * as path31 from "path";
48659
48835
  var MAX_TEXT_LENGTH = 200;
48660
48836
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
48661
48837
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -48726,9 +48902,9 @@ function validatePathsInput(paths, cwd) {
48726
48902
  return { error: "paths contains path traversal", resolvedPath: null };
48727
48903
  }
48728
48904
  try {
48729
- const resolvedPath = path30.resolve(paths);
48730
- const normalizedCwd = path30.resolve(cwd);
48731
- const normalizedResolved = path30.resolve(resolvedPath);
48905
+ const resolvedPath = path31.resolve(paths);
48906
+ const normalizedCwd = path31.resolve(cwd);
48907
+ const normalizedResolved = path31.resolve(resolvedPath);
48732
48908
  if (!normalizedResolved.startsWith(normalizedCwd)) {
48733
48909
  return {
48734
48910
  error: "paths must be within the current working directory",
@@ -48744,7 +48920,7 @@ function validatePathsInput(paths, cwd) {
48744
48920
  }
48745
48921
  }
48746
48922
  function isSupportedExtension(filePath) {
48747
- const ext = path30.extname(filePath).toLowerCase();
48923
+ const ext = path31.extname(filePath).toLowerCase();
48748
48924
  return SUPPORTED_EXTENSIONS2.has(ext);
48749
48925
  }
48750
48926
  function findSourceFiles3(dir, files = []) {
@@ -48759,7 +48935,7 @@ function findSourceFiles3(dir, files = []) {
48759
48935
  if (SKIP_DIRECTORIES3.has(entry)) {
48760
48936
  continue;
48761
48937
  }
48762
- const fullPath = path30.join(dir, entry);
48938
+ const fullPath = path31.join(dir, entry);
48763
48939
  let stat;
48764
48940
  try {
48765
48941
  stat = fs24.statSync(fullPath);
@@ -48871,7 +49047,7 @@ var todo_extract = tool({
48871
49047
  filesToScan.push(scanPath);
48872
49048
  } else {
48873
49049
  const errorResult = {
48874
- error: `unsupported file extension: ${path30.extname(scanPath)}`,
49050
+ error: `unsupported file extension: ${path31.extname(scanPath)}`,
48875
49051
  total: 0,
48876
49052
  byPriority: { high: 0, medium: 0, low: 0 },
48877
49053
  entries: []
@@ -48986,7 +49162,7 @@ var OpenCodeSwarm = async (ctx) => {
48986
49162
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
48987
49163
  preflightTriggerManager = new PTM(automationConfig);
48988
49164
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
48989
- const swarmDir = path31.resolve(ctx.directory, ".swarm");
49165
+ const swarmDir = path32.resolve(ctx.directory, ".swarm");
48990
49166
  statusArtifact = new ASA(swarmDir);
48991
49167
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
48992
49168
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -49092,6 +49268,7 @@ var OpenCodeSwarm = async (ctx) => {
49092
49268
  phase_complete,
49093
49269
  pre_check_batch,
49094
49270
  retrieve_summary,
49271
+ save_plan,
49095
49272
  schema_drift,
49096
49273
  secretscan,
49097
49274
  symbols,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -9,6 +9,8 @@ export { fetchGitingest, type GitingestArgs, gitingest } from './gitingest';
9
9
  export { imports } from './imports';
10
10
  export { lint } from './lint';
11
11
  export { phase_complete } from './phase-complete';
12
+ export { save_plan } from './save-plan';
13
+ export type { SavePlanArgs, SavePlanResult } from './save-plan';
12
14
  export { pkg_audit } from './pkg-audit';
13
15
  export { type PlaceholderFinding, type PlaceholderScanInput, type PlaceholderScanResult, placeholderScan, } from './placeholder-scan';
14
16
  export { type PreCheckBatchInput, type PreCheckBatchResult, pre_check_batch, runPreCheckBatch, type ToolResult, } from './pre-check-batch';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Save plan tool for persisting validated implementation plans.
3
+ * Allows the Architect agent to save structured plans to .swarm/plan.json and .swarm/plan.md.
4
+ */
5
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
6
+ /**
7
+ * Arguments for the save_plan tool
8
+ */
9
+ export interface SavePlanArgs {
10
+ title: string;
11
+ swarm_id: string;
12
+ phases: Array<{
13
+ id: number;
14
+ name: string;
15
+ tasks: Array<{
16
+ id: string;
17
+ description: string;
18
+ size?: 'small' | 'medium' | 'large';
19
+ depends?: string[];
20
+ acceptance?: string;
21
+ }>;
22
+ }>;
23
+ working_directory?: string;
24
+ }
25
+ /**
26
+ * Result from executing save_plan
27
+ */
28
+ export interface SavePlanResult {
29
+ success: boolean;
30
+ message: string;
31
+ plan_path?: string;
32
+ phases_count?: number;
33
+ tasks_count?: number;
34
+ errors?: string[];
35
+ }
36
+ /**
37
+ * Detect template placeholder content (e.g., [task], [Project], [description], [N]).
38
+ * These patterns indicate the LLM reproduced template examples literally rather than
39
+ * filling in real content from the specification.
40
+ * @param args - The save plan arguments to validate
41
+ * @returns Array of issue strings describing found placeholders
42
+ */
43
+ export declare function detectPlaceholderContent(args: SavePlanArgs): string[];
44
+ /**
45
+ * Execute the save_plan tool.
46
+ * Validates for placeholder content, builds a Plan object, and saves to disk.
47
+ * @param args - The save plan arguments
48
+ * @returns SavePlanResult with success status and details
49
+ */
50
+ export declare function executeSavePlan(args: SavePlanArgs): Promise<SavePlanResult>;
51
+ /**
52
+ * Tool definition for save_plan
53
+ */
54
+ export declare const save_plan: ToolDefinition;
@@ -3,7 +3,7 @@
3
3
  * Used for constants and agent setup references.
4
4
  */
5
5
  /** Union type of all valid tool names */
6
- export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete';
6
+ export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan';
7
7
  /** Readonly array of all tool names */
8
8
  export declare const TOOL_NAMES: readonly ToolName[];
9
9
  /** Set for O(1) tool name validation */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.14.0",
3
+ "version": "6.14.11",
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",
@@ -55,6 +55,7 @@
55
55
  "devDependencies": {
56
56
  "@biomejs/biome": "2.3.14",
57
57
  "bun-types": "latest",
58
+ "js-yaml": "^4.1.1",
58
59
  "typescript": "^5.7.3"
59
60
  }
60
61
  }