opencode-swarm 6.14.0 → 6.14.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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);
@@ -14352,6 +14341,12 @@ function validateSwarmPath(directory, filename) {
14352
14341
  if (/\.\.[/\\]/.test(filename)) {
14353
14342
  throw new Error("Invalid filename: path traversal detected");
14354
14343
  }
14344
+ if (/^[A-Za-z]:[\\/]/.test(filename)) {
14345
+ throw new Error("Invalid filename: path escapes .swarm directory");
14346
+ }
14347
+ if (filename.startsWith("/")) {
14348
+ throw new Error("Invalid filename: path escapes .swarm directory");
14349
+ }
14355
14350
  const baseDir = path2.normalize(path2.resolve(directory, ".swarm"));
14356
14351
  const resolved = path2.normalize(path2.resolve(baseDir, filename));
14357
14352
  if (process.platform === "win32") {
@@ -14816,7 +14811,7 @@ function migrateLegacyPlan(planContent, swarmId) {
14816
14811
  }
14817
14812
  continue;
14818
14813
  }
14819
- const phaseMatch = trimmed.match(/^##\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
14814
+ const phaseMatch = trimmed.match(/^#{2,3}\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
14820
14815
  if (phaseMatch) {
14821
14816
  if (currentPhase !== null) {
14822
14817
  phases.push(currentPhase);
@@ -14884,12 +14879,87 @@ function migrateLegacyPlan(planContent, swarmId) {
14884
14879
  };
14885
14880
  currentPhase.tasks.push(task);
14886
14881
  }
14882
+ const numberedTaskMatch = trimmed.match(/^(\d+)\.\s+(.+?)(?:\s*\[(\w+)\])?$/);
14883
+ if (numberedTaskMatch && currentPhase !== null) {
14884
+ const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
14885
+ let description = numberedTaskMatch[2].trim();
14886
+ const sizeText = numberedTaskMatch[3]?.toLowerCase() || "small";
14887
+ const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
14888
+ const depends = [];
14889
+ if (dependsMatch) {
14890
+ const depsText = dependsMatch[1];
14891
+ depends.push(...depsText.split(",").map((d) => d.trim()));
14892
+ description = description.substring(0, dependsMatch.index).trim();
14893
+ }
14894
+ const sizeMap = {
14895
+ small: "small",
14896
+ medium: "medium",
14897
+ large: "large"
14898
+ };
14899
+ const task = {
14900
+ id: taskId,
14901
+ phase: currentPhase.id,
14902
+ status: "pending",
14903
+ size: sizeMap[sizeText] || "small",
14904
+ description,
14905
+ depends,
14906
+ acceptance: undefined,
14907
+ files_touched: [],
14908
+ evidence_path: undefined,
14909
+ blocked_reason: undefined
14910
+ };
14911
+ currentPhase.tasks.push(task);
14912
+ }
14913
+ const noPrefixTaskMatch = trimmed.match(/^-\s*\[([^\]]+)\]\s+(?!\d+\.\d+:)(.+?)(?:\s*\[(\w+)\])?(?:\s*-\s*(.+))?$/i);
14914
+ if (noPrefixTaskMatch && currentPhase !== null) {
14915
+ const checkbox = noPrefixTaskMatch[1].toLowerCase();
14916
+ const taskId = `${currentPhase.id}.${currentPhase.tasks.length + 1}`;
14917
+ let description = noPrefixTaskMatch[2].trim();
14918
+ const sizeText = noPrefixTaskMatch[3]?.toLowerCase() || "small";
14919
+ let blockedReason;
14920
+ const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
14921
+ const depends = [];
14922
+ if (dependsMatch) {
14923
+ const depsText = dependsMatch[1];
14924
+ depends.push(...depsText.split(",").map((d) => d.trim()));
14925
+ description = description.substring(0, dependsMatch.index).trim();
14926
+ }
14927
+ let status = "pending";
14928
+ if (checkbox === "x") {
14929
+ status = "completed";
14930
+ } else if (checkbox === "blocked") {
14931
+ status = "blocked";
14932
+ const blockedReasonMatch = noPrefixTaskMatch[4];
14933
+ if (blockedReasonMatch) {
14934
+ blockedReason = blockedReasonMatch.trim();
14935
+ }
14936
+ }
14937
+ const sizeMap = {
14938
+ small: "small",
14939
+ medium: "medium",
14940
+ large: "large"
14941
+ };
14942
+ const task = {
14943
+ id: taskId,
14944
+ phase: currentPhase.id,
14945
+ status,
14946
+ size: sizeMap[sizeText] || "small",
14947
+ description,
14948
+ depends,
14949
+ acceptance: undefined,
14950
+ files_touched: [],
14951
+ evidence_path: undefined,
14952
+ blocked_reason: blockedReason
14953
+ };
14954
+ currentPhase.tasks.push(task);
14955
+ }
14887
14956
  }
14888
14957
  if (currentPhase !== null) {
14889
14958
  phases.push(currentPhase);
14890
14959
  }
14891
14960
  let migrationStatus = "migrated";
14892
14961
  if (phases.length === 0) {
14962
+ console.warn(`migrateLegacyPlan: 0 phases parsed from ${lines.length} lines. First 3 lines: ${lines.slice(0, 3).join(" | ")}`);
14893
14963
  migrationStatus = "migration_failed";
14894
14964
  phases.push({
14895
14965
  id: 1,
@@ -31440,7 +31510,7 @@ var init_preflight_integration = __esm(() => {
31440
31510
  });
31441
31511
 
31442
31512
  // src/index.ts
31443
- import * as path31 from "path";
31513
+ import * as path32 from "path";
31444
31514
 
31445
31515
  // src/tools/tool-names.ts
31446
31516
  var TOOL_NAMES = [
@@ -31467,7 +31537,8 @@ var TOOL_NAMES = [
31467
31537
  "gitingest",
31468
31538
  "retrieve_summary",
31469
31539
  "extract_code_blocks",
31470
- "phase_complete"
31540
+ "phase_complete",
31541
+ "save_plan"
31471
31542
  ];
31472
31543
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
31473
31544
 
@@ -31500,6 +31571,7 @@ var AGENT_TOOL_MAP = {
31500
31571
  "pkg_audit",
31501
31572
  "pre_check_batch",
31502
31573
  "retrieve_summary",
31574
+ "save_plan",
31503
31575
  "schema_drift",
31504
31576
  "secretscan",
31505
31577
  "symbols",
@@ -31728,7 +31800,14 @@ var ContextBudgetConfigSchema = exports_external.object({
31728
31800
  critical_threshold: exports_external.number().min(0).max(1).default(0.9),
31729
31801
  model_limits: exports_external.record(exports_external.string(), exports_external.number().min(1000)).default({ default: 128000 }),
31730
31802
  max_injection_tokens: exports_external.number().min(100).max(50000).default(4000),
31731
- scoring: ScoringConfigSchema.optional()
31803
+ tracked_agents: exports_external.array(exports_external.string()).default(["architect"]),
31804
+ scoring: ScoringConfigSchema.optional(),
31805
+ enforce: exports_external.boolean().default(true),
31806
+ prune_target: exports_external.number().min(0).max(1).default(0.7),
31807
+ preserve_last_n_turns: exports_external.number().min(0).max(100).default(4),
31808
+ recent_window: exports_external.number().min(1).max(100).default(10),
31809
+ enforce_on_agent_switch: exports_external.boolean().default(true),
31810
+ tool_output_mask_threshold: exports_external.number().min(100).max(1e5).default(2000)
31732
31811
  });
31733
31812
  var EvidenceConfigSchema = exports_external.object({
31734
31813
  enabled: exports_external.boolean().default(true),
@@ -32531,10 +32610,21 @@ This briefing is a HARD REQUIREMENT for ALL phases. Skipping it is a process vio
32531
32610
 
32532
32611
  ### MODE: PLAN
32533
32612
 
32534
- Create .swarm/plan.md
32535
- - Phases with discrete tasks
32536
- - Dependencies (depends: X.Y)
32537
- - Acceptance criteria per task
32613
+ Use the \`save_plan\` tool to create the implementation plan. Required parameters:
32614
+ - \`title\`: The real project name from the spec (NOT a placeholder like [Project])
32615
+ - \`swarm_id\`: The swarm identifier (e.g. "mega", "local", "paid")
32616
+ - \`phases\`: Array of phases, each with \`id\` (number), \`name\` (string), and \`tasks\` (array)
32617
+ - Each task needs: \`id\` (e.g. "1.1"), \`description\` (real content from spec \u2014 bracket placeholders like [task] will be REJECTED)
32618
+ - Optional task fields: \`size\` (small/medium/large), \`depends\` (array of task IDs), \`acceptance\` (string)
32619
+
32620
+ Example call:
32621
+ 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" }] }] })
32622
+
32623
+ \u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
32624
+ TASK: Write the implementation plan to .swarm/plan.md
32625
+ FILE: .swarm/plan.md
32626
+ INPUT: [provide the complete plan content below]
32627
+ CONSTRAINT: Write EXACTLY the content provided. Do not modify, summarize, or interpret.
32538
32628
 
32539
32629
  TASK GRANULARITY RULES:
32540
32630
  - SMALL task: 1 file, 1 function/class/component, 1 logical concern. Delegate as-is.
@@ -32553,8 +32643,7 @@ PHASE COUNT GUIDANCE:
32553
32643
  - Rationale: Retrospectives at phase boundaries capture lessons that improve subsequent
32554
32644
  phases. A single-phase plan gets zero iterative learning benefit.
32555
32645
 
32556
- Create .swarm/context.md
32557
- - Decisions, patterns, SME cache, file map
32646
+ Also create .swarm/context.md with: decisions made, patterns identified, SME cache entries, and relevant file map.
32558
32647
 
32559
32648
  ### MODE: CRITIC-GATE
32560
32649
  Delegate plan to {{AGENT_PREFIX}}critic for review BEFORE any implementation begins.
@@ -32765,19 +32854,21 @@ Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
32765
32854
 
32766
32855
  ## FILES
32767
32856
 
32857
+ \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.
32858
+
32768
32859
  .swarm/plan.md:
32769
32860
  \`\`\`
32770
- # [Project]
32861
+ # <real project name derived from the spec>
32771
32862
  Swarm: {{SWARM_ID}}
32772
- Phase: [N] | Updated: [date]
32863
+ Phase: <current phase number> | Updated: <today's date in ISO format>
32773
32864
 
32774
- ## Phase 1 [COMPLETE]
32775
- - [x] 1.1: [task] [SMALL]
32865
+ ## Phase 1: <descriptive phase name> [COMPLETE]
32866
+ - [x] 1.1: <specific completed task description from spec> [SMALL]
32776
32867
 
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]
32868
+ ## Phase 2: <descriptive phase name> [IN PROGRESS]
32869
+ - [x] 2.1: <specific task description from spec> [MEDIUM]
32870
+ - [ ] 2.2: <specific task description from spec> (depends: 2.1) \u2190 CURRENT
32871
+ - [BLOCKED] 2.3: <specific task description from spec> - <reason for blockage>
32781
32872
  \`\`\`
32782
32873
 
32783
32874
  .swarm/context.md:
@@ -32786,14 +32877,14 @@ Phase: [N] | Updated: [date]
32786
32877
  Swarm: {{SWARM_ID}}
32787
32878
 
32788
32879
  ## Decisions
32789
- - [decision]: [rationale]
32880
+ - <specific technical decision made>: <rationale for the decision>
32790
32881
 
32791
32882
  ## SME Cache
32792
- ### [domain]
32793
- - [guidance]
32883
+ ### <domain name e.g. security, cross-platform>
32884
+ - <specific guidance from the SME consultation>
32794
32885
 
32795
32886
  ## Patterns
32796
- - [pattern]: [usage]
32887
+ - <pattern name>: <how and when to use it in this codebase>
32797
32888
  \`\`\``;
32798
32889
  function createArchitectAgent(model, customPrompt, customAppendPrompt) {
32799
32890
  let prompt = ARCHITECT_PROMPT;
@@ -36620,8 +36711,232 @@ function createCompactionCustomizerHook(config3, directory) {
36620
36711
  })
36621
36712
  };
36622
36713
  }
36714
+ // src/hooks/context-budget.ts
36715
+ init_utils();
36716
+
36717
+ // src/hooks/message-priority.ts
36718
+ var MessagePriority = {
36719
+ CRITICAL: 0,
36720
+ HIGH: 1,
36721
+ MEDIUM: 2,
36722
+ LOW: 3,
36723
+ DISPOSABLE: 4
36724
+ };
36725
+ function containsPlanContent(text) {
36726
+ if (!text)
36727
+ return false;
36728
+ const lowerText = text.toLowerCase();
36729
+ return lowerText.includes(".swarm/plan") || lowerText.includes(".swarm/context") || lowerText.includes("swarm/plan.md") || lowerText.includes("swarm/context.md");
36730
+ }
36731
+ function isToolResult(message) {
36732
+ if (!message?.info)
36733
+ return false;
36734
+ const role = message.info.role;
36735
+ const toolName = message.info.toolName;
36736
+ return role === "assistant" && !!toolName;
36737
+ }
36738
+ function isDuplicateToolRead(current, previous) {
36739
+ if (!current?.info || !previous?.info)
36740
+ return false;
36741
+ const currentTool = current.info.toolName;
36742
+ const previousTool = previous.info.toolName;
36743
+ if (currentTool !== previousTool)
36744
+ return false;
36745
+ const isReadTool = currentTool?.toLowerCase().includes("read") && previousTool?.toLowerCase().includes("read");
36746
+ if (!isReadTool)
36747
+ return false;
36748
+ const currentArgs = current.info.toolArgs;
36749
+ const previousArgs = previous.info.toolArgs;
36750
+ if (!currentArgs || !previousArgs)
36751
+ return false;
36752
+ const currentKeys = Object.keys(currentArgs);
36753
+ const previousKeys = Object.keys(previousArgs);
36754
+ if (currentKeys.length === 0 || previousKeys.length === 0)
36755
+ return false;
36756
+ const firstKey = currentKeys[0];
36757
+ return currentArgs[firstKey] === previousArgs[firstKey];
36758
+ }
36759
+ function isStaleError(text, turnsAgo) {
36760
+ if (!text)
36761
+ return false;
36762
+ if (turnsAgo <= 6)
36763
+ return false;
36764
+ const lowerText = text.toLowerCase();
36765
+ const errorPatterns = [
36766
+ "error:",
36767
+ "failed to",
36768
+ "could not",
36769
+ "unable to",
36770
+ "exception",
36771
+ "errno",
36772
+ "cannot read",
36773
+ "not found",
36774
+ "access denied",
36775
+ "timeout"
36776
+ ];
36777
+ return errorPatterns.some((pattern) => lowerText.includes(pattern));
36778
+ }
36779
+ function extractMessageText(message) {
36780
+ if (!message?.parts || message.parts.length === 0)
36781
+ return "";
36782
+ return message.parts.map((part) => part?.text || "").join("");
36783
+ }
36784
+ function classifyMessage(message, index, totalMessages, recentWindowSize = 10) {
36785
+ const role = message?.info?.role;
36786
+ const text = extractMessageText(message);
36787
+ if (containsPlanContent(text)) {
36788
+ return MessagePriority.CRITICAL;
36789
+ }
36790
+ if (role === "system") {
36791
+ return MessagePriority.CRITICAL;
36792
+ }
36793
+ if (role === "user") {
36794
+ return MessagePriority.HIGH;
36795
+ }
36796
+ if (isToolResult(message)) {
36797
+ const positionFromEnd = totalMessages - 1 - index;
36798
+ if (positionFromEnd < recentWindowSize) {
36799
+ return MessagePriority.MEDIUM;
36800
+ }
36801
+ if (isStaleError(text, positionFromEnd)) {
36802
+ return MessagePriority.DISPOSABLE;
36803
+ }
36804
+ return MessagePriority.LOW;
36805
+ }
36806
+ if (role === "assistant") {
36807
+ const positionFromEnd = totalMessages - 1 - index;
36808
+ if (positionFromEnd < recentWindowSize) {
36809
+ return MessagePriority.MEDIUM;
36810
+ }
36811
+ if (isStaleError(text, positionFromEnd)) {
36812
+ return MessagePriority.DISPOSABLE;
36813
+ }
36814
+ return MessagePriority.LOW;
36815
+ }
36816
+ return MessagePriority.LOW;
36817
+ }
36818
+ function classifyMessages(messages, recentWindowSize = 10) {
36819
+ const results = [];
36820
+ const totalMessages = messages.length;
36821
+ for (let i2 = 0;i2 < messages.length; i2++) {
36822
+ const message = messages[i2];
36823
+ const priority = classifyMessage(message, i2, totalMessages, recentWindowSize);
36824
+ if (i2 > 0) {
36825
+ const current = messages[i2];
36826
+ const previous = messages[i2 - 1];
36827
+ if (isDuplicateToolRead(current, previous)) {
36828
+ if (results[i2 - 1] >= MessagePriority.MEDIUM) {
36829
+ results[i2 - 1] = MessagePriority.DISPOSABLE;
36830
+ }
36831
+ }
36832
+ }
36833
+ results.push(priority);
36834
+ }
36835
+ return results;
36836
+ }
36837
+
36838
+ // src/hooks/model-limits.ts
36839
+ init_utils();
36840
+ var NATIVE_MODEL_LIMITS = {
36841
+ "claude-sonnet-4": 200000,
36842
+ "claude-opus-4": 200000,
36843
+ "claude-haiku-4": 200000,
36844
+ "gpt-5": 400000,
36845
+ "gpt-5.1-codex": 400000,
36846
+ "gpt-5.1": 264000,
36847
+ "gpt-4.1": 1047576,
36848
+ "gemini-2.5-pro": 1048576,
36849
+ "gemini-2.5-flash": 1048576,
36850
+ o3: 200000,
36851
+ "o4-mini": 200000,
36852
+ "deepseek-r1": 163840,
36853
+ "deepseek-chat": 163840,
36854
+ "qwen3.5": 131072
36855
+ };
36856
+ var PROVIDER_CAPS = {
36857
+ copilot: 128000,
36858
+ "github-copilot": 128000
36859
+ };
36860
+ function extractModelInfo(messages) {
36861
+ if (!messages || messages.length === 0) {
36862
+ return {};
36863
+ }
36864
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
36865
+ const message = messages[i2];
36866
+ if (!message?.info)
36867
+ continue;
36868
+ if (message.info.role === "assistant") {
36869
+ const modelID = message.info.modelID;
36870
+ const providerID = message.info.providerID;
36871
+ if (modelID || providerID) {
36872
+ return {
36873
+ ...modelID ? { modelID } : {},
36874
+ ...providerID ? { providerID } : {}
36875
+ };
36876
+ }
36877
+ }
36878
+ }
36879
+ return {};
36880
+ }
36881
+ var loggedFirstCalls = new Set;
36882
+ function resolveModelLimit(modelID, providerID, configOverrides = {}) {
36883
+ const normalizedModelID = modelID ?? "";
36884
+ const normalizedProviderID = providerID ?? "";
36885
+ if (normalizedProviderID && normalizedModelID) {
36886
+ const providerModelKey = `${normalizedProviderID}/${normalizedModelID}`;
36887
+ if (configOverrides[providerModelKey] !== undefined) {
36888
+ logFirstCall(normalizedModelID, normalizedProviderID, "override(provider/model)", configOverrides[providerModelKey]);
36889
+ return configOverrides[providerModelKey];
36890
+ }
36891
+ }
36892
+ if (normalizedModelID && configOverrides[normalizedModelID] !== undefined) {
36893
+ logFirstCall(normalizedModelID, normalizedProviderID, "override(model)", configOverrides[normalizedModelID]);
36894
+ return configOverrides[normalizedModelID];
36895
+ }
36896
+ if (normalizedProviderID && PROVIDER_CAPS[normalizedProviderID] !== undefined) {
36897
+ const cap = PROVIDER_CAPS[normalizedProviderID];
36898
+ logFirstCall(normalizedModelID, normalizedProviderID, "provider_cap", cap);
36899
+ return cap;
36900
+ }
36901
+ if (normalizedModelID) {
36902
+ const matchedLimit = findNativeLimit(normalizedModelID);
36903
+ if (matchedLimit !== undefined) {
36904
+ logFirstCall(normalizedModelID, normalizedProviderID, "native", matchedLimit);
36905
+ return matchedLimit;
36906
+ }
36907
+ }
36908
+ if (configOverrides.default !== undefined) {
36909
+ logFirstCall(normalizedModelID, normalizedProviderID, "default_override", configOverrides.default);
36910
+ return configOverrides.default;
36911
+ }
36912
+ logFirstCall(normalizedModelID, normalizedProviderID, "fallback", 128000);
36913
+ return 128000;
36914
+ }
36915
+ function findNativeLimit(modelID) {
36916
+ if (NATIVE_MODEL_LIMITS[modelID] !== undefined) {
36917
+ return NATIVE_MODEL_LIMITS[modelID];
36918
+ }
36919
+ let bestMatch;
36920
+ for (const key of Object.keys(NATIVE_MODEL_LIMITS)) {
36921
+ if (modelID.startsWith(key)) {
36922
+ if (!bestMatch || key.length > bestMatch.length) {
36923
+ bestMatch = key;
36924
+ }
36925
+ }
36926
+ }
36927
+ return bestMatch ? NATIVE_MODEL_LIMITS[bestMatch] : undefined;
36928
+ }
36929
+ function logFirstCall(modelID, providerID, source, limit) {
36930
+ const key = `${modelID || "unknown"}::${providerID || "unknown"}`;
36931
+ if (!loggedFirstCalls.has(key)) {
36932
+ loggedFirstCalls.add(key);
36933
+ warn(`[model-limits] Resolved limit for ${modelID || "(no model)"}@${providerID || "(no provider)"}: ${limit} (source: ${source})`);
36934
+ }
36935
+ }
36936
+
36623
36937
  // src/hooks/context-budget.ts
36624
36938
  init_utils2();
36939
+ var lastSeenAgent;
36625
36940
  function createContextBudgetHandler(config3) {
36626
36941
  const enabled = config3.context_budget?.enabled !== false;
36627
36942
  if (!enabled) {
@@ -36629,14 +36944,19 @@ function createContextBudgetHandler(config3) {
36629
36944
  }
36630
36945
  const warnThreshold = config3.context_budget?.warn_threshold ?? 0.7;
36631
36946
  const criticalThreshold = config3.context_budget?.critical_threshold ?? 0.9;
36632
- const modelLimits = config3.context_budget?.model_limits ?? {
36633
- default: 128000
36634
- };
36635
- const modelLimit = modelLimits.default ?? 128000;
36636
- return async (_input, output) => {
36947
+ const modelLimitsConfig = config3.context_budget?.model_limits ?? {};
36948
+ const loggedLimits = new Set;
36949
+ const handler = async (_input, output) => {
36637
36950
  const messages = output?.messages;
36638
36951
  if (!messages || messages.length === 0)
36639
36952
  return;
36953
+ const { modelID, providerID } = extractModelInfo(messages);
36954
+ const modelLimit = resolveModelLimit(modelID, providerID, modelLimitsConfig);
36955
+ const cacheKey = `${modelID || "unknown"}::${providerID || "unknown"}`;
36956
+ if (!loggedLimits.has(cacheKey)) {
36957
+ loggedLimits.add(cacheKey);
36958
+ warn(`[swarm] Context budget: model=${modelID || "unknown"} provider=${providerID || "unknown"} limit=${modelLimit}`);
36959
+ }
36640
36960
  let totalTokens = 0;
36641
36961
  for (const message of messages) {
36642
36962
  if (!message?.parts)
@@ -36648,6 +36968,79 @@ function createContextBudgetHandler(config3) {
36648
36968
  }
36649
36969
  }
36650
36970
  const usagePercent = totalTokens / modelLimit;
36971
+ let baseAgent;
36972
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
36973
+ const msg = messages[i2];
36974
+ if (msg?.info?.role === "user" && msg?.info?.agent) {
36975
+ baseAgent = stripKnownSwarmPrefix(msg.info.agent);
36976
+ break;
36977
+ }
36978
+ }
36979
+ let ratio = usagePercent;
36980
+ if (lastSeenAgent !== undefined && baseAgent !== undefined && baseAgent !== lastSeenAgent) {
36981
+ const enforceOnSwitch = config3.context_budget?.enforce_on_agent_switch ?? true;
36982
+ if (enforceOnSwitch && usagePercent > (config3.context_budget?.warn_threshold ?? 0.7)) {
36983
+ warn(`[swarm] Agent switch detected: ${lastSeenAgent} \u2192 ${baseAgent}, enforcing context budget`, {
36984
+ from: lastSeenAgent,
36985
+ to: baseAgent
36986
+ });
36987
+ ratio = 1;
36988
+ }
36989
+ }
36990
+ lastSeenAgent = baseAgent;
36991
+ if (ratio >= criticalThreshold) {
36992
+ const enforce = config3.context_budget?.enforce ?? true;
36993
+ if (enforce) {
36994
+ const targetTokens = modelLimit * (config3.context_budget?.prune_target ?? 0.7);
36995
+ const recentWindow = config3.context_budget?.recent_window ?? 10;
36996
+ const priorities = classifyMessages(output.messages || [], recentWindow);
36997
+ const toolMaskThreshold = config3.context_budget?.tool_output_mask_threshold ?? 2000;
36998
+ let toolMaskFreedTokens = 0;
36999
+ const maskedIndices = new Set;
37000
+ for (let i2 = 0;i2 < (output.messages || []).length; i2++) {
37001
+ const msg = (output.messages || [])[i2];
37002
+ if (shouldMaskToolOutput(msg, i2, (output.messages || []).length, recentWindow, toolMaskThreshold)) {
37003
+ toolMaskFreedTokens += maskToolOutput(msg, toolMaskThreshold);
37004
+ maskedIndices.add(i2);
37005
+ }
37006
+ }
37007
+ if (toolMaskFreedTokens > 0) {
37008
+ totalTokens -= toolMaskFreedTokens;
37009
+ warn(`[swarm] Tool output masking: masked ${maskedIndices.size} tool results, freed ~${toolMaskFreedTokens} tokens`, {
37010
+ maskedCount: maskedIndices.size,
37011
+ freedTokens: toolMaskFreedTokens
37012
+ });
37013
+ }
37014
+ const preserveLastNTurns = config3.context_budget?.preserve_last_n_turns ?? 4;
37015
+ const removableMessages = identifyRemovableMessages(output.messages || [], priorities, preserveLastNTurns);
37016
+ let freedTokens = 0;
37017
+ const toRemove = new Set;
37018
+ for (const idx of removableMessages) {
37019
+ if (totalTokens - freedTokens <= targetTokens)
37020
+ break;
37021
+ toRemove.add(idx);
37022
+ freedTokens += estimateTokens(extractMessageText2(output.messages[idx]));
37023
+ }
37024
+ const beforeTokens = totalTokens;
37025
+ if (toRemove.size > 0) {
37026
+ const actualFreedTokens = applyObservationMasking(output.messages || [], toRemove);
37027
+ totalTokens -= actualFreedTokens;
37028
+ warn(`[swarm] Context enforcement: pruned ${toRemove.size} messages, freed ${actualFreedTokens} tokens (${beforeTokens}\u2192${totalTokens} of ${modelLimit})`, {
37029
+ pruned: toRemove.size,
37030
+ freedTokens: actualFreedTokens,
37031
+ before: beforeTokens,
37032
+ after: totalTokens,
37033
+ limit: modelLimit
37034
+ });
37035
+ } else if (removableMessages.length === 0 && totalTokens > targetTokens) {
37036
+ warn(`[swarm] Context enforcement: no removable messages found but still ${totalTokens} tokens (target: ${targetTokens})`, {
37037
+ currentTokens: totalTokens,
37038
+ targetTokens,
37039
+ limit: modelLimit
37040
+ });
37041
+ }
37042
+ }
37043
+ }
36651
37044
  let lastUserMessageIndex = -1;
36652
37045
  for (let i2 = messages.length - 1;i2 >= 0; i2--) {
36653
37046
  if (messages[i2]?.info?.role === "user") {
@@ -36660,8 +37053,10 @@ function createContextBudgetHandler(config3) {
36660
37053
  const lastUserMessage = messages[lastUserMessageIndex];
36661
37054
  if (!lastUserMessage?.parts)
36662
37055
  return;
36663
- const agent = lastUserMessage.info?.agent;
36664
- if (agent && agent !== "architect")
37056
+ const trackedAgents = config3.context_budget?.tracked_agents ?? [
37057
+ "architect"
37058
+ ];
37059
+ if (baseAgent && !trackedAgents.includes(baseAgent))
36665
37060
  return;
36666
37061
  const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
36667
37062
  if (textPartIndex === -1)
@@ -36682,6 +37077,110 @@ function createContextBudgetHandler(config3) {
36682
37077
  lastUserMessage.parts[textPartIndex].text = `${warningText}${originalText}`;
36683
37078
  }
36684
37079
  };
37080
+ return handler;
37081
+ }
37082
+ function identifyRemovableMessages(messages, priorities, preserveLastNTurns) {
37083
+ let turnCount = 0;
37084
+ const protectedIndices = new Set;
37085
+ for (let i2 = messages.length - 1;i2 >= 0 && turnCount < preserveLastNTurns * 2; i2--) {
37086
+ const role = messages[i2]?.info?.role;
37087
+ if (role === "user" || role === "assistant") {
37088
+ protectedIndices.add(i2);
37089
+ if (role === "user")
37090
+ turnCount++;
37091
+ }
37092
+ }
37093
+ let lastUserIdx = -1;
37094
+ let lastAssistantIdx = -1;
37095
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
37096
+ const role = messages[i2]?.info?.role;
37097
+ if (role === "user" && lastUserIdx === -1) {
37098
+ lastUserIdx = i2;
37099
+ }
37100
+ if (role === "assistant" && lastAssistantIdx === -1) {
37101
+ lastAssistantIdx = i2;
37102
+ }
37103
+ if (lastUserIdx !== -1 && lastAssistantIdx !== -1)
37104
+ break;
37105
+ }
37106
+ if (lastUserIdx !== -1)
37107
+ protectedIndices.add(lastUserIdx);
37108
+ if (lastAssistantIdx !== -1)
37109
+ protectedIndices.add(lastAssistantIdx);
37110
+ const HIGH = MessagePriority.HIGH;
37111
+ const MEDIUM = MessagePriority.MEDIUM;
37112
+ const LOW = MessagePriority.LOW;
37113
+ const DISPOSABLE = MessagePriority.DISPOSABLE;
37114
+ const byPriority = [[], [], [], [], []];
37115
+ for (let i2 = 0;i2 < priorities.length; i2++) {
37116
+ const priority = priorities[i2];
37117
+ if (!protectedIndices.has(i2) && priority > HIGH) {
37118
+ byPriority[priority].push(i2);
37119
+ }
37120
+ }
37121
+ return [...byPriority[DISPOSABLE], ...byPriority[LOW], ...byPriority[MEDIUM]];
37122
+ }
37123
+ function applyObservationMasking(messages, toRemove) {
37124
+ let actualFreedTokens = 0;
37125
+ for (const idx of toRemove) {
37126
+ const msg = messages[idx];
37127
+ if (msg?.parts) {
37128
+ for (const part of msg.parts) {
37129
+ if (part.type === "text" && part.text) {
37130
+ const originalTokens = estimateTokens(part.text);
37131
+ const placeholder = `[Context pruned \u2014 message from turn ${idx}, ~${originalTokens} tokens freed. Use retrieve_summary if needed.]`;
37132
+ const maskedTokens = estimateTokens(placeholder);
37133
+ part.text = placeholder;
37134
+ actualFreedTokens += originalTokens - maskedTokens;
37135
+ }
37136
+ }
37137
+ }
37138
+ }
37139
+ return actualFreedTokens;
37140
+ }
37141
+ function extractMessageText2(msg) {
37142
+ if (!msg?.parts)
37143
+ return "";
37144
+ return msg.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
37145
+ `);
37146
+ }
37147
+ function extractToolName(text) {
37148
+ const match = text.match(/^(read_file|write|edit|apply_patch|task|bun|npm|git|bash|glob|grep|mkdir|cp|mv|rm)\b/i);
37149
+ return match?.[1];
37150
+ }
37151
+ function shouldMaskToolOutput(msg, index, totalMessages, recentWindowSize, threshold) {
37152
+ if (!isToolResult(msg))
37153
+ return false;
37154
+ const text = extractMessageText2(msg);
37155
+ if (text.includes("[Tool output masked") || text.includes("[Context pruned")) {
37156
+ return false;
37157
+ }
37158
+ const toolName = extractToolName(text);
37159
+ if (toolName && ["retrieve_summary", "task"].includes(toolName.toLowerCase())) {
37160
+ return false;
37161
+ }
37162
+ const age = totalMessages - 1 - index;
37163
+ return age > recentWindowSize || text.length > threshold;
37164
+ }
37165
+ function maskToolOutput(msg, threshold) {
37166
+ if (!msg?.parts)
37167
+ return 0;
37168
+ let freedTokens = 0;
37169
+ for (const part of msg.parts) {
37170
+ if (part.type === "text" && part.text) {
37171
+ if (part.text.includes("[Tool output masked") || part.text.includes("[Context pruned")) {
37172
+ continue;
37173
+ }
37174
+ const originalTokens = estimateTokens(part.text);
37175
+ const toolName = extractToolName(part.text) || "unknown";
37176
+ const excerpt = part.text.substring(0, 200).replace(/\n/g, " ");
37177
+ const placeholder = `[Tool output masked \u2014 ${toolName} returned ~${originalTokens} tokens. First 200 chars: "${excerpt}..." Use retrieve_summary if needed.]`;
37178
+ const maskedTokens = estimateTokens(placeholder);
37179
+ part.text = placeholder;
37180
+ freedTokens += originalTokens - maskedTokens;
37181
+ }
37182
+ }
37183
+ return freedTokens;
36685
37184
  }
36686
37185
  // src/hooks/delegation-gate.ts
36687
37186
  function extractTaskLine(text) {
@@ -36910,6 +37409,12 @@ function isSourceCodePath(filePath) {
36910
37409
  ];
36911
37410
  return !nonSourcePatterns.some((pattern) => pattern.test(normalized));
36912
37411
  }
37412
+ function hasTraversalSegments(filePath) {
37413
+ if (!filePath)
37414
+ return false;
37415
+ const normalized = filePath.replace(/\\/g, "/");
37416
+ return normalized.startsWith("..") || normalized.includes("/../") || normalized.endsWith("/..");
37417
+ }
36913
37418
  function isGateTool(toolName) {
36914
37419
  const normalized = toolName.replace(/^[^:]+[:.]/, "");
36915
37420
  const gateTools = [
@@ -36952,10 +37457,43 @@ function createGuardrailsHooks(config3) {
36952
37457
  const inputArgsByCallID = new Map;
36953
37458
  return {
36954
37459
  toolBefore: async (input, output) => {
36955
- if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
37460
+ const currentSession = swarmState.agentSessions.get(input.sessionID);
37461
+ if (currentSession?.delegationActive) {} else if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
36956
37462
  const args2 = output.args;
36957
37463
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
36958
- if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath) && isSourceCodePath(targetPath)) {
37464
+ if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
37465
+ const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
37466
+ if (typeof patchText === "string") {
37467
+ const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
37468
+ const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
37469
+ const paths = new Set;
37470
+ let match;
37471
+ while ((match = patchPathPattern.exec(patchText)) !== null) {
37472
+ paths.add(match[1].trim());
37473
+ }
37474
+ while ((match = diffPathPattern.exec(patchText)) !== null) {
37475
+ const p = match[1].trim();
37476
+ if (p !== "/dev/null")
37477
+ paths.add(p);
37478
+ }
37479
+ for (const p of paths) {
37480
+ if (isOutsideSwarmDir(p) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
37481
+ const session2 = swarmState.agentSessions.get(input.sessionID);
37482
+ if (session2) {
37483
+ session2.architectWriteCount++;
37484
+ warn("Architect direct code edit detected via apply_patch", {
37485
+ tool: input.tool,
37486
+ sessionID: input.sessionID,
37487
+ targetPath: p,
37488
+ writeCount: session2.architectWriteCount
37489
+ });
37490
+ }
37491
+ break;
37492
+ }
37493
+ }
37494
+ }
37495
+ }
37496
+ if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath) && (isSourceCodePath(targetPath) || hasTraversalSegments(targetPath))) {
36959
37497
  const session2 = swarmState.agentSessions.get(input.sessionID);
36960
37498
  if (session2) {
36961
37499
  session2.architectWriteCount++;
@@ -41017,10 +41555,108 @@ var phase_complete = tool({
41017
41555
  return executePhaseComplete(phaseCompleteArgs);
41018
41556
  }
41019
41557
  });
41558
+ // src/tools/save-plan.ts
41559
+ init_tool();
41560
+ init_manager2();
41561
+ import * as path23 from "path";
41562
+ function detectPlaceholderContent(args2) {
41563
+ const issues = [];
41564
+ const placeholderPattern = /^\[\w[\w\s]*\]$/;
41565
+ if (placeholderPattern.test(args2.title.trim())) {
41566
+ issues.push(`Plan title appears to be a template placeholder: "${args2.title}"`);
41567
+ }
41568
+ for (const phase of args2.phases) {
41569
+ if (placeholderPattern.test(phase.name.trim())) {
41570
+ issues.push(`Phase ${phase.id} name appears to be a template placeholder: "${phase.name}"`);
41571
+ }
41572
+ for (const task of phase.tasks) {
41573
+ if (placeholderPattern.test(task.description.trim())) {
41574
+ issues.push(`Task ${task.id} description appears to be a template placeholder: "${task.description}"`);
41575
+ }
41576
+ }
41577
+ }
41578
+ return issues;
41579
+ }
41580
+ async function executeSavePlan(args2) {
41581
+ const placeholderIssues = detectPlaceholderContent(args2);
41582
+ if (placeholderIssues.length > 0) {
41583
+ return {
41584
+ success: false,
41585
+ message: "Plan rejected: contains template placeholder content",
41586
+ errors: placeholderIssues
41587
+ };
41588
+ }
41589
+ const plan = {
41590
+ schema_version: "1.0.0",
41591
+ title: args2.title,
41592
+ swarm: args2.swarm_id,
41593
+ migration_status: "native",
41594
+ current_phase: args2.phases[0]?.id,
41595
+ phases: args2.phases.map((phase) => {
41596
+ return {
41597
+ id: phase.id,
41598
+ name: phase.name,
41599
+ status: "pending",
41600
+ tasks: phase.tasks.map((task) => {
41601
+ return {
41602
+ id: task.id,
41603
+ phase: phase.id,
41604
+ status: "pending",
41605
+ size: task.size ?? "small",
41606
+ description: task.description,
41607
+ depends: task.depends ?? [],
41608
+ acceptance: task.acceptance,
41609
+ files_touched: []
41610
+ };
41611
+ })
41612
+ };
41613
+ })
41614
+ };
41615
+ const tasksCount = plan.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
41616
+ const dir = args2.working_directory ?? process.cwd();
41617
+ try {
41618
+ await savePlan(dir, plan);
41619
+ return {
41620
+ success: true,
41621
+ message: "Plan saved successfully",
41622
+ plan_path: path23.join(dir, ".swarm", "plan.json"),
41623
+ phases_count: plan.phases.length,
41624
+ tasks_count: tasksCount
41625
+ };
41626
+ } catch (error93) {
41627
+ return {
41628
+ success: false,
41629
+ message: "Failed to save plan",
41630
+ errors: [String(error93)]
41631
+ };
41632
+ }
41633
+ }
41634
+ var save_plan = tool({
41635
+ 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.",
41636
+ args: {
41637
+ title: tool.schema.string().min(1).describe("Plan title \u2014 the REAL project name from the spec. NOT a placeholder like [Project]."),
41638
+ swarm_id: tool.schema.string().min(1).describe('Swarm identifier (e.g. "mega")'),
41639
+ phases: tool.schema.array(tool.schema.object({
41640
+ id: tool.schema.number().int().positive().describe("Phase number, starting at 1"),
41641
+ name: tool.schema.string().min(1).describe("Descriptive phase name derived from the spec"),
41642
+ tasks: tool.schema.array(tool.schema.object({
41643
+ 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"'),
41644
+ description: tool.schema.string().min(1).describe("Specific task description from the spec. NOT a placeholder like [task]."),
41645
+ size: tool.schema.enum(["small", "medium", "large"]).optional().describe("Task size estimate (default: small)"),
41646
+ depends: tool.schema.array(tool.schema.string()).optional().describe('Task IDs this task depends on, e.g. ["1.1", "1.2"]'),
41647
+ acceptance: tool.schema.string().optional().describe("Acceptance criteria for this task")
41648
+ })).min(1).describe("Tasks in this phase")
41649
+ })).min(1).describe("Implementation phases"),
41650
+ working_directory: tool.schema.string().optional().describe("Working directory (defaults to process.cwd())")
41651
+ },
41652
+ execute: async (args2) => {
41653
+ return JSON.stringify(await executeSavePlan(args2), null, 2);
41654
+ }
41655
+ });
41020
41656
  // src/tools/pkg-audit.ts
41021
41657
  init_dist();
41022
41658
  import * as fs18 from "fs";
41023
- import * as path23 from "path";
41659
+ import * as path24 from "path";
41024
41660
  var MAX_OUTPUT_BYTES5 = 52428800;
41025
41661
  var AUDIT_TIMEOUT_MS = 120000;
41026
41662
  function isValidEcosystem(value) {
@@ -41038,13 +41674,13 @@ function validateArgs3(args2) {
41038
41674
  function detectEcosystems() {
41039
41675
  const ecosystems = [];
41040
41676
  const cwd = process.cwd();
41041
- if (fs18.existsSync(path23.join(cwd, "package.json"))) {
41677
+ if (fs18.existsSync(path24.join(cwd, "package.json"))) {
41042
41678
  ecosystems.push("npm");
41043
41679
  }
41044
- if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
41680
+ if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
41045
41681
  ecosystems.push("pip");
41046
41682
  }
41047
- if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
41683
+ if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
41048
41684
  ecosystems.push("cargo");
41049
41685
  }
41050
41686
  return ecosystems;
@@ -42952,11 +43588,11 @@ var Module2 = (() => {
42952
43588
  throw toThrow;
42953
43589
  }, "quit_");
42954
43590
  var scriptDirectory = "";
42955
- function locateFile(path24) {
43591
+ function locateFile(path25) {
42956
43592
  if (Module["locateFile"]) {
42957
- return Module["locateFile"](path24, scriptDirectory);
43593
+ return Module["locateFile"](path25, scriptDirectory);
42958
43594
  }
42959
- return scriptDirectory + path24;
43595
+ return scriptDirectory + path25;
42960
43596
  }
42961
43597
  __name(locateFile, "locateFile");
42962
43598
  var readAsync, readBinary;
@@ -44770,7 +45406,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
44770
45406
  ]);
44771
45407
  // src/tools/pre-check-batch.ts
44772
45408
  init_dist();
44773
- import * as path26 from "path";
45409
+ import * as path27 from "path";
44774
45410
 
44775
45411
  // node_modules/yocto-queue/index.js
44776
45412
  class Node2 {
@@ -44937,7 +45573,7 @@ init_manager();
44937
45573
 
44938
45574
  // src/quality/metrics.ts
44939
45575
  import * as fs19 from "fs";
44940
- import * as path24 from "path";
45576
+ import * as path25 from "path";
44941
45577
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
44942
45578
  var MIN_DUPLICATION_LINES = 10;
44943
45579
  function estimateCyclomaticComplexity(content) {
@@ -44989,7 +45625,7 @@ async function computeComplexityDelta(files, workingDir) {
44989
45625
  let totalComplexity = 0;
44990
45626
  const analyzedFiles = [];
44991
45627
  for (const file3 of files) {
44992
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45628
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
44993
45629
  if (!fs19.existsSync(fullPath)) {
44994
45630
  continue;
44995
45631
  }
@@ -45112,7 +45748,7 @@ function countGoExports(content) {
45112
45748
  function getExportCountForFile(filePath) {
45113
45749
  try {
45114
45750
  const content = fs19.readFileSync(filePath, "utf-8");
45115
- const ext = path24.extname(filePath).toLowerCase();
45751
+ const ext = path25.extname(filePath).toLowerCase();
45116
45752
  switch (ext) {
45117
45753
  case ".ts":
45118
45754
  case ".tsx":
@@ -45138,7 +45774,7 @@ async function computePublicApiDelta(files, workingDir) {
45138
45774
  let totalExports = 0;
45139
45775
  const analyzedFiles = [];
45140
45776
  for (const file3 of files) {
45141
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45777
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45142
45778
  if (!fs19.existsSync(fullPath)) {
45143
45779
  continue;
45144
45780
  }
@@ -45172,7 +45808,7 @@ async function computeDuplicationRatio(files, workingDir) {
45172
45808
  let duplicateLines = 0;
45173
45809
  const analyzedFiles = [];
45174
45810
  for (const file3 of files) {
45175
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
45811
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45176
45812
  if (!fs19.existsSync(fullPath)) {
45177
45813
  continue;
45178
45814
  }
@@ -45205,8 +45841,8 @@ function countCodeLines(content) {
45205
45841
  return lines.length;
45206
45842
  }
45207
45843
  function isTestFile(filePath) {
45208
- const basename5 = path24.basename(filePath);
45209
- const _ext = path24.extname(filePath).toLowerCase();
45844
+ const basename5 = path25.basename(filePath);
45845
+ const _ext = path25.extname(filePath).toLowerCase();
45210
45846
  const testPatterns = [
45211
45847
  ".test.",
45212
45848
  ".spec.",
@@ -45248,7 +45884,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
45248
45884
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45249
45885
  let testLines = 0;
45250
45886
  let codeLines = 0;
45251
- const srcDir = path24.join(workingDir, "src");
45887
+ const srcDir = path25.join(workingDir, "src");
45252
45888
  if (fs19.existsSync(srcDir)) {
45253
45889
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
45254
45890
  codeLines += lines;
@@ -45256,14 +45892,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45256
45892
  }
45257
45893
  const possibleSrcDirs = ["lib", "app", "source", "core"];
45258
45894
  for (const dir of possibleSrcDirs) {
45259
- const dirPath = path24.join(workingDir, dir);
45895
+ const dirPath = path25.join(workingDir, dir);
45260
45896
  if (fs19.existsSync(dirPath)) {
45261
45897
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
45262
45898
  codeLines += lines;
45263
45899
  });
45264
45900
  }
45265
45901
  }
45266
- const testsDir = path24.join(workingDir, "tests");
45902
+ const testsDir = path25.join(workingDir, "tests");
45267
45903
  if (fs19.existsSync(testsDir)) {
45268
45904
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
45269
45905
  testLines += lines;
@@ -45271,7 +45907,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
45271
45907
  }
45272
45908
  const possibleTestDirs = ["test", "__tests__", "specs"];
45273
45909
  for (const dir of possibleTestDirs) {
45274
- const dirPath = path24.join(workingDir, dir);
45910
+ const dirPath = path25.join(workingDir, dir);
45275
45911
  if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
45276
45912
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
45277
45913
  testLines += lines;
@@ -45286,7 +45922,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
45286
45922
  try {
45287
45923
  const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
45288
45924
  for (const entry of entries) {
45289
- const fullPath = path24.join(dirPath, entry.name);
45925
+ const fullPath = path25.join(dirPath, entry.name);
45290
45926
  if (entry.isDirectory()) {
45291
45927
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
45292
45928
  continue;
@@ -45294,7 +45930,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
45294
45930
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
45295
45931
  } else if (entry.isFile()) {
45296
45932
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
45297
- const ext = path24.extname(entry.name).toLowerCase();
45933
+ const ext = path25.extname(entry.name).toLowerCase();
45298
45934
  const validExts = [
45299
45935
  ".ts",
45300
45936
  ".tsx",
@@ -45549,7 +46185,7 @@ async function qualityBudget(input, directory) {
45549
46185
  // src/tools/sast-scan.ts
45550
46186
  init_manager();
45551
46187
  import * as fs20 from "fs";
45552
- import * as path25 from "path";
46188
+ import * as path26 from "path";
45553
46189
  import { extname as extname7 } from "path";
45554
46190
 
45555
46191
  // src/sast/rules/c.ts
@@ -46504,7 +47140,7 @@ async function sastScan(input, directory, config3) {
46504
47140
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
46505
47141
  const filesByLanguage = new Map;
46506
47142
  for (const filePath of changed_files) {
46507
- const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
47143
+ const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
46508
47144
  if (!fs20.existsSync(resolvedPath)) {
46509
47145
  _filesSkipped++;
46510
47146
  continue;
@@ -46613,10 +47249,10 @@ function validatePath(inputPath, baseDir) {
46613
47249
  if (!inputPath || inputPath.length === 0) {
46614
47250
  return "path is required";
46615
47251
  }
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)) {
47252
+ const resolved = path27.resolve(baseDir, inputPath);
47253
+ const baseResolved = path27.resolve(baseDir);
47254
+ const relative3 = path27.relative(baseResolved, resolved);
47255
+ if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
46620
47256
  return "path traversal detected";
46621
47257
  }
46622
47258
  return null;
@@ -46738,7 +47374,7 @@ async function runPreCheckBatch(input) {
46738
47374
  warn(`pre_check_batch: Invalid file path: ${file3}`);
46739
47375
  continue;
46740
47376
  }
46741
- changedFiles.push(path26.resolve(directory, file3));
47377
+ changedFiles.push(path27.resolve(directory, file3));
46742
47378
  }
46743
47379
  } else {
46744
47380
  changedFiles = [];
@@ -46929,7 +47565,7 @@ var retrieve_summary = tool({
46929
47565
  init_dist();
46930
47566
  init_manager();
46931
47567
  import * as fs21 from "fs";
46932
- import * as path27 from "path";
47568
+ import * as path28 from "path";
46933
47569
 
46934
47570
  // src/sbom/detectors/dart.ts
46935
47571
  function parsePubspecLock(content) {
@@ -47776,7 +48412,7 @@ function findManifestFiles(rootDir) {
47776
48412
  try {
47777
48413
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47778
48414
  for (const entry of entries) {
47779
- const fullPath = path27.join(dir, entry.name);
48415
+ const fullPath = path28.join(dir, entry.name);
47780
48416
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
47781
48417
  continue;
47782
48418
  }
@@ -47786,7 +48422,7 @@ function findManifestFiles(rootDir) {
47786
48422
  for (const pattern of patterns) {
47787
48423
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47788
48424
  if (new RegExp(regex, "i").test(entry.name)) {
47789
- manifestFiles.push(path27.relative(cwd, fullPath));
48425
+ manifestFiles.push(path28.relative(cwd, fullPath));
47790
48426
  break;
47791
48427
  }
47792
48428
  }
@@ -47805,12 +48441,12 @@ function findManifestFilesInDirs(directories, workingDir) {
47805
48441
  try {
47806
48442
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47807
48443
  for (const entry of entries) {
47808
- const fullPath = path27.join(dir, entry.name);
48444
+ const fullPath = path28.join(dir, entry.name);
47809
48445
  if (entry.isFile()) {
47810
48446
  for (const pattern of patterns) {
47811
48447
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47812
48448
  if (new RegExp(regex, "i").test(entry.name)) {
47813
- found.push(path27.relative(workingDir, fullPath));
48449
+ found.push(path28.relative(workingDir, fullPath));
47814
48450
  break;
47815
48451
  }
47816
48452
  }
@@ -47823,11 +48459,11 @@ function findManifestFilesInDirs(directories, workingDir) {
47823
48459
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
47824
48460
  const dirs = new Set;
47825
48461
  for (const file3 of changedFiles) {
47826
- let currentDir = path27.dirname(file3);
48462
+ let currentDir = path28.dirname(file3);
47827
48463
  while (true) {
47828
- if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
47829
- dirs.add(path27.join(workingDir, currentDir));
47830
- const parent = path27.dirname(currentDir);
48464
+ if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
48465
+ dirs.add(path28.join(workingDir, currentDir));
48466
+ const parent = path28.dirname(currentDir);
47831
48467
  if (parent === currentDir)
47832
48468
  break;
47833
48469
  currentDir = parent;
@@ -47934,7 +48570,7 @@ var sbom_generate = tool({
47934
48570
  const processedFiles = [];
47935
48571
  for (const manifestFile of manifestFiles) {
47936
48572
  try {
47937
- const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
48573
+ const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
47938
48574
  if (!fs21.existsSync(fullPath)) {
47939
48575
  continue;
47940
48576
  }
@@ -47951,7 +48587,7 @@ var sbom_generate = tool({
47951
48587
  const bom = generateCycloneDX(allComponents);
47952
48588
  const bomJson = serializeCycloneDX(bom);
47953
48589
  const filename = generateSbomFilename();
47954
- const outputPath = path27.join(outputDir, filename);
48590
+ const outputPath = path28.join(outputDir, filename);
47955
48591
  fs21.writeFileSync(outputPath, bomJson, "utf-8");
47956
48592
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
47957
48593
  try {
@@ -47994,7 +48630,7 @@ var sbom_generate = tool({
47994
48630
  // src/tools/schema-drift.ts
47995
48631
  init_dist();
47996
48632
  import * as fs22 from "fs";
47997
- import * as path28 from "path";
48633
+ import * as path29 from "path";
47998
48634
  var SPEC_CANDIDATES = [
47999
48635
  "openapi.json",
48000
48636
  "openapi.yaml",
@@ -48026,12 +48662,12 @@ function normalizePath(p) {
48026
48662
  }
48027
48663
  function discoverSpecFile(cwd, specFileArg) {
48028
48664
  if (specFileArg) {
48029
- const resolvedPath = path28.resolve(cwd, specFileArg);
48030
- const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
48665
+ const resolvedPath = path29.resolve(cwd, specFileArg);
48666
+ const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
48031
48667
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
48032
48668
  throw new Error("Invalid spec_file: path traversal detected");
48033
48669
  }
48034
- const ext = path28.extname(resolvedPath).toLowerCase();
48670
+ const ext = path29.extname(resolvedPath).toLowerCase();
48035
48671
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
48036
48672
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
48037
48673
  }
@@ -48045,7 +48681,7 @@ function discoverSpecFile(cwd, specFileArg) {
48045
48681
  return resolvedPath;
48046
48682
  }
48047
48683
  for (const candidate of SPEC_CANDIDATES) {
48048
- const candidatePath = path28.resolve(cwd, candidate);
48684
+ const candidatePath = path29.resolve(cwd, candidate);
48049
48685
  if (fs22.existsSync(candidatePath)) {
48050
48686
  const stats = fs22.statSync(candidatePath);
48051
48687
  if (stats.size <= MAX_SPEC_SIZE) {
@@ -48057,7 +48693,7 @@ function discoverSpecFile(cwd, specFileArg) {
48057
48693
  }
48058
48694
  function parseSpec(specFile) {
48059
48695
  const content = fs22.readFileSync(specFile, "utf-8");
48060
- const ext = path28.extname(specFile).toLowerCase();
48696
+ const ext = path29.extname(specFile).toLowerCase();
48061
48697
  if (ext === ".json") {
48062
48698
  return parseJsonSpec(content);
48063
48699
  }
@@ -48128,7 +48764,7 @@ function extractRoutes(cwd) {
48128
48764
  return;
48129
48765
  }
48130
48766
  for (const entry of entries) {
48131
- const fullPath = path28.join(dir, entry.name);
48767
+ const fullPath = path29.join(dir, entry.name);
48132
48768
  if (entry.isSymbolicLink()) {
48133
48769
  continue;
48134
48770
  }
@@ -48138,7 +48774,7 @@ function extractRoutes(cwd) {
48138
48774
  }
48139
48775
  walkDir(fullPath);
48140
48776
  } else if (entry.isFile()) {
48141
- const ext = path28.extname(entry.name).toLowerCase();
48777
+ const ext = path29.extname(entry.name).toLowerCase();
48142
48778
  const baseName = entry.name.toLowerCase();
48143
48779
  if (![".ts", ".js", ".mjs"].includes(ext)) {
48144
48780
  continue;
@@ -48307,7 +48943,7 @@ init_secretscan();
48307
48943
  // src/tools/symbols.ts
48308
48944
  init_tool();
48309
48945
  import * as fs23 from "fs";
48310
- import * as path29 from "path";
48946
+ import * as path30 from "path";
48311
48947
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
48312
48948
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
48313
48949
  function containsControlCharacters(str) {
@@ -48336,11 +48972,11 @@ function containsWindowsAttacks(str) {
48336
48972
  }
48337
48973
  function isPathInWorkspace(filePath, workspace) {
48338
48974
  try {
48339
- const resolvedPath = path29.resolve(workspace, filePath);
48975
+ const resolvedPath = path30.resolve(workspace, filePath);
48340
48976
  const realWorkspace = fs23.realpathSync(workspace);
48341
48977
  const realResolvedPath = fs23.realpathSync(resolvedPath);
48342
- const relativePath = path29.relative(realWorkspace, realResolvedPath);
48343
- if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
48978
+ const relativePath = path30.relative(realWorkspace, realResolvedPath);
48979
+ if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
48344
48980
  return false;
48345
48981
  }
48346
48982
  return true;
@@ -48352,7 +48988,7 @@ function validatePathForRead(filePath, workspace) {
48352
48988
  return isPathInWorkspace(filePath, workspace);
48353
48989
  }
48354
48990
  function extractTSSymbols(filePath, cwd) {
48355
- const fullPath = path29.join(cwd, filePath);
48991
+ const fullPath = path30.join(cwd, filePath);
48356
48992
  if (!validatePathForRead(fullPath, cwd)) {
48357
48993
  return [];
48358
48994
  }
@@ -48504,7 +49140,7 @@ function extractTSSymbols(filePath, cwd) {
48504
49140
  });
48505
49141
  }
48506
49142
  function extractPythonSymbols(filePath, cwd) {
48507
- const fullPath = path29.join(cwd, filePath);
49143
+ const fullPath = path30.join(cwd, filePath);
48508
49144
  if (!validatePathForRead(fullPath, cwd)) {
48509
49145
  return [];
48510
49146
  }
@@ -48586,7 +49222,7 @@ var symbols = tool({
48586
49222
  }, null, 2);
48587
49223
  }
48588
49224
  const cwd = process.cwd();
48589
- const ext = path29.extname(file3);
49225
+ const ext = path30.extname(file3);
48590
49226
  if (containsControlCharacters(file3)) {
48591
49227
  return JSON.stringify({
48592
49228
  file: file3,
@@ -48655,7 +49291,7 @@ init_test_runner();
48655
49291
  // src/tools/todo-extract.ts
48656
49292
  init_dist();
48657
49293
  import * as fs24 from "fs";
48658
- import * as path30 from "path";
49294
+ import * as path31 from "path";
48659
49295
  var MAX_TEXT_LENGTH = 200;
48660
49296
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
48661
49297
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -48726,9 +49362,9 @@ function validatePathsInput(paths, cwd) {
48726
49362
  return { error: "paths contains path traversal", resolvedPath: null };
48727
49363
  }
48728
49364
  try {
48729
- const resolvedPath = path30.resolve(paths);
48730
- const normalizedCwd = path30.resolve(cwd);
48731
- const normalizedResolved = path30.resolve(resolvedPath);
49365
+ const resolvedPath = path31.resolve(paths);
49366
+ const normalizedCwd = path31.resolve(cwd);
49367
+ const normalizedResolved = path31.resolve(resolvedPath);
48732
49368
  if (!normalizedResolved.startsWith(normalizedCwd)) {
48733
49369
  return {
48734
49370
  error: "paths must be within the current working directory",
@@ -48744,7 +49380,7 @@ function validatePathsInput(paths, cwd) {
48744
49380
  }
48745
49381
  }
48746
49382
  function isSupportedExtension(filePath) {
48747
- const ext = path30.extname(filePath).toLowerCase();
49383
+ const ext = path31.extname(filePath).toLowerCase();
48748
49384
  return SUPPORTED_EXTENSIONS2.has(ext);
48749
49385
  }
48750
49386
  function findSourceFiles3(dir, files = []) {
@@ -48759,7 +49395,7 @@ function findSourceFiles3(dir, files = []) {
48759
49395
  if (SKIP_DIRECTORIES3.has(entry)) {
48760
49396
  continue;
48761
49397
  }
48762
- const fullPath = path30.join(dir, entry);
49398
+ const fullPath = path31.join(dir, entry);
48763
49399
  let stat;
48764
49400
  try {
48765
49401
  stat = fs24.statSync(fullPath);
@@ -48871,7 +49507,7 @@ var todo_extract = tool({
48871
49507
  filesToScan.push(scanPath);
48872
49508
  } else {
48873
49509
  const errorResult = {
48874
- error: `unsupported file extension: ${path30.extname(scanPath)}`,
49510
+ error: `unsupported file extension: ${path31.extname(scanPath)}`,
48875
49511
  total: 0,
48876
49512
  byPriority: { high: 0, medium: 0, low: 0 },
48877
49513
  entries: []
@@ -48986,7 +49622,7 @@ var OpenCodeSwarm = async (ctx) => {
48986
49622
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
48987
49623
  preflightTriggerManager = new PTM(automationConfig);
48988
49624
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
48989
- const swarmDir = path31.resolve(ctx.directory, ".swarm");
49625
+ const swarmDir = path32.resolve(ctx.directory, ".swarm");
48990
49626
  statusArtifact = new ASA(swarmDir);
48991
49627
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
48992
49628
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -49092,6 +49728,7 @@ var OpenCodeSwarm = async (ctx) => {
49092
49728
  phase_complete,
49093
49729
  pre_check_batch,
49094
49730
  retrieve_summary,
49731
+ save_plan,
49095
49732
  schema_drift,
49096
49733
  secretscan,
49097
49734
  symbols,