opencode-swarm 6.20.3 → 6.21.1

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
@@ -14841,6 +14841,17 @@ ${markdown}`;
14841
14841
  unlinkSync(mdTempPath);
14842
14842
  } catch {}
14843
14843
  }
14844
+ try {
14845
+ const markerPath = path4.join(swarmDir, ".plan-write-marker");
14846
+ const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
14847
+ const marker = JSON.stringify({
14848
+ source: "plan_manager",
14849
+ timestamp: new Date().toISOString(),
14850
+ phases_count: validated.phases.length,
14851
+ tasks_count: tasksCount
14852
+ });
14853
+ await Bun.write(markerPath, marker);
14854
+ } catch {}
14844
14855
  }
14845
14856
  async function updateTaskStatus(directory, taskId, status) {
14846
14857
  const plan = await loadPlan(directory);
@@ -36285,8 +36296,8 @@ var init_tree_sitter = __esm(() => {
36285
36296
  bytes = Promise.resolve(input);
36286
36297
  } else {
36287
36298
  if (globalThis.process?.versions.node) {
36288
- const fs24 = await import("fs/promises");
36289
- bytes = fs24.readFile(input);
36299
+ const fs25 = await import("fs/promises");
36300
+ bytes = fs25.readFile(input);
36290
36301
  } else {
36291
36302
  bytes = fetch(input).then((response) => response.arrayBuffer().then((buffer) => {
36292
36303
  if (response.ok) {
@@ -36318,8 +36329,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
36318
36329
  var moduleRtn;
36319
36330
  var Module = moduleArg;
36320
36331
  var readyPromiseResolve, readyPromiseReject;
36321
- var readyPromise = new Promise((resolve12, reject) => {
36322
- readyPromiseResolve = resolve12;
36332
+ var readyPromise = new Promise((resolve13, reject) => {
36333
+ readyPromiseResolve = resolve13;
36323
36334
  readyPromiseReject = reject;
36324
36335
  });
36325
36336
  var ENVIRONMENT_IS_WEB = typeof window == "object";
@@ -36341,11 +36352,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
36341
36352
  throw toThrow;
36342
36353
  }, "quit_");
36343
36354
  var scriptDirectory = "";
36344
- function locateFile(path35) {
36355
+ function locateFile(path36) {
36345
36356
  if (Module["locateFile"]) {
36346
- return Module["locateFile"](path35, scriptDirectory);
36357
+ return Module["locateFile"](path36, scriptDirectory);
36347
36358
  }
36348
- return scriptDirectory + path35;
36359
+ return scriptDirectory + path36;
36349
36360
  }
36350
36361
  __name(locateFile, "locateFile");
36351
36362
  var readAsync, readBinary;
@@ -36399,13 +36410,13 @@ ${JSON.stringify(symbolNames, null, 2)}`);
36399
36410
  }
36400
36411
  readAsync = /* @__PURE__ */ __name(async (url3) => {
36401
36412
  if (isFileURI(url3)) {
36402
- return new Promise((resolve12, reject) => {
36413
+ return new Promise((resolve13, reject) => {
36403
36414
  var xhr = new XMLHttpRequest;
36404
36415
  xhr.open("GET", url3, true);
36405
36416
  xhr.responseType = "arraybuffer";
36406
36417
  xhr.onload = () => {
36407
36418
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
36408
- resolve12(xhr.response);
36419
+ resolve13(xhr.response);
36409
36420
  return;
36410
36421
  }
36411
36422
  reject(xhr.status);
@@ -36625,10 +36636,10 @@ ${JSON.stringify(symbolNames, null, 2)}`);
36625
36636
  __name(receiveInstantiationResult, "receiveInstantiationResult");
36626
36637
  var info2 = getWasmImports();
36627
36638
  if (Module["instantiateWasm"]) {
36628
- return new Promise((resolve12, reject) => {
36639
+ return new Promise((resolve13, reject) => {
36629
36640
  Module["instantiateWasm"](info2, (mod, inst) => {
36630
36641
  receiveInstance(mod, inst);
36631
- resolve12(mod.exports);
36642
+ resolve13(mod.exports);
36632
36643
  });
36633
36644
  });
36634
36645
  }
@@ -38093,7 +38104,7 @@ var init_runtime = __esm(() => {
38093
38104
  });
38094
38105
 
38095
38106
  // src/index.ts
38096
- import * as path44 from "path";
38107
+ import * as path45 from "path";
38097
38108
 
38098
38109
  // src/tools/tool-names.ts
38099
38110
  var TOOL_NAMES = [
@@ -38123,7 +38134,8 @@ var TOOL_NAMES = [
38123
38134
  "phase_complete",
38124
38135
  "save_plan",
38125
38136
  "update_task_status",
38126
- "write_retro"
38137
+ "write_retro",
38138
+ "declare_scope"
38127
38139
  ];
38128
38140
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
38129
38141
 
@@ -38163,7 +38175,8 @@ var AGENT_TOOL_MAP = {
38163
38175
  "test_runner",
38164
38176
  "todo_extract",
38165
38177
  "update_task_status",
38166
- "write_retro"
38178
+ "write_retro",
38179
+ "declare_scope"
38167
38180
  ],
38168
38181
  explorer: [
38169
38182
  "complexity_hotspots",
@@ -38276,6 +38289,13 @@ var DEFAULT_SCORING_CONFIG = {
38276
38289
  json: 0.35
38277
38290
  }
38278
38291
  };
38292
+ var LOW_CAPABILITY_MODELS = ["mini", "nano", "small", "free"];
38293
+ function isLowCapabilityModel(modelId) {
38294
+ if (!modelId)
38295
+ return false;
38296
+ const lower = modelId.toLowerCase();
38297
+ return LOW_CAPABILITY_MODELS.some((substr) => lower.includes(substr));
38298
+ }
38279
38299
 
38280
38300
  // src/config/index.ts
38281
38301
  init_evidence_schema();
@@ -38476,11 +38496,11 @@ var PhaseCompleteConfigSchema = exports_external.object({
38476
38496
  });
38477
38497
  var SummaryConfigSchema = exports_external.object({
38478
38498
  enabled: exports_external.boolean().default(true),
38479
- threshold_bytes: exports_external.number().min(1024).max(1048576).default(20480),
38499
+ threshold_bytes: exports_external.number().min(1024).max(1048576).default(102400),
38480
38500
  max_summary_chars: exports_external.number().min(100).max(5000).default(1000),
38481
38501
  max_stored_bytes: exports_external.number().min(10240).max(104857600).default(10485760),
38482
38502
  retention_days: exports_external.number().min(1).max(365).default(7),
38483
- exempt_tools: exports_external.array(exports_external.string()).default(["retrieve_summary", "task"])
38503
+ exempt_tools: exports_external.array(exports_external.string()).default(["retrieve_summary", "task", "read"])
38484
38504
  });
38485
38505
  var ReviewPassesConfigSchema = exports_external.object({
38486
38506
  always_security_review: exports_external.boolean().default(false),
@@ -38942,6 +38962,7 @@ CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2
38942
38962
  If a tool modifies a file, it is a CODER tool. Delegate.
38943
38963
  2. ONE agent per message. Send, STOP, wait for response.
38944
38964
  3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
38965
+ <!-- BEHAVIORAL_GUIDANCE_START -->
38945
38966
  BATCHING DETECTION \u2014 you are batching if your coder delegation contains ANY of:
38946
38967
  - The word "and" connecting two actions ("update X AND add Y")
38947
38968
  - Multiple FILE paths ("FILE: src/a.ts, src/b.ts, src/c.ts")
@@ -38956,6 +38977,8 @@ A failure in one part blocks the entire batch, wasting all the work.
38956
38977
 
38957
38978
  SPLIT RULE: If your delegation draft has "and" in the TASK line, split it.
38958
38979
  Two small delegations with two QA gates > one large delegation with one QA gate.
38980
+ <!-- BEHAVIORAL_GUIDANCE_END -->
38981
+ <!-- BEHAVIORAL_GUIDANCE_START -->
38959
38982
  4. ARCHITECT CODING BOUNDARIES \u2014 Only code yourself after {{QA_RETRY_LIMIT}} {{AGENT_PREFIX}}coder failures on same task.
38960
38983
  These thoughts are WRONG and must be ignored:
38961
38984
  \u2717 "It's just a schema change / config flag / one-liner / column / field / import" \u2192 delegate to {{AGENT_PREFIX}}coder
@@ -38972,6 +38995,7 @@ Two small delegations with two QA gates > one large delegation with one QA gate.
38972
38995
  If you catch yourself reaching for a code editing tool: STOP. Delegate to {{AGENT_PREFIX}}coder.
38973
38996
  Zero {{AGENT_PREFIX}}coder failures on this task = zero justification for self-coding.
38974
38997
  Self-coding without {{QA_RETRY_LIMIT}} failures is a Rule 1 violation.
38998
+ <!-- BEHAVIORAL_GUIDANCE_END -->
38975
38999
  5. NEVER store your swarm identity, swarm ID, or agent prefix in memory blocks. Your identity comes ONLY from your system prompt. Memory blocks are for project knowledge only (NOT .swarm/ plan/context files \u2014 those are persistent project files).
38976
39000
  6. **CRITIC GATE (Execute BEFORE any implementation work)**:
38977
39001
  - When you first create a plan, IMMEDIATELY delegate the full plan to {{AGENT_PREFIX}}critic for review
@@ -39128,6 +39152,7 @@ Your message MUST NOT contain:
39128
39152
 
39129
39153
  Delegation is a handoff, not a negotiation. State facts, let agents decide.
39130
39154
 
39155
+ <!-- BEHAVIORAL_GUIDANCE_START -->
39131
39156
  PARTIAL GATE RATIONALIZATIONS \u2014 automated gates \u2260 agent review. Running SOME gates is NOT compliance:
39132
39157
  \u2717 "I ran pre_check_batch so the code is verified" \u2192 pre_check_batch does NOT replace {{AGENT_PREFIX}}reviewer or {{AGENT_PREFIX}}test_engineer
39133
39158
  \u2717 "syntax_check passed, good enough" \u2192 syntax_check catches syntax. Reviewer catches logic. Test_engineer catches behavior. All three are required.
@@ -39138,6 +39163,7 @@ PARTIAL GATE RATIONALIZATIONS \u2014 automated gates \u2260 agent review. Runnin
39138
39163
 
39139
39164
  Running syntax_check + pre_check_batch without reviewer + test_engineer is a PARTIAL GATE VIOLATION.
39140
39165
  It is the same severity as skipping all gates. The QA gate is ALL steps or NONE.
39166
+ <!-- BEHAVIORAL_GUIDANCE_END -->
39141
39167
 
39142
39168
  8. **COVERAGE CHECK**: After adversarial tests pass, check if test_engineer reports coverage < 70%. If so, delegate {{AGENT_PREFIX}}test_engineer for an additional test pass targeting uncovered paths. This is a soft guideline; use judgment for trivial tasks.
39143
39169
  9. **UI/UX DESIGN GATE**: Before delegating UI tasks to {{AGENT_PREFIX}}coder, check if the task involves UI components. Trigger conditions (ANY match):
@@ -41651,6 +41677,7 @@ class PlanSyncWorker {
41651
41677
  this.syncing = true;
41652
41678
  try {
41653
41679
  log("[PlanSyncWorker] Syncing plan...");
41680
+ this.checkForUnauthorizedWrite();
41654
41681
  const plan = await this.withTimeout(loadPlan(this.directory), this.syncTimeoutMs, "Sync operation timed out");
41655
41682
  if (plan) {
41656
41683
  log("[PlanSyncWorker] Sync complete", {
@@ -41692,6 +41719,21 @@ class PlanSyncWorker {
41692
41719
  }
41693
41720
  }
41694
41721
  }
41722
+ checkForUnauthorizedWrite() {
41723
+ try {
41724
+ const swarmDir = this.getSwarmDir();
41725
+ const planJsonPath = path6.join(swarmDir, "plan.json");
41726
+ const markerPath = path6.join(swarmDir, ".plan-write-marker");
41727
+ const planStats = fs3.statSync(planJsonPath);
41728
+ const planMtimeMs = planStats.mtimeMs;
41729
+ const markerContent = fs3.readFileSync(markerPath, "utf8");
41730
+ const marker = JSON.parse(markerContent);
41731
+ const markerTimestampMs = new Date(marker.timestamp).getTime();
41732
+ if (planMtimeMs > markerTimestampMs + 5000) {
41733
+ log("[PlanSyncWorker] WARNING: plan.json may have been written outside save_plan/savePlan - unauthorized direct write suspected", { planMtimeMs, markerTimestampMs });
41734
+ }
41735
+ } catch {}
41736
+ }
41695
41737
  withTimeout(promise2, ms, timeoutMessage) {
41696
41738
  return new Promise((resolve4, reject) => {
41697
41739
  const timer = setTimeout(() => {
@@ -41886,7 +41928,13 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
41886
41928
  lastPhaseCompletePhase: 0,
41887
41929
  phaseAgentsDispatched: new Set,
41888
41930
  qaSkipCount: 0,
41889
- qaSkipTaskIds: []
41931
+ qaSkipTaskIds: [],
41932
+ taskWorkflowStates: new Map,
41933
+ lastGateOutcome: null,
41934
+ declaredCoderScope: null,
41935
+ lastScopeViolation: null,
41936
+ scopeViolationDetected: false,
41937
+ modifiedFilesThisCoderTask: []
41890
41938
  };
41891
41939
  swarmState.agentSessions.set(sessionId, sessionState);
41892
41940
  swarmState.activeAgent.set(sessionId, agentName);
@@ -41955,6 +42003,24 @@ function ensureAgentSession(sessionId, agentName) {
41955
42003
  if (!session.qaSkipTaskIds) {
41956
42004
  session.qaSkipTaskIds = [];
41957
42005
  }
42006
+ if (!session.taskWorkflowStates) {
42007
+ session.taskWorkflowStates = new Map;
42008
+ }
42009
+ if (session.lastGateOutcome === undefined) {
42010
+ session.lastGateOutcome = null;
42011
+ }
42012
+ if (session.declaredCoderScope === undefined) {
42013
+ session.declaredCoderScope = null;
42014
+ }
42015
+ if (session.lastScopeViolation === undefined) {
42016
+ session.lastScopeViolation = null;
42017
+ }
42018
+ if (session.modifiedFilesThisCoderTask === undefined) {
42019
+ session.modifiedFilesThisCoderTask = [];
42020
+ }
42021
+ if (session.scopeViolationDetected === undefined) {
42022
+ session.scopeViolationDetected = false;
42023
+ }
41958
42024
  session.lastToolCallTime = now;
41959
42025
  return session;
41960
42026
  }
@@ -42034,6 +42100,35 @@ function recordPhaseAgentDispatch(sessionId, agentName) {
42034
42100
  const normalizedName = stripKnownSwarmPrefix(agentName);
42035
42101
  session.phaseAgentsDispatched.add(normalizedName);
42036
42102
  }
42103
+ function advanceTaskState(session, taskId, newState) {
42104
+ if (!session.taskWorkflowStates) {
42105
+ session.taskWorkflowStates = new Map;
42106
+ }
42107
+ const STATE_ORDER = [
42108
+ "idle",
42109
+ "coder_delegated",
42110
+ "pre_check_passed",
42111
+ "reviewer_run",
42112
+ "tests_run",
42113
+ "complete"
42114
+ ];
42115
+ const current = session.taskWorkflowStates.get(taskId) ?? "idle";
42116
+ const currentIndex = STATE_ORDER.indexOf(current);
42117
+ const newIndex = STATE_ORDER.indexOf(newState);
42118
+ if (newIndex <= currentIndex) {
42119
+ throw new Error(`INVALID_TASK_STATE_TRANSITION: ${taskId} ${current} \u2192 ${newState}`);
42120
+ }
42121
+ if (newState === "complete" && current !== "tests_run") {
42122
+ throw new Error(`INVALID_TASK_STATE_TRANSITION: ${taskId} cannot reach complete from ${current} \u2014 must pass through tests_run first`);
42123
+ }
42124
+ session.taskWorkflowStates.set(taskId, newState);
42125
+ }
42126
+ function getTaskState(session, taskId) {
42127
+ if (!session.taskWorkflowStates) {
42128
+ session.taskWorkflowStates = new Map;
42129
+ }
42130
+ return session.taskWorkflowStates.get(taskId) ?? "idle";
42131
+ }
42037
42132
 
42038
42133
  // src/commands/benchmark.ts
42039
42134
  init_utils();
@@ -42923,7 +43018,7 @@ async function handleDarkMatterCommand(directory, args2) {
42923
43018
 
42924
43019
  // src/services/diagnose-service.ts
42925
43020
  import { execSync } from "child_process";
42926
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
43021
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
42927
43022
  import path12 from "path";
42928
43023
  init_manager();
42929
43024
  init_utils2();
@@ -43229,7 +43324,7 @@ async function checkConfigParseability(directory) {
43229
43324
  };
43230
43325
  }
43231
43326
  try {
43232
- const content = readFileSync3(configPath, "utf-8");
43327
+ const content = readFileSync4(configPath, "utf-8");
43233
43328
  JSON.parse(content);
43234
43329
  return {
43235
43330
  name: "Config Parseability",
@@ -43296,7 +43391,7 @@ async function checkCheckpointManifest(directory) {
43296
43391
  };
43297
43392
  }
43298
43393
  try {
43299
- const content = readFileSync3(manifestPath, "utf-8");
43394
+ const content = readFileSync4(manifestPath, "utf-8");
43300
43395
  const parsed = JSON.parse(content);
43301
43396
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
43302
43397
  return {
@@ -43348,7 +43443,7 @@ async function checkEventStreamIntegrity(directory) {
43348
43443
  };
43349
43444
  }
43350
43445
  try {
43351
- const content = readFileSync3(eventsPath, "utf-8");
43446
+ const content = readFileSync4(eventsPath, "utf-8");
43352
43447
  const lines = content.split(`
43353
43448
  `).filter((line) => line.trim() !== "");
43354
43449
  let malformedCount = 0;
@@ -43389,7 +43484,7 @@ async function checkSteeringDirectives(directory) {
43389
43484
  };
43390
43485
  }
43391
43486
  try {
43392
- const content = readFileSync3(eventsPath, "utf-8");
43487
+ const content = readFileSync4(eventsPath, "utf-8");
43393
43488
  const lines = content.split(`
43394
43489
  `).filter((line) => line.trim() !== "");
43395
43490
  const directivesIssued = [];
@@ -44357,7 +44452,7 @@ async function handleHistoryCommand(directory, _args) {
44357
44452
  }
44358
44453
  // src/hooks/knowledge-migrator.ts
44359
44454
  import { randomUUID as randomUUID2 } from "crypto";
44360
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
44455
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
44361
44456
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
44362
44457
  import * as path16 from "path";
44363
44458
 
@@ -44914,7 +45009,7 @@ function inferProjectName(directory) {
44914
45009
  const packageJsonPath = path16.join(directory, "package.json");
44915
45010
  if (existsSync8(packageJsonPath)) {
44916
45011
  try {
44917
- const pkg = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
45012
+ const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
44918
45013
  if (pkg.name && typeof pkg.name === "string") {
44919
45014
  return pkg.name;
44920
45015
  }
@@ -47099,7 +47194,7 @@ function shouldMaskToolOutput(msg, index, totalMessages, recentWindowSize, thres
47099
47194
  return false;
47100
47195
  }
47101
47196
  const toolName = extractToolName(text);
47102
- if (toolName && ["retrieve_summary", "task"].includes(toolName.toLowerCase())) {
47197
+ if (toolName && ["retrieve_summary", "task", "read"].includes(toolName.toLowerCase())) {
47103
47198
  return false;
47104
47199
  }
47105
47200
  const age = totalMessages - 1 - index;
@@ -47149,12 +47244,40 @@ function createDelegationGateHook(config3) {
47149
47244
  if (normalized === "Task" || normalized === "task") {
47150
47245
  const delegationChain = swarmState.delegationChains.get(input.sessionID);
47151
47246
  if (delegationChain && delegationChain.length > 0) {
47152
- const lastDelegation = delegationChain[delegationChain.length - 1];
47153
- const target = stripKnownSwarmPrefix(lastDelegation.to);
47154
- if (target === "reviewer" || target === "test_engineer") {
47247
+ let lastCoderIndex = -1;
47248
+ for (let i2 = delegationChain.length - 1;i2 >= 0; i2--) {
47249
+ const target = stripKnownSwarmPrefix(delegationChain[i2].to);
47250
+ if (target.includes("coder")) {
47251
+ lastCoderIndex = i2;
47252
+ break;
47253
+ }
47254
+ }
47255
+ if (lastCoderIndex === -1)
47256
+ return;
47257
+ const afterCoder = delegationChain.slice(lastCoderIndex);
47258
+ let hasReviewer = false;
47259
+ let hasTestEngineer = false;
47260
+ for (const delegation of afterCoder) {
47261
+ const target = stripKnownSwarmPrefix(delegation.to);
47262
+ if (target === "reviewer")
47263
+ hasReviewer = true;
47264
+ if (target === "test_engineer")
47265
+ hasTestEngineer = true;
47266
+ }
47267
+ if (hasReviewer && hasTestEngineer) {
47155
47268
  session.qaSkipCount = 0;
47156
47269
  session.qaSkipTaskIds = [];
47157
47270
  }
47271
+ if (hasReviewer && session.currentTaskId) {
47272
+ try {
47273
+ advanceTaskState(session, session.currentTaskId, "reviewer_run");
47274
+ } catch {}
47275
+ }
47276
+ if (hasReviewer && hasTestEngineer && session.currentTaskId) {
47277
+ try {
47278
+ advanceTaskState(session, session.currentTaskId, "tests_run");
47279
+ } catch {}
47280
+ }
47158
47281
  }
47159
47282
  }
47160
47283
  };
@@ -47184,14 +47307,71 @@ function createDelegationGateHook(config3) {
47184
47307
  return;
47185
47308
  const textPart = lastUserMessage.parts[textPartIndex];
47186
47309
  const text = textPart.text ?? "";
47310
+ const taskDisclosureSessionID = lastUserMessage.info?.sessionID;
47311
+ if (taskDisclosureSessionID) {
47312
+ const taskSession = ensureAgentSession(taskDisclosureSessionID);
47313
+ const currentTaskIdForWindow = taskSession.currentTaskId;
47314
+ if (currentTaskIdForWindow) {
47315
+ const taskLineRegex = /^[ \t]*-[ \t]*(?:\[[ x]\][ \t]+)?(\d+\.\d+(?:\.\d+)*)[:. ].*/gm;
47316
+ const taskLines = [];
47317
+ taskLineRegex.lastIndex = 0;
47318
+ let regexMatch = taskLineRegex.exec(text);
47319
+ while (regexMatch !== null) {
47320
+ taskLines.push({
47321
+ line: regexMatch[0],
47322
+ taskId: regexMatch[1],
47323
+ index: regexMatch.index
47324
+ });
47325
+ regexMatch = taskLineRegex.exec(text);
47326
+ }
47327
+ if (taskLines.length > 5) {
47328
+ const currentIdx = taskLines.findIndex((t) => t.taskId === currentTaskIdForWindow);
47329
+ const windowStart = Math.max(0, currentIdx - 2);
47330
+ const windowEnd = Math.min(taskLines.length - 1, currentIdx + 3);
47331
+ const visibleTasks = taskLines.slice(windowStart, windowEnd + 1);
47332
+ const hiddenBefore = windowStart;
47333
+ const hiddenAfter = taskLines.length - 1 - windowEnd;
47334
+ const totalTasks = taskLines.length;
47335
+ const visibleCount = visibleTasks.length;
47336
+ const firstTaskIndex = taskLines[0].index;
47337
+ const lastTask = taskLines[taskLines.length - 1];
47338
+ const lastTaskEnd = lastTask.index + lastTask.line.length;
47339
+ const before = text.slice(0, firstTaskIndex);
47340
+ const after = text.slice(lastTaskEnd);
47341
+ const visibleLines = visibleTasks.map((t) => t.line).join(`
47342
+ `);
47343
+ const trimComment = `[Task window: showing ${visibleCount} of ${totalTasks} tasks]`;
47344
+ const trimmedMiddle = (hiddenBefore > 0 ? `[...${hiddenBefore} tasks hidden...]
47345
+ ` : "") + visibleLines + (hiddenAfter > 0 ? `
47346
+ [...${hiddenAfter} tasks hidden...]` : "");
47347
+ textPart.text = `${before}${trimmedMiddle}
47348
+ ${trimComment}${after}`;
47349
+ }
47350
+ }
47351
+ }
47187
47352
  const sessionID = lastUserMessage.info?.sessionID;
47188
47353
  const taskIdMatch = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
47189
47354
  const currentTaskId = taskIdMatch ? taskIdMatch[1].trim() : null;
47190
47355
  const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
47191
47356
  const isCoderDelegation = coderDelegationPattern.test(text);
47357
+ const priorCoderTaskId = sessionID ? ensureAgentSession(sessionID).lastCoderDelegationTaskId ?? null : null;
47192
47358
  if (sessionID && isCoderDelegation && currentTaskId) {
47193
47359
  const session = ensureAgentSession(sessionID);
47194
47360
  session.lastCoderDelegationTaskId = currentTaskId;
47361
+ const fileDirPattern = /^FILE:\s*(.+)$/gm;
47362
+ const declaredFiles = [];
47363
+ for (const match of text.matchAll(fileDirPattern)) {
47364
+ const filePath = match[1].trim();
47365
+ if (filePath.length > 0 && !declaredFiles.includes(filePath)) {
47366
+ declaredFiles.push(filePath);
47367
+ }
47368
+ }
47369
+ session.declaredCoderScope = declaredFiles.length > 0 ? declaredFiles : null;
47370
+ try {
47371
+ advanceTaskState(session, currentTaskId, "coder_delegated");
47372
+ } catch (err2) {
47373
+ console.warn(`[delegation-gate] state machine warn: ${err2 instanceof Error ? err2.message : String(err2)}`);
47374
+ }
47195
47375
  }
47196
47376
  if (sessionID && !isCoderDelegation && currentTaskId) {
47197
47377
  const session = ensureAgentSession(sessionID);
@@ -47203,6 +47383,29 @@ Rule 1: DELEGATE all coding to coder. You do NOT write code.`;
47203
47383
  ${text}`;
47204
47384
  }
47205
47385
  }
47386
+ {
47387
+ const deliberationSessionID = lastUserMessage.info?.sessionID;
47388
+ if (deliberationSessionID) {
47389
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(deliberationSessionID)) {} else {
47390
+ const deliberationSession = ensureAgentSession(deliberationSessionID);
47391
+ const lastGate = deliberationSession.lastGateOutcome;
47392
+ let preamble;
47393
+ if (lastGate) {
47394
+ const gateResult = lastGate.passed ? "PASSED" : "FAILED";
47395
+ const sanitizedGate = lastGate.gate.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 64);
47396
+ const sanitizedTaskId = lastGate.taskId.replace(/\[/g, "(").replace(/\]/g, ")").replace(/[\r\n]/g, " ").slice(0, 32);
47397
+ preamble = `[Last gate: ${sanitizedGate} ${gateResult} for task ${sanitizedTaskId}]
47398
+ [DELIBERATE: Before proceeding \u2014 what is the SINGLE next task? What gates must it pass?]`;
47399
+ } else {
47400
+ preamble = `[DELIBERATE: Identify the first task from the plan. What gates must it pass before marking complete?]`;
47401
+ }
47402
+ const currentText = textPart.text ?? "";
47403
+ textPart.text = `${preamble}
47404
+
47405
+ ${currentText}`;
47406
+ }
47407
+ }
47408
+ }
47206
47409
  if (!isCoderDelegation)
47207
47410
  return;
47208
47411
  const warnings = [];
@@ -47245,8 +47448,9 @@ ${text}`;
47245
47448
  const betweenCoders = delegationChain.slice(prevCoderIndex + 1);
47246
47449
  const hasReviewer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "reviewer");
47247
47450
  const hasTestEngineer = betweenCoders.some((d) => stripKnownSwarmPrefix(d.to) === "test_engineer");
47248
- if (!hasReviewer || !hasTestEngineer) {
47249
- const session = ensureAgentSession(sessionID);
47451
+ const session = ensureAgentSession(sessionID);
47452
+ const priorTaskStuckAtCoder = priorCoderTaskId !== null && getTaskState(session, priorCoderTaskId) === "coder_delegated";
47453
+ if (!hasReviewer || !hasTestEngineer || priorTaskStuckAtCoder) {
47250
47454
  if (session.qaSkipCount >= 1) {
47251
47455
  const skippedTasks = session.qaSkipTaskIds.join(", ");
47252
47456
  throw new Error(`\uD83D\uDED1 QA GATE ENFORCEMENT: ${session.qaSkipCount + 1} consecutive coder delegations without reviewer/test_engineer. ` + `Skipped tasks: [${skippedTasks}]. ` + `DELEGATE to reviewer and test_engineer NOW before any further coder work.`);
@@ -47506,6 +47710,16 @@ function getCurrentTaskId(sessionId) {
47506
47710
  const session = swarmState.agentSessions.get(sessionId);
47507
47711
  return session?.currentTaskId ?? `${sessionId}:unknown`;
47508
47712
  }
47713
+ function isInDeclaredScope(filePath, scopeEntries) {
47714
+ const resolvedFile = path25.resolve(filePath);
47715
+ return scopeEntries.some((scope) => {
47716
+ const resolvedScope = path25.resolve(scope);
47717
+ if (resolvedFile === resolvedScope)
47718
+ return true;
47719
+ const rel = path25.relative(resolvedScope, resolvedFile);
47720
+ return rel.length > 0 && !rel.startsWith("..") && !path25.isAbsolute(rel);
47721
+ });
47722
+ }
47509
47723
  function createGuardrailsHooks(directoryOrConfig, config3) {
47510
47724
  let directory;
47511
47725
  let guardrailsConfig;
@@ -47528,19 +47742,40 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47528
47742
  return {
47529
47743
  toolBefore: async (input, output) => {
47530
47744
  const currentSession = swarmState.agentSessions.get(input.sessionID);
47531
- if (currentSession?.delegationActive) {} else if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
47745
+ if (currentSession?.delegationActive) {
47746
+ if (isWriteTool(input.tool)) {
47747
+ const delegArgs = output.args;
47748
+ const delegTargetPath = delegArgs?.filePath ?? delegArgs?.path ?? delegArgs?.file ?? delegArgs?.target;
47749
+ if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
47750
+ if (!currentSession.modifiedFilesThisCoderTask.includes(delegTargetPath)) {
47751
+ currentSession.modifiedFilesThisCoderTask.push(delegTargetPath);
47752
+ }
47753
+ }
47754
+ }
47755
+ } else if (isArchitect(input.sessionID)) {
47756
+ const coderDelegArgs = output.args;
47757
+ const coderDeleg = isAgentDelegation(input.tool, coderDelegArgs);
47758
+ if (coderDeleg.isDelegation && coderDeleg.targetAgent === "coder") {
47759
+ const coderSession = swarmState.agentSessions.get(input.sessionID);
47760
+ if (coderSession) {
47761
+ coderSession.modifiedFilesThisCoderTask = [];
47762
+ }
47763
+ }
47764
+ }
47765
+ if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
47532
47766
  const args2 = output.args;
47533
47767
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
47534
47768
  if (typeof targetPath === "string" && targetPath.length > 0) {
47535
47769
  const resolvedTarget = path25.resolve(directory, targetPath).toLowerCase();
47536
47770
  const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
47537
- if (resolvedTarget === planMdPath) {
47538
- throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47771
+ const planJsonPath = path25.resolve(directory, ".swarm", "plan.json").toLowerCase();
47772
+ if (resolvedTarget === planMdPath || resolvedTarget === planJsonPath) {
47773
+ throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47539
47774
  }
47540
47775
  }
47541
47776
  if (!targetPath && (input.tool === "apply_patch" || input.tool === "patch")) {
47542
47777
  const patchText = args2?.input ?? args2?.patch ?? (Array.isArray(args2?.cmd) ? args2.cmd[1] : undefined);
47543
- if (typeof patchText === "string") {
47778
+ if (typeof patchText === "string" && patchText.length <= 1e6) {
47544
47779
  const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
47545
47780
  const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
47546
47781
  const paths = new Set;
@@ -47552,11 +47787,41 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47552
47787
  if (p !== "/dev/null")
47553
47788
  paths.add(p);
47554
47789
  }
47790
+ const gitDiffPathPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
47791
+ for (const match of patchText.matchAll(gitDiffPathPattern)) {
47792
+ const aPath = match[1].trim();
47793
+ const bPath = match[2].trim();
47794
+ if (aPath !== "/dev/null")
47795
+ paths.add(aPath);
47796
+ if (bPath !== "/dev/null")
47797
+ paths.add(bPath);
47798
+ }
47799
+ const minusPathPattern = /^---\s+a\/(.+)$/gm;
47800
+ for (const match of patchText.matchAll(minusPathPattern)) {
47801
+ const p = match[1].trim();
47802
+ if (p !== "/dev/null")
47803
+ paths.add(p);
47804
+ }
47805
+ const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
47806
+ const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
47807
+ for (const match of patchText.matchAll(traditionalMinusPattern)) {
47808
+ const p = match[1].trim();
47809
+ if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
47810
+ paths.add(p);
47811
+ }
47812
+ }
47813
+ for (const match of patchText.matchAll(traditionalPlusPattern)) {
47814
+ const p = match[1].trim();
47815
+ if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
47816
+ paths.add(p);
47817
+ }
47818
+ }
47555
47819
  for (const p of paths) {
47556
47820
  const resolvedP = path25.resolve(directory, p);
47557
- const planMdPath = path25.resolve(directory, ".swarm", "plan.md");
47558
- if (resolvedP.toLowerCase() === planMdPath.toLowerCase()) {
47559
- throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47821
+ const planMdPath = path25.resolve(directory, ".swarm", "plan.md").toLowerCase();
47822
+ const planJsonPath = path25.resolve(directory, ".swarm", "plan.json").toLowerCase();
47823
+ if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
47824
+ throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
47560
47825
  }
47561
47826
  if (isOutsideSwarmDir(p, directory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
47562
47827
  const session2 = swarmState.agentSessions.get(input.sessionID);
@@ -47746,6 +48011,26 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47746
48011
  };
47747
48012
  } else {
47748
48013
  session.lastGateFailure = null;
48014
+ if (input.tool === "pre_check_batch") {
48015
+ const successStr = typeof output.output === "string" ? output.output : "";
48016
+ let isPassed = false;
48017
+ try {
48018
+ const result = JSON.parse(successStr);
48019
+ isPassed = result.gates_passed === true;
48020
+ } catch {
48021
+ isPassed = false;
48022
+ }
48023
+ if (isPassed && session.currentTaskId) {
48024
+ try {
48025
+ advanceTaskState(session, session.currentTaskId, "pre_check_passed");
48026
+ } catch (err2) {
48027
+ warn("Failed to advance task state after pre_check_batch pass", {
48028
+ taskId: session.currentTaskId,
48029
+ error: String(err2)
48030
+ });
48031
+ }
48032
+ }
48033
+ }
47749
48034
  }
47750
48035
  }
47751
48036
  const inputArgs = inputArgsByCallID.get(input.callID);
@@ -47766,6 +48051,15 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47766
48051
  if (delegation.isDelegation && delegation.targetAgent === "coder" && session.lastCoderDelegationTaskId) {
47767
48052
  session.currentTaskId = session.lastCoderDelegationTaskId;
47768
48053
  session.partialGateWarningsIssuedForTask?.delete(session.currentTaskId);
48054
+ if (session.declaredCoderScope !== null) {
48055
+ const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope));
48056
+ if (undeclaredFiles.length > 2) {
48057
+ const safeTaskId = String(session.currentTaskId ?? "").replace(/[\r\n\t]/g, "_");
48058
+ session.lastScopeViolation = `Scope violation for task ${safeTaskId}: ` + `${undeclaredFiles.length} undeclared files modified: ` + undeclaredFiles.join(", ");
48059
+ session.scopeViolationDetected = true;
48060
+ }
48061
+ }
48062
+ session.modifiedFilesThisCoderTask = [];
47769
48063
  }
47770
48064
  }
47771
48065
  const window2 = getActiveWindow(input.sessionID);
@@ -47789,6 +48083,26 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47789
48083
  if (!sessionId) {
47790
48084
  return;
47791
48085
  }
48086
+ {
48087
+ const { modelID } = extractModelInfo(messages);
48088
+ if (modelID && isLowCapabilityModel(modelID)) {
48089
+ for (const msg of messages) {
48090
+ if (msg.info?.role !== "system")
48091
+ continue;
48092
+ for (const part of msg.parts) {
48093
+ try {
48094
+ if (part == null)
48095
+ continue;
48096
+ if (part.type !== "text" || typeof part.text !== "string")
48097
+ continue;
48098
+ if (!part.text.includes("<!-- BEHAVIORAL_GUIDANCE_START -->"))
48099
+ continue;
48100
+ part.text = part.text.replace(/<!--\s*BEHAVIORAL_GUIDANCE_START\s*-->[\s\S]*?<!--\s*BEHAVIORAL_GUIDANCE_END\s*-->/g, "[Enforcement: programmatic gates active]");
48101
+ } catch {}
48102
+ }
48103
+ }
48104
+ }
48105
+ }
47792
48106
  const session = swarmState.agentSessions.get(sessionId);
47793
48107
  const activeAgent = swarmState.activeAgent.get(sessionId);
47794
48108
  const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
@@ -47861,6 +48175,16 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
47861
48175
  }
47862
48176
  }
47863
48177
  }
48178
+ if (isArchitectSessionForGates && session && session.scopeViolationDetected) {
48179
+ session.scopeViolationDetected = false;
48180
+ const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
48181
+ if (textPart2 && session.lastScopeViolation) {
48182
+ textPart2.text = `\u26A0\uFE0F SCOPE VIOLATION: ${session.lastScopeViolation}
48183
+ ` + `Only modify files within your declared scope. Request scope expansion from architect if needed.
48184
+
48185
+ ` + textPart2.text;
48186
+ }
48187
+ }
47864
48188
  if (isArchitectSessionForGates && session && session.catastrophicPhaseWarnings) {
47865
48189
  try {
47866
48190
  const plan = await loadPlan(directory);
@@ -49707,7 +50031,11 @@ function createToolSummarizerHook(config3, directory) {
49707
50031
  if (typeof output.output !== "string" || output.output.length === 0) {
49708
50032
  return;
49709
50033
  }
49710
- const exemptTools = config3.exempt_tools ?? ["retrieve_summary", "task"];
50034
+ const exemptTools = config3.exempt_tools ?? [
50035
+ "retrieve_summary",
50036
+ "task",
50037
+ "read"
50038
+ ];
49711
50039
  if (exemptTools.includes(input.tool)) {
49712
50040
  return;
49713
50041
  }
@@ -50700,7 +51028,12 @@ function deserializeAgentSession(s) {
50700
51028
  lastPhaseCompletePhase: s.lastPhaseCompletePhase ?? 0,
50701
51029
  phaseAgentsDispatched,
50702
51030
  qaSkipCount: s.qaSkipCount ?? 0,
50703
- qaSkipTaskIds: s.qaSkipTaskIds ?? []
51031
+ qaSkipTaskIds: s.qaSkipTaskIds ?? [],
51032
+ taskWorkflowStates: new Map,
51033
+ lastGateOutcome: null,
51034
+ declaredCoderScope: null,
51035
+ lastScopeViolation: null,
51036
+ modifiedFilesThisCoderTask: []
50704
51037
  };
50705
51038
  }
50706
51039
  async function readSnapshot(directory) {
@@ -51512,6 +51845,184 @@ var complexity_hotspots = createSwarmTool({
51512
51845
  }
51513
51846
  }
51514
51847
  });
51848
+ // src/tools/declare-scope.ts
51849
+ init_tool();
51850
+ import * as fs19 from "fs";
51851
+ import * as path30 from "path";
51852
+ init_create_tool();
51853
+ function validateTaskIdFormat(taskId) {
51854
+ const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
51855
+ if (!taskIdPattern.test(taskId)) {
51856
+ return `Invalid taskId "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
51857
+ }
51858
+ return;
51859
+ }
51860
+ function validateFiles(files) {
51861
+ const errors5 = [];
51862
+ for (const file3 of files) {
51863
+ if (file3.includes("\x00")) {
51864
+ errors5.push(`Invalid file "${file3}": null bytes are not allowed`);
51865
+ }
51866
+ if (file3.includes("..")) {
51867
+ errors5.push(`Invalid file "${file3}": path traversal sequences (..) are not allowed`);
51868
+ }
51869
+ if (file3.length > 4096) {
51870
+ errors5.push(`Invalid file "${file3}": path exceeds maximum length of 4096 characters`);
51871
+ }
51872
+ }
51873
+ return errors5;
51874
+ }
51875
+ async function executeDeclareScope(args2, fallbackDir) {
51876
+ const taskIdError = validateTaskIdFormat(args2.taskId);
51877
+ if (taskIdError) {
51878
+ return {
51879
+ success: false,
51880
+ message: "Validation failed",
51881
+ errors: [taskIdError]
51882
+ };
51883
+ }
51884
+ if (!Array.isArray(args2.files) || args2.files.length === 0) {
51885
+ return {
51886
+ success: false,
51887
+ message: "Validation failed",
51888
+ errors: ["files must be a non-empty array"]
51889
+ };
51890
+ }
51891
+ const fileErrors = validateFiles(args2.files);
51892
+ if (fileErrors.length > 0) {
51893
+ return {
51894
+ success: false,
51895
+ message: "Validation failed",
51896
+ errors: fileErrors
51897
+ };
51898
+ }
51899
+ if (args2.whitelist) {
51900
+ const whitelistErrors = validateFiles(args2.whitelist);
51901
+ if (whitelistErrors.length > 0) {
51902
+ return {
51903
+ success: false,
51904
+ message: "Validation failed",
51905
+ errors: whitelistErrors
51906
+ };
51907
+ }
51908
+ }
51909
+ let normalizedDir;
51910
+ if (args2.working_directory != null) {
51911
+ if (args2.working_directory.includes("\x00")) {
51912
+ return {
51913
+ success: false,
51914
+ message: "Invalid working_directory: null bytes are not allowed",
51915
+ errors: ["Invalid working_directory: null bytes are not allowed"]
51916
+ };
51917
+ }
51918
+ if (process.platform === "win32") {
51919
+ const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
51920
+ if (devicePathPattern.test(args2.working_directory)) {
51921
+ return {
51922
+ success: false,
51923
+ message: "Invalid working_directory: Windows device paths are not allowed",
51924
+ errors: [
51925
+ "Invalid working_directory: Windows device paths are not allowed"
51926
+ ]
51927
+ };
51928
+ }
51929
+ }
51930
+ normalizedDir = path30.normalize(args2.working_directory);
51931
+ const pathParts = normalizedDir.split(path30.sep);
51932
+ if (pathParts.includes("..")) {
51933
+ return {
51934
+ success: false,
51935
+ message: "Invalid working_directory: path traversal sequences (..) are not allowed",
51936
+ errors: [
51937
+ "Invalid working_directory: path traversal sequences (..) are not allowed"
51938
+ ]
51939
+ };
51940
+ }
51941
+ const resolvedDir = path30.resolve(normalizedDir);
51942
+ try {
51943
+ const realPath = fs19.realpathSync(resolvedDir);
51944
+ const planPath2 = path30.join(realPath, ".swarm", "plan.json");
51945
+ if (!fs19.existsSync(planPath2)) {
51946
+ return {
51947
+ success: false,
51948
+ message: `Invalid working_directory: plan not found in "${realPath}"`,
51949
+ errors: [
51950
+ `Invalid working_directory: plan not found in "${realPath}"`
51951
+ ]
51952
+ };
51953
+ }
51954
+ } catch {
51955
+ return {
51956
+ success: false,
51957
+ message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
51958
+ errors: [
51959
+ `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
51960
+ ]
51961
+ };
51962
+ }
51963
+ }
51964
+ const directory = normalizedDir ?? fallbackDir ?? process.cwd();
51965
+ const planPath = path30.resolve(directory, ".swarm", "plan.json");
51966
+ if (!fs19.existsSync(planPath)) {
51967
+ return {
51968
+ success: false,
51969
+ message: "No plan found",
51970
+ errors: ["plan.json not found"]
51971
+ };
51972
+ }
51973
+ let planContent;
51974
+ try {
51975
+ planContent = JSON.parse(fs19.readFileSync(planPath, "utf-8"));
51976
+ } catch {
51977
+ return {
51978
+ success: false,
51979
+ message: "Failed to parse plan.json",
51980
+ errors: ["plan.json is not valid JSON"]
51981
+ };
51982
+ }
51983
+ const allTasks = planContent.phases?.flatMap((p) => p.tasks ?? []) ?? [];
51984
+ const taskExists = allTasks.some((t) => t.id === args2.taskId);
51985
+ if (!taskExists) {
51986
+ return {
51987
+ success: false,
51988
+ message: `Task ${args2.taskId} not found in plan`,
51989
+ errors: [`Task ${args2.taskId} does not exist in plan.json`]
51990
+ };
51991
+ }
51992
+ for (const [_sessionId, session] of swarmState.agentSessions) {
51993
+ const taskState = getTaskState(session, args2.taskId);
51994
+ if (taskState === "complete") {
51995
+ return {
51996
+ success: false,
51997
+ message: `Task ${args2.taskId} is already completed`,
51998
+ errors: [`Cannot declare scope for completed task ${args2.taskId}`]
51999
+ };
52000
+ }
52001
+ }
52002
+ const mergedFiles = [...args2.files, ...args2.whitelist ?? []];
52003
+ for (const [_sessionId, session] of swarmState.agentSessions) {
52004
+ session.declaredCoderScope = mergedFiles;
52005
+ session.lastScopeViolation = null;
52006
+ }
52007
+ return {
52008
+ success: true,
52009
+ message: "Scope declared successfully",
52010
+ taskId: args2.taskId,
52011
+ fileCount: mergedFiles.length
52012
+ };
52013
+ }
52014
+ var declare_scope = createSwarmTool({
52015
+ description: "Declare the file scope for the next coder delegation. " + "Sets the list of files the coder is permitted to modify for a specific task. " + "Must be called before delegating to mega_coder to enable scope containment checking.",
52016
+ args: {
52017
+ taskId: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID for which scope is being declared, e.g. "1.1", "1.2.3"'),
52018
+ files: tool.schema.array(tool.schema.string().min(1).max(4096)).min(1).describe("Array of file paths the coder is permitted to modify"),
52019
+ whitelist: tool.schema.array(tool.schema.string().min(1).max(4096)).optional().describe("Additional file paths to whitelist (merged with files)"),
52020
+ working_directory: tool.schema.string().optional().describe("Working directory where the plan is located")
52021
+ },
52022
+ execute: async (args2, _directory) => {
52023
+ return JSON.stringify(await executeDeclareScope(args2, _directory), null, 2);
52024
+ }
52025
+ });
51515
52026
  // src/tools/diff.ts
51516
52027
  init_dist();
51517
52028
  import { execFileSync } from "child_process";
@@ -51541,20 +52052,20 @@ function validateBase(base) {
51541
52052
  function validatePaths(paths) {
51542
52053
  if (!paths)
51543
52054
  return null;
51544
- for (const path30 of paths) {
51545
- if (!path30 || path30.length === 0) {
52055
+ for (const path31 of paths) {
52056
+ if (!path31 || path31.length === 0) {
51546
52057
  return "empty path not allowed";
51547
52058
  }
51548
- if (path30.length > MAX_PATH_LENGTH) {
52059
+ if (path31.length > MAX_PATH_LENGTH) {
51549
52060
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
51550
52061
  }
51551
- if (SHELL_METACHARACTERS2.test(path30)) {
52062
+ if (SHELL_METACHARACTERS2.test(path31)) {
51552
52063
  return "path contains shell metacharacters";
51553
52064
  }
51554
- if (path30.startsWith("-")) {
52065
+ if (path31.startsWith("-")) {
51555
52066
  return 'path cannot start with "-" (option-like arguments not allowed)';
51556
52067
  }
51557
- if (CONTROL_CHAR_PATTERN2.test(path30)) {
52068
+ if (CONTROL_CHAR_PATTERN2.test(path31)) {
51558
52069
  return "path contains control characters";
51559
52070
  }
51560
52071
  }
@@ -51634,8 +52145,8 @@ var diff = tool({
51634
52145
  if (parts2.length >= 3) {
51635
52146
  const additions = parseInt(parts2[0], 10) || 0;
51636
52147
  const deletions = parseInt(parts2[1], 10) || 0;
51637
- const path30 = parts2[2];
51638
- files.push({ path: path30, additions, deletions });
52148
+ const path31 = parts2[2];
52149
+ files.push({ path: path31, additions, deletions });
51639
52150
  }
51640
52151
  }
51641
52152
  const contractChanges = [];
@@ -51864,8 +52375,8 @@ Use these as DOMAIN values when delegating to @sme.`;
51864
52375
  // src/tools/evidence-check.ts
51865
52376
  init_dist();
51866
52377
  init_create_tool();
51867
- import * as fs19 from "fs";
51868
- import * as path30 from "path";
52378
+ import * as fs20 from "fs";
52379
+ import * as path31 from "path";
51869
52380
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
51870
52381
  var MAX_EVIDENCE_FILES = 1000;
51871
52382
  var EVIDENCE_DIR = ".swarm/evidence";
@@ -51888,9 +52399,9 @@ function validateRequiredTypes(input) {
51888
52399
  return null;
51889
52400
  }
51890
52401
  function isPathWithinSwarm(filePath, cwd) {
51891
- const normalizedCwd = path30.resolve(cwd);
51892
- const swarmPath = path30.join(normalizedCwd, ".swarm");
51893
- const normalizedPath = path30.resolve(filePath);
52402
+ const normalizedCwd = path31.resolve(cwd);
52403
+ const swarmPath = path31.join(normalizedCwd, ".swarm");
52404
+ const normalizedPath = path31.resolve(filePath);
51894
52405
  return normalizedPath.startsWith(swarmPath);
51895
52406
  }
51896
52407
  function parseCompletedTasks(planContent) {
@@ -51906,12 +52417,12 @@ function parseCompletedTasks(planContent) {
51906
52417
  }
51907
52418
  function readEvidenceFiles(evidenceDir, _cwd) {
51908
52419
  const evidence = [];
51909
- if (!fs19.existsSync(evidenceDir) || !fs19.statSync(evidenceDir).isDirectory()) {
52420
+ if (!fs20.existsSync(evidenceDir) || !fs20.statSync(evidenceDir).isDirectory()) {
51910
52421
  return evidence;
51911
52422
  }
51912
52423
  let files;
51913
52424
  try {
51914
- files = fs19.readdirSync(evidenceDir);
52425
+ files = fs20.readdirSync(evidenceDir);
51915
52426
  } catch {
51916
52427
  return evidence;
51917
52428
  }
@@ -51920,14 +52431,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
51920
52431
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
51921
52432
  continue;
51922
52433
  }
51923
- const filePath = path30.join(evidenceDir, filename);
52434
+ const filePath = path31.join(evidenceDir, filename);
51924
52435
  try {
51925
- const resolvedPath = path30.resolve(filePath);
51926
- const evidenceDirResolved = path30.resolve(evidenceDir);
52436
+ const resolvedPath = path31.resolve(filePath);
52437
+ const evidenceDirResolved = path31.resolve(evidenceDir);
51927
52438
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
51928
52439
  continue;
51929
52440
  }
51930
- const stat2 = fs19.lstatSync(filePath);
52441
+ const stat2 = fs20.lstatSync(filePath);
51931
52442
  if (!stat2.isFile()) {
51932
52443
  continue;
51933
52444
  }
@@ -51936,7 +52447,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
51936
52447
  }
51937
52448
  let fileStat;
51938
52449
  try {
51939
- fileStat = fs19.statSync(filePath);
52450
+ fileStat = fs20.statSync(filePath);
51940
52451
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
51941
52452
  continue;
51942
52453
  }
@@ -51945,7 +52456,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
51945
52456
  }
51946
52457
  let content;
51947
52458
  try {
51948
- content = fs19.readFileSync(filePath, "utf-8");
52459
+ content = fs20.readFileSync(filePath, "utf-8");
51949
52460
  } catch {
51950
52461
  continue;
51951
52462
  }
@@ -52030,7 +52541,7 @@ var evidence_check = createSwarmTool({
52030
52541
  return JSON.stringify(errorResult, null, 2);
52031
52542
  }
52032
52543
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
52033
- const planPath = path30.join(cwd, PLAN_FILE);
52544
+ const planPath = path31.join(cwd, PLAN_FILE);
52034
52545
  if (!isPathWithinSwarm(planPath, cwd)) {
52035
52546
  const errorResult = {
52036
52547
  error: "plan file path validation failed",
@@ -52044,7 +52555,7 @@ var evidence_check = createSwarmTool({
52044
52555
  }
52045
52556
  let planContent;
52046
52557
  try {
52047
- planContent = fs19.readFileSync(planPath, "utf-8");
52558
+ planContent = fs20.readFileSync(planPath, "utf-8");
52048
52559
  } catch {
52049
52560
  const result2 = {
52050
52561
  message: "No completed tasks found in plan.",
@@ -52062,7 +52573,7 @@ var evidence_check = createSwarmTool({
52062
52573
  };
52063
52574
  return JSON.stringify(result2, null, 2);
52064
52575
  }
52065
- const evidenceDir = path30.join(cwd, EVIDENCE_DIR);
52576
+ const evidenceDir = path31.join(cwd, EVIDENCE_DIR);
52066
52577
  const evidence = readEvidenceFiles(evidenceDir, cwd);
52067
52578
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
52068
52579
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -52079,8 +52590,8 @@ var evidence_check = createSwarmTool({
52079
52590
  // src/tools/file-extractor.ts
52080
52591
  init_tool();
52081
52592
  init_create_tool();
52082
- import * as fs20 from "fs";
52083
- import * as path31 from "path";
52593
+ import * as fs21 from "fs";
52594
+ import * as path32 from "path";
52084
52595
  var EXT_MAP = {
52085
52596
  python: ".py",
52086
52597
  py: ".py",
@@ -52142,8 +52653,8 @@ var extract_code_blocks = createSwarmTool({
52142
52653
  execute: async (args2, directory) => {
52143
52654
  const { content, output_dir, prefix } = args2;
52144
52655
  const targetDir = output_dir || directory;
52145
- if (!fs20.existsSync(targetDir)) {
52146
- fs20.mkdirSync(targetDir, { recursive: true });
52656
+ if (!fs21.existsSync(targetDir)) {
52657
+ fs21.mkdirSync(targetDir, { recursive: true });
52147
52658
  }
52148
52659
  if (!content) {
52149
52660
  return "Error: content is required";
@@ -52161,16 +52672,16 @@ var extract_code_blocks = createSwarmTool({
52161
52672
  if (prefix) {
52162
52673
  filename = `${prefix}_${filename}`;
52163
52674
  }
52164
- let filepath = path31.join(targetDir, filename);
52165
- const base = path31.basename(filepath, path31.extname(filepath));
52166
- const ext = path31.extname(filepath);
52675
+ let filepath = path32.join(targetDir, filename);
52676
+ const base = path32.basename(filepath, path32.extname(filepath));
52677
+ const ext = path32.extname(filepath);
52167
52678
  let counter = 1;
52168
- while (fs20.existsSync(filepath)) {
52169
- filepath = path31.join(targetDir, `${base}_${counter}${ext}`);
52679
+ while (fs21.existsSync(filepath)) {
52680
+ filepath = path32.join(targetDir, `${base}_${counter}${ext}`);
52170
52681
  counter++;
52171
52682
  }
52172
52683
  try {
52173
- fs20.writeFileSync(filepath, code.trim(), "utf-8");
52684
+ fs21.writeFileSync(filepath, code.trim(), "utf-8");
52174
52685
  savedFiles.push(filepath);
52175
52686
  } catch (error93) {
52176
52687
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -52199,7 +52710,7 @@ init_dist();
52199
52710
  var GITINGEST_TIMEOUT_MS = 1e4;
52200
52711
  var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
52201
52712
  var GITINGEST_MAX_RETRIES = 2;
52202
- var delay = (ms) => new Promise((resolve11) => setTimeout(resolve11, ms));
52713
+ var delay = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
52203
52714
  async function fetchGitingest(args2) {
52204
52715
  for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
52205
52716
  try {
@@ -52278,8 +52789,8 @@ var gitingest = tool({
52278
52789
  });
52279
52790
  // src/tools/imports.ts
52280
52791
  init_dist();
52281
- import * as fs21 from "fs";
52282
- import * as path32 from "path";
52792
+ import * as fs22 from "fs";
52793
+ import * as path33 from "path";
52283
52794
  var MAX_FILE_PATH_LENGTH2 = 500;
52284
52795
  var MAX_SYMBOL_LENGTH = 256;
52285
52796
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -52333,7 +52844,7 @@ function validateSymbolInput(symbol3) {
52333
52844
  return null;
52334
52845
  }
52335
52846
  function isBinaryFile2(filePath, buffer) {
52336
- const ext = path32.extname(filePath).toLowerCase();
52847
+ const ext = path33.extname(filePath).toLowerCase();
52337
52848
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
52338
52849
  return false;
52339
52850
  }
@@ -52357,15 +52868,15 @@ function parseImports(content, targetFile, targetSymbol) {
52357
52868
  const imports = [];
52358
52869
  let _resolvedTarget;
52359
52870
  try {
52360
- _resolvedTarget = path32.resolve(targetFile);
52871
+ _resolvedTarget = path33.resolve(targetFile);
52361
52872
  } catch {
52362
52873
  _resolvedTarget = targetFile;
52363
52874
  }
52364
- const targetBasename = path32.basename(targetFile, path32.extname(targetFile));
52875
+ const targetBasename = path33.basename(targetFile, path33.extname(targetFile));
52365
52876
  const targetWithExt = targetFile;
52366
52877
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
52367
- const normalizedTargetWithExt = path32.normalize(targetWithExt).replace(/\\/g, "/");
52368
- const normalizedTargetWithoutExt = path32.normalize(targetWithoutExt).replace(/\\/g, "/");
52878
+ const normalizedTargetWithExt = path33.normalize(targetWithExt).replace(/\\/g, "/");
52879
+ const normalizedTargetWithoutExt = path33.normalize(targetWithoutExt).replace(/\\/g, "/");
52369
52880
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
52370
52881
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
52371
52882
  const modulePath = match[1] || match[2] || match[3];
@@ -52388,9 +52899,9 @@ function parseImports(content, targetFile, targetSymbol) {
52388
52899
  }
52389
52900
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
52390
52901
  let isMatch = false;
52391
- const _targetDir = path32.dirname(targetFile);
52392
- const targetExt = path32.extname(targetFile);
52393
- const targetBasenameNoExt = path32.basename(targetFile, targetExt);
52902
+ const _targetDir = path33.dirname(targetFile);
52903
+ const targetExt = path33.extname(targetFile);
52904
+ const targetBasenameNoExt = path33.basename(targetFile, targetExt);
52394
52905
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
52395
52906
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
52396
52907
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -52447,7 +52958,7 @@ var SKIP_DIRECTORIES2 = new Set([
52447
52958
  function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
52448
52959
  let entries;
52449
52960
  try {
52450
- entries = fs21.readdirSync(dir);
52961
+ entries = fs22.readdirSync(dir);
52451
52962
  } catch (e) {
52452
52963
  stats.fileErrors.push({
52453
52964
  path: dir,
@@ -52458,13 +52969,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
52458
52969
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
52459
52970
  for (const entry of entries) {
52460
52971
  if (SKIP_DIRECTORIES2.has(entry)) {
52461
- stats.skippedDirs.push(path32.join(dir, entry));
52972
+ stats.skippedDirs.push(path33.join(dir, entry));
52462
52973
  continue;
52463
52974
  }
52464
- const fullPath = path32.join(dir, entry);
52975
+ const fullPath = path33.join(dir, entry);
52465
52976
  let stat2;
52466
52977
  try {
52467
- stat2 = fs21.statSync(fullPath);
52978
+ stat2 = fs22.statSync(fullPath);
52468
52979
  } catch (e) {
52469
52980
  stats.fileErrors.push({
52470
52981
  path: fullPath,
@@ -52475,7 +52986,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
52475
52986
  if (stat2.isDirectory()) {
52476
52987
  findSourceFiles2(fullPath, files, stats);
52477
52988
  } else if (stat2.isFile()) {
52478
- const ext = path32.extname(fullPath).toLowerCase();
52989
+ const ext = path33.extname(fullPath).toLowerCase();
52479
52990
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
52480
52991
  files.push(fullPath);
52481
52992
  }
@@ -52531,8 +53042,8 @@ var imports = tool({
52531
53042
  return JSON.stringify(errorResult, null, 2);
52532
53043
  }
52533
53044
  try {
52534
- const targetFile = path32.resolve(file3);
52535
- if (!fs21.existsSync(targetFile)) {
53045
+ const targetFile = path33.resolve(file3);
53046
+ if (!fs22.existsSync(targetFile)) {
52536
53047
  const errorResult = {
52537
53048
  error: `target file not found: ${file3}`,
52538
53049
  target: file3,
@@ -52542,7 +53053,7 @@ var imports = tool({
52542
53053
  };
52543
53054
  return JSON.stringify(errorResult, null, 2);
52544
53055
  }
52545
- const targetStat = fs21.statSync(targetFile);
53056
+ const targetStat = fs22.statSync(targetFile);
52546
53057
  if (!targetStat.isFile()) {
52547
53058
  const errorResult = {
52548
53059
  error: "target must be a file, not a directory",
@@ -52553,7 +53064,7 @@ var imports = tool({
52553
53064
  };
52554
53065
  return JSON.stringify(errorResult, null, 2);
52555
53066
  }
52556
- const baseDir = path32.dirname(targetFile);
53067
+ const baseDir = path33.dirname(targetFile);
52557
53068
  const scanStats = {
52558
53069
  skippedDirs: [],
52559
53070
  skippedFiles: 0,
@@ -52568,12 +53079,12 @@ var imports = tool({
52568
53079
  if (consumers.length >= MAX_CONSUMERS)
52569
53080
  break;
52570
53081
  try {
52571
- const stat2 = fs21.statSync(filePath);
53082
+ const stat2 = fs22.statSync(filePath);
52572
53083
  if (stat2.size > MAX_FILE_SIZE_BYTES4) {
52573
53084
  skippedFileCount++;
52574
53085
  continue;
52575
53086
  }
52576
- const buffer = fs21.readFileSync(filePath);
53087
+ const buffer = fs22.readFileSync(filePath);
52577
53088
  if (isBinaryFile2(filePath, buffer)) {
52578
53089
  skippedFileCount++;
52579
53090
  continue;
@@ -52642,8 +53153,8 @@ init_lint();
52642
53153
 
52643
53154
  // src/tools/phase-complete.ts
52644
53155
  init_dist();
52645
- import * as fs22 from "fs";
52646
- import * as path33 from "path";
53156
+ import * as fs23 from "fs";
53157
+ import * as path34 from "path";
52647
53158
  init_manager();
52648
53159
  init_utils2();
52649
53160
  init_create_tool();
@@ -52833,7 +53344,7 @@ async function executePhaseComplete(args2, workingDirectory) {
52833
53344
  }
52834
53345
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
52835
53346
  try {
52836
- const projectName = path33.basename(dir);
53347
+ const projectName = path34.basename(dir);
52837
53348
  const knowledgeConfig = {
52838
53349
  enabled: true,
52839
53350
  swarm_max_entries: 100,
@@ -52894,7 +53405,7 @@ async function executePhaseComplete(args2, workingDirectory) {
52894
53405
  };
52895
53406
  try {
52896
53407
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
52897
- fs22.appendFileSync(eventsPath, `${JSON.stringify(event)}
53408
+ fs23.appendFileSync(eventsPath, `${JSON.stringify(event)}
52898
53409
  `, "utf-8");
52899
53410
  } catch (writeError) {
52900
53411
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -52952,8 +53463,8 @@ init_dist();
52952
53463
  init_discovery();
52953
53464
  init_utils();
52954
53465
  init_create_tool();
52955
- import * as fs23 from "fs";
52956
- import * as path34 from "path";
53466
+ import * as fs24 from "fs";
53467
+ import * as path35 from "path";
52957
53468
  var MAX_OUTPUT_BYTES5 = 52428800;
52958
53469
  var AUDIT_TIMEOUT_MS = 120000;
52959
53470
  function isValidEcosystem(value) {
@@ -52971,28 +53482,28 @@ function validateArgs3(args2) {
52971
53482
  function detectEcosystems(directory) {
52972
53483
  const ecosystems = [];
52973
53484
  const cwd = directory;
52974
- if (fs23.existsSync(path34.join(cwd, "package.json"))) {
53485
+ if (fs24.existsSync(path35.join(cwd, "package.json"))) {
52975
53486
  ecosystems.push("npm");
52976
53487
  }
52977
- if (fs23.existsSync(path34.join(cwd, "pyproject.toml")) || fs23.existsSync(path34.join(cwd, "requirements.txt"))) {
53488
+ if (fs24.existsSync(path35.join(cwd, "pyproject.toml")) || fs24.existsSync(path35.join(cwd, "requirements.txt"))) {
52978
53489
  ecosystems.push("pip");
52979
53490
  }
52980
- if (fs23.existsSync(path34.join(cwd, "Cargo.toml"))) {
53491
+ if (fs24.existsSync(path35.join(cwd, "Cargo.toml"))) {
52981
53492
  ecosystems.push("cargo");
52982
53493
  }
52983
- if (fs23.existsSync(path34.join(cwd, "go.mod"))) {
53494
+ if (fs24.existsSync(path35.join(cwd, "go.mod"))) {
52984
53495
  ecosystems.push("go");
52985
53496
  }
52986
53497
  try {
52987
- const files = fs23.readdirSync(cwd);
53498
+ const files = fs24.readdirSync(cwd);
52988
53499
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
52989
53500
  ecosystems.push("dotnet");
52990
53501
  }
52991
53502
  } catch {}
52992
- if (fs23.existsSync(path34.join(cwd, "Gemfile")) || fs23.existsSync(path34.join(cwd, "Gemfile.lock"))) {
53503
+ if (fs24.existsSync(path35.join(cwd, "Gemfile")) || fs24.existsSync(path35.join(cwd, "Gemfile.lock"))) {
52993
53504
  ecosystems.push("ruby");
52994
53505
  }
52995
- if (fs23.existsSync(path34.join(cwd, "pubspec.yaml"))) {
53506
+ if (fs24.existsSync(path35.join(cwd, "pubspec.yaml"))) {
52996
53507
  ecosystems.push("dart");
52997
53508
  }
52998
53509
  return ecosystems;
@@ -53005,7 +53516,7 @@ async function runNpmAudit(directory) {
53005
53516
  stderr: "pipe",
53006
53517
  cwd: directory
53007
53518
  });
53008
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53519
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53009
53520
  const result = await Promise.race([
53010
53521
  Promise.all([
53011
53522
  new Response(proc.stdout).text(),
@@ -53128,7 +53639,7 @@ async function runPipAudit(directory) {
53128
53639
  stderr: "pipe",
53129
53640
  cwd: directory
53130
53641
  });
53131
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53642
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53132
53643
  const result = await Promise.race([
53133
53644
  Promise.all([
53134
53645
  new Response(proc.stdout).text(),
@@ -53259,7 +53770,7 @@ async function runCargoAudit(directory) {
53259
53770
  stderr: "pipe",
53260
53771
  cwd: directory
53261
53772
  });
53262
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53773
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53263
53774
  const result = await Promise.race([
53264
53775
  Promise.all([
53265
53776
  new Response(proc.stdout).text(),
@@ -53386,7 +53897,7 @@ async function runGoAudit(directory) {
53386
53897
  stderr: "pipe",
53387
53898
  cwd: directory
53388
53899
  });
53389
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
53900
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53390
53901
  const result = await Promise.race([
53391
53902
  Promise.all([
53392
53903
  new Response(proc.stdout).text(),
@@ -53522,7 +54033,7 @@ async function runDotnetAudit(directory) {
53522
54033
  stderr: "pipe",
53523
54034
  cwd: directory
53524
54035
  });
53525
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
54036
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53526
54037
  const result = await Promise.race([
53527
54038
  Promise.all([
53528
54039
  new Response(proc.stdout).text(),
@@ -53641,7 +54152,7 @@ async function runBundleAudit(directory) {
53641
54152
  stderr: "pipe",
53642
54153
  cwd: directory
53643
54154
  });
53644
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
54155
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53645
54156
  const result = await Promise.race([
53646
54157
  Promise.all([
53647
54158
  new Response(proc.stdout).text(),
@@ -53788,7 +54299,7 @@ async function runDartAudit(directory) {
53788
54299
  stderr: "pipe",
53789
54300
  cwd: directory
53790
54301
  });
53791
- const timeoutPromise = new Promise((resolve12) => setTimeout(() => resolve12("timeout"), AUDIT_TIMEOUT_MS));
54302
+ const timeoutPromise = new Promise((resolve13) => setTimeout(() => resolve13("timeout"), AUDIT_TIMEOUT_MS));
53792
54303
  const result = await Promise.race([
53793
54304
  Promise.all([
53794
54305
  new Response(proc.stdout).text(),
@@ -54053,8 +54564,8 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
54053
54564
  ]);
54054
54565
  // src/tools/pre-check-batch.ts
54055
54566
  init_dist();
54056
- import * as fs26 from "fs";
54057
- import * as path37 from "path";
54567
+ import * as fs27 from "fs";
54568
+ import * as path38 from "path";
54058
54569
 
54059
54570
  // node_modules/yocto-queue/index.js
54060
54571
  class Node2 {
@@ -54145,26 +54656,26 @@ function pLimit(concurrency) {
54145
54656
  activeCount--;
54146
54657
  resumeNext();
54147
54658
  };
54148
- const run2 = async (function_, resolve12, arguments_2) => {
54659
+ const run2 = async (function_, resolve13, arguments_2) => {
54149
54660
  const result = (async () => function_(...arguments_2))();
54150
- resolve12(result);
54661
+ resolve13(result);
54151
54662
  try {
54152
54663
  await result;
54153
54664
  } catch {}
54154
54665
  next();
54155
54666
  };
54156
- const enqueue = (function_, resolve12, reject, arguments_2) => {
54667
+ const enqueue = (function_, resolve13, reject, arguments_2) => {
54157
54668
  const queueItem = { reject };
54158
54669
  new Promise((internalResolve) => {
54159
54670
  queueItem.run = internalResolve;
54160
54671
  queue.enqueue(queueItem);
54161
- }).then(run2.bind(undefined, function_, resolve12, arguments_2));
54672
+ }).then(run2.bind(undefined, function_, resolve13, arguments_2));
54162
54673
  if (activeCount < concurrency) {
54163
54674
  resumeNext();
54164
54675
  }
54165
54676
  };
54166
- const generator = (function_, ...arguments_2) => new Promise((resolve12, reject) => {
54167
- enqueue(function_, resolve12, reject, arguments_2);
54677
+ const generator = (function_, ...arguments_2) => new Promise((resolve13, reject) => {
54678
+ enqueue(function_, resolve13, reject, arguments_2);
54168
54679
  });
54169
54680
  Object.defineProperties(generator, {
54170
54681
  activeCount: {
@@ -54221,8 +54732,8 @@ init_lint();
54221
54732
  init_manager();
54222
54733
 
54223
54734
  // src/quality/metrics.ts
54224
- import * as fs24 from "fs";
54225
- import * as path35 from "path";
54735
+ import * as fs25 from "fs";
54736
+ import * as path36 from "path";
54226
54737
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
54227
54738
  var MIN_DUPLICATION_LINES = 10;
54228
54739
  function estimateCyclomaticComplexity(content) {
@@ -54260,11 +54771,11 @@ function estimateCyclomaticComplexity(content) {
54260
54771
  }
54261
54772
  function getComplexityForFile2(filePath) {
54262
54773
  try {
54263
- const stat2 = fs24.statSync(filePath);
54774
+ const stat2 = fs25.statSync(filePath);
54264
54775
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
54265
54776
  return null;
54266
54777
  }
54267
- const content = fs24.readFileSync(filePath, "utf-8");
54778
+ const content = fs25.readFileSync(filePath, "utf-8");
54268
54779
  return estimateCyclomaticComplexity(content);
54269
54780
  } catch {
54270
54781
  return null;
@@ -54274,8 +54785,8 @@ async function computeComplexityDelta(files, workingDir) {
54274
54785
  let totalComplexity = 0;
54275
54786
  const analyzedFiles = [];
54276
54787
  for (const file3 of files) {
54277
- const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
54278
- if (!fs24.existsSync(fullPath)) {
54788
+ const fullPath = path36.isAbsolute(file3) ? file3 : path36.join(workingDir, file3);
54789
+ if (!fs25.existsSync(fullPath)) {
54279
54790
  continue;
54280
54791
  }
54281
54792
  const complexity = getComplexityForFile2(fullPath);
@@ -54396,8 +54907,8 @@ function countGoExports(content) {
54396
54907
  }
54397
54908
  function getExportCountForFile(filePath) {
54398
54909
  try {
54399
- const content = fs24.readFileSync(filePath, "utf-8");
54400
- const ext = path35.extname(filePath).toLowerCase();
54910
+ const content = fs25.readFileSync(filePath, "utf-8");
54911
+ const ext = path36.extname(filePath).toLowerCase();
54401
54912
  switch (ext) {
54402
54913
  case ".ts":
54403
54914
  case ".tsx":
@@ -54423,8 +54934,8 @@ async function computePublicApiDelta(files, workingDir) {
54423
54934
  let totalExports = 0;
54424
54935
  const analyzedFiles = [];
54425
54936
  for (const file3 of files) {
54426
- const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
54427
- if (!fs24.existsSync(fullPath)) {
54937
+ const fullPath = path36.isAbsolute(file3) ? file3 : path36.join(workingDir, file3);
54938
+ if (!fs25.existsSync(fullPath)) {
54428
54939
  continue;
54429
54940
  }
54430
54941
  const exports = getExportCountForFile(fullPath);
@@ -54457,16 +54968,16 @@ async function computeDuplicationRatio(files, workingDir) {
54457
54968
  let duplicateLines = 0;
54458
54969
  const analyzedFiles = [];
54459
54970
  for (const file3 of files) {
54460
- const fullPath = path35.isAbsolute(file3) ? file3 : path35.join(workingDir, file3);
54461
- if (!fs24.existsSync(fullPath)) {
54971
+ const fullPath = path36.isAbsolute(file3) ? file3 : path36.join(workingDir, file3);
54972
+ if (!fs25.existsSync(fullPath)) {
54462
54973
  continue;
54463
54974
  }
54464
54975
  try {
54465
- const stat2 = fs24.statSync(fullPath);
54976
+ const stat2 = fs25.statSync(fullPath);
54466
54977
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
54467
54978
  continue;
54468
54979
  }
54469
- const content = fs24.readFileSync(fullPath, "utf-8");
54980
+ const content = fs25.readFileSync(fullPath, "utf-8");
54470
54981
  const lines = content.split(`
54471
54982
  `).filter((line) => line.trim().length > 0);
54472
54983
  if (lines.length < MIN_DUPLICATION_LINES) {
@@ -54490,8 +55001,8 @@ function countCodeLines(content) {
54490
55001
  return lines.length;
54491
55002
  }
54492
55003
  function isTestFile(filePath) {
54493
- const basename8 = path35.basename(filePath);
54494
- const _ext = path35.extname(filePath).toLowerCase();
55004
+ const basename8 = path36.basename(filePath);
55005
+ const _ext = path36.extname(filePath).toLowerCase();
54495
55006
  const testPatterns = [
54496
55007
  ".test.",
54497
55008
  ".spec.",
@@ -54572,8 +55083,8 @@ function matchGlobSegment(globSegments, pathSegments) {
54572
55083
  }
54573
55084
  return gIndex === globSegments.length && pIndex === pathSegments.length;
54574
55085
  }
54575
- function matchesGlobSegment(path36, glob) {
54576
- const normalizedPath = path36.replace(/\\/g, "/");
55086
+ function matchesGlobSegment(path37, glob) {
55087
+ const normalizedPath = path37.replace(/\\/g, "/");
54577
55088
  const normalizedGlob = glob.replace(/\\/g, "/");
54578
55089
  if (normalizedPath.includes("//")) {
54579
55090
  return false;
@@ -54604,8 +55115,8 @@ function simpleGlobToRegex(glob) {
54604
55115
  function hasGlobstar(glob) {
54605
55116
  return glob.includes("**");
54606
55117
  }
54607
- function globMatches(path36, glob) {
54608
- const normalizedPath = path36.replace(/\\/g, "/");
55118
+ function globMatches(path37, glob) {
55119
+ const normalizedPath = path37.replace(/\\/g, "/");
54609
55120
  if (!glob || glob === "") {
54610
55121
  if (normalizedPath.includes("//")) {
54611
55122
  return false;
@@ -54641,31 +55152,31 @@ function shouldExcludeFile(filePath, excludeGlobs) {
54641
55152
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
54642
55153
  let testLines = 0;
54643
55154
  let codeLines = 0;
54644
- const srcDir = path35.join(workingDir, "src");
54645
- if (fs24.existsSync(srcDir)) {
55155
+ const srcDir = path36.join(workingDir, "src");
55156
+ if (fs25.existsSync(srcDir)) {
54646
55157
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
54647
55158
  codeLines += lines;
54648
55159
  });
54649
55160
  }
54650
55161
  const possibleSrcDirs = ["lib", "app", "source", "core"];
54651
55162
  for (const dir of possibleSrcDirs) {
54652
- const dirPath = path35.join(workingDir, dir);
54653
- if (fs24.existsSync(dirPath)) {
55163
+ const dirPath = path36.join(workingDir, dir);
55164
+ if (fs25.existsSync(dirPath)) {
54654
55165
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
54655
55166
  codeLines += lines;
54656
55167
  });
54657
55168
  }
54658
55169
  }
54659
- const testsDir = path35.join(workingDir, "tests");
54660
- if (fs24.existsSync(testsDir)) {
55170
+ const testsDir = path36.join(workingDir, "tests");
55171
+ if (fs25.existsSync(testsDir)) {
54661
55172
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
54662
55173
  testLines += lines;
54663
55174
  });
54664
55175
  }
54665
55176
  const possibleTestDirs = ["test", "__tests__", "specs"];
54666
55177
  for (const dir of possibleTestDirs) {
54667
- const dirPath = path35.join(workingDir, dir);
54668
- if (fs24.existsSync(dirPath) && dirPath !== testsDir) {
55178
+ const dirPath = path36.join(workingDir, dir);
55179
+ if (fs25.existsSync(dirPath) && dirPath !== testsDir) {
54669
55180
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
54670
55181
  testLines += lines;
54671
55182
  });
@@ -54677,9 +55188,9 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
54677
55188
  }
54678
55189
  async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
54679
55190
  try {
54680
- const entries = fs24.readdirSync(dirPath, { withFileTypes: true });
55191
+ const entries = fs25.readdirSync(dirPath, { withFileTypes: true });
54681
55192
  for (const entry of entries) {
54682
- const fullPath = path35.join(dirPath, entry.name);
55193
+ const fullPath = path36.join(dirPath, entry.name);
54683
55194
  if (entry.isDirectory()) {
54684
55195
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
54685
55196
  continue;
@@ -54687,7 +55198,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
54687
55198
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
54688
55199
  } else if (entry.isFile()) {
54689
55200
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
54690
- const ext = path35.extname(entry.name).toLowerCase();
55201
+ const ext = path36.extname(entry.name).toLowerCase();
54691
55202
  const validExts = [
54692
55203
  ".ts",
54693
55204
  ".tsx",
@@ -54723,7 +55234,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
54723
55234
  continue;
54724
55235
  }
54725
55236
  try {
54726
- const content = fs24.readFileSync(fullPath, "utf-8");
55237
+ const content = fs25.readFileSync(fullPath, "utf-8");
54727
55238
  const lines = countCodeLines(content);
54728
55239
  callback(lines);
54729
55240
  } catch {}
@@ -54937,8 +55448,8 @@ async function qualityBudget(input, directory) {
54937
55448
  init_dist();
54938
55449
  init_manager();
54939
55450
  init_detector();
54940
- import * as fs25 from "fs";
54941
- import * as path36 from "path";
55451
+ import * as fs26 from "fs";
55452
+ import * as path37 from "path";
54942
55453
  import { extname as extname9 } from "path";
54943
55454
 
54944
55455
  // src/sast/rules/c.ts
@@ -55686,7 +56197,7 @@ function mapSemgrepSeverity(severity) {
55686
56197
  }
55687
56198
  }
55688
56199
  async function executeWithTimeout(command, args2, options) {
55689
- return new Promise((resolve12) => {
56200
+ return new Promise((resolve13) => {
55690
56201
  const child = spawn(command, args2, {
55691
56202
  shell: false,
55692
56203
  cwd: options.cwd
@@ -55695,7 +56206,7 @@ async function executeWithTimeout(command, args2, options) {
55695
56206
  let stderr = "";
55696
56207
  const timeout = setTimeout(() => {
55697
56208
  child.kill("SIGTERM");
55698
- resolve12({
56209
+ resolve13({
55699
56210
  stdout,
55700
56211
  stderr: "Process timed out",
55701
56212
  exitCode: 124
@@ -55709,7 +56220,7 @@ async function executeWithTimeout(command, args2, options) {
55709
56220
  });
55710
56221
  child.on("close", (code) => {
55711
56222
  clearTimeout(timeout);
55712
- resolve12({
56223
+ resolve13({
55713
56224
  stdout,
55714
56225
  stderr,
55715
56226
  exitCode: code ?? 0
@@ -55717,7 +56228,7 @@ async function executeWithTimeout(command, args2, options) {
55717
56228
  });
55718
56229
  child.on("error", (err2) => {
55719
56230
  clearTimeout(timeout);
55720
- resolve12({
56231
+ resolve13({
55721
56232
  stdout,
55722
56233
  stderr: err2.message,
55723
56234
  exitCode: 1
@@ -55805,17 +56316,17 @@ var SEVERITY_ORDER = {
55805
56316
  };
55806
56317
  function shouldSkipFile(filePath) {
55807
56318
  try {
55808
- const stats = fs25.statSync(filePath);
56319
+ const stats = fs26.statSync(filePath);
55809
56320
  if (stats.size > MAX_FILE_SIZE_BYTES6) {
55810
56321
  return { skip: true, reason: "file too large" };
55811
56322
  }
55812
56323
  if (stats.size === 0) {
55813
56324
  return { skip: true, reason: "empty file" };
55814
56325
  }
55815
- const fd = fs25.openSync(filePath, "r");
56326
+ const fd = fs26.openSync(filePath, "r");
55816
56327
  const buffer = Buffer.alloc(8192);
55817
- const bytesRead = fs25.readSync(fd, buffer, 0, 8192, 0);
55818
- fs25.closeSync(fd);
56328
+ const bytesRead = fs26.readSync(fd, buffer, 0, 8192, 0);
56329
+ fs26.closeSync(fd);
55819
56330
  if (bytesRead > 0) {
55820
56331
  let nullCount = 0;
55821
56332
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -55854,7 +56365,7 @@ function countBySeverity(findings) {
55854
56365
  }
55855
56366
  function scanFileWithTierA(filePath, language) {
55856
56367
  try {
55857
- const content = fs25.readFileSync(filePath, "utf-8");
56368
+ const content = fs26.readFileSync(filePath, "utf-8");
55858
56369
  const findings = executeRulesSync(filePath, content, language);
55859
56370
  return findings.map((f) => ({
55860
56371
  rule_id: f.rule_id,
@@ -55901,8 +56412,8 @@ async function sastScan(input, directory, config3) {
55901
56412
  _filesSkipped++;
55902
56413
  continue;
55903
56414
  }
55904
- const resolvedPath = path36.isAbsolute(filePath) ? filePath : path36.resolve(directory, filePath);
55905
- if (!fs25.existsSync(resolvedPath)) {
56415
+ const resolvedPath = path37.isAbsolute(filePath) ? filePath : path37.resolve(directory, filePath);
56416
+ if (!fs26.existsSync(resolvedPath)) {
55906
56417
  _filesSkipped++;
55907
56418
  continue;
55908
56419
  }
@@ -56100,18 +56611,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
56100
56611
  let resolved;
56101
56612
  const isWinAbs = isWindowsAbsolutePath(inputPath);
56102
56613
  if (isWinAbs) {
56103
- resolved = path37.win32.resolve(inputPath);
56104
- } else if (path37.isAbsolute(inputPath)) {
56105
- resolved = path37.resolve(inputPath);
56614
+ resolved = path38.win32.resolve(inputPath);
56615
+ } else if (path38.isAbsolute(inputPath)) {
56616
+ resolved = path38.resolve(inputPath);
56106
56617
  } else {
56107
- resolved = path37.resolve(baseDir, inputPath);
56618
+ resolved = path38.resolve(baseDir, inputPath);
56108
56619
  }
56109
- const workspaceResolved = path37.resolve(workspaceDir);
56620
+ const workspaceResolved = path38.resolve(workspaceDir);
56110
56621
  let relative4;
56111
56622
  if (isWinAbs) {
56112
- relative4 = path37.win32.relative(workspaceResolved, resolved);
56623
+ relative4 = path38.win32.relative(workspaceResolved, resolved);
56113
56624
  } else {
56114
- relative4 = path37.relative(workspaceResolved, resolved);
56625
+ relative4 = path38.relative(workspaceResolved, resolved);
56115
56626
  }
56116
56627
  if (relative4.startsWith("..")) {
56117
56628
  return "path traversal detected";
@@ -56172,13 +56683,13 @@ async function runLintWrapped(files, directory, _config) {
56172
56683
  }
56173
56684
  async function runLintOnFiles(linter, files, workspaceDir) {
56174
56685
  const isWindows = process.platform === "win32";
56175
- const binDir = path37.join(workspaceDir, "node_modules", ".bin");
56686
+ const binDir = path38.join(workspaceDir, "node_modules", ".bin");
56176
56687
  const validatedFiles = [];
56177
56688
  for (const file3 of files) {
56178
56689
  if (typeof file3 !== "string") {
56179
56690
  continue;
56180
56691
  }
56181
- const resolvedPath = path37.resolve(file3);
56692
+ const resolvedPath = path38.resolve(file3);
56182
56693
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
56183
56694
  if (validationError) {
56184
56695
  continue;
@@ -56196,10 +56707,10 @@ async function runLintOnFiles(linter, files, workspaceDir) {
56196
56707
  }
56197
56708
  let command;
56198
56709
  if (linter === "biome") {
56199
- const biomeBin = isWindows ? path37.join(binDir, "biome.EXE") : path37.join(binDir, "biome");
56710
+ const biomeBin = isWindows ? path38.join(binDir, "biome.EXE") : path38.join(binDir, "biome");
56200
56711
  command = [biomeBin, "check", ...validatedFiles];
56201
56712
  } else {
56202
- const eslintBin = isWindows ? path37.join(binDir, "eslint.cmd") : path37.join(binDir, "eslint");
56713
+ const eslintBin = isWindows ? path38.join(binDir, "eslint.cmd") : path38.join(binDir, "eslint");
56203
56714
  command = [eslintBin, ...validatedFiles];
56204
56715
  }
56205
56716
  try {
@@ -56336,7 +56847,7 @@ async function runSecretscanWithFiles(files, directory) {
56336
56847
  skippedFiles++;
56337
56848
  continue;
56338
56849
  }
56339
- const resolvedPath = path37.resolve(file3);
56850
+ const resolvedPath = path38.resolve(file3);
56340
56851
  const validationError = validatePath(resolvedPath, directory, directory);
56341
56852
  if (validationError) {
56342
56853
  skippedFiles++;
@@ -56354,14 +56865,14 @@ async function runSecretscanWithFiles(files, directory) {
56354
56865
  };
56355
56866
  }
56356
56867
  for (const file3 of validatedFiles) {
56357
- const ext = path37.extname(file3).toLowerCase();
56868
+ const ext = path38.extname(file3).toLowerCase();
56358
56869
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
56359
56870
  skippedFiles++;
56360
56871
  continue;
56361
56872
  }
56362
56873
  let stat2;
56363
56874
  try {
56364
- stat2 = fs26.statSync(file3);
56875
+ stat2 = fs27.statSync(file3);
56365
56876
  } catch {
56366
56877
  skippedFiles++;
56367
56878
  continue;
@@ -56372,7 +56883,7 @@ async function runSecretscanWithFiles(files, directory) {
56372
56883
  }
56373
56884
  let content;
56374
56885
  try {
56375
- const buffer = fs26.readFileSync(file3);
56886
+ const buffer = fs27.readFileSync(file3);
56376
56887
  if (buffer.includes(0)) {
56377
56888
  skippedFiles++;
56378
56889
  continue;
@@ -56513,7 +57024,7 @@ async function runPreCheckBatch(input, workspaceDir) {
56513
57024
  warn(`pre_check_batch: Invalid file path: ${file3}`);
56514
57025
  continue;
56515
57026
  }
56516
- changedFiles.push(path37.resolve(directory, file3));
57027
+ changedFiles.push(path38.resolve(directory, file3));
56517
57028
  }
56518
57029
  if (changedFiles.length === 0) {
56519
57030
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -56664,7 +57175,7 @@ var pre_check_batch = createSwarmTool({
56664
57175
  };
56665
57176
  return JSON.stringify(errorResult, null, 2);
56666
57177
  }
56667
- const resolvedDirectory = path37.resolve(typedArgs.directory);
57178
+ const resolvedDirectory = path38.resolve(typedArgs.directory);
56668
57179
  const workspaceAnchor = resolvedDirectory;
56669
57180
  const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
56670
57181
  if (dirError) {
@@ -56706,10 +57217,14 @@ var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
56706
57217
  var retrieve_summary = tool({
56707
57218
  description: "Retrieve the full content of a stored tool output summary by its ID (e.g. S1, S2). Use this when a prior tool output was summarized and you need the full content.",
56708
57219
  args: {
56709
- id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
57220
+ id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits."),
57221
+ offset: tool.schema.number().min(0).default(0).describe("Line offset to start from (default: 0)."),
57222
+ limit: tool.schema.number().min(1).max(500).default(100).describe("Number of lines to return (default: 100, max: 500).")
56710
57223
  },
56711
57224
  async execute(args2, context) {
56712
57225
  const directory = context.directory;
57226
+ const offset = args2.offset ?? 0;
57227
+ const limit = Math.min(args2.limit ?? 100, 500);
56713
57228
  let sanitizedId;
56714
57229
  try {
56715
57230
  sanitizedId = sanitizeSummaryId(args2.id);
@@ -56728,14 +57243,47 @@ var retrieve_summary = tool({
56728
57243
  if (fullOutput.length > RETRIEVE_MAX_BYTES) {
56729
57244
  return `Error: summary content exceeds maximum size limit (10 MB).`;
56730
57245
  }
56731
- return fullOutput;
57246
+ if (fullOutput.length === 0) {
57247
+ return `--- No content (0 lines) ---
57248
+
57249
+ (Summary is empty)`;
57250
+ }
57251
+ const lines = fullOutput.split(`
57252
+ `);
57253
+ const totalLines = lines.length;
57254
+ const clampedOffset = Math.max(0, offset);
57255
+ if (clampedOffset >= totalLines) {
57256
+ const response2 = `--- Offset beyond range ---
57257
+
57258
+ (Range exhausted. Valid offset range: 0-${totalLines - 1})
57259
+ (Content has ${totalLines} line${totalLines === 1 ? "" : "s"})`;
57260
+ return response2;
57261
+ }
57262
+ const startLine = Math.min(clampedOffset, totalLines);
57263
+ const endLine = Math.min(startLine + limit, totalLines);
57264
+ const paginatedLines = lines.slice(startLine, endLine);
57265
+ const paginatedContent = paginatedLines.join(`
57266
+ `);
57267
+ const headerStart = startLine + 1;
57268
+ const headerEnd = endLine;
57269
+ const rangeHeader = `--- Lines ${headerStart}-${headerEnd} of ${totalLines} ---`;
57270
+ let response = `${rangeHeader}
57271
+ ${paginatedContent}`;
57272
+ if (endLine < totalLines) {
57273
+ const remaining = totalLines - endLine;
57274
+ response += `
57275
+
57276
+ ... ${remaining} more line${remaining === 1 ? "" : "s"}. Use offset=${endLine} to retrieve more.`;
57277
+ }
57278
+ return response;
56732
57279
  }
56733
57280
  });
56734
57281
  // src/tools/save-plan.ts
56735
57282
  init_tool();
56736
57283
  init_manager2();
56737
57284
  init_create_tool();
56738
- import * as path38 from "path";
57285
+ import * as fs28 from "fs";
57286
+ import * as path39 from "path";
56739
57287
  function detectPlaceholderContent(args2) {
56740
57288
  const issues = [];
56741
57289
  const placeholderPattern = /^\[\w[\w\s]*\]$/;
@@ -56769,12 +57317,33 @@ function validateTargetWorkspace(target, source) {
56769
57317
  return;
56770
57318
  }
56771
57319
  async function executeSavePlan(args2, fallbackDir) {
57320
+ const validationErrors = [];
57321
+ for (const phase of args2.phases) {
57322
+ if (!Number.isInteger(phase.id) || phase.id <= 0) {
57323
+ validationErrors.push(`Phase ${phase.id} has invalid id: must be a positive integer`);
57324
+ }
57325
+ const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
57326
+ for (const task of phase.tasks) {
57327
+ if (!taskIdPattern.test(task.id)) {
57328
+ validationErrors.push(`Task '${task.id}' in phase ${phase.id} has invalid id format: must match N.M pattern (e.g. '1.1', '2.3')`);
57329
+ }
57330
+ }
57331
+ }
57332
+ if (validationErrors.length > 0) {
57333
+ return {
57334
+ success: false,
57335
+ message: "Plan rejected: invalid phase or task IDs",
57336
+ errors: validationErrors,
57337
+ recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
57338
+ };
57339
+ }
56772
57340
  const placeholderIssues = detectPlaceholderContent(args2);
56773
57341
  if (placeholderIssues.length > 0) {
56774
57342
  return {
56775
57343
  success: false,
56776
57344
  message: "Plan rejected: contains template placeholder content",
56777
- errors: placeholderIssues
57345
+ errors: placeholderIssues,
57346
+ recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
56778
57347
  };
56779
57348
  }
56780
57349
  const targetWorkspace = args2.working_directory ?? fallbackDir;
@@ -56782,8 +57351,9 @@ async function executeSavePlan(args2, fallbackDir) {
56782
57351
  if (workspaceError) {
56783
57352
  return {
56784
57353
  success: false,
56785
- message: "Target workspace validation failed",
56786
- errors: [workspaceError]
57354
+ message: "Target workspace validation failed: provide working_directory parameter to save_plan",
57355
+ errors: [workspaceError],
57356
+ recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
56787
57357
  };
56788
57358
  }
56789
57359
  const plan = {
@@ -56816,18 +57386,29 @@ async function executeSavePlan(args2, fallbackDir) {
56816
57386
  const dir = targetWorkspace;
56817
57387
  try {
56818
57388
  await savePlan(dir, plan);
57389
+ try {
57390
+ const markerPath = path39.join(dir, ".swarm", ".plan-write-marker");
57391
+ const marker = JSON.stringify({
57392
+ source: "save_plan",
57393
+ timestamp: new Date().toISOString(),
57394
+ phases_count: plan.phases.length,
57395
+ tasks_count: tasksCount
57396
+ });
57397
+ await fs28.promises.writeFile(markerPath, marker, "utf8");
57398
+ } catch {}
56819
57399
  return {
56820
57400
  success: true,
56821
57401
  message: "Plan saved successfully",
56822
- plan_path: path38.join(dir, ".swarm", "plan.json"),
57402
+ plan_path: path39.join(dir, ".swarm", "plan.json"),
56823
57403
  phases_count: plan.phases.length,
56824
57404
  tasks_count: tasksCount
56825
57405
  };
56826
57406
  } catch (error93) {
56827
57407
  return {
56828
57408
  success: false,
56829
- message: "Failed to save plan",
56830
- errors: [String(error93)]
57409
+ message: "Failed to save plan: retry with save_plan after resolving the error above",
57410
+ errors: [String(error93)],
57411
+ recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
56831
57412
  };
56832
57413
  }
56833
57414
  }
@@ -56856,8 +57437,8 @@ var save_plan = createSwarmTool({
56856
57437
  // src/tools/sbom-generate.ts
56857
57438
  init_dist();
56858
57439
  init_manager();
56859
- import * as fs27 from "fs";
56860
- import * as path39 from "path";
57440
+ import * as fs29 from "fs";
57441
+ import * as path40 from "path";
56861
57442
 
56862
57443
  // src/sbom/detectors/dart.ts
56863
57444
  function parsePubspecLock(content) {
@@ -57703,9 +58284,9 @@ function findManifestFiles(rootDir) {
57703
58284
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
57704
58285
  function searchDir(dir) {
57705
58286
  try {
57706
- const entries = fs27.readdirSync(dir, { withFileTypes: true });
58287
+ const entries = fs29.readdirSync(dir, { withFileTypes: true });
57707
58288
  for (const entry of entries) {
57708
- const fullPath = path39.join(dir, entry.name);
58289
+ const fullPath = path40.join(dir, entry.name);
57709
58290
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
57710
58291
  continue;
57711
58292
  }
@@ -57715,7 +58296,7 @@ function findManifestFiles(rootDir) {
57715
58296
  for (const pattern of patterns) {
57716
58297
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
57717
58298
  if (new RegExp(regex, "i").test(entry.name)) {
57718
- manifestFiles.push(path39.relative(cwd, fullPath));
58299
+ manifestFiles.push(path40.relative(cwd, fullPath));
57719
58300
  break;
57720
58301
  }
57721
58302
  }
@@ -57732,14 +58313,14 @@ function findManifestFilesInDirs(directories, workingDir) {
57732
58313
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
57733
58314
  for (const dir of directories) {
57734
58315
  try {
57735
- const entries = fs27.readdirSync(dir, { withFileTypes: true });
58316
+ const entries = fs29.readdirSync(dir, { withFileTypes: true });
57736
58317
  for (const entry of entries) {
57737
- const fullPath = path39.join(dir, entry.name);
58318
+ const fullPath = path40.join(dir, entry.name);
57738
58319
  if (entry.isFile()) {
57739
58320
  for (const pattern of patterns) {
57740
58321
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
57741
58322
  if (new RegExp(regex, "i").test(entry.name)) {
57742
- found.push(path39.relative(workingDir, fullPath));
58323
+ found.push(path40.relative(workingDir, fullPath));
57743
58324
  break;
57744
58325
  }
57745
58326
  }
@@ -57752,11 +58333,11 @@ function findManifestFilesInDirs(directories, workingDir) {
57752
58333
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
57753
58334
  const dirs = new Set;
57754
58335
  for (const file3 of changedFiles) {
57755
- let currentDir = path39.dirname(file3);
58336
+ let currentDir = path40.dirname(file3);
57756
58337
  while (true) {
57757
- if (currentDir && currentDir !== "." && currentDir !== path39.sep) {
57758
- dirs.add(path39.join(workingDir, currentDir));
57759
- const parent = path39.dirname(currentDir);
58338
+ if (currentDir && currentDir !== "." && currentDir !== path40.sep) {
58339
+ dirs.add(path40.join(workingDir, currentDir));
58340
+ const parent = path40.dirname(currentDir);
57760
58341
  if (parent === currentDir)
57761
58342
  break;
57762
58343
  currentDir = parent;
@@ -57770,7 +58351,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
57770
58351
  }
57771
58352
  function ensureOutputDir(outputDir) {
57772
58353
  try {
57773
- fs27.mkdirSync(outputDir, { recursive: true });
58354
+ fs29.mkdirSync(outputDir, { recursive: true });
57774
58355
  } catch (error93) {
57775
58356
  if (!error93 || error93.code !== "EEXIST") {
57776
58357
  throw error93;
@@ -57862,11 +58443,11 @@ var sbom_generate = createSwarmTool({
57862
58443
  const processedFiles = [];
57863
58444
  for (const manifestFile of manifestFiles) {
57864
58445
  try {
57865
- const fullPath = path39.isAbsolute(manifestFile) ? manifestFile : path39.join(workingDir, manifestFile);
57866
- if (!fs27.existsSync(fullPath)) {
58446
+ const fullPath = path40.isAbsolute(manifestFile) ? manifestFile : path40.join(workingDir, manifestFile);
58447
+ if (!fs29.existsSync(fullPath)) {
57867
58448
  continue;
57868
58449
  }
57869
- const content = fs27.readFileSync(fullPath, "utf-8");
58450
+ const content = fs29.readFileSync(fullPath, "utf-8");
57870
58451
  const components = detectComponents(manifestFile, content);
57871
58452
  processedFiles.push(manifestFile);
57872
58453
  if (components.length > 0) {
@@ -57879,8 +58460,8 @@ var sbom_generate = createSwarmTool({
57879
58460
  const bom = generateCycloneDX(allComponents);
57880
58461
  const bomJson = serializeCycloneDX(bom);
57881
58462
  const filename = generateSbomFilename();
57882
- const outputPath = path39.join(outputDir, filename);
57883
- fs27.writeFileSync(outputPath, bomJson, "utf-8");
58463
+ const outputPath = path40.join(outputDir, filename);
58464
+ fs29.writeFileSync(outputPath, bomJson, "utf-8");
57884
58465
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
57885
58466
  try {
57886
58467
  const timestamp = new Date().toISOString();
@@ -57922,8 +58503,8 @@ var sbom_generate = createSwarmTool({
57922
58503
  // src/tools/schema-drift.ts
57923
58504
  init_dist();
57924
58505
  init_create_tool();
57925
- import * as fs28 from "fs";
57926
- import * as path40 from "path";
58506
+ import * as fs30 from "fs";
58507
+ import * as path41 from "path";
57927
58508
  var SPEC_CANDIDATES = [
57928
58509
  "openapi.json",
57929
58510
  "openapi.yaml",
@@ -57955,28 +58536,28 @@ function normalizePath2(p) {
57955
58536
  }
57956
58537
  function discoverSpecFile(cwd, specFileArg) {
57957
58538
  if (specFileArg) {
57958
- const resolvedPath = path40.resolve(cwd, specFileArg);
57959
- const normalizedCwd = cwd.endsWith(path40.sep) ? cwd : cwd + path40.sep;
58539
+ const resolvedPath = path41.resolve(cwd, specFileArg);
58540
+ const normalizedCwd = cwd.endsWith(path41.sep) ? cwd : cwd + path41.sep;
57960
58541
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
57961
58542
  throw new Error("Invalid spec_file: path traversal detected");
57962
58543
  }
57963
- const ext = path40.extname(resolvedPath).toLowerCase();
58544
+ const ext = path41.extname(resolvedPath).toLowerCase();
57964
58545
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
57965
58546
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
57966
58547
  }
57967
- const stats = fs28.statSync(resolvedPath);
58548
+ const stats = fs30.statSync(resolvedPath);
57968
58549
  if (stats.size > MAX_SPEC_SIZE) {
57969
58550
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
57970
58551
  }
57971
- if (!fs28.existsSync(resolvedPath)) {
58552
+ if (!fs30.existsSync(resolvedPath)) {
57972
58553
  throw new Error(`Spec file not found: ${resolvedPath}`);
57973
58554
  }
57974
58555
  return resolvedPath;
57975
58556
  }
57976
58557
  for (const candidate of SPEC_CANDIDATES) {
57977
- const candidatePath = path40.resolve(cwd, candidate);
57978
- if (fs28.existsSync(candidatePath)) {
57979
- const stats = fs28.statSync(candidatePath);
58558
+ const candidatePath = path41.resolve(cwd, candidate);
58559
+ if (fs30.existsSync(candidatePath)) {
58560
+ const stats = fs30.statSync(candidatePath);
57980
58561
  if (stats.size <= MAX_SPEC_SIZE) {
57981
58562
  return candidatePath;
57982
58563
  }
@@ -57985,8 +58566,8 @@ function discoverSpecFile(cwd, specFileArg) {
57985
58566
  return null;
57986
58567
  }
57987
58568
  function parseSpec(specFile) {
57988
- const content = fs28.readFileSync(specFile, "utf-8");
57989
- const ext = path40.extname(specFile).toLowerCase();
58569
+ const content = fs30.readFileSync(specFile, "utf-8");
58570
+ const ext = path41.extname(specFile).toLowerCase();
57990
58571
  if (ext === ".json") {
57991
58572
  return parseJsonSpec(content);
57992
58573
  }
@@ -58052,12 +58633,12 @@ function extractRoutes(cwd) {
58052
58633
  function walkDir(dir) {
58053
58634
  let entries;
58054
58635
  try {
58055
- entries = fs28.readdirSync(dir, { withFileTypes: true });
58636
+ entries = fs30.readdirSync(dir, { withFileTypes: true });
58056
58637
  } catch {
58057
58638
  return;
58058
58639
  }
58059
58640
  for (const entry of entries) {
58060
- const fullPath = path40.join(dir, entry.name);
58641
+ const fullPath = path41.join(dir, entry.name);
58061
58642
  if (entry.isSymbolicLink()) {
58062
58643
  continue;
58063
58644
  }
@@ -58067,7 +58648,7 @@ function extractRoutes(cwd) {
58067
58648
  }
58068
58649
  walkDir(fullPath);
58069
58650
  } else if (entry.isFile()) {
58070
- const ext = path40.extname(entry.name).toLowerCase();
58651
+ const ext = path41.extname(entry.name).toLowerCase();
58071
58652
  const baseName = entry.name.toLowerCase();
58072
58653
  if (![".ts", ".js", ".mjs"].includes(ext)) {
58073
58654
  continue;
@@ -58085,7 +58666,7 @@ function extractRoutes(cwd) {
58085
58666
  }
58086
58667
  function extractRoutesFromFile(filePath) {
58087
58668
  const routes = [];
58088
- const content = fs28.readFileSync(filePath, "utf-8");
58669
+ const content = fs30.readFileSync(filePath, "utf-8");
58089
58670
  const lines = content.split(/\r?\n/);
58090
58671
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
58091
58672
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -58236,8 +58817,8 @@ init_secretscan();
58236
58817
  // src/tools/symbols.ts
58237
58818
  init_tool();
58238
58819
  init_create_tool();
58239
- import * as fs29 from "fs";
58240
- import * as path41 from "path";
58820
+ import * as fs31 from "fs";
58821
+ import * as path42 from "path";
58241
58822
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
58242
58823
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
58243
58824
  function containsControlCharacters(str) {
@@ -58266,11 +58847,11 @@ function containsWindowsAttacks(str) {
58266
58847
  }
58267
58848
  function isPathInWorkspace(filePath, workspace) {
58268
58849
  try {
58269
- const resolvedPath = path41.resolve(workspace, filePath);
58270
- const realWorkspace = fs29.realpathSync(workspace);
58271
- const realResolvedPath = fs29.realpathSync(resolvedPath);
58272
- const relativePath = path41.relative(realWorkspace, realResolvedPath);
58273
- if (relativePath.startsWith("..") || path41.isAbsolute(relativePath)) {
58850
+ const resolvedPath = path42.resolve(workspace, filePath);
58851
+ const realWorkspace = fs31.realpathSync(workspace);
58852
+ const realResolvedPath = fs31.realpathSync(resolvedPath);
58853
+ const relativePath = path42.relative(realWorkspace, realResolvedPath);
58854
+ if (relativePath.startsWith("..") || path42.isAbsolute(relativePath)) {
58274
58855
  return false;
58275
58856
  }
58276
58857
  return true;
@@ -58282,17 +58863,17 @@ function validatePathForRead(filePath, workspace) {
58282
58863
  return isPathInWorkspace(filePath, workspace);
58283
58864
  }
58284
58865
  function extractTSSymbols(filePath, cwd) {
58285
- const fullPath = path41.join(cwd, filePath);
58866
+ const fullPath = path42.join(cwd, filePath);
58286
58867
  if (!validatePathForRead(fullPath, cwd)) {
58287
58868
  return [];
58288
58869
  }
58289
58870
  let content;
58290
58871
  try {
58291
- const stats = fs29.statSync(fullPath);
58872
+ const stats = fs31.statSync(fullPath);
58292
58873
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
58293
58874
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
58294
58875
  }
58295
- content = fs29.readFileSync(fullPath, "utf-8");
58876
+ content = fs31.readFileSync(fullPath, "utf-8");
58296
58877
  } catch {
58297
58878
  return [];
58298
58879
  }
@@ -58434,17 +59015,17 @@ function extractTSSymbols(filePath, cwd) {
58434
59015
  });
58435
59016
  }
58436
59017
  function extractPythonSymbols(filePath, cwd) {
58437
- const fullPath = path41.join(cwd, filePath);
59018
+ const fullPath = path42.join(cwd, filePath);
58438
59019
  if (!validatePathForRead(fullPath, cwd)) {
58439
59020
  return [];
58440
59021
  }
58441
59022
  let content;
58442
59023
  try {
58443
- const stats = fs29.statSync(fullPath);
59024
+ const stats = fs31.statSync(fullPath);
58444
59025
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
58445
59026
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
58446
59027
  }
58447
- content = fs29.readFileSync(fullPath, "utf-8");
59028
+ content = fs31.readFileSync(fullPath, "utf-8");
58448
59029
  } catch {
58449
59030
  return [];
58450
59031
  }
@@ -58517,7 +59098,7 @@ var symbols = createSwarmTool({
58517
59098
  }, null, 2);
58518
59099
  }
58519
59100
  const cwd = directory;
58520
- const ext = path41.extname(file3);
59101
+ const ext = path42.extname(file3);
58521
59102
  if (containsControlCharacters(file3)) {
58522
59103
  return JSON.stringify({
58523
59104
  file: file3,
@@ -58584,132 +59165,10 @@ var MAX_FILE_SIZE2 = 5 * 1024 * 1024;
58584
59165
  // src/tools/index.ts
58585
59166
  init_test_runner();
58586
59167
 
58587
- // src/tools/update-task-status.ts
58588
- init_tool();
58589
- init_manager2();
58590
- init_create_tool();
58591
- import * as fs30 from "fs";
58592
- import * as path42 from "path";
58593
- var VALID_STATUSES = [
58594
- "pending",
58595
- "in_progress",
58596
- "completed",
58597
- "blocked"
58598
- ];
58599
- function validateStatus(status) {
58600
- if (!VALID_STATUSES.includes(status)) {
58601
- return `Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`;
58602
- }
58603
- return;
58604
- }
58605
- function validateTaskId(taskId) {
58606
- const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
58607
- if (!taskIdPattern.test(taskId)) {
58608
- return `Invalid task_id "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
58609
- }
58610
- return;
58611
- }
58612
- async function executeUpdateTaskStatus(args2, fallbackDir) {
58613
- const statusError = validateStatus(args2.status);
58614
- if (statusError) {
58615
- return {
58616
- success: false,
58617
- message: "Validation failed",
58618
- errors: [statusError]
58619
- };
58620
- }
58621
- const taskIdError = validateTaskId(args2.task_id);
58622
- if (taskIdError) {
58623
- return {
58624
- success: false,
58625
- message: "Validation failed",
58626
- errors: [taskIdError]
58627
- };
58628
- }
58629
- let normalizedDir;
58630
- if (args2.working_directory != null) {
58631
- if (args2.working_directory.includes("\x00")) {
58632
- return {
58633
- success: false,
58634
- message: "Invalid working_directory: null bytes are not allowed"
58635
- };
58636
- }
58637
- if (process.platform === "win32") {
58638
- const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
58639
- if (devicePathPattern.test(args2.working_directory)) {
58640
- return {
58641
- success: false,
58642
- message: "Invalid working_directory: Windows device paths are not allowed"
58643
- };
58644
- }
58645
- }
58646
- normalizedDir = path42.normalize(args2.working_directory);
58647
- const pathParts = normalizedDir.split(path42.sep);
58648
- if (pathParts.includes("..")) {
58649
- return {
58650
- success: false,
58651
- message: "Invalid working_directory: path traversal sequences (..) are not allowed",
58652
- errors: [
58653
- "Invalid working_directory: path traversal sequences (..) are not allowed"
58654
- ]
58655
- };
58656
- }
58657
- const resolvedDir = path42.resolve(normalizedDir);
58658
- try {
58659
- const realPath = fs30.realpathSync(resolvedDir);
58660
- const planPath = path42.join(realPath, ".swarm", "plan.json");
58661
- if (!fs30.existsSync(planPath)) {
58662
- return {
58663
- success: false,
58664
- message: `Invalid working_directory: plan not found in "${realPath}"`,
58665
- errors: [
58666
- `Invalid working_directory: plan not found in "${realPath}"`
58667
- ]
58668
- };
58669
- }
58670
- } catch {
58671
- return {
58672
- success: false,
58673
- message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
58674
- errors: [
58675
- `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
58676
- ]
58677
- };
58678
- }
58679
- }
58680
- const directory = normalizedDir ?? fallbackDir ?? process.cwd();
58681
- try {
58682
- const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
58683
- return {
58684
- success: true,
58685
- message: "Task status updated successfully",
58686
- task_id: args2.task_id,
58687
- new_status: args2.status,
58688
- current_phase: updatedPlan.current_phase
58689
- };
58690
- } catch (error93) {
58691
- return {
58692
- success: false,
58693
- message: "Failed to update task status",
58694
- errors: [String(error93)]
58695
- };
58696
- }
58697
- }
58698
- var update_task_status = createSwarmTool({
58699
- description: "Update the status of a specific task in the implementation plan. " + "Task status can be one of: pending, in_progress, completed, blocked.",
58700
- args: {
58701
- task_id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID in N.M format, e.g. "1.1", "1.2.3"'),
58702
- status: tool.schema.enum(["pending", "in_progress", "completed", "blocked"]).describe("New status for the task: pending, in_progress, completed, or blocked"),
58703
- working_directory: tool.schema.string().optional().describe("Working directory where the plan is located")
58704
- },
58705
- execute: async (args2, _directory) => {
58706
- return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
58707
- }
58708
- });
58709
59168
  // src/tools/todo-extract.ts
58710
59169
  init_dist();
58711
59170
  init_create_tool();
58712
- import * as fs31 from "fs";
59171
+ import * as fs32 from "fs";
58713
59172
  import * as path43 from "path";
58714
59173
  var MAX_TEXT_LENGTH = 200;
58715
59174
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
@@ -58805,7 +59264,7 @@ function isSupportedExtension(filePath) {
58805
59264
  function findSourceFiles3(dir, files = []) {
58806
59265
  let entries;
58807
59266
  try {
58808
- entries = fs31.readdirSync(dir);
59267
+ entries = fs32.readdirSync(dir);
58809
59268
  } catch {
58810
59269
  return files;
58811
59270
  }
@@ -58817,7 +59276,7 @@ function findSourceFiles3(dir, files = []) {
58817
59276
  const fullPath = path43.join(dir, entry);
58818
59277
  let stat2;
58819
59278
  try {
58820
- stat2 = fs31.statSync(fullPath);
59279
+ stat2 = fs32.statSync(fullPath);
58821
59280
  } catch {
58822
59281
  continue;
58823
59282
  }
@@ -58910,7 +59369,7 @@ var todo_extract = createSwarmTool({
58910
59369
  return JSON.stringify(errorResult, null, 2);
58911
59370
  }
58912
59371
  const scanPath = resolvedPath;
58913
- if (!fs31.existsSync(scanPath)) {
59372
+ if (!fs32.existsSync(scanPath)) {
58914
59373
  const errorResult = {
58915
59374
  error: `path not found: ${pathsInput}`,
58916
59375
  total: 0,
@@ -58920,7 +59379,7 @@ var todo_extract = createSwarmTool({
58920
59379
  return JSON.stringify(errorResult, null, 2);
58921
59380
  }
58922
59381
  const filesToScan = [];
58923
- const stat2 = fs31.statSync(scanPath);
59382
+ const stat2 = fs32.statSync(scanPath);
58924
59383
  if (stat2.isFile()) {
58925
59384
  if (isSupportedExtension(scanPath)) {
58926
59385
  filesToScan.push(scanPath);
@@ -58939,11 +59398,11 @@ var todo_extract = createSwarmTool({
58939
59398
  const allEntries = [];
58940
59399
  for (const filePath of filesToScan) {
58941
59400
  try {
58942
- const fileStat = fs31.statSync(filePath);
59401
+ const fileStat = fs32.statSync(filePath);
58943
59402
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
58944
59403
  continue;
58945
59404
  }
58946
- const content = fs31.readFileSync(filePath, "utf-8");
59405
+ const content = fs32.readFileSync(filePath, "utf-8");
58947
59406
  const entries = parseTodoComments(content, filePath, tagsSet);
58948
59407
  allEntries.push(...entries);
58949
59408
  } catch {}
@@ -58968,6 +59427,157 @@ var todo_extract = createSwarmTool({
58968
59427
  return JSON.stringify(result, null, 2);
58969
59428
  }
58970
59429
  });
59430
+ // src/tools/update-task-status.ts
59431
+ init_tool();
59432
+ init_manager2();
59433
+ import * as fs33 from "fs";
59434
+ import * as path44 from "path";
59435
+ init_create_tool();
59436
+ var VALID_STATUSES = [
59437
+ "pending",
59438
+ "in_progress",
59439
+ "completed",
59440
+ "blocked"
59441
+ ];
59442
+ function validateStatus(status) {
59443
+ if (!VALID_STATUSES.includes(status)) {
59444
+ return `Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(", ")}`;
59445
+ }
59446
+ return;
59447
+ }
59448
+ function validateTaskId(taskId) {
59449
+ const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
59450
+ if (!taskIdPattern.test(taskId)) {
59451
+ return `Invalid task_id "${taskId}". Must match pattern N.M or N.M.P (e.g., "1.1", "1.2.3")`;
59452
+ }
59453
+ return;
59454
+ }
59455
+ function checkReviewerGate(taskId) {
59456
+ try {
59457
+ if (swarmState.agentSessions.size === 0) {
59458
+ return { blocked: false, reason: "" };
59459
+ }
59460
+ for (const [_sessionId, session] of swarmState.agentSessions) {
59461
+ const state = getTaskState(session, taskId);
59462
+ if (state === "tests_run" || state === "complete") {
59463
+ return { blocked: false, reason: "" };
59464
+ }
59465
+ }
59466
+ return {
59467
+ blocked: true,
59468
+ reason: `Task ${taskId} has not passed QA gates (state machine requires tests_run or complete, current state indicates gates not yet passed). Call mega_reviewer and mega_test_engineer before marking task as completed.`
59469
+ };
59470
+ } catch {
59471
+ return { blocked: false, reason: "" };
59472
+ }
59473
+ }
59474
+ async function executeUpdateTaskStatus(args2, fallbackDir) {
59475
+ const statusError = validateStatus(args2.status);
59476
+ if (statusError) {
59477
+ return {
59478
+ success: false,
59479
+ message: "Validation failed",
59480
+ errors: [statusError]
59481
+ };
59482
+ }
59483
+ const taskIdError = validateTaskId(args2.task_id);
59484
+ if (taskIdError) {
59485
+ return {
59486
+ success: false,
59487
+ message: "Validation failed",
59488
+ errors: [taskIdError]
59489
+ };
59490
+ }
59491
+ if (args2.status === "completed") {
59492
+ const reviewerCheck = checkReviewerGate(args2.task_id);
59493
+ if (reviewerCheck.blocked) {
59494
+ return {
59495
+ success: false,
59496
+ message: "Gate check failed: reviewer delegation required before marking task as completed",
59497
+ errors: [reviewerCheck.reason]
59498
+ };
59499
+ }
59500
+ }
59501
+ let normalizedDir;
59502
+ if (args2.working_directory != null) {
59503
+ if (args2.working_directory.includes("\x00")) {
59504
+ return {
59505
+ success: false,
59506
+ message: "Invalid working_directory: null bytes are not allowed"
59507
+ };
59508
+ }
59509
+ if (process.platform === "win32") {
59510
+ const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
59511
+ if (devicePathPattern.test(args2.working_directory)) {
59512
+ return {
59513
+ success: false,
59514
+ message: "Invalid working_directory: Windows device paths are not allowed"
59515
+ };
59516
+ }
59517
+ }
59518
+ normalizedDir = path44.normalize(args2.working_directory);
59519
+ const pathParts = normalizedDir.split(path44.sep);
59520
+ if (pathParts.includes("..")) {
59521
+ return {
59522
+ success: false,
59523
+ message: "Invalid working_directory: path traversal sequences (..) are not allowed",
59524
+ errors: [
59525
+ "Invalid working_directory: path traversal sequences (..) are not allowed"
59526
+ ]
59527
+ };
59528
+ }
59529
+ const resolvedDir = path44.resolve(normalizedDir);
59530
+ try {
59531
+ const realPath = fs33.realpathSync(resolvedDir);
59532
+ const planPath = path44.join(realPath, ".swarm", "plan.json");
59533
+ if (!fs33.existsSync(planPath)) {
59534
+ return {
59535
+ success: false,
59536
+ message: `Invalid working_directory: plan not found in "${realPath}"`,
59537
+ errors: [
59538
+ `Invalid working_directory: plan not found in "${realPath}"`
59539
+ ]
59540
+ };
59541
+ }
59542
+ } catch {
59543
+ return {
59544
+ success: false,
59545
+ message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
59546
+ errors: [
59547
+ `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
59548
+ ]
59549
+ };
59550
+ }
59551
+ }
59552
+ const directory = normalizedDir ?? fallbackDir ?? process.cwd();
59553
+ try {
59554
+ const updatedPlan = await updateTaskStatus(directory, args2.task_id, args2.status);
59555
+ return {
59556
+ success: true,
59557
+ message: "Task status updated successfully",
59558
+ task_id: args2.task_id,
59559
+ new_status: args2.status,
59560
+ current_phase: updatedPlan.current_phase
59561
+ };
59562
+ } catch (error93) {
59563
+ return {
59564
+ success: false,
59565
+ message: "Failed to update task status",
59566
+ errors: [String(error93)]
59567
+ };
59568
+ }
59569
+ }
59570
+ var update_task_status = createSwarmTool({
59571
+ description: "Update the status of a specific task in the implementation plan. " + "Task status can be one of: pending, in_progress, completed, blocked.",
59572
+ args: {
59573
+ task_id: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID in N.M format, e.g. "1.1", "1.2.3"'),
59574
+ status: tool.schema.enum(["pending", "in_progress", "completed", "blocked"]).describe("New status for the task: pending, in_progress, completed, or blocked"),
59575
+ working_directory: tool.schema.string().optional().describe("Working directory where the plan is located")
59576
+ },
59577
+ execute: async (args2, _directory) => {
59578
+ return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
59579
+ }
59580
+ });
58971
59581
  // src/index.ts
58972
59582
  init_utils();
58973
59583
 
@@ -59051,7 +59661,7 @@ var OpenCodeSwarm = async (ctx) => {
59051
59661
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
59052
59662
  preflightTriggerManager = new PTM(automationConfig);
59053
59663
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
59054
- const swarmDir = path44.resolve(ctx.directory, ".swarm");
59664
+ const swarmDir = path45.resolve(ctx.directory, ".swarm");
59055
59665
  statusArtifact = new ASA(swarmDir);
59056
59666
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
59057
59667
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -59165,7 +59775,8 @@ var OpenCodeSwarm = async (ctx) => {
59165
59775
  test_runner,
59166
59776
  todo_extract,
59167
59777
  update_task_status,
59168
- write_retro
59778
+ write_retro,
59779
+ declare_scope
59169
59780
  },
59170
59781
  config: async (opencodeConfig) => {
59171
59782
  if (!opencodeConfig.agent) {