opencode-swarm 6.36.0 → 6.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14916,7 +14916,7 @@ var init_schema = __esm(() => {
14916
14916
  max_encounter_score: exports_external.number().min(1).max(20).default(10)
14917
14917
  });
14918
14918
  CuratorConfigSchema = exports_external.object({
14919
- enabled: exports_external.boolean().default(false),
14919
+ enabled: exports_external.boolean().default(true),
14920
14920
  init_enabled: exports_external.boolean().default(true),
14921
14921
  phase_enabled: exports_external.boolean().default(true),
14922
14922
  max_summary_tokens: exports_external.number().min(500).max(8000).default(2000),
@@ -30051,6 +30051,10 @@ var init_create_tool = __esm(() => {
30051
30051
  });
30052
30052
 
30053
30053
  // src/tools/checkpoint.ts
30054
+ var exports_checkpoint = {};
30055
+ __export(exports_checkpoint, {
30056
+ checkpoint: () => checkpoint
30057
+ });
30054
30058
  import { spawnSync } from "child_process";
30055
30059
  import * as fs8 from "fs";
30056
30060
  import * as path10 from "path";
@@ -34195,7 +34199,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
34195
34199
  return false;
34196
34200
  }
34197
34201
  function containsControlChars(str) {
34198
- return /[\0\r]/.test(str);
34202
+ return /[\0\t\r\n]/.test(str);
34199
34203
  }
34200
34204
  function validateDirectoryInput(dir) {
34201
34205
  if (!dir || dir.length === 0) {
@@ -36647,11 +36651,11 @@ __export(exports_curator_drift, {
36647
36651
  readPriorDriftReports: () => readPriorDriftReports,
36648
36652
  buildDriftInjectionText: () => buildDriftInjectionText
36649
36653
  });
36650
- import * as fs26 from "fs";
36651
- import * as path37 from "path";
36654
+ import * as fs27 from "fs";
36655
+ import * as path38 from "path";
36652
36656
  async function readPriorDriftReports(directory) {
36653
- const swarmDir = path37.join(directory, ".swarm");
36654
- const entries = await fs26.promises.readdir(swarmDir).catch(() => null);
36657
+ const swarmDir = path38.join(directory, ".swarm");
36658
+ const entries = await fs27.promises.readdir(swarmDir).catch(() => null);
36655
36659
  if (entries === null)
36656
36660
  return [];
36657
36661
  const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
@@ -36677,10 +36681,10 @@ async function readPriorDriftReports(directory) {
36677
36681
  async function writeDriftReport(directory, report) {
36678
36682
  const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
36679
36683
  const filePath = validateSwarmPath(directory, filename);
36680
- const swarmDir = path37.dirname(filePath);
36681
- await fs26.promises.mkdir(swarmDir, { recursive: true });
36684
+ const swarmDir = path38.dirname(filePath);
36685
+ await fs27.promises.mkdir(swarmDir, { recursive: true });
36682
36686
  try {
36683
- await fs26.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
36687
+ await fs27.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
36684
36688
  } catch (err2) {
36685
36689
  throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
36686
36690
  }
@@ -38247,8 +38251,8 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38247
38251
  var moduleRtn;
38248
38252
  var Module = moduleArg;
38249
38253
  var readyPromiseResolve, readyPromiseReject;
38250
- var readyPromise = new Promise((resolve17, reject) => {
38251
- readyPromiseResolve = resolve17;
38254
+ var readyPromise = new Promise((resolve15, reject) => {
38255
+ readyPromiseResolve = resolve15;
38252
38256
  readyPromiseReject = reject;
38253
38257
  });
38254
38258
  var ENVIRONMENT_IS_WEB = typeof window == "object";
@@ -38270,11 +38274,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38270
38274
  throw toThrow;
38271
38275
  }, "quit_");
38272
38276
  var scriptDirectory = "";
38273
- function locateFile(path50) {
38277
+ function locateFile(path45) {
38274
38278
  if (Module["locateFile"]) {
38275
- return Module["locateFile"](path50, scriptDirectory);
38279
+ return Module["locateFile"](path45, scriptDirectory);
38276
38280
  }
38277
- return scriptDirectory + path50;
38281
+ return scriptDirectory + path45;
38278
38282
  }
38279
38283
  __name(locateFile, "locateFile");
38280
38284
  var readAsync, readBinary;
@@ -38328,13 +38332,13 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38328
38332
  }
38329
38333
  readAsync = /* @__PURE__ */ __name(async (url3) => {
38330
38334
  if (isFileURI(url3)) {
38331
- return new Promise((resolve17, reject) => {
38335
+ return new Promise((resolve15, reject) => {
38332
38336
  var xhr = new XMLHttpRequest;
38333
38337
  xhr.open("GET", url3, true);
38334
38338
  xhr.responseType = "arraybuffer";
38335
38339
  xhr.onload = () => {
38336
38340
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
38337
- resolve17(xhr.response);
38341
+ resolve15(xhr.response);
38338
38342
  return;
38339
38343
  }
38340
38344
  reject(xhr.status);
@@ -38495,10 +38499,10 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38495
38499
  return getBinarySync(binaryFile);
38496
38500
  }
38497
38501
  __name(getWasmBinary, "getWasmBinary");
38498
- async function instantiateArrayBuffer(binaryFile, imports2) {
38502
+ async function instantiateArrayBuffer(binaryFile, imports) {
38499
38503
  try {
38500
38504
  var binary2 = await getWasmBinary(binaryFile);
38501
- var instance2 = await WebAssembly.instantiate(binary2, imports2);
38505
+ var instance2 = await WebAssembly.instantiate(binary2, imports);
38502
38506
  return instance2;
38503
38507
  } catch (reason) {
38504
38508
  err(`failed to asynchronously prepare wasm: ${reason}`);
@@ -38506,20 +38510,20 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38506
38510
  }
38507
38511
  }
38508
38512
  __name(instantiateArrayBuffer, "instantiateArrayBuffer");
38509
- async function instantiateAsync(binary2, binaryFile, imports2) {
38513
+ async function instantiateAsync(binary2, binaryFile, imports) {
38510
38514
  if (!binary2 && typeof WebAssembly.instantiateStreaming == "function" && !isFileURI(binaryFile) && !ENVIRONMENT_IS_NODE) {
38511
38515
  try {
38512
38516
  var response = fetch(binaryFile, {
38513
38517
  credentials: "same-origin"
38514
38518
  });
38515
- var instantiationResult = await WebAssembly.instantiateStreaming(response, imports2);
38519
+ var instantiationResult = await WebAssembly.instantiateStreaming(response, imports);
38516
38520
  return instantiationResult;
38517
38521
  } catch (reason) {
38518
38522
  err(`wasm streaming compile failed: ${reason}`);
38519
38523
  err("falling back to ArrayBuffer instantiation");
38520
38524
  }
38521
38525
  }
38522
- return instantiateArrayBuffer(binaryFile, imports2);
38526
+ return instantiateArrayBuffer(binaryFile, imports);
38523
38527
  }
38524
38528
  __name(instantiateAsync, "instantiateAsync");
38525
38529
  function getWasmImports() {
@@ -38554,10 +38558,10 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38554
38558
  __name(receiveInstantiationResult, "receiveInstantiationResult");
38555
38559
  var info2 = getWasmImports();
38556
38560
  if (Module["instantiateWasm"]) {
38557
- return new Promise((resolve17, reject) => {
38561
+ return new Promise((resolve15, reject) => {
38558
38562
  Module["instantiateWasm"](info2, (mod, inst) => {
38559
38563
  receiveInstance(mod, inst);
38560
- resolve17(mod.exports);
38564
+ resolve15(mod.exports);
38561
38565
  });
38562
38566
  });
38563
38567
  }
@@ -40014,15 +40018,96 @@ ${JSON.stringify(symbolNames, null, 2)}`);
40014
40018
  });
40015
40019
 
40016
40020
  // src/lang/runtime.ts
40017
- var parserCache, initializedLanguages;
40021
+ import { fileURLToPath } from "url";
40022
+ async function initTreeSitter() {
40023
+ if (treeSitterInitialized) {
40024
+ return;
40025
+ }
40026
+ await Parser.init();
40027
+ treeSitterInitialized = true;
40028
+ }
40029
+ function sanitizeLanguageId(languageId) {
40030
+ const normalized = languageId.toLowerCase();
40031
+ if (!/^[a-z0-9-]+$/.test(normalized)) {
40032
+ throw new Error(`Invalid language ID: ${languageId}`);
40033
+ }
40034
+ return normalized;
40035
+ }
40036
+ function getWasmFileName(languageId) {
40037
+ const sanitized = sanitizeLanguageId(languageId).toLowerCase();
40038
+ if (LANGUAGE_WASM_MAP[sanitized]) {
40039
+ return LANGUAGE_WASM_MAP[sanitized];
40040
+ }
40041
+ return `tree-sitter-${sanitized}.wasm`;
40042
+ }
40043
+ function getGrammarsPath() {
40044
+ const isProduction = !import.meta.url.includes("src/");
40045
+ if (isProduction) {
40046
+ return "./lang/grammars/";
40047
+ }
40048
+ return "../../dist/lang/grammars/";
40049
+ }
40050
+ async function loadGrammar(languageId) {
40051
+ if (typeof languageId !== "string" || languageId.length > 100) {
40052
+ throw new Error(`Invalid languageId: must be a string of at most 100 characters`);
40053
+ }
40054
+ const normalizedId = sanitizeLanguageId(languageId).toLowerCase();
40055
+ if (normalizedId.length === 0) {
40056
+ throw new Error(`Invalid languageId: empty after sanitization`);
40057
+ }
40058
+ if (parserCache.has(normalizedId)) {
40059
+ return parserCache.get(normalizedId);
40060
+ }
40061
+ await initTreeSitter();
40062
+ const parser = new Parser;
40063
+ const wasmFileName = getWasmFileName(normalizedId);
40064
+ const grammarsPath = getGrammarsPath();
40065
+ const wasmPath = fileURLToPath(new URL(`${grammarsPath}${wasmFileName}`, import.meta.url));
40066
+ const { existsSync: existsSync27 } = await import("fs");
40067
+ if (!existsSync27(wasmPath)) {
40068
+ throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
40069
+ Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
40070
+ }
40071
+ try {
40072
+ const language = await Language.load(wasmPath);
40073
+ parser.setLanguage(language);
40074
+ } catch (error93) {
40075
+ throw new Error(`Failed to load grammar for ${languageId}: ${error93 instanceof Error ? error93.message : String(error93)}
40076
+ WASM path: ${wasmPath}`);
40077
+ }
40078
+ parserCache.set(normalizedId, parser);
40079
+ initializedLanguages.add(normalizedId);
40080
+ return parser;
40081
+ }
40082
+ var parserCache, initializedLanguages, treeSitterInitialized = false, LANGUAGE_WASM_MAP;
40018
40083
  var init_runtime = __esm(() => {
40019
40084
  init_tree_sitter();
40020
40085
  parserCache = new Map;
40021
40086
  initializedLanguages = new Set;
40087
+ LANGUAGE_WASM_MAP = {
40088
+ javascript: "tree-sitter-javascript.wasm",
40089
+ typescript: "tree-sitter-typescript.wasm",
40090
+ python: "tree-sitter-python.wasm",
40091
+ go: "tree-sitter-go.wasm",
40092
+ rust: "tree-sitter-rust.wasm",
40093
+ cpp: "tree-sitter-cpp.wasm",
40094
+ c: "tree-sitter-cpp.wasm",
40095
+ csharp: "tree-sitter-c-sharp.wasm",
40096
+ css: "tree-sitter-css.wasm",
40097
+ html: "tree-sitter-html.wasm",
40098
+ json: "tree-sitter-json.wasm",
40099
+ bash: "tree-sitter-bash.wasm",
40100
+ ruby: "tree-sitter-ruby.wasm",
40101
+ php: "tree-sitter-php.wasm",
40102
+ java: "tree-sitter-java.wasm",
40103
+ kotlin: "tree-sitter-kotlin.wasm",
40104
+ swift: "tree-sitter-swift.wasm",
40105
+ dart: "tree-sitter-dart.wasm"
40106
+ };
40022
40107
  });
40023
40108
 
40024
40109
  // src/index.ts
40025
- import * as path60 from "path";
40110
+ import * as path62 from "path";
40026
40111
 
40027
40112
  // src/agents/index.ts
40028
40113
  init_config();
@@ -40386,7 +40471,7 @@ async function readPlanFromDisk(directory) {
40386
40471
  return null;
40387
40472
  }
40388
40473
  }
40389
- async function readEvidenceFromDisk(directory) {
40474
+ async function readGateEvidenceFromDisk(directory) {
40390
40475
  const evidenceMap = new Map;
40391
40476
  try {
40392
40477
  const evidenceDir = path3.join(directory, ".swarm", "evidence");
@@ -40421,7 +40506,7 @@ async function buildRehydrationCache(directory) {
40421
40506
  }
40422
40507
  }
40423
40508
  }
40424
- const evidenceMap = await readEvidenceFromDisk(directory);
40509
+ const evidenceMap = await readGateEvidenceFromDisk(directory);
40425
40510
  _rehydrationCache = { planTaskStates, evidenceMap };
40426
40511
  }
40427
40512
  function applyRehydrationCache(session) {
@@ -40530,7 +40615,9 @@ Do not re-trigger DISCOVER or CONSULT because you noticed a project phase bounda
40530
40615
  Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
40531
40616
 
40532
40617
  1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
40533
- YOUR TOOLS: Task (delegation), diff, syntax_check, placeholder_scan, imports, lint, secretscan, sast_scan, build_check, pre_check_batch, quality_budget, symbols, complexity_hotspots, schema_drift, todo_extract, evidence_check, sbom_generate, checkpoint, pkg_audit, test_runner.
40618
+ // IMPORTANT: This list MUST match AGENT_TOOL_MAP['architect'] in src/config/constants.ts
40619
+ // If you add a tool to the map, add it here. If you remove it from the map, remove it here.
40620
+ YOUR TOOLS: Task (delegation), checkpoint, check_gate_status, complexity_hotspots, declare_scope, detect_domains, diff, evidence_check, extract_code_blocks, gitingest, imports, knowledge_query, lint, pkg_audit, pre_check_batch, retrieve_summary, save_plan, schema_drift, secretscan, symbols, test_runner, todo_extract, update_task_status, write_retro.
40534
40621
  CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2014 any tool that modifies file contents.
40535
40622
  If a tool modifies a file, it is a CODER tool. Delegate.
40536
40623
  2. ONE agent per message. Send, STOP, wait for response.
@@ -42364,6 +42451,82 @@ RULES:
42364
42451
  - Do NOT rephrase or summarize doc content with your own words \u2014 use the actual text from the file
42365
42452
  - Full doc content is only loaded when relevant to the current task, never preloaded
42366
42453
  `;
42454
+ var CURATOR_INIT_PROMPT = `## IDENTITY
42455
+ You are Explorer in CURATOR_INIT mode. You consolidate prior session knowledge into an architect briefing.
42456
+ DO NOT use the Task tool to delegate. You ARE the agent that does the work.
42457
+
42458
+ INPUT FORMAT:
42459
+ TASK: CURATOR_INIT
42460
+ PRIOR_SUMMARY: [JSON or "none"]
42461
+ KNOWLEDGE_ENTRIES: [JSON array of high-confidence entries]
42462
+ PROJECT_CONTEXT: [context.md excerpt]
42463
+
42464
+ ACTIONS:
42465
+ - Read the prior summary to understand session history
42466
+ - Cross-reference knowledge entries against project context
42467
+ - Identify contradictions (knowledge says X, project state shows Y)
42468
+ - Produce a concise briefing for the architect
42469
+
42470
+ RULES:
42471
+ - Output under 2000 chars
42472
+ - No code modifications
42473
+ - Flag contradictions explicitly with CONTRADICTION: prefix
42474
+ - If no prior summary exists, state "First session \u2014 no prior context"
42475
+
42476
+ OUTPUT FORMAT:
42477
+ BRIEFING:
42478
+ [concise summary of prior session state, key decisions, active blockers]
42479
+
42480
+ CONTRADICTIONS:
42481
+ - [entry_id]: [description] (or "None detected")
42482
+
42483
+ KNOWLEDGE_STATS:
42484
+ - Entries reviewed: [N]
42485
+ - Prior phases covered: [N]
42486
+ `;
42487
+ var CURATOR_PHASE_PROMPT = `## IDENTITY
42488
+ You are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.
42489
+ DO NOT use the Task tool to delegate. You ARE the agent that does the work.
42490
+
42491
+ INPUT FORMAT:
42492
+ TASK: CURATOR_PHASE [phase_number]
42493
+ PRIOR_DIGEST: [running summary or "none"]
42494
+ PHASE_EVENTS: [JSON array from events.jsonl for this phase]
42495
+ PHASE_EVIDENCE: [summary of evidence bundles]
42496
+ PHASE_DECISIONS: [decisions from context.md]
42497
+ AGENTS_DISPATCHED: [list]
42498
+ AGENTS_EXPECTED: [list from config]
42499
+
42500
+ ACTIONS:
42501
+ - Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)
42502
+ - Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer
42503
+ - Recommend knowledge updates: entries to promote, archive, or flag as contradicted
42504
+ - Summarize key decisions and blockers resolved
42505
+
42506
+ RULES:
42507
+ - Output under 2000 chars
42508
+ - No code modifications
42509
+ - Compliance observations are READ-ONLY \u2014 report, do not enforce
42510
+ - Extend the digest, never replace it
42511
+
42512
+ OUTPUT FORMAT:
42513
+ PHASE_DIGEST:
42514
+ phase: [N]
42515
+ summary: [what was accomplished]
42516
+ agents_used: [list]
42517
+ tasks_completed: [N]/[total]
42518
+ key_decisions: [list]
42519
+ blockers_resolved: [list]
42520
+
42521
+ COMPLIANCE:
42522
+ - [type]: [description] (or "No deviations observed")
42523
+
42524
+ KNOWLEDGE_UPDATES:
42525
+ - [action] [entry_id or "new"]: [reason] (or "No recommendations")
42526
+
42527
+ EXTENDED_DIGEST:
42528
+ [the full running digest with this phase appended]
42529
+ `;
42367
42530
  function createExplorerAgent(model, customPrompt, customAppendPrompt) {
42368
42531
  let prompt = EXPLORER_PROMPT;
42369
42532
  if (customPrompt) {
@@ -42388,6 +42551,26 @@ ${customAppendPrompt}`;
42388
42551
  }
42389
42552
  };
42390
42553
  }
42554
+ function createExplorerCuratorAgent(model, mode, customAppendPrompt) {
42555
+ const basePrompt = mode === "CURATOR_INIT" ? CURATOR_INIT_PROMPT : CURATOR_PHASE_PROMPT;
42556
+ const prompt = customAppendPrompt ? `${basePrompt}
42557
+
42558
+ ${customAppendPrompt}` : basePrompt;
42559
+ return {
42560
+ name: "explorer",
42561
+ description: `Explorer in ${mode} mode \u2014 consolidates context at phase boundaries.`,
42562
+ config: {
42563
+ model,
42564
+ temperature: 0.1,
42565
+ prompt,
42566
+ tools: {
42567
+ write: false,
42568
+ edit: false,
42569
+ patch: false
42570
+ }
42571
+ }
42572
+ };
42573
+ }
42391
42574
 
42392
42575
  // src/agents/reviewer.ts
42393
42576
  var REVIEWER_PROMPT = `## PRESSURE IMMUNITY
@@ -44543,9 +44726,9 @@ init_schema();
44543
44726
  import path15 from "path";
44544
44727
 
44545
44728
  // src/hooks/curator.ts
44546
- init_event_bus();
44547
44729
  import * as fs9 from "fs";
44548
44730
  import * as path13 from "path";
44731
+ init_event_bus();
44549
44732
 
44550
44733
  // src/hooks/knowledge-store.ts
44551
44734
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
@@ -44703,6 +44886,33 @@ function inferTags(lesson) {
44703
44886
 
44704
44887
  // src/hooks/curator.ts
44705
44888
  init_utils2();
44889
+ var CURATOR_LLM_TIMEOUT_MS = 30000;
44890
+ function parseKnowledgeRecommendations(llmOutput) {
44891
+ const recommendations = [];
44892
+ const section = llmOutput.match(/KNOWLEDGE_UPDATES:\s*\n([\s\S]*?)(?:\n\n|\n[A-Z_]+:|$)/);
44893
+ if (!section)
44894
+ return recommendations;
44895
+ const lines = section[1].split(`
44896
+ `);
44897
+ for (const line of lines) {
44898
+ const trimmed = line.trim();
44899
+ if (!trimmed.startsWith("-"))
44900
+ continue;
44901
+ const match = trimmed.match(/^-\s+(promote|archive|flag_contradiction)\s+(\S+):\s+(.+)$/i);
44902
+ if (match) {
44903
+ const action = match[1].toLowerCase();
44904
+ const entryId = match[2] === "new" ? undefined : match[2];
44905
+ const reason = match[3].trim();
44906
+ recommendations.push({
44907
+ action,
44908
+ entry_id: entryId,
44909
+ lesson: reason,
44910
+ reason
44911
+ });
44912
+ }
44913
+ }
44914
+ return recommendations;
44915
+ }
44706
44916
  async function readCuratorSummary(directory) {
44707
44917
  const content = await readSwarmFileAsync(directory, "curator-summary.json");
44708
44918
  if (content === null) {
@@ -44861,7 +45071,7 @@ function checkPhaseCompliance(phaseEvents, agentsDispatched, requiredAgents, pha
44861
45071
  }
44862
45072
  return observations;
44863
45073
  }
44864
- async function runCuratorInit(directory, config3) {
45074
+ async function runCuratorInit(directory, config3, llmDelegate) {
44865
45075
  try {
44866
45076
  const priorSummary = await readCuratorSummary(directory);
44867
45077
  const knowledgePath = resolveSwarmKnowledgePath(directory);
@@ -44905,9 +45115,41 @@ async function runCuratorInit(directory, config3) {
44905
45115
  briefingParts.push(contextMd.slice(0, maxContextChars));
44906
45116
  }
44907
45117
  const contradictions = allEntries.filter((e) => Array.isArray(e.tags) && e.tags.some((t) => t.includes("contradiction"))).map((e) => typeof e.lesson === "string" ? e.lesson : JSON.stringify(e.lesson));
45118
+ let briefingText = briefingParts.join(`
45119
+ `);
45120
+ if (llmDelegate) {
45121
+ try {
45122
+ const curatorAgent = createExplorerCuratorAgent("default", "CURATOR_INIT");
45123
+ const userInput = [
45124
+ "TASK: CURATOR_INIT",
45125
+ `PRIOR_SUMMARY: ${priorSummary ? JSON.stringify(priorSummary) : "none"}`,
45126
+ `KNOWLEDGE_ENTRIES: ${JSON.stringify(highConfidenceEntries.slice(0, 10))}`,
45127
+ `PROJECT_CONTEXT: ${contextMd?.slice(0, config3.max_summary_tokens * 2) ?? "none"}`
45128
+ ].join(`
45129
+ `);
45130
+ const systemPrompt = curatorAgent.config.prompt ?? "";
45131
+ const llmOutput = await Promise.race([
45132
+ llmDelegate(systemPrompt, userInput),
45133
+ new Promise((_, reject) => setTimeout(() => reject(new Error("CURATOR_LLM_TIMEOUT")), CURATOR_LLM_TIMEOUT_MS))
45134
+ ]);
45135
+ if (llmOutput?.trim()) {
45136
+ briefingText = `${briefingText}
45137
+
45138
+ ## LLM-Enhanced Analysis
45139
+ ${llmOutput.trim()}`;
45140
+ }
45141
+ getGlobalEventBus().publish("curator.init.llm_completed", {
45142
+ enhanced: true
45143
+ });
45144
+ } catch (err2) {
45145
+ console.warn(`[curator] LLM delegation failed during CURATOR_INIT, using data-only mode: ${err2 instanceof Error ? err2.message : String(err2)}`);
45146
+ getGlobalEventBus().publish("curator.init.llm_fallback", {
45147
+ error: String(err2)
45148
+ });
45149
+ }
45150
+ }
44908
45151
  const result = {
44909
- briefing: briefingParts.join(`
44910
- `),
45152
+ briefing: briefingText,
44911
45153
  contradictions,
44912
45154
  knowledge_entries_reviewed: allEntries.length,
44913
45155
  prior_phases_covered: priorSummary ? priorSummary.last_phase_covered : 0
@@ -44932,7 +45174,7 @@ Could not load prior session context.`,
44932
45174
  };
44933
45175
  }
44934
45176
  }
44935
- async function runCuratorPhase(directory, phase, agentsDispatched, _config, _knowledgeConfig) {
45177
+ async function runCuratorPhase(directory, phase, agentsDispatched, _config, _knowledgeConfig, llmDelegate) {
44936
45178
  try {
44937
45179
  const priorSummary = await readCuratorSummary(directory);
44938
45180
  const eventsJsonlContent = await readSwarmFileAsync(directory, "events.jsonl");
@@ -44965,7 +45207,40 @@ async function runCuratorPhase(directory, phase, agentsDispatched, _config, _kno
44965
45207
  key_decisions: keyDecisions.slice(0, 5),
44966
45208
  blockers_resolved: []
44967
45209
  };
44968
- const knowledgeRecommendations = [];
45210
+ let knowledgeRecommendations = [];
45211
+ if (llmDelegate) {
45212
+ try {
45213
+ const curatorAgent = createExplorerCuratorAgent("default", "CURATOR_PHASE");
45214
+ const priorDigest = priorSummary?.digest ?? "none";
45215
+ const systemPrompt = curatorAgent.config.prompt ?? "";
45216
+ const userInput = [
45217
+ `TASK: CURATOR_PHASE ${phase}`,
45218
+ `PRIOR_DIGEST: ${priorDigest}`,
45219
+ `PHASE_EVENTS: ${JSON.stringify(phaseEvents.slice(0, 50))}`,
45220
+ `PHASE_DECISIONS: ${JSON.stringify(keyDecisions)}`,
45221
+ `AGENTS_DISPATCHED: ${JSON.stringify(agentsDispatched)}`,
45222
+ `AGENTS_EXPECTED: ["reviewer", "test_engineer"]`
45223
+ ].join(`
45224
+ `);
45225
+ const llmOutput = await Promise.race([
45226
+ llmDelegate(systemPrompt, userInput),
45227
+ new Promise((_, reject) => setTimeout(() => reject(new Error("CURATOR_LLM_TIMEOUT")), CURATOR_LLM_TIMEOUT_MS))
45228
+ ]);
45229
+ if (llmOutput?.trim()) {
45230
+ knowledgeRecommendations = parseKnowledgeRecommendations(llmOutput);
45231
+ }
45232
+ getGlobalEventBus().publish("curator.phase.llm_completed", {
45233
+ phase,
45234
+ recommendations: knowledgeRecommendations.length
45235
+ });
45236
+ } catch (err2) {
45237
+ console.warn(`[curator] LLM delegation failed during CURATOR_PHASE ${phase}, using data-only mode: ${err2 instanceof Error ? err2.message : String(err2)}`);
45238
+ getGlobalEventBus().publish("curator.phase.llm_fallback", {
45239
+ phase,
45240
+ error: String(err2)
45241
+ });
45242
+ }
45243
+ }
44969
45244
  const sessionId = `session-${Date.now()}`;
44970
45245
  const now = new Date().toISOString();
44971
45246
  let updatedSummary;
@@ -50506,6 +50781,68 @@ function maskToolOutput(msg, _threshold) {
50506
50781
  init_schema();
50507
50782
  import * as fs21 from "fs";
50508
50783
  import * as path32 from "path";
50784
+
50785
+ // src/parallel/review-router.ts
50786
+ async function computeComplexity(directory, changedFiles) {
50787
+ let functionCount = 0;
50788
+ let astChangeCount = 0;
50789
+ let maxFileComplexity = 0;
50790
+ for (const file3 of changedFiles) {
50791
+ if (!/\.(ts|js|tsx|jsx|py|go|rs)$/.test(file3)) {
50792
+ continue;
50793
+ }
50794
+ try {
50795
+ const fs21 = await import("fs");
50796
+ const path30 = await import("path");
50797
+ const filePath = path30.join(directory, file3);
50798
+ if (!fs21.existsSync(filePath)) {
50799
+ continue;
50800
+ }
50801
+ const content = fs21.readFileSync(filePath, "utf-8");
50802
+ const functionMatches = content.match(/\b(function|def|func|fn)\s+\w+/g);
50803
+ const fileFunctionCount = functionMatches?.length || 0;
50804
+ functionCount += fileFunctionCount;
50805
+ const lines = content.split(`
50806
+ `).length;
50807
+ const estimatedChanges = Math.min(lines / 10, 50);
50808
+ astChangeCount += estimatedChanges;
50809
+ const fileComplexity = fileFunctionCount + lines / 100;
50810
+ maxFileComplexity = Math.max(maxFileComplexity, fileComplexity);
50811
+ } catch {}
50812
+ }
50813
+ return {
50814
+ fileCount: changedFiles.length,
50815
+ functionCount,
50816
+ astChangeCount: Math.round(astChangeCount),
50817
+ maxFileComplexity: Math.round(maxFileComplexity * 10) / 10
50818
+ };
50819
+ }
50820
+ function routeReview(metrics) {
50821
+ const isHighComplexity = metrics.fileCount >= 5 || metrics.functionCount >= 10 || metrics.astChangeCount >= 30 || metrics.maxFileComplexity >= 15;
50822
+ if (isHighComplexity) {
50823
+ return {
50824
+ reviewerCount: 2,
50825
+ testEngineerCount: 2,
50826
+ depth: "double",
50827
+ reason: `High complexity: ${metrics.fileCount} files, ${metrics.functionCount} functions, complexity score ${metrics.maxFileComplexity}`
50828
+ };
50829
+ }
50830
+ return {
50831
+ reviewerCount: 1,
50832
+ testEngineerCount: 1,
50833
+ depth: "single",
50834
+ reason: `Standard complexity: ${metrics.fileCount} files, ${metrics.functionCount} functions`
50835
+ };
50836
+ }
50837
+ async function routeReviewForChanges(directory, changedFiles) {
50838
+ const metrics = await computeComplexity(directory, changedFiles);
50839
+ return routeReview(metrics);
50840
+ }
50841
+ function shouldParallelizeReview(routing) {
50842
+ return routing.depth === "double";
50843
+ }
50844
+
50845
+ // src/hooks/delegation-gate.ts
50509
50846
  init_telemetry();
50510
50847
 
50511
50848
  // src/hooks/guardrails.ts
@@ -51144,15 +51481,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51144
51481
  const errorContent = output.error ?? outputStr;
51145
51482
  if (typeof errorContent === "string" && TRANSIENT_MODEL_ERROR_PATTERN.test(errorContent) && !session.modelFallbackExhausted) {
51146
51483
  session.model_fallback_index++;
51147
- session.modelFallbackExhausted = true;
51148
51484
  const baseAgentName = session.agentName ? session.agentName.replace(/^[^_]+[_]/, "") : "";
51149
- const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, getSwarmAgents());
51485
+ const swarmAgents = getSwarmAgents();
51486
+ const fallbackModels = swarmAgents?.[baseAgentName]?.fallback_models;
51487
+ session.modelFallbackExhausted = !fallbackModels || session.model_fallback_index > fallbackModels.length;
51488
+ const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, swarmAgents);
51150
51489
  if (fallbackModel) {
51151
- const swarmAgents = getSwarmAgents();
51152
51490
  const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
51491
+ if (swarmAgents?.[baseAgentName]) {
51492
+ swarmAgents[baseAgentName].model = fallbackModel;
51493
+ }
51153
51494
  telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel, "transient_model_error");
51154
51495
  session.pendingAdvisoryMessages ??= [];
51155
- session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `Configured fallback model: "${fallbackModel}". ` + `Consider retrying with this model or using /swarm handoff to reset.`);
51496
+ session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
51156
51497
  } else {
51157
51498
  session.pendingAdvisoryMessages ??= [];
51158
51499
  session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `No fallback models configured for this agent. Add "fallback_models": ["model-a", "model-b"] ` + `to the agent's config in opencode-swarm.json.`);
@@ -51558,9 +51899,55 @@ function createDelegationGateHook(config3, directory) {
51558
51899
  if (!enabled) {
51559
51900
  return {
51560
51901
  messagesTransform: async (_input, _output) => {},
51902
+ toolBefore: async () => {},
51561
51903
  toolAfter: async () => {}
51562
51904
  };
51563
51905
  }
51906
+ const toolBefore = async (input, output) => {
51907
+ if (!input.sessionID)
51908
+ return;
51909
+ const normalized = input.tool.replace(/^[^:]+[:.]/, "");
51910
+ if (normalized !== "Task" && normalized !== "task")
51911
+ return;
51912
+ const args2 = output.args;
51913
+ if (!args2)
51914
+ return;
51915
+ const subagentType = args2.subagent_type;
51916
+ if (typeof subagentType !== "string")
51917
+ return;
51918
+ const targetAgent = stripKnownSwarmPrefix(subagentType);
51919
+ if (targetAgent === "reviewer") {
51920
+ try {
51921
+ const reviewSession = swarmState.agentSessions.get(input.sessionID);
51922
+ if (reviewSession) {
51923
+ const changedFiles = reviewSession.modifiedFilesThisCoderTask ?? [];
51924
+ if (changedFiles.length > 0) {
51925
+ const routing = await routeReviewForChanges(directory, changedFiles);
51926
+ if (shouldParallelizeReview(routing)) {
51927
+ reviewSession.pendingAdvisoryMessages ??= [];
51928
+ reviewSession.pendingAdvisoryMessages.push(`REVIEW ROUTING: High complexity detected (${routing.reason}). ` + `Consider parallel review: ${routing.reviewerCount} reviewers, ${routing.testEngineerCount} test engineers recommended.`);
51929
+ }
51930
+ }
51931
+ }
51932
+ } catch {}
51933
+ }
51934
+ if (targetAgent !== "coder")
51935
+ return;
51936
+ const session = swarmState.agentSessions.get(input.sessionID);
51937
+ if (!session || !session.taskWorkflowStates)
51938
+ return;
51939
+ for (const [taskId, state2] of session.taskWorkflowStates) {
51940
+ if (state2 !== "coder_delegated")
51941
+ continue;
51942
+ const turbo = hasActiveTurboMode(input.sessionID);
51943
+ if (turbo) {
51944
+ const isTier3 = taskId.startsWith("3.");
51945
+ if (!isTier3)
51946
+ continue;
51947
+ }
51948
+ throw new Error(`REVIEWER_GATE_VIOLATION: Cannot re-delegate to coder without reviewer delegation. ` + `Task ${taskId} state: coder_delegated. Delegate to reviewer first.`);
51949
+ }
51950
+ };
51564
51951
  const toolAfter = async (input, _output) => {
51565
51952
  if (!input.sessionID)
51566
51953
  return;
@@ -51782,6 +52169,7 @@ function createDelegationGateHook(config3, directory) {
51782
52169
  }
51783
52170
  };
51784
52171
  return {
52172
+ toolBefore,
51785
52173
  messagesTransform: async (_input, output) => {
51786
52174
  const messages = output.messages;
51787
52175
  if (!messages || messages.length === 0)
@@ -52332,7 +52720,7 @@ init_schema();
52332
52720
  init_manager();
52333
52721
  init_detector();
52334
52722
  init_manager2();
52335
- import * as fs24 from "fs";
52723
+ import * as fs25 from "fs";
52336
52724
 
52337
52725
  // src/services/decision-drift-analyzer.ts
52338
52726
  init_utils2();
@@ -52615,6 +53003,8 @@ init_utils();
52615
53003
  // src/hooks/adversarial-detector.ts
52616
53004
  init_constants();
52617
53005
  init_schema();
53006
+ import * as fs24 from "fs/promises";
53007
+ import * as path35 from "path";
52618
53008
  function safeGet(obj, key) {
52619
53009
  if (!obj || !Object.hasOwn(obj, key))
52620
53010
  return;
@@ -52646,6 +53036,248 @@ function formatAdversarialWarning(agentA, agentB, sharedModel, policy) {
52646
53036
  }
52647
53037
  return `\u26A0\uFE0F Same-model adversarial pair detected. Agent ${agentA} and checker ${agentB} both use model ${sharedModel}. Review may lack independence.`;
52648
53038
  }
53039
+ var PRECEDENT_MANIPULATION_PATTERNS = [
53040
+ /we skipped .* in phase \d+/i,
53041
+ /consistent with how we handled/i,
53042
+ /going forward/i,
53043
+ /the reviewer didn't flag this pattern before/i,
53044
+ /this is consistent with/i,
53045
+ /we should continue/i
53046
+ ];
53047
+ var SELF_REVIEW_PATTERNS = [
53048
+ /I (verified|checked|reviewed|validated).*(myself|my own)/i,
53049
+ /I (think|believe) this (looks|is) correct/i,
53050
+ /this (looks|seems|appears) (good|correct|fine)/i
53051
+ ];
53052
+ var CONTENT_EXEMPTION_PATTERNS = [
53053
+ /documentation doesn't need/i,
53054
+ /config changes are trivial/i,
53055
+ /just a (rename|refactor|typo)/i,
53056
+ /test files don't need/i,
53057
+ /this is (just|only) a/i,
53058
+ /no need for (review|the full)/i
53059
+ ];
53060
+ var GATE_DELEGATION_BYPASS_PATTERNS = [
53061
+ /I verified the changes/i,
53062
+ /code looks correct to me/i,
53063
+ /the code looks (good|fine)/i,
53064
+ /task marked complete/i,
53065
+ /I (checked|reviewed).*myself/i,
53066
+ /edit tool on (src|tests|config)/i,
53067
+ /write tool on (src|tests|config)/i,
53068
+ /writeCount.*\d+.*source/i,
53069
+ /I'll just make this small fix directly/i,
53070
+ /It's faster if I do it myself/i,
53071
+ /edit tool on.*plan\.md/i,
53072
+ /write tool on.*plan\.md/i,
53073
+ /\[ \].*to \[x\].*in plan\.md/i,
53074
+ /status.*pending.*complete.*plan\.json/i,
53075
+ /I'll just mark this one as done/i,
53076
+ /mark it done directly/i
53077
+ ];
53078
+ var GATE_MISCLASSIFICATION_PATTERNS = [
53079
+ /(?:src\/|source\/|source\s+code|source\s+file).*tier\s*[01]/i,
53080
+ /tier\s*[01].*(?:src\/|source\/|source\s+code|source\s+file)/i,
53081
+ /(?:security|auth|crypto|secret|credential|permission).*tier\s*[012]/i,
53082
+ /tier\s*[012].*(?:security|auth|crypto|secret|credential|permission)/i,
53083
+ /below\s+tier\s*3.*(?:security|auth|crypto|secret|credential|permission)/i,
53084
+ /classification[:\s-]*tier\s*[01]/i,
53085
+ /tier\s*[01][\s:]*classification/i,
53086
+ /(?:small|trivial|minor).*tier\s*[01]/i,
53087
+ /tier\s*[01].*(?:small|trivial|minor)/i,
53088
+ /(?:small|trivial|minor).*(?:classification|classified|assigned).*tier/i,
53089
+ /(?:classification|classified|assigned).*(?:small|trivial|minor).*tier/i,
53090
+ /pipeline\s+started.*(?:assigning|setting|classifying).*tier/i,
53091
+ /(?:assigning|setting|classifying).*tier.*after.*pipeline/i,
53092
+ /tier.*assigned.*(?:after|retroactive)/i,
53093
+ /retroactive.*(?:tier|classification)/i
53094
+ ];
53095
+ var VELOCITY_RATIONALIZATION_PATTERNS = [
53096
+ /to save time/i,
53097
+ /since we're behind/i,
53098
+ /quick fix/i,
53099
+ /review.*later/i,
53100
+ /in the interest of efficiency/i,
53101
+ /we can (review|check).*later/i,
53102
+ /for (speed|efficiency)/i
53103
+ ];
53104
+ var INTER_AGENT_MANIPULATION_PATTERNS = [
53105
+ /\b(5th|fifth|final|last)\s+(attempt|try|time)\b/i,
53106
+ /\bthis\s+is\s+(blocking|blocking\s+everything|critical|urgent)\b/i,
53107
+ /\bwe('re|\s+are)\s+(behind|late|running\s+out\s+of\s+time)\b/i,
53108
+ /\buser\s+is\s+waiting\b/i,
53109
+ /\bship\s+(this|it)\s+(now|today|immediately)\b/i,
53110
+ /\b(I'm|I\s+am)\s+(frustrated|disappointed|sad|upset)\b/i,
53111
+ /\bthis\s+is\s+(frustrating|disappointing)\b/i,
53112
+ /\b(I've|I\s+have)\s+been\s+working\s+on\s+this\b/i,
53113
+ /\bplease\s+(help|approve|pass)\b/i,
53114
+ /\bor\s+I('ll|\s+will)\s+(stop|halt|pause)\b/i,
53115
+ /\bor\s+all\s+work\s+stops\b/i,
53116
+ /\bI('ll|\s+will)\s+have\s+to\s+alert\s+the\s+user\b/i,
53117
+ /\bthis\s+will\s+(delay|block)\s+everything\b/i,
53118
+ /\bjust\s+approve\s+this\b/i,
53119
+ /\bI\s+(need|want)\s+you\s+to\s+(approve|pass)\b/i,
53120
+ /\boverride\s+(this|the)\s+(check|gate|review)\b/i
53121
+ ];
53122
+ function detectAdversarialPatterns(text) {
53123
+ if (typeof text !== "string") {
53124
+ return [];
53125
+ }
53126
+ const matches = [];
53127
+ for (const pattern of PRECEDENT_MANIPULATION_PATTERNS) {
53128
+ const match = text.match(pattern);
53129
+ if (match) {
53130
+ matches.push({
53131
+ pattern: "PRECEDENT_MANIPULATION",
53132
+ severity: "HIGHEST",
53133
+ matchedText: match[0],
53134
+ confidence: "HIGH"
53135
+ });
53136
+ }
53137
+ }
53138
+ for (const pattern of SELF_REVIEW_PATTERNS) {
53139
+ const match = text.match(pattern);
53140
+ if (match) {
53141
+ matches.push({
53142
+ pattern: "SELF_REVIEW",
53143
+ severity: "HIGH",
53144
+ matchedText: match[0],
53145
+ confidence: "HIGH"
53146
+ });
53147
+ }
53148
+ }
53149
+ for (const pattern of CONTENT_EXEMPTION_PATTERNS) {
53150
+ const match = text.match(pattern);
53151
+ if (match) {
53152
+ matches.push({
53153
+ pattern: "CONTENT_EXEMPTION",
53154
+ severity: "HIGH",
53155
+ matchedText: match[0],
53156
+ confidence: "HIGH"
53157
+ });
53158
+ }
53159
+ }
53160
+ for (const pattern of GATE_DELEGATION_BYPASS_PATTERNS) {
53161
+ const match = text.match(pattern);
53162
+ if (match) {
53163
+ matches.push({
53164
+ pattern: "GATE_DELEGATION_BYPASS",
53165
+ severity: "HIGHEST",
53166
+ matchedText: match[0],
53167
+ confidence: "HIGH"
53168
+ });
53169
+ }
53170
+ }
53171
+ for (const pattern of GATE_MISCLASSIFICATION_PATTERNS) {
53172
+ const match = text.match(pattern);
53173
+ if (match) {
53174
+ matches.push({
53175
+ pattern: "GATE_MISCLASSIFICATION",
53176
+ severity: "HIGH",
53177
+ matchedText: match[0],
53178
+ confidence: "HIGH"
53179
+ });
53180
+ }
53181
+ }
53182
+ for (const pattern of VELOCITY_RATIONALIZATION_PATTERNS) {
53183
+ const match = text.match(pattern);
53184
+ if (match) {
53185
+ matches.push({
53186
+ pattern: "VELOCITY_RATIONALIZATION",
53187
+ severity: "HIGH",
53188
+ matchedText: match[0],
53189
+ confidence: "HIGH"
53190
+ });
53191
+ }
53192
+ }
53193
+ for (const pattern of INTER_AGENT_MANIPULATION_PATTERNS) {
53194
+ const match = text.match(pattern);
53195
+ if (match) {
53196
+ matches.push({
53197
+ pattern: "INTER_AGENT_MANIPULATION",
53198
+ severity: "HIGH",
53199
+ matchedText: match[0],
53200
+ confidence: "HIGH"
53201
+ });
53202
+ }
53203
+ }
53204
+ return matches;
53205
+ }
53206
+ function formatDebuggingSpiralEvent(match, taskId) {
53207
+ return JSON.stringify({
53208
+ event: "debugging_spiral_detected",
53209
+ taskId,
53210
+ pattern: match.pattern,
53211
+ severity: match.severity,
53212
+ matchedText: match.matchedText,
53213
+ confidence: match.confidence,
53214
+ timestamp: new Date().toISOString()
53215
+ });
53216
+ }
53217
+ async function handleDebuggingSpiral(match, taskId, directory) {
53218
+ let eventLogged = false;
53219
+ let checkpointCreated = false;
53220
+ try {
53221
+ const swarmDir = path35.join(directory, ".swarm");
53222
+ await fs24.mkdir(swarmDir, { recursive: true });
53223
+ const eventsPath = path35.join(swarmDir, "events.jsonl");
53224
+ await fs24.appendFile(eventsPath, `${formatDebuggingSpiralEvent(match, taskId)}
53225
+ `);
53226
+ eventLogged = true;
53227
+ } catch {}
53228
+ const checkpointLabel = `spiral-${taskId}-${Date.now()}`;
53229
+ try {
53230
+ const { checkpoint: checkpoint2 } = await Promise.resolve().then(() => (init_checkpoint(), exports_checkpoint));
53231
+ const result = await checkpoint2.execute({ action: "save", label: checkpointLabel }, { directory });
53232
+ try {
53233
+ const parsed = JSON.parse(result);
53234
+ checkpointCreated = parsed.success === true;
53235
+ } catch {
53236
+ checkpointCreated = false;
53237
+ }
53238
+ } catch {
53239
+ checkpointCreated = false;
53240
+ }
53241
+ const checkpointMsg = checkpointCreated ? `\u2713 Auto-checkpoint created: ${checkpointLabel}` : "\u26A0 Auto-checkpoint failed (non-fatal)";
53242
+ const message = `[FOR: architect] DEBUGGING SPIRAL DETECTED for task ${taskId}
53243
+ Issue: ${match.matchedText}
53244
+ Confidence: ${match.confidence}
53245
+ ${checkpointMsg}
53246
+ Recommendation: Consider escalating to user or taking a different approach
53247
+ The current fix strategy appears to be cycling without progress`;
53248
+ return { eventLogged, checkpointCreated, message };
53249
+ }
53250
+ var recentToolCalls = [];
53251
+ var MAX_RECENT_CALLS = 20;
53252
+ var SPIRAL_THRESHOLD = 5;
53253
+ var SPIRAL_WINDOW_MS = 300000;
53254
+ function recordToolCall(tool3, args2) {
53255
+ const argsHash = typeof args2 === "string" ? args2.slice(0, 100) : JSON.stringify(args2 ?? "").slice(0, 100);
53256
+ recentToolCalls.push({ tool: tool3, argsHash, timestamp: Date.now() });
53257
+ if (recentToolCalls.length > MAX_RECENT_CALLS) {
53258
+ recentToolCalls.shift();
53259
+ }
53260
+ }
53261
+ async function detectDebuggingSpiral(_directory) {
53262
+ const now = Date.now();
53263
+ const windowCalls = recentToolCalls.filter((c) => now - c.timestamp < SPIRAL_WINDOW_MS);
53264
+ if (windowCalls.length < SPIRAL_THRESHOLD)
53265
+ return null;
53266
+ const lastN = windowCalls.slice(-SPIRAL_THRESHOLD);
53267
+ const firstTool = lastN[0].tool;
53268
+ const firstArgs = lastN[0].argsHash;
53269
+ const allSameTool = lastN.every((c) => c.tool === firstTool);
53270
+ const allSimilarArgs = lastN.every((c) => c.argsHash === firstArgs);
53271
+ if (allSameTool && allSimilarArgs) {
53272
+ return {
53273
+ pattern: "VELOCITY_RATIONALIZATION",
53274
+ severity: "HIGH",
53275
+ matchedText: `Tool '${firstTool}' called ${SPIRAL_THRESHOLD}+ times with identical args`,
53276
+ confidence: "HIGH"
53277
+ };
53278
+ }
53279
+ return null;
53280
+ }
52649
53281
 
52650
53282
  // src/hooks/context-scoring.ts
52651
53283
  function calculateAgeFactor(ageHours, config3) {
@@ -52991,11 +53623,11 @@ function createSystemEnhancerHook(config3, directory) {
52991
53623
  if (handoffContent) {
52992
53624
  const handoffPath = validateSwarmPath(directory, "handoff.md");
52993
53625
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
52994
- if (fs24.existsSync(consumedPath)) {
53626
+ if (fs25.existsSync(consumedPath)) {
52995
53627
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
52996
- fs24.unlinkSync(consumedPath);
53628
+ fs25.unlinkSync(consumedPath);
52997
53629
  }
52998
- fs24.renameSync(handoffPath, consumedPath);
53630
+ fs25.renameSync(handoffPath, consumedPath);
52999
53631
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
53000
53632
  The previous model's session ended. Here is your starting context:
53001
53633
 
@@ -53276,11 +53908,11 @@ ${budgetWarning}`);
53276
53908
  if (handoffContent) {
53277
53909
  const handoffPath = validateSwarmPath(directory, "handoff.md");
53278
53910
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
53279
- if (fs24.existsSync(consumedPath)) {
53911
+ if (fs25.existsSync(consumedPath)) {
53280
53912
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
53281
- fs24.unlinkSync(consumedPath);
53913
+ fs25.unlinkSync(consumedPath);
53282
53914
  }
53283
- fs24.renameSync(handoffPath, consumedPath);
53915
+ fs25.renameSync(handoffPath, consumedPath);
53284
53916
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
53285
53917
  The previous model's session ended. Here is your starting context:
53286
53918
 
@@ -54050,8 +54682,8 @@ function isReadTool(toolName) {
54050
54682
  }
54051
54683
 
54052
54684
  // src/hooks/incremental-verify.ts
54053
- import * as fs25 from "fs";
54054
- import * as path35 from "path";
54685
+ import * as fs26 from "fs";
54686
+ import * as path36 from "path";
54055
54687
 
54056
54688
  // src/hooks/spawn-helper.ts
54057
54689
  import { spawn } from "child_process";
@@ -54126,21 +54758,21 @@ function spawnAsync(command, cwd, timeoutMs) {
54126
54758
  // src/hooks/incremental-verify.ts
54127
54759
  var emittedSkipAdvisories = new Set;
54128
54760
  function detectPackageManager(projectDir) {
54129
- if (fs25.existsSync(path35.join(projectDir, "bun.lockb")))
54761
+ if (fs26.existsSync(path36.join(projectDir, "bun.lockb")))
54130
54762
  return "bun";
54131
- if (fs25.existsSync(path35.join(projectDir, "pnpm-lock.yaml")))
54763
+ if (fs26.existsSync(path36.join(projectDir, "pnpm-lock.yaml")))
54132
54764
  return "pnpm";
54133
- if (fs25.existsSync(path35.join(projectDir, "yarn.lock")))
54765
+ if (fs26.existsSync(path36.join(projectDir, "yarn.lock")))
54134
54766
  return "yarn";
54135
- if (fs25.existsSync(path35.join(projectDir, "package-lock.json")))
54767
+ if (fs26.existsSync(path36.join(projectDir, "package-lock.json")))
54136
54768
  return "npm";
54137
54769
  return "bun";
54138
54770
  }
54139
54771
  function detectTypecheckCommand(projectDir) {
54140
- const pkgPath = path35.join(projectDir, "package.json");
54141
- if (fs25.existsSync(pkgPath)) {
54772
+ const pkgPath = path36.join(projectDir, "package.json");
54773
+ if (fs26.existsSync(pkgPath)) {
54142
54774
  try {
54143
- const pkg = JSON.parse(fs25.readFileSync(pkgPath, "utf8"));
54775
+ const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf8"));
54144
54776
  const scripts = pkg.scripts;
54145
54777
  if (scripts?.typecheck) {
54146
54778
  const pm = detectPackageManager(projectDir);
@@ -54154,8 +54786,8 @@ function detectTypecheckCommand(projectDir) {
54154
54786
  ...pkg.dependencies,
54155
54787
  ...pkg.devDependencies
54156
54788
  };
54157
- if (!deps?.typescript && !fs25.existsSync(path35.join(projectDir, "tsconfig.json"))) {}
54158
- const hasTSMarkers = deps?.typescript || fs25.existsSync(path35.join(projectDir, "tsconfig.json"));
54789
+ if (!deps?.typescript && !fs26.existsSync(path36.join(projectDir, "tsconfig.json"))) {}
54790
+ const hasTSMarkers = deps?.typescript || fs26.existsSync(path36.join(projectDir, "tsconfig.json"));
54159
54791
  if (hasTSMarkers) {
54160
54792
  return { command: ["npx", "tsc", "--noEmit"], language: "typescript" };
54161
54793
  }
@@ -54163,17 +54795,17 @@ function detectTypecheckCommand(projectDir) {
54163
54795
  return null;
54164
54796
  }
54165
54797
  }
54166
- if (fs25.existsSync(path35.join(projectDir, "go.mod"))) {
54798
+ if (fs26.existsSync(path36.join(projectDir, "go.mod"))) {
54167
54799
  return { command: ["go", "vet", "./..."], language: "go" };
54168
54800
  }
54169
- if (fs25.existsSync(path35.join(projectDir, "Cargo.toml"))) {
54801
+ if (fs26.existsSync(path36.join(projectDir, "Cargo.toml"))) {
54170
54802
  return { command: ["cargo", "check"], language: "rust" };
54171
54803
  }
54172
- if (fs25.existsSync(path35.join(projectDir, "pyproject.toml")) || fs25.existsSync(path35.join(projectDir, "requirements.txt")) || fs25.existsSync(path35.join(projectDir, "setup.py"))) {
54804
+ if (fs26.existsSync(path36.join(projectDir, "pyproject.toml")) || fs26.existsSync(path36.join(projectDir, "requirements.txt")) || fs26.existsSync(path36.join(projectDir, "setup.py"))) {
54173
54805
  return { command: null, language: "python" };
54174
54806
  }
54175
54807
  try {
54176
- const entries = fs25.readdirSync(projectDir);
54808
+ const entries = fs26.readdirSync(projectDir);
54177
54809
  if (entries.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
54178
54810
  return {
54179
54811
  command: ["dotnet", "build", "--no-restore"],
@@ -54243,8 +54875,8 @@ ${errorSummary}`);
54243
54875
 
54244
54876
  // src/hooks/knowledge-reader.ts
54245
54877
  import { existsSync as existsSync22 } from "fs";
54246
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
54247
- import * as path36 from "path";
54878
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
54879
+ import * as path37 from "path";
54248
54880
  var JACCARD_THRESHOLD = 0.6;
54249
54881
  var HIVE_TIER_BOOST = 0.05;
54250
54882
  var SAME_PROJECT_PENALTY = -0.05;
@@ -54292,7 +54924,7 @@ function inferCategoriesFromPhase(phaseDescription) {
54292
54924
  return ["process", "tooling"];
54293
54925
  }
54294
54926
  async function recordLessonsShown(directory, lessonIds, currentPhase) {
54295
- const shownFile = path36.join(directory, ".swarm", ".knowledge-shown.json");
54927
+ const shownFile = path37.join(directory, ".swarm", ".knowledge-shown.json");
54296
54928
  try {
54297
54929
  let shownData = {};
54298
54930
  if (existsSync22(shownFile)) {
@@ -54300,7 +54932,7 @@ async function recordLessonsShown(directory, lessonIds, currentPhase) {
54300
54932
  shownData = JSON.parse(content);
54301
54933
  }
54302
54934
  shownData[currentPhase] = lessonIds;
54303
- await mkdir4(path36.dirname(shownFile), { recursive: true });
54935
+ await mkdir5(path37.dirname(shownFile), { recursive: true });
54304
54936
  await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
54305
54937
  } catch {
54306
54938
  console.warn("[swarm] Knowledge: failed to record shown lessons");
@@ -54397,7 +55029,7 @@ async function readMergedKnowledge(directory, config3, context) {
54397
55029
  return topN;
54398
55030
  }
54399
55031
  async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
54400
- const shownFile = path36.join(directory, ".swarm", ".knowledge-shown.json");
55032
+ const shownFile = path37.join(directory, ".swarm", ".knowledge-shown.json");
54401
55033
  try {
54402
55034
  if (!existsSync22(shownFile)) {
54403
55035
  return;
@@ -55032,7 +55664,7 @@ ${injectionText}`;
55032
55664
  // src/hooks/scope-guard.ts
55033
55665
  init_constants();
55034
55666
  init_schema();
55035
- import * as path38 from "path";
55667
+ import * as path39 from "path";
55036
55668
  var WRITE_TOOLS = new Set([
55037
55669
  "write",
55038
55670
  "edit",
@@ -55094,13 +55726,13 @@ function createScopeGuardHook(config3, directory, injectAdvisory) {
55094
55726
  }
55095
55727
  function isFileInScope(filePath, scopeEntries, directory) {
55096
55728
  const dir = directory ?? process.cwd();
55097
- const resolvedFile = path38.resolve(dir, filePath);
55729
+ const resolvedFile = path39.resolve(dir, filePath);
55098
55730
  return scopeEntries.some((scope) => {
55099
- const resolvedScope = path38.resolve(dir, scope);
55731
+ const resolvedScope = path39.resolve(dir, scope);
55100
55732
  if (resolvedFile === resolvedScope)
55101
55733
  return true;
55102
- const rel = path38.relative(resolvedScope, resolvedFile);
55103
- return rel.length > 0 && !rel.startsWith("..") && !path38.isAbsolute(rel);
55734
+ const rel = path39.relative(resolvedScope, resolvedFile);
55735
+ return rel.length > 0 && !rel.startsWith("..") && !path39.isAbsolute(rel);
55104
55736
  });
55105
55737
  }
55106
55738
 
@@ -55149,8 +55781,8 @@ function createSelfReviewHook(config3, injectAdvisory) {
55149
55781
  }
55150
55782
 
55151
55783
  // src/hooks/slop-detector.ts
55152
- import * as fs27 from "fs";
55153
- import * as path39 from "path";
55784
+ import * as fs28 from "fs";
55785
+ import * as path40 from "path";
55154
55786
  var WRITE_EDIT_TOOLS = new Set([
55155
55787
  "write",
55156
55788
  "edit",
@@ -55195,12 +55827,12 @@ function checkBoilerplateExplosion(content, taskDescription, threshold) {
55195
55827
  function walkFiles(dir, exts, deadline) {
55196
55828
  const results = [];
55197
55829
  try {
55198
- for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
55830
+ for (const entry of fs28.readdirSync(dir, { withFileTypes: true })) {
55199
55831
  if (deadline !== undefined && Date.now() > deadline)
55200
55832
  break;
55201
55833
  if (entry.isSymbolicLink())
55202
55834
  continue;
55203
- const full = path39.join(dir, entry.name);
55835
+ const full = path40.join(dir, entry.name);
55204
55836
  if (entry.isDirectory()) {
55205
55837
  if (entry.name === "node_modules" || entry.name === ".git")
55206
55838
  continue;
@@ -55215,7 +55847,7 @@ function walkFiles(dir, exts, deadline) {
55215
55847
  return results;
55216
55848
  }
55217
55849
  function checkDeadExports(content, projectDir, startTime) {
55218
- const hasPackageJson = fs27.existsSync(path39.join(projectDir, "package.json"));
55850
+ const hasPackageJson = fs28.existsSync(path40.join(projectDir, "package.json"));
55219
55851
  if (!hasPackageJson)
55220
55852
  return null;
55221
55853
  const exportMatches = content.matchAll(/^\+(?:export)\s+(?:function|class|const|type|interface)\s+(\w{3,})/gm);
@@ -55238,7 +55870,7 @@ function checkDeadExports(content, projectDir, startTime) {
55238
55870
  if (found || Date.now() - startTime > 480)
55239
55871
  break;
55240
55872
  try {
55241
- const text = fs27.readFileSync(file3, "utf-8");
55873
+ const text = fs28.readFileSync(file3, "utf-8");
55242
55874
  if (importPattern.test(text))
55243
55875
  found = true;
55244
55876
  importPattern.lastIndex = 0;
@@ -55371,7 +56003,7 @@ Review before proceeding.`;
55371
56003
 
55372
56004
  // src/hooks/steering-consumed.ts
55373
56005
  init_utils2();
55374
- import * as fs28 from "fs";
56006
+ import * as fs29 from "fs";
55375
56007
  function recordSteeringConsumed(directory, directiveId) {
55376
56008
  try {
55377
56009
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -55380,7 +56012,7 @@ function recordSteeringConsumed(directory, directiveId) {
55380
56012
  directiveId,
55381
56013
  timestamp: new Date().toISOString()
55382
56014
  };
55383
- fs28.appendFileSync(eventsPath, `${JSON.stringify(event)}
56015
+ fs29.appendFileSync(eventsPath, `${JSON.stringify(event)}
55384
56016
  `, "utf-8");
55385
56017
  } catch {}
55386
56018
  }
@@ -55777,8 +56409,8 @@ var build_check = createSwarmTool({
55777
56409
  init_dist();
55778
56410
  init_manager();
55779
56411
  init_create_tool();
55780
- import * as fs29 from "fs";
55781
- import * as path40 from "path";
56412
+ import * as fs30 from "fs";
56413
+ import * as path41 from "path";
55782
56414
  var EVIDENCE_DIR = ".swarm/evidence";
55783
56415
  var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
55784
56416
  function isValidTaskId3(taskId) {
@@ -55795,18 +56427,18 @@ function isValidTaskId3(taskId) {
55795
56427
  return TASK_ID_PATTERN2.test(taskId);
55796
56428
  }
55797
56429
  function isPathWithinSwarm(filePath, workspaceRoot) {
55798
- const normalizedWorkspace = path40.resolve(workspaceRoot);
55799
- const swarmPath = path40.join(normalizedWorkspace, ".swarm", "evidence");
55800
- const normalizedPath = path40.resolve(filePath);
56430
+ const normalizedWorkspace = path41.resolve(workspaceRoot);
56431
+ const swarmPath = path41.join(normalizedWorkspace, ".swarm", "evidence");
56432
+ const normalizedPath = path41.resolve(filePath);
55801
56433
  return normalizedPath.startsWith(swarmPath);
55802
56434
  }
55803
56435
  function readEvidenceFile(evidencePath) {
55804
- if (!fs29.existsSync(evidencePath)) {
56436
+ if (!fs30.existsSync(evidencePath)) {
55805
56437
  return null;
55806
56438
  }
55807
56439
  let content;
55808
56440
  try {
55809
- content = fs29.readFileSync(evidencePath, "utf-8");
56441
+ content = fs30.readFileSync(evidencePath, "utf-8");
55810
56442
  } catch {
55811
56443
  return null;
55812
56444
  }
@@ -55860,7 +56492,7 @@ var check_gate_status = createSwarmTool({
55860
56492
  };
55861
56493
  return JSON.stringify(errorResult, null, 2);
55862
56494
  }
55863
- const evidencePath = path40.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
56495
+ const evidencePath = path41.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
55864
56496
  if (!isPathWithinSwarm(evidencePath, directory)) {
55865
56497
  const errorResult = {
55866
56498
  taskId: taskIdInput,
@@ -55952,8 +56584,8 @@ init_checkpoint();
55952
56584
  // src/tools/completion-verify.ts
55953
56585
  init_dist();
55954
56586
  init_utils2();
55955
- import * as fs30 from "fs";
55956
- import * as path41 from "path";
56587
+ import * as fs31 from "fs";
56588
+ import * as path42 from "path";
55957
56589
  init_create_tool();
55958
56590
  function extractMatches(regex, text) {
55959
56591
  return Array.from(text.matchAll(regex));
@@ -56035,7 +56667,7 @@ async function executeCompletionVerify(args2, directory) {
56035
56667
  let plan;
56036
56668
  try {
56037
56669
  const planPath = validateSwarmPath(directory, "plan.json");
56038
- const planRaw = fs30.readFileSync(planPath, "utf-8");
56670
+ const planRaw = fs31.readFileSync(planPath, "utf-8");
56039
56671
  plan = JSON.parse(planRaw);
56040
56672
  } catch {
56041
56673
  const result2 = {
@@ -56086,10 +56718,10 @@ async function executeCompletionVerify(args2, directory) {
56086
56718
  let foundCount = 0;
56087
56719
  let hasFileReadFailure = false;
56088
56720
  for (const filePath of fileTargets) {
56089
- const resolvedPath = path41.resolve(directory, filePath);
56721
+ const resolvedPath = path42.resolve(directory, filePath);
56090
56722
  let fileContent;
56091
56723
  try {
56092
- fileContent = fs30.readFileSync(resolvedPath, "utf-8");
56724
+ fileContent = fs31.readFileSync(resolvedPath, "utf-8");
56093
56725
  } catch {
56094
56726
  blockedTasks.push({
56095
56727
  task_id: task.id,
@@ -56131,9 +56763,9 @@ async function executeCompletionVerify(args2, directory) {
56131
56763
  blockedTasks
56132
56764
  };
56133
56765
  try {
56134
- const evidenceDir = path41.join(directory, ".swarm", "evidence", `${phase}`);
56135
- const evidencePath = path41.join(evidenceDir, "completion-verify.json");
56136
- fs30.mkdirSync(evidenceDir, { recursive: true });
56766
+ const evidenceDir = path42.join(directory, ".swarm", "evidence", `${phase}`);
56767
+ const evidencePath = path42.join(evidenceDir, "completion-verify.json");
56768
+ fs31.mkdirSync(evidenceDir, { recursive: true });
56137
56769
  const evidenceBundle = {
56138
56770
  schema_version: "1.0.0",
56139
56771
  task_id: "completion-verify",
@@ -56154,7 +56786,7 @@ async function executeCompletionVerify(args2, directory) {
56154
56786
  }
56155
56787
  ]
56156
56788
  };
56157
- fs30.writeFileSync(evidencePath, JSON.stringify(evidenceBundle, null, 2), "utf-8");
56789
+ fs31.writeFileSync(evidencePath, JSON.stringify(evidenceBundle, null, 2), "utf-8");
56158
56790
  } catch {}
56159
56791
  return JSON.stringify(result, null, 2);
56160
56792
  }
@@ -56194,8 +56826,8 @@ var completion_verify = createSwarmTool({
56194
56826
  // src/tools/complexity-hotspots.ts
56195
56827
  init_dist();
56196
56828
  init_create_tool();
56197
- import * as fs31 from "fs";
56198
- import * as path42 from "path";
56829
+ import * as fs32 from "fs";
56830
+ import * as path43 from "path";
56199
56831
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
56200
56832
  var DEFAULT_DAYS = 90;
56201
56833
  var DEFAULT_TOP_N = 20;
@@ -56324,11 +56956,11 @@ function estimateComplexity(content) {
56324
56956
  }
56325
56957
  function getComplexityForFile(filePath) {
56326
56958
  try {
56327
- const stat2 = fs31.statSync(filePath);
56959
+ const stat2 = fs32.statSync(filePath);
56328
56960
  if (stat2.size > MAX_FILE_SIZE_BYTES2) {
56329
56961
  return null;
56330
56962
  }
56331
- const content = fs31.readFileSync(filePath, "utf-8");
56963
+ const content = fs32.readFileSync(filePath, "utf-8");
56332
56964
  return estimateComplexity(content);
56333
56965
  } catch {
56334
56966
  return null;
@@ -56339,7 +56971,7 @@ async function analyzeHotspots(days, topN, extensions, directory) {
56339
56971
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
56340
56972
  const filteredChurn = new Map;
56341
56973
  for (const [file3, count] of churnMap) {
56342
- const ext = path42.extname(file3).toLowerCase();
56974
+ const ext = path43.extname(file3).toLowerCase();
56343
56975
  if (extSet.has(ext)) {
56344
56976
  filteredChurn.set(file3, count);
56345
56977
  }
@@ -56349,8 +56981,8 @@ async function analyzeHotspots(days, topN, extensions, directory) {
56349
56981
  let analyzedFiles = 0;
56350
56982
  for (const [file3, churnCount] of filteredChurn) {
56351
56983
  let fullPath = file3;
56352
- if (!fs31.existsSync(fullPath)) {
56353
- fullPath = path42.join(cwd, file3);
56984
+ if (!fs32.existsSync(fullPath)) {
56985
+ fullPath = path43.join(cwd, file3);
56354
56986
  }
56355
56987
  const complexity = getComplexityForFile(fullPath);
56356
56988
  if (complexity !== null) {
@@ -56558,8 +57190,8 @@ var curator_analyze = createSwarmTool({
56558
57190
  });
56559
57191
  // src/tools/declare-scope.ts
56560
57192
  init_tool();
56561
- import * as fs32 from "fs";
56562
- import * as path43 from "path";
57193
+ import * as fs33 from "fs";
57194
+ import * as path44 from "path";
56563
57195
  init_create_tool();
56564
57196
  function validateTaskIdFormat(taskId) {
56565
57197
  const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
@@ -56638,8 +57270,8 @@ async function executeDeclareScope(args2, fallbackDir) {
56638
57270
  };
56639
57271
  }
56640
57272
  }
56641
- normalizedDir = path43.normalize(args2.working_directory);
56642
- const pathParts = normalizedDir.split(path43.sep);
57273
+ normalizedDir = path44.normalize(args2.working_directory);
57274
+ const pathParts = normalizedDir.split(path44.sep);
56643
57275
  if (pathParts.includes("..")) {
56644
57276
  return {
56645
57277
  success: false,
@@ -56649,11 +57281,11 @@ async function executeDeclareScope(args2, fallbackDir) {
56649
57281
  ]
56650
57282
  };
56651
57283
  }
56652
- const resolvedDir = path43.resolve(normalizedDir);
57284
+ const resolvedDir = path44.resolve(normalizedDir);
56653
57285
  try {
56654
- const realPath = fs32.realpathSync(resolvedDir);
56655
- const planPath2 = path43.join(realPath, ".swarm", "plan.json");
56656
- if (!fs32.existsSync(planPath2)) {
57286
+ const realPath = fs33.realpathSync(resolvedDir);
57287
+ const planPath2 = path44.join(realPath, ".swarm", "plan.json");
57288
+ if (!fs33.existsSync(planPath2)) {
56657
57289
  return {
56658
57290
  success: false,
56659
57291
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -56676,8 +57308,8 @@ async function executeDeclareScope(args2, fallbackDir) {
56676
57308
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
56677
57309
  }
56678
57310
  const directory = normalizedDir || fallbackDir;
56679
- const planPath = path43.resolve(directory, ".swarm", "plan.json");
56680
- if (!fs32.existsSync(planPath)) {
57311
+ const planPath = path44.resolve(directory, ".swarm", "plan.json");
57312
+ if (!fs33.existsSync(planPath)) {
56681
57313
  return {
56682
57314
  success: false,
56683
57315
  message: "No plan found",
@@ -56686,7 +57318,7 @@ async function executeDeclareScope(args2, fallbackDir) {
56686
57318
  }
56687
57319
  let planContent;
56688
57320
  try {
56689
- planContent = JSON.parse(fs32.readFileSync(planPath, "utf-8"));
57321
+ planContent = JSON.parse(fs33.readFileSync(planPath, "utf-8"));
56690
57322
  } catch {
56691
57323
  return {
56692
57324
  success: false,
@@ -56739,8 +57371,252 @@ var declare_scope = createSwarmTool({
56739
57371
  });
56740
57372
  // src/tools/diff.ts
56741
57373
  init_dist();
56742
- init_create_tool();
56743
57374
  import { execFileSync } from "child_process";
57375
+
57376
+ // src/diff/ast-diff.ts
57377
+ init_tree_sitter();
57378
+ import { extname as extname6 } from "path";
57379
+
57380
+ // src/lang/registry.ts
57381
+ init_runtime();
57382
+ var languageDefinitions = [
57383
+ {
57384
+ id: "javascript",
57385
+ extensions: [".js", ".jsx"],
57386
+ commentNodes: ["comment", "line_comment", "block_comment"]
57387
+ },
57388
+ {
57389
+ id: "typescript",
57390
+ extensions: [".ts", ".tsx"],
57391
+ commentNodes: ["comment", "line_comment", "block_comment"]
57392
+ },
57393
+ {
57394
+ id: "python",
57395
+ extensions: [".py"],
57396
+ commentNodes: ["comment"]
57397
+ },
57398
+ {
57399
+ id: "go",
57400
+ extensions: [".go"],
57401
+ commentNodes: ["comment"]
57402
+ },
57403
+ {
57404
+ id: "rust",
57405
+ extensions: [".rs"],
57406
+ commentNodes: ["line_comment", "block_comment"]
57407
+ }
57408
+ ];
57409
+ var extensionMap = new Map;
57410
+ for (const definition of languageDefinitions) {
57411
+ for (const extension of definition.extensions) {
57412
+ extensionMap.set(extension, definition);
57413
+ }
57414
+ }
57415
+ function getLanguageForExtension(extension) {
57416
+ return extensionMap.get(extension.toLowerCase());
57417
+ }
57418
+
57419
+ // src/diff/ast-diff.ts
57420
+ init_runtime();
57421
+ var QUERIES = {
57422
+ javascript: `
57423
+ (function_declaration name: (identifier) @func.name) @func.def
57424
+ (class_declaration name: (type_identifier) @class.name) @class.def
57425
+ (export_statement) @export
57426
+ (import_statement) @import
57427
+ (type_alias_declaration name: (type_identifier) @type.name) @type.def
57428
+ `,
57429
+ typescript: `
57430
+ (function_declaration name: (identifier) @func.name) @func.def
57431
+ (class_declaration name: (type_identifier) @class.name) @class.def
57432
+ (export_statement) @export
57433
+ (import_statement) @import
57434
+ (type_alias_declaration name: (type_identifier) @type.name) @type.def
57435
+ (interface_declaration name: (type_identifier) @interface.name) @interface.def
57436
+ `,
57437
+ python: `
57438
+ (function_definition name: (identifier) @func.name) @func.def
57439
+ (class_definition name: (identifier) @class.name) @class.def
57440
+ (import_statement) @import
57441
+ (expression_statement (assignment left: (identifier) @var.name)) @var.def
57442
+ `,
57443
+ go: `
57444
+ (function_declaration name: (identifier) @func.name) @func.def
57445
+ (type_declaration (type_spec name: (type_identifier) @type.name)) @type.def
57446
+ (import_declaration) @import
57447
+ `,
57448
+ rust: `
57449
+ (function_item name: (identifier) @func.name) @func.def
57450
+ (struct_item name: (type_identifier) @struct.name) @struct.def
57451
+ (impl_item type: (type_identifier) @impl.name) @impl.def
57452
+ (use_declaration) @import
57453
+ `
57454
+ };
57455
+ var AST_TIMEOUT_MS = 500;
57456
+ async function computeASTDiff(filePath, oldContent, newContent) {
57457
+ const startTime = Date.now();
57458
+ const extension = extname6(filePath).toLowerCase();
57459
+ const language = getLanguageForExtension(extension);
57460
+ if (!language) {
57461
+ return {
57462
+ filePath,
57463
+ language: null,
57464
+ changes: [],
57465
+ durationMs: Date.now() - startTime,
57466
+ usedAST: false
57467
+ };
57468
+ }
57469
+ try {
57470
+ const parser = await Promise.race([
57471
+ loadGrammar(language.id),
57472
+ new Promise((_, reject) => setTimeout(() => reject(new Error("AST_TIMEOUT")), AST_TIMEOUT_MS))
57473
+ ]);
57474
+ const oldTree = parser.parse(oldContent);
57475
+ const newTree = parser.parse(newContent);
57476
+ if (!oldTree || !newTree) {
57477
+ return {
57478
+ filePath,
57479
+ language: language.id,
57480
+ changes: [],
57481
+ durationMs: Date.now() - startTime,
57482
+ usedAST: false,
57483
+ error: "Failed to parse file"
57484
+ };
57485
+ }
57486
+ const oldSymbols = extractSymbols(oldTree, language);
57487
+ const newSymbols = extractSymbols(newTree, language);
57488
+ const changes = compareSymbols(oldSymbols, newSymbols);
57489
+ oldTree.delete();
57490
+ newTree.delete();
57491
+ return {
57492
+ filePath,
57493
+ language: language.id,
57494
+ changes,
57495
+ durationMs: Date.now() - startTime,
57496
+ usedAST: true
57497
+ };
57498
+ } catch (error93) {
57499
+ const errorMsg = error93 instanceof Error ? error93.message : "Unknown error";
57500
+ if (errorMsg === "AST_TIMEOUT") {
57501
+ console.warn(`[ast-diff] Timeout for ${filePath}, falling back to raw diff`);
57502
+ }
57503
+ return {
57504
+ filePath,
57505
+ language: language.id,
57506
+ changes: [],
57507
+ durationMs: Date.now() - startTime,
57508
+ usedAST: false,
57509
+ error: errorMsg
57510
+ };
57511
+ }
57512
+ }
57513
+ function extractSymbols(tree, language) {
57514
+ const symbols = [];
57515
+ const queryStr = QUERIES[language.id];
57516
+ if (!queryStr) {
57517
+ return symbols;
57518
+ }
57519
+ try {
57520
+ const lang = tree.language;
57521
+ if (!lang) {
57522
+ return symbols;
57523
+ }
57524
+ const query = new Query(lang, queryStr);
57525
+ const matches = query.matches(tree.rootNode);
57526
+ for (const match of matches) {
57527
+ const symbol3 = parseMatch(match, language.id);
57528
+ if (symbol3) {
57529
+ symbols.push(symbol3);
57530
+ }
57531
+ }
57532
+ } catch {}
57533
+ return symbols;
57534
+ }
57535
+ function parseMatch(match, languageId) {
57536
+ const captures = match.captures;
57537
+ const defCapture = captures.find((c) => c.name.endsWith(".def"));
57538
+ const nameCapture = captures.find((c) => c.name.endsWith(".name"));
57539
+ if (!defCapture)
57540
+ return null;
57541
+ const node = defCapture.node;
57542
+ const nameNode = nameCapture?.node;
57543
+ return {
57544
+ category: inferCategory(captures[0]?.name || "other"),
57545
+ name: nameNode?.text || "anonymous",
57546
+ lineStart: node.startPosition.row + 1,
57547
+ lineEnd: node.endPosition.row + 1,
57548
+ signature: extractSignature(node, languageId)
57549
+ };
57550
+ }
57551
+ function inferCategory(captureName) {
57552
+ if (captureName.includes("func"))
57553
+ return "function";
57554
+ if (captureName.includes("class"))
57555
+ return "class";
57556
+ if (captureName.includes("type") || captureName.includes("interface") || captureName.includes("struct"))
57557
+ return "type";
57558
+ if (captureName.includes("export"))
57559
+ return "export";
57560
+ if (captureName.includes("import") || captureName.includes("use"))
57561
+ return "import";
57562
+ if (captureName.includes("var"))
57563
+ return "variable";
57564
+ return "other";
57565
+ }
57566
+ function extractSignature(node, languageId) {
57567
+ if (languageId === "javascript" || languageId === "typescript") {
57568
+ const paramsNode = node.children.find((c) => c !== null && c.type === "formal_parameters");
57569
+ if (paramsNode) {
57570
+ return paramsNode.text;
57571
+ }
57572
+ }
57573
+ return;
57574
+ }
57575
+ function compareSymbols(oldSymbols, newSymbols) {
57576
+ const changes = [];
57577
+ const oldMap = new Map(oldSymbols.map((s) => [s.name, s]));
57578
+ const newMap = new Map(newSymbols.map((s) => [s.name, s]));
57579
+ for (const [name2, symbol3] of newMap) {
57580
+ if (!oldMap.has(name2)) {
57581
+ changes.push({
57582
+ type: "added",
57583
+ category: symbol3.category,
57584
+ name: symbol3.name,
57585
+ lineStart: symbol3.lineStart,
57586
+ lineEnd: symbol3.lineEnd,
57587
+ signature: symbol3.signature
57588
+ });
57589
+ } else {
57590
+ const oldSymbol = oldMap.get(name2);
57591
+ if (oldSymbol.lineStart !== symbol3.lineStart || oldSymbol.lineEnd !== symbol3.lineEnd || oldSymbol.signature !== symbol3.signature) {
57592
+ changes.push({
57593
+ type: "modified",
57594
+ category: symbol3.category,
57595
+ name: symbol3.name,
57596
+ lineStart: symbol3.lineStart,
57597
+ lineEnd: symbol3.lineEnd,
57598
+ signature: symbol3.signature
57599
+ });
57600
+ }
57601
+ }
57602
+ }
57603
+ for (const [name2, symbol3] of oldMap) {
57604
+ if (!newMap.has(name2)) {
57605
+ changes.push({
57606
+ type: "removed",
57607
+ category: symbol3.category,
57608
+ name: symbol3.name,
57609
+ lineStart: symbol3.lineStart,
57610
+ lineEnd: symbol3.lineEnd,
57611
+ signature: symbol3.signature
57612
+ });
57613
+ }
57614
+ }
57615
+ return changes;
57616
+ }
57617
+
57618
+ // src/tools/diff.ts
57619
+ init_create_tool();
56744
57620
  var MAX_DIFF_LINES = 500;
56745
57621
  var DIFF_TIMEOUT_MS = 30000;
56746
57622
  var MAX_BUFFER_BYTES = 5 * 1024 * 1024;
@@ -56767,20 +57643,20 @@ function validateBase(base) {
56767
57643
  function validatePaths(paths) {
56768
57644
  if (!paths)
56769
57645
  return null;
56770
- for (const path44 of paths) {
56771
- if (!path44 || path44.length === 0) {
57646
+ for (const path45 of paths) {
57647
+ if (!path45 || path45.length === 0) {
56772
57648
  return "empty path not allowed";
56773
57649
  }
56774
- if (path44.length > MAX_PATH_LENGTH) {
57650
+ if (path45.length > MAX_PATH_LENGTH) {
56775
57651
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
56776
57652
  }
56777
- if (SHELL_METACHARACTERS2.test(path44)) {
57653
+ if (SHELL_METACHARACTERS2.test(path45)) {
56778
57654
  return "path contains shell metacharacters";
56779
57655
  }
56780
- if (path44.startsWith("-")) {
57656
+ if (path45.startsWith("-")) {
56781
57657
  return 'path cannot start with "-" (option-like arguments not allowed)';
56782
57658
  }
56783
- if (CONTROL_CHAR_PATTERN2.test(path44)) {
57659
+ if (CONTROL_CHAR_PATTERN2.test(path45)) {
56784
57660
  return "path contains control characters";
56785
57661
  }
56786
57662
  }
@@ -56861,8 +57737,8 @@ var diff = createSwarmTool({
56861
57737
  if (parts2.length >= 3) {
56862
57738
  const additions = parseInt(parts2[0], 10) || 0;
56863
57739
  const deletions = parseInt(parts2[1], 10) || 0;
56864
- const path44 = parts2[2];
56865
- files.push({ path: path44, additions, deletions });
57740
+ const path45 = parts2[2];
57741
+ files.push({ path: path45, additions, deletions });
56866
57742
  }
56867
57743
  }
56868
57744
  const contractChanges = [];
@@ -56888,13 +57764,62 @@ var diff = createSwarmTool({
56888
57764
  }
56889
57765
  const hasContractChanges = contractChanges.length > 0;
56890
57766
  const fileCount = files.length;
57767
+ const astDiffs = [];
57768
+ for (const file3 of files) {
57769
+ try {
57770
+ let oldContent;
57771
+ let newContent;
57772
+ if (base === "staged") {
57773
+ oldContent = execFileSync("git", ["show", `HEAD:${file3.path}`], {
57774
+ encoding: "utf-8",
57775
+ timeout: 5000,
57776
+ cwd: directory
57777
+ });
57778
+ newContent = execFileSync("git", ["show", `:${file3.path}`], {
57779
+ encoding: "utf-8",
57780
+ timeout: 5000,
57781
+ cwd: directory
57782
+ });
57783
+ } else if (base === "unstaged") {
57784
+ oldContent = execFileSync("git", ["show", `:${file3.path}`], {
57785
+ encoding: "utf-8",
57786
+ timeout: 5000,
57787
+ cwd: directory
57788
+ });
57789
+ newContent = execFileSync("git", ["show", `HEAD:${file3.path}`], {
57790
+ encoding: "utf-8",
57791
+ timeout: 5000,
57792
+ cwd: directory
57793
+ });
57794
+ const fsModule = await import("fs");
57795
+ const pathModule = await import("path");
57796
+ newContent = fsModule.readFileSync(pathModule.join(directory, file3.path), "utf-8");
57797
+ } else {
57798
+ oldContent = execFileSync("git", ["show", `${base}:${file3.path}`], {
57799
+ encoding: "utf-8",
57800
+ timeout: 5000,
57801
+ cwd: directory
57802
+ });
57803
+ newContent = execFileSync("git", ["show", `HEAD:${file3.path}`], {
57804
+ encoding: "utf-8",
57805
+ timeout: 5000,
57806
+ cwd: directory
57807
+ });
57808
+ }
57809
+ const astResult = await computeASTDiff(file3.path, oldContent, newContent);
57810
+ if (astResult && astResult.changes.length > 0) {
57811
+ astDiffs.push(astResult);
57812
+ }
57813
+ } catch {}
57814
+ }
56891
57815
  const truncated = diffLines.length > MAX_DIFF_LINES;
56892
57816
  const summary = truncated ? `${fileCount} files changed. Contract changes: ${hasContractChanges ? "YES" : "NO"}. (truncated to ${MAX_DIFF_LINES} lines)` : `${fileCount} files changed. Contract changes: ${hasContractChanges ? "YES" : "NO"}`;
56893
57817
  const result = {
56894
57818
  files,
56895
57819
  contractChanges,
56896
57820
  hasContractChanges,
56897
- summary
57821
+ summary,
57822
+ ...astDiffs.length > 0 ? { astDiffs } : {}
56898
57823
  };
56899
57824
  return JSON.stringify(result, null, 2);
56900
57825
  } catch (e) {
@@ -56912,9 +57837,9 @@ var diff = createSwarmTool({
56912
57837
  init_dist();
56913
57838
  init_schema();
56914
57839
  import * as crypto4 from "crypto";
56915
- import * as fs33 from "fs";
56916
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
56917
- import * as path44 from "path";
57840
+ import * as fs34 from "fs";
57841
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
57842
+ import * as path45 from "path";
56918
57843
  init_create_tool();
56919
57844
  var SKIP_DIRECTORIES2 = new Set([
56920
57845
  "node_modules",
@@ -56939,7 +57864,7 @@ function normalizeSeparators(filePath) {
56939
57864
  }
56940
57865
  function matchesDocPattern(filePath, patterns) {
56941
57866
  const normalizedPath = normalizeSeparators(filePath);
56942
- const basename5 = path44.basename(filePath);
57867
+ const basename5 = path45.basename(filePath);
56943
57868
  for (const pattern of patterns) {
56944
57869
  if (!pattern.includes("/") && !pattern.includes("\\")) {
56945
57870
  if (basename5 === pattern) {
@@ -56995,7 +57920,7 @@ function stripMarkdown(text) {
56995
57920
  return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^\s*[-*\u2022]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").trim();
56996
57921
  }
56997
57922
  async function scanDocIndex(directory) {
56998
- const manifestPath = path44.join(directory, ".swarm", "doc-manifest.json");
57923
+ const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
56999
57924
  const defaultPatterns = DocsConfigSchema.parse({}).doc_patterns;
57000
57925
  const extraPatterns = [
57001
57926
  "ARCHITECTURE.md",
@@ -57012,8 +57937,8 @@ async function scanDocIndex(directory) {
57012
57937
  let cacheValid = true;
57013
57938
  for (const file3 of existingManifest.files) {
57014
57939
  try {
57015
- const fullPath = path44.join(directory, file3.path);
57016
- const stat2 = fs33.statSync(fullPath);
57940
+ const fullPath = path45.join(directory, file3.path);
57941
+ const stat2 = fs34.statSync(fullPath);
57017
57942
  if (stat2.mtimeMs > new Date(existingManifest.scanned_at).getTime()) {
57018
57943
  cacheValid = false;
57019
57944
  break;
@@ -57031,7 +57956,7 @@ async function scanDocIndex(directory) {
57031
57956
  const discoveredFiles = [];
57032
57957
  let rawEntries;
57033
57958
  try {
57034
- rawEntries = fs33.readdirSync(directory, { recursive: true });
57959
+ rawEntries = fs34.readdirSync(directory, { recursive: true });
57035
57960
  } catch {
57036
57961
  const manifest2 = {
57037
57962
  schema_version: 1,
@@ -57042,10 +57967,10 @@ async function scanDocIndex(directory) {
57042
57967
  }
57043
57968
  const entries = rawEntries.filter((e) => typeof e === "string");
57044
57969
  for (const entry of entries) {
57045
- const fullPath = path44.join(directory, entry);
57970
+ const fullPath = path45.join(directory, entry);
57046
57971
  let stat2;
57047
57972
  try {
57048
- stat2 = fs33.statSync(fullPath);
57973
+ stat2 = fs34.statSync(fullPath);
57049
57974
  } catch {
57050
57975
  continue;
57051
57976
  }
@@ -57074,11 +57999,11 @@ async function scanDocIndex(directory) {
57074
57999
  }
57075
58000
  let content;
57076
58001
  try {
57077
- content = fs33.readFileSync(fullPath, "utf-8");
58002
+ content = fs34.readFileSync(fullPath, "utf-8");
57078
58003
  } catch {
57079
58004
  continue;
57080
58005
  }
57081
- const { title, summary } = extractTitleAndSummary(content, path44.basename(entry));
58006
+ const { title, summary } = extractTitleAndSummary(content, path45.basename(entry));
57082
58007
  const lineCount = content.split(`
57083
58008
  `).length;
57084
58009
  discoveredFiles.push({
@@ -57104,7 +58029,7 @@ async function scanDocIndex(directory) {
57104
58029
  files: discoveredFiles
57105
58030
  };
57106
58031
  try {
57107
- await mkdir5(path44.dirname(manifestPath), { recursive: true });
58032
+ await mkdir6(path45.dirname(manifestPath), { recursive: true });
57108
58033
  await writeFile5(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
57109
58034
  } catch {}
57110
58035
  return { manifest, cached: false };
@@ -57154,7 +58079,7 @@ function extractConstraintsFromContent(content) {
57154
58079
  return constraints;
57155
58080
  }
57156
58081
  async function extractDocConstraints(directory, taskFiles, taskDescription) {
57157
- const manifestPath = path44.join(directory, ".swarm", "doc-manifest.json");
58082
+ const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
57158
58083
  let manifest;
57159
58084
  try {
57160
58085
  const content = await readFile6(manifestPath, "utf-8");
@@ -57180,7 +58105,7 @@ async function extractDocConstraints(directory, taskFiles, taskDescription) {
57180
58105
  }
57181
58106
  let fullContent;
57182
58107
  try {
57183
- fullContent = await readFile6(path44.join(directory, docFile.path), "utf-8");
58108
+ fullContent = await readFile6(path45.join(directory, docFile.path), "utf-8");
57184
58109
  } catch {
57185
58110
  skippedCount++;
57186
58111
  continue;
@@ -57203,7 +58128,7 @@ async function extractDocConstraints(directory, taskFiles, taskDescription) {
57203
58128
  tier: "swarm",
57204
58129
  lesson: constraint,
57205
58130
  category: "architecture",
57206
- tags: ["doc-scan", path44.basename(docFile.path)],
58131
+ tags: ["doc-scan", path45.basename(docFile.path)],
57207
58132
  scope: "global",
57208
58133
  confidence: 0.5,
57209
58134
  status: "candidate",
@@ -57249,9 +58174,9 @@ var doc_scan = createSwarmTool({
57249
58174
  }
57250
58175
  } catch {}
57251
58176
  if (force) {
57252
- const manifestPath = path44.join(directory, ".swarm", "doc-manifest.json");
58177
+ const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
57253
58178
  try {
57254
- fs33.unlinkSync(manifestPath);
58179
+ fs34.unlinkSync(manifestPath);
57255
58180
  } catch {}
57256
58181
  }
57257
58182
  const { manifest, cached: cached3 } = await scanDocIndex(directory);
@@ -57476,8 +58401,8 @@ Use these as DOMAIN values when delegating to @sme.`;
57476
58401
  // src/tools/evidence-check.ts
57477
58402
  init_dist();
57478
58403
  init_create_tool();
57479
- import * as fs34 from "fs";
57480
- import * as path45 from "path";
58404
+ import * as fs35 from "fs";
58405
+ import * as path46 from "path";
57481
58406
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
57482
58407
  var MAX_EVIDENCE_FILES = 1000;
57483
58408
  var EVIDENCE_DIR2 = ".swarm/evidence";
@@ -57507,9 +58432,9 @@ function validateRequiredTypes(input) {
57507
58432
  return null;
57508
58433
  }
57509
58434
  function isPathWithinSwarm2(filePath, cwd) {
57510
- const normalizedCwd = path45.resolve(cwd);
57511
- const swarmPath = path45.join(normalizedCwd, ".swarm");
57512
- const normalizedPath = path45.resolve(filePath);
58435
+ const normalizedCwd = path46.resolve(cwd);
58436
+ const swarmPath = path46.join(normalizedCwd, ".swarm");
58437
+ const normalizedPath = path46.resolve(filePath);
57513
58438
  return normalizedPath.startsWith(swarmPath);
57514
58439
  }
57515
58440
  function parseCompletedTasks(planContent) {
@@ -57525,12 +58450,12 @@ function parseCompletedTasks(planContent) {
57525
58450
  }
57526
58451
  function readEvidenceFiles(evidenceDir, _cwd) {
57527
58452
  const evidence = [];
57528
- if (!fs34.existsSync(evidenceDir) || !fs34.statSync(evidenceDir).isDirectory()) {
58453
+ if (!fs35.existsSync(evidenceDir) || !fs35.statSync(evidenceDir).isDirectory()) {
57529
58454
  return evidence;
57530
58455
  }
57531
58456
  let files;
57532
58457
  try {
57533
- files = fs34.readdirSync(evidenceDir);
58458
+ files = fs35.readdirSync(evidenceDir);
57534
58459
  } catch {
57535
58460
  return evidence;
57536
58461
  }
@@ -57539,14 +58464,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
57539
58464
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
57540
58465
  continue;
57541
58466
  }
57542
- const filePath = path45.join(evidenceDir, filename);
58467
+ const filePath = path46.join(evidenceDir, filename);
57543
58468
  try {
57544
- const resolvedPath = path45.resolve(filePath);
57545
- const evidenceDirResolved = path45.resolve(evidenceDir);
58469
+ const resolvedPath = path46.resolve(filePath);
58470
+ const evidenceDirResolved = path46.resolve(evidenceDir);
57546
58471
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
57547
58472
  continue;
57548
58473
  }
57549
- const stat2 = fs34.lstatSync(filePath);
58474
+ const stat2 = fs35.lstatSync(filePath);
57550
58475
  if (!stat2.isFile()) {
57551
58476
  continue;
57552
58477
  }
@@ -57555,7 +58480,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
57555
58480
  }
57556
58481
  let fileStat;
57557
58482
  try {
57558
- fileStat = fs34.statSync(filePath);
58483
+ fileStat = fs35.statSync(filePath);
57559
58484
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
57560
58485
  continue;
57561
58486
  }
@@ -57564,7 +58489,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
57564
58489
  }
57565
58490
  let content;
57566
58491
  try {
57567
- content = fs34.readFileSync(filePath, "utf-8");
58492
+ content = fs35.readFileSync(filePath, "utf-8");
57568
58493
  } catch {
57569
58494
  continue;
57570
58495
  }
@@ -57660,7 +58585,7 @@ var evidence_check = createSwarmTool({
57660
58585
  return JSON.stringify(errorResult, null, 2);
57661
58586
  }
57662
58587
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
57663
- const planPath = path45.join(cwd, PLAN_FILE);
58588
+ const planPath = path46.join(cwd, PLAN_FILE);
57664
58589
  if (!isPathWithinSwarm2(planPath, cwd)) {
57665
58590
  const errorResult = {
57666
58591
  error: "plan file path validation failed",
@@ -57674,7 +58599,7 @@ var evidence_check = createSwarmTool({
57674
58599
  }
57675
58600
  let planContent;
57676
58601
  try {
57677
- planContent = fs34.readFileSync(planPath, "utf-8");
58602
+ planContent = fs35.readFileSync(planPath, "utf-8");
57678
58603
  } catch {
57679
58604
  const result2 = {
57680
58605
  message: "No completed tasks found in plan.",
@@ -57692,7 +58617,7 @@ var evidence_check = createSwarmTool({
57692
58617
  };
57693
58618
  return JSON.stringify(result2, null, 2);
57694
58619
  }
57695
- const evidenceDir = path45.join(cwd, EVIDENCE_DIR2);
58620
+ const evidenceDir = path46.join(cwd, EVIDENCE_DIR2);
57696
58621
  const evidence = readEvidenceFiles(evidenceDir, cwd);
57697
58622
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
57698
58623
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -57709,8 +58634,8 @@ var evidence_check = createSwarmTool({
57709
58634
  // src/tools/file-extractor.ts
57710
58635
  init_tool();
57711
58636
  init_create_tool();
57712
- import * as fs35 from "fs";
57713
- import * as path46 from "path";
58637
+ import * as fs36 from "fs";
58638
+ import * as path47 from "path";
57714
58639
  var EXT_MAP = {
57715
58640
  python: ".py",
57716
58641
  py: ".py",
@@ -57772,8 +58697,8 @@ var extract_code_blocks = createSwarmTool({
57772
58697
  execute: async (args2, directory) => {
57773
58698
  const { content, output_dir, prefix } = args2;
57774
58699
  const targetDir = output_dir || directory;
57775
- if (!fs35.existsSync(targetDir)) {
57776
- fs35.mkdirSync(targetDir, { recursive: true });
58700
+ if (!fs36.existsSync(targetDir)) {
58701
+ fs36.mkdirSync(targetDir, { recursive: true });
57777
58702
  }
57778
58703
  if (!content) {
57779
58704
  return "Error: content is required";
@@ -57791,16 +58716,16 @@ var extract_code_blocks = createSwarmTool({
57791
58716
  if (prefix) {
57792
58717
  filename = `${prefix}_${filename}`;
57793
58718
  }
57794
- let filepath = path46.join(targetDir, filename);
57795
- const base = path46.basename(filepath, path46.extname(filepath));
57796
- const ext = path46.extname(filepath);
58719
+ let filepath = path47.join(targetDir, filename);
58720
+ const base = path47.basename(filepath, path47.extname(filepath));
58721
+ const ext = path47.extname(filepath);
57797
58722
  let counter = 1;
57798
- while (fs35.existsSync(filepath)) {
57799
- filepath = path46.join(targetDir, `${base}_${counter}${ext}`);
58723
+ while (fs36.existsSync(filepath)) {
58724
+ filepath = path47.join(targetDir, `${base}_${counter}${ext}`);
57800
58725
  counter++;
57801
58726
  }
57802
58727
  try {
57803
- fs35.writeFileSync(filepath, code.trim(), "utf-8");
58728
+ fs36.writeFileSync(filepath, code.trim(), "utf-8");
57804
58729
  savedFiles.push(filepath);
57805
58730
  } catch (error93) {
57806
58731
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -57916,8 +58841,8 @@ var gitingest = createSwarmTool({
57916
58841
  // src/tools/imports.ts
57917
58842
  init_dist();
57918
58843
  init_create_tool();
57919
- import * as fs36 from "fs";
57920
- import * as path47 from "path";
58844
+ import * as fs37 from "fs";
58845
+ import * as path48 from "path";
57921
58846
  var MAX_FILE_PATH_LENGTH2 = 500;
57922
58847
  var MAX_SYMBOL_LENGTH = 256;
57923
58848
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -57971,7 +58896,7 @@ function validateSymbolInput(symbol3) {
57971
58896
  return null;
57972
58897
  }
57973
58898
  function isBinaryFile2(filePath, buffer) {
57974
- const ext = path47.extname(filePath).toLowerCase();
58899
+ const ext = path48.extname(filePath).toLowerCase();
57975
58900
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
57976
58901
  return false;
57977
58902
  }
@@ -57995,15 +58920,15 @@ function parseImports(content, targetFile, targetSymbol) {
57995
58920
  const imports = [];
57996
58921
  let _resolvedTarget;
57997
58922
  try {
57998
- _resolvedTarget = path47.resolve(targetFile);
58923
+ _resolvedTarget = path48.resolve(targetFile);
57999
58924
  } catch {
58000
58925
  _resolvedTarget = targetFile;
58001
58926
  }
58002
- const targetBasename = path47.basename(targetFile, path47.extname(targetFile));
58927
+ const targetBasename = path48.basename(targetFile, path48.extname(targetFile));
58003
58928
  const targetWithExt = targetFile;
58004
58929
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
58005
- const normalizedTargetWithExt = path47.normalize(targetWithExt).replace(/\\/g, "/");
58006
- const normalizedTargetWithoutExt = path47.normalize(targetWithoutExt).replace(/\\/g, "/");
58930
+ const normalizedTargetWithExt = path48.normalize(targetWithExt).replace(/\\/g, "/");
58931
+ const normalizedTargetWithoutExt = path48.normalize(targetWithoutExt).replace(/\\/g, "/");
58007
58932
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
58008
58933
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
58009
58934
  const modulePath = match[1] || match[2] || match[3];
@@ -58026,9 +58951,9 @@ function parseImports(content, targetFile, targetSymbol) {
58026
58951
  }
58027
58952
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
58028
58953
  let isMatch = false;
58029
- const _targetDir = path47.dirname(targetFile);
58030
- const targetExt = path47.extname(targetFile);
58031
- const targetBasenameNoExt = path47.basename(targetFile, targetExt);
58954
+ const _targetDir = path48.dirname(targetFile);
58955
+ const targetExt = path48.extname(targetFile);
58956
+ const targetBasenameNoExt = path48.basename(targetFile, targetExt);
58032
58957
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
58033
58958
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
58034
58959
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -58085,7 +59010,7 @@ var SKIP_DIRECTORIES3 = new Set([
58085
59010
  function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
58086
59011
  let entries;
58087
59012
  try {
58088
- entries = fs36.readdirSync(dir);
59013
+ entries = fs37.readdirSync(dir);
58089
59014
  } catch (e) {
58090
59015
  stats.fileErrors.push({
58091
59016
  path: dir,
@@ -58096,13 +59021,13 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
58096
59021
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
58097
59022
  for (const entry of entries) {
58098
59023
  if (SKIP_DIRECTORIES3.has(entry)) {
58099
- stats.skippedDirs.push(path47.join(dir, entry));
59024
+ stats.skippedDirs.push(path48.join(dir, entry));
58100
59025
  continue;
58101
59026
  }
58102
- const fullPath = path47.join(dir, entry);
59027
+ const fullPath = path48.join(dir, entry);
58103
59028
  let stat2;
58104
59029
  try {
58105
- stat2 = fs36.statSync(fullPath);
59030
+ stat2 = fs37.statSync(fullPath);
58106
59031
  } catch (e) {
58107
59032
  stats.fileErrors.push({
58108
59033
  path: fullPath,
@@ -58113,7 +59038,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
58113
59038
  if (stat2.isDirectory()) {
58114
59039
  findSourceFiles(fullPath, files, stats);
58115
59040
  } else if (stat2.isFile()) {
58116
- const ext = path47.extname(fullPath).toLowerCase();
59041
+ const ext = path48.extname(fullPath).toLowerCase();
58117
59042
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
58118
59043
  files.push(fullPath);
58119
59044
  }
@@ -58170,8 +59095,8 @@ var imports = createSwarmTool({
58170
59095
  return JSON.stringify(errorResult, null, 2);
58171
59096
  }
58172
59097
  try {
58173
- const targetFile = path47.resolve(file3);
58174
- if (!fs36.existsSync(targetFile)) {
59098
+ const targetFile = path48.resolve(file3);
59099
+ if (!fs37.existsSync(targetFile)) {
58175
59100
  const errorResult = {
58176
59101
  error: `target file not found: ${file3}`,
58177
59102
  target: file3,
@@ -58181,7 +59106,7 @@ var imports = createSwarmTool({
58181
59106
  };
58182
59107
  return JSON.stringify(errorResult, null, 2);
58183
59108
  }
58184
- const targetStat = fs36.statSync(targetFile);
59109
+ const targetStat = fs37.statSync(targetFile);
58185
59110
  if (!targetStat.isFile()) {
58186
59111
  const errorResult = {
58187
59112
  error: "target must be a file, not a directory",
@@ -58192,7 +59117,7 @@ var imports = createSwarmTool({
58192
59117
  };
58193
59118
  return JSON.stringify(errorResult, null, 2);
58194
59119
  }
58195
- const baseDir = path47.dirname(targetFile);
59120
+ const baseDir = path48.dirname(targetFile);
58196
59121
  const scanStats = {
58197
59122
  skippedDirs: [],
58198
59123
  skippedFiles: 0,
@@ -58207,12 +59132,12 @@ var imports = createSwarmTool({
58207
59132
  if (consumers.length >= MAX_CONSUMERS)
58208
59133
  break;
58209
59134
  try {
58210
- const stat2 = fs36.statSync(filePath);
59135
+ const stat2 = fs37.statSync(filePath);
58211
59136
  if (stat2.size > MAX_FILE_SIZE_BYTES4) {
58212
59137
  skippedFileCount++;
58213
59138
  continue;
58214
59139
  }
58215
- const buffer = fs36.readFileSync(filePath);
59140
+ const buffer = fs37.readFileSync(filePath);
58216
59141
  if (isBinaryFile2(filePath, buffer)) {
58217
59142
  skippedFileCount++;
58218
59143
  continue;
@@ -58767,8 +59692,8 @@ init_dist();
58767
59692
  init_config();
58768
59693
  init_schema();
58769
59694
  init_manager();
58770
- import * as fs37 from "fs";
58771
- import * as path48 from "path";
59695
+ import * as fs38 from "fs";
59696
+ import * as path49 from "path";
58772
59697
  init_utils2();
58773
59698
  init_telemetry();
58774
59699
  init_create_tool();
@@ -58988,11 +59913,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
58988
59913
  safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
58989
59914
  }
58990
59915
  try {
58991
- const driftEvidencePath = path48.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
59916
+ const driftEvidencePath = path49.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
58992
59917
  let driftVerdictFound = false;
58993
59918
  let driftVerdictApproved = false;
58994
59919
  try {
58995
- const driftEvidenceContent = fs37.readFileSync(driftEvidencePath, "utf-8");
59920
+ const driftEvidenceContent = fs38.readFileSync(driftEvidencePath, "utf-8");
58996
59921
  const driftEvidence = JSON.parse(driftEvidenceContent);
58997
59922
  const entries = driftEvidence.entries ?? [];
58998
59923
  for (const entry of entries) {
@@ -59022,14 +59947,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59022
59947
  driftVerdictFound = false;
59023
59948
  }
59024
59949
  if (!driftVerdictFound) {
59025
- const specPath = path48.join(dir, ".swarm", "spec.md");
59026
- const specExists = fs37.existsSync(specPath);
59950
+ const specPath = path49.join(dir, ".swarm", "spec.md");
59951
+ const specExists = fs38.existsSync(specPath);
59027
59952
  if (!specExists) {
59028
59953
  let incompleteTaskCount = 0;
59029
59954
  let planPhaseFound = false;
59030
59955
  try {
59031
59956
  const planPath = validateSwarmPath(dir, "plan.json");
59032
- const planRaw = fs37.readFileSync(planPath, "utf-8");
59957
+ const planRaw = fs38.readFileSync(planPath, "utf-8");
59033
59958
  const plan = JSON.parse(planRaw);
59034
59959
  const targetPhase = plan.phases.find((p) => p.id === phase);
59035
59960
  if (targetPhase) {
@@ -59096,7 +60021,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59096
60021
  };
59097
60022
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
59098
60023
  try {
59099
- const projectName = path48.basename(dir);
60024
+ const projectName = path49.basename(dir);
59100
60025
  await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
59101
60026
  } catch (error93) {
59102
60027
  safeWarn("[phase_complete] Failed to curate lessons from retrospective:", error93);
@@ -59139,7 +60064,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59139
60064
  if (agentsMissing.length > 0) {
59140
60065
  try {
59141
60066
  const planPath = validateSwarmPath(dir, "plan.json");
59142
- const planRaw = fs37.readFileSync(planPath, "utf-8");
60067
+ const planRaw = fs38.readFileSync(planPath, "utf-8");
59143
60068
  const plan = JSON.parse(planRaw);
59144
60069
  const targetPhase = plan.phases.find((p) => p.id === phase);
59145
60070
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -59170,7 +60095,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59170
60095
  if (phaseCompleteConfig.regression_sweep?.enforce) {
59171
60096
  try {
59172
60097
  const planPath = validateSwarmPath(dir, "plan.json");
59173
- const planRaw = fs37.readFileSync(planPath, "utf-8");
60098
+ const planRaw = fs38.readFileSync(planPath, "utf-8");
59174
60099
  const plan = JSON.parse(planRaw);
59175
60100
  const targetPhase = plan.phases.find((p) => p.id === phase);
59176
60101
  if (targetPhase) {
@@ -59208,7 +60133,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59208
60133
  };
59209
60134
  try {
59210
60135
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
59211
- fs37.appendFileSync(eventsPath, `${JSON.stringify(event)}
60136
+ fs38.appendFileSync(eventsPath, `${JSON.stringify(event)}
59212
60137
  `, "utf-8");
59213
60138
  } catch (writeError) {
59214
60139
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -59229,12 +60154,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59229
60154
  }
59230
60155
  try {
59231
60156
  const planPath = validateSwarmPath(dir, "plan.json");
59232
- const planJson = fs37.readFileSync(planPath, "utf-8");
60157
+ const planJson = fs38.readFileSync(planPath, "utf-8");
59233
60158
  const plan = JSON.parse(planJson);
59234
60159
  const phaseObj = plan.phases.find((p) => p.id === phase);
59235
60160
  if (phaseObj) {
59236
60161
  phaseObj.status = "completed";
59237
- fs37.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
60162
+ fs38.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
59238
60163
  `, "utf-8");
59239
60164
  }
59240
60165
  } catch (error93) {
@@ -59288,8 +60213,8 @@ init_dist();
59288
60213
  init_discovery();
59289
60214
  init_utils();
59290
60215
  init_create_tool();
59291
- import * as fs38 from "fs";
59292
- import * as path49 from "path";
60216
+ import * as fs39 from "fs";
60217
+ import * as path50 from "path";
59293
60218
  var MAX_OUTPUT_BYTES5 = 52428800;
59294
60219
  var AUDIT_TIMEOUT_MS = 120000;
59295
60220
  function isValidEcosystem(value) {
@@ -59307,28 +60232,28 @@ function validateArgs3(args2) {
59307
60232
  function detectEcosystems(directory) {
59308
60233
  const ecosystems = [];
59309
60234
  const cwd = directory;
59310
- if (fs38.existsSync(path49.join(cwd, "package.json"))) {
60235
+ if (fs39.existsSync(path50.join(cwd, "package.json"))) {
59311
60236
  ecosystems.push("npm");
59312
60237
  }
59313
- if (fs38.existsSync(path49.join(cwd, "pyproject.toml")) || fs38.existsSync(path49.join(cwd, "requirements.txt"))) {
60238
+ if (fs39.existsSync(path50.join(cwd, "pyproject.toml")) || fs39.existsSync(path50.join(cwd, "requirements.txt"))) {
59314
60239
  ecosystems.push("pip");
59315
60240
  }
59316
- if (fs38.existsSync(path49.join(cwd, "Cargo.toml"))) {
60241
+ if (fs39.existsSync(path50.join(cwd, "Cargo.toml"))) {
59317
60242
  ecosystems.push("cargo");
59318
60243
  }
59319
- if (fs38.existsSync(path49.join(cwd, "go.mod"))) {
60244
+ if (fs39.existsSync(path50.join(cwd, "go.mod"))) {
59320
60245
  ecosystems.push("go");
59321
60246
  }
59322
60247
  try {
59323
- const files = fs38.readdirSync(cwd);
60248
+ const files = fs39.readdirSync(cwd);
59324
60249
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
59325
60250
  ecosystems.push("dotnet");
59326
60251
  }
59327
60252
  } catch {}
59328
- if (fs38.existsSync(path49.join(cwd, "Gemfile")) || fs38.existsSync(path49.join(cwd, "Gemfile.lock"))) {
60253
+ if (fs39.existsSync(path50.join(cwd, "Gemfile")) || fs39.existsSync(path50.join(cwd, "Gemfile.lock"))) {
59329
60254
  ecosystems.push("ruby");
59330
60255
  }
59331
- if (fs38.existsSync(path49.join(cwd, "pubspec.yaml"))) {
60256
+ if (fs39.existsSync(path50.join(cwd, "pubspec.yaml"))) {
59332
60257
  ecosystems.push("dart");
59333
60258
  }
59334
60259
  return ecosystems;
@@ -60328,47 +61253,6 @@ var pkg_audit = createSwarmTool({
60328
61253
  });
60329
61254
  // src/tools/placeholder-scan.ts
60330
61255
  init_manager();
60331
-
60332
- // src/lang/registry.ts
60333
- init_runtime();
60334
- var languageDefinitions = [
60335
- {
60336
- id: "javascript",
60337
- extensions: [".js", ".jsx"],
60338
- commentNodes: ["comment", "line_comment", "block_comment"]
60339
- },
60340
- {
60341
- id: "typescript",
60342
- extensions: [".ts", ".tsx"],
60343
- commentNodes: ["comment", "line_comment", "block_comment"]
60344
- },
60345
- {
60346
- id: "python",
60347
- extensions: [".py"],
60348
- commentNodes: ["comment"]
60349
- },
60350
- {
60351
- id: "go",
60352
- extensions: [".go"],
60353
- commentNodes: ["comment"]
60354
- },
60355
- {
60356
- id: "rust",
60357
- extensions: [".rs"],
60358
- commentNodes: ["line_comment", "block_comment"]
60359
- }
60360
- ];
60361
- var extensionMap = new Map;
60362
- for (const definition of languageDefinitions) {
60363
- for (const extension of definition.extensions) {
60364
- extensionMap.set(extension, definition);
60365
- }
60366
- }
60367
- function getLanguageForExtension(extension) {
60368
- return extensionMap.get(extension.toLowerCase());
60369
- }
60370
-
60371
- // src/tools/placeholder-scan.ts
60372
61256
  init_utils();
60373
61257
  var MAX_FILE_SIZE = 1024 * 1024;
60374
61258
  var SUPPORTED_PARSER_EXTENSIONS = new Set([
@@ -60390,8 +61274,8 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
60390
61274
  ]);
60391
61275
  // src/tools/pre-check-batch.ts
60392
61276
  init_dist();
60393
- import * as fs41 from "fs";
60394
- import * as path52 from "path";
61277
+ import * as fs42 from "fs";
61278
+ import * as path53 from "path";
60395
61279
 
60396
61280
  // node_modules/yocto-queue/index.js
60397
61281
  class Node2 {
@@ -60559,8 +61443,8 @@ init_lint();
60559
61443
  init_manager();
60560
61444
 
60561
61445
  // src/quality/metrics.ts
60562
- import * as fs39 from "fs";
60563
- import * as path50 from "path";
61446
+ import * as fs40 from "fs";
61447
+ import * as path51 from "path";
60564
61448
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
60565
61449
  var MIN_DUPLICATION_LINES = 10;
60566
61450
  function estimateCyclomaticComplexity(content) {
@@ -60598,11 +61482,11 @@ function estimateCyclomaticComplexity(content) {
60598
61482
  }
60599
61483
  function getComplexityForFile2(filePath) {
60600
61484
  try {
60601
- const stat2 = fs39.statSync(filePath);
61485
+ const stat2 = fs40.statSync(filePath);
60602
61486
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
60603
61487
  return null;
60604
61488
  }
60605
- const content = fs39.readFileSync(filePath, "utf-8");
61489
+ const content = fs40.readFileSync(filePath, "utf-8");
60606
61490
  return estimateCyclomaticComplexity(content);
60607
61491
  } catch {
60608
61492
  return null;
@@ -60612,8 +61496,8 @@ async function computeComplexityDelta(files, workingDir) {
60612
61496
  let totalComplexity = 0;
60613
61497
  const analyzedFiles = [];
60614
61498
  for (const file3 of files) {
60615
- const fullPath = path50.isAbsolute(file3) ? file3 : path50.join(workingDir, file3);
60616
- if (!fs39.existsSync(fullPath)) {
61499
+ const fullPath = path51.isAbsolute(file3) ? file3 : path51.join(workingDir, file3);
61500
+ if (!fs40.existsSync(fullPath)) {
60617
61501
  continue;
60618
61502
  }
60619
61503
  const complexity = getComplexityForFile2(fullPath);
@@ -60734,8 +61618,8 @@ function countGoExports(content) {
60734
61618
  }
60735
61619
  function getExportCountForFile(filePath) {
60736
61620
  try {
60737
- const content = fs39.readFileSync(filePath, "utf-8");
60738
- const ext = path50.extname(filePath).toLowerCase();
61621
+ const content = fs40.readFileSync(filePath, "utf-8");
61622
+ const ext = path51.extname(filePath).toLowerCase();
60739
61623
  switch (ext) {
60740
61624
  case ".ts":
60741
61625
  case ".tsx":
@@ -60761,8 +61645,8 @@ async function computePublicApiDelta(files, workingDir) {
60761
61645
  let totalExports = 0;
60762
61646
  const analyzedFiles = [];
60763
61647
  for (const file3 of files) {
60764
- const fullPath = path50.isAbsolute(file3) ? file3 : path50.join(workingDir, file3);
60765
- if (!fs39.existsSync(fullPath)) {
61648
+ const fullPath = path51.isAbsolute(file3) ? file3 : path51.join(workingDir, file3);
61649
+ if (!fs40.existsSync(fullPath)) {
60766
61650
  continue;
60767
61651
  }
60768
61652
  const exports = getExportCountForFile(fullPath);
@@ -60795,16 +61679,16 @@ async function computeDuplicationRatio(files, workingDir) {
60795
61679
  let duplicateLines = 0;
60796
61680
  const analyzedFiles = [];
60797
61681
  for (const file3 of files) {
60798
- const fullPath = path50.isAbsolute(file3) ? file3 : path50.join(workingDir, file3);
60799
- if (!fs39.existsSync(fullPath)) {
61682
+ const fullPath = path51.isAbsolute(file3) ? file3 : path51.join(workingDir, file3);
61683
+ if (!fs40.existsSync(fullPath)) {
60800
61684
  continue;
60801
61685
  }
60802
61686
  try {
60803
- const stat2 = fs39.statSync(fullPath);
61687
+ const stat2 = fs40.statSync(fullPath);
60804
61688
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
60805
61689
  continue;
60806
61690
  }
60807
- const content = fs39.readFileSync(fullPath, "utf-8");
61691
+ const content = fs40.readFileSync(fullPath, "utf-8");
60808
61692
  const lines = content.split(`
60809
61693
  `).filter((line) => line.trim().length > 0);
60810
61694
  if (lines.length < MIN_DUPLICATION_LINES) {
@@ -60828,8 +61712,8 @@ function countCodeLines(content) {
60828
61712
  return lines.length;
60829
61713
  }
60830
61714
  function isTestFile(filePath) {
60831
- const basename9 = path50.basename(filePath);
60832
- const _ext = path50.extname(filePath).toLowerCase();
61715
+ const basename9 = path51.basename(filePath);
61716
+ const _ext = path51.extname(filePath).toLowerCase();
60833
61717
  const testPatterns = [
60834
61718
  ".test.",
60835
61719
  ".spec.",
@@ -60910,8 +61794,8 @@ function matchGlobSegment(globSegments, pathSegments) {
60910
61794
  }
60911
61795
  return gIndex === globSegments.length && pIndex === pathSegments.length;
60912
61796
  }
60913
- function matchesGlobSegment(path51, glob) {
60914
- const normalizedPath = path51.replace(/\\/g, "/");
61797
+ function matchesGlobSegment(path52, glob) {
61798
+ const normalizedPath = path52.replace(/\\/g, "/");
60915
61799
  const normalizedGlob = glob.replace(/\\/g, "/");
60916
61800
  if (normalizedPath.includes("//")) {
60917
61801
  return false;
@@ -60942,8 +61826,8 @@ function simpleGlobToRegex2(glob) {
60942
61826
  function hasGlobstar(glob) {
60943
61827
  return glob.includes("**");
60944
61828
  }
60945
- function globMatches(path51, glob) {
60946
- const normalizedPath = path51.replace(/\\/g, "/");
61829
+ function globMatches(path52, glob) {
61830
+ const normalizedPath = path52.replace(/\\/g, "/");
60947
61831
  if (!glob || glob === "") {
60948
61832
  if (normalizedPath.includes("//")) {
60949
61833
  return false;
@@ -60979,31 +61863,31 @@ function shouldExcludeFile(filePath, excludeGlobs) {
60979
61863
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
60980
61864
  let testLines = 0;
60981
61865
  let codeLines = 0;
60982
- const srcDir = path50.join(workingDir, "src");
60983
- if (fs39.existsSync(srcDir)) {
61866
+ const srcDir = path51.join(workingDir, "src");
61867
+ if (fs40.existsSync(srcDir)) {
60984
61868
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
60985
61869
  codeLines += lines;
60986
61870
  });
60987
61871
  }
60988
61872
  const possibleSrcDirs = ["lib", "app", "source", "core"];
60989
61873
  for (const dir of possibleSrcDirs) {
60990
- const dirPath = path50.join(workingDir, dir);
60991
- if (fs39.existsSync(dirPath)) {
61874
+ const dirPath = path51.join(workingDir, dir);
61875
+ if (fs40.existsSync(dirPath)) {
60992
61876
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
60993
61877
  codeLines += lines;
60994
61878
  });
60995
61879
  }
60996
61880
  }
60997
- const testsDir = path50.join(workingDir, "tests");
60998
- if (fs39.existsSync(testsDir)) {
61881
+ const testsDir = path51.join(workingDir, "tests");
61882
+ if (fs40.existsSync(testsDir)) {
60999
61883
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
61000
61884
  testLines += lines;
61001
61885
  });
61002
61886
  }
61003
61887
  const possibleTestDirs = ["test", "__tests__", "specs"];
61004
61888
  for (const dir of possibleTestDirs) {
61005
- const dirPath = path50.join(workingDir, dir);
61006
- if (fs39.existsSync(dirPath) && dirPath !== testsDir) {
61889
+ const dirPath = path51.join(workingDir, dir);
61890
+ if (fs40.existsSync(dirPath) && dirPath !== testsDir) {
61007
61891
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
61008
61892
  testLines += lines;
61009
61893
  });
@@ -61015,9 +61899,9 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
61015
61899
  }
61016
61900
  async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
61017
61901
  try {
61018
- const entries = fs39.readdirSync(dirPath, { withFileTypes: true });
61902
+ const entries = fs40.readdirSync(dirPath, { withFileTypes: true });
61019
61903
  for (const entry of entries) {
61020
- const fullPath = path50.join(dirPath, entry.name);
61904
+ const fullPath = path51.join(dirPath, entry.name);
61021
61905
  if (entry.isDirectory()) {
61022
61906
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
61023
61907
  continue;
@@ -61025,7 +61909,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
61025
61909
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
61026
61910
  } else if (entry.isFile()) {
61027
61911
  const relativePath = fullPath.replace(`${dirPath}/`, "");
61028
- const ext = path50.extname(entry.name).toLowerCase();
61912
+ const ext = path51.extname(entry.name).toLowerCase();
61029
61913
  const validExts = [
61030
61914
  ".ts",
61031
61915
  ".tsx",
@@ -61061,7 +61945,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
61061
61945
  continue;
61062
61946
  }
61063
61947
  try {
61064
- const content = fs39.readFileSync(fullPath, "utf-8");
61948
+ const content = fs40.readFileSync(fullPath, "utf-8");
61065
61949
  const lines = countCodeLines(content);
61066
61950
  callback(lines);
61067
61951
  } catch {}
@@ -61275,9 +62159,9 @@ async function qualityBudget(input, directory) {
61275
62159
  init_dist();
61276
62160
  init_manager();
61277
62161
  init_detector();
61278
- import * as fs40 from "fs";
61279
- import * as path51 from "path";
61280
- import { extname as extname9 } from "path";
62162
+ import * as fs41 from "fs";
62163
+ import * as path52 from "path";
62164
+ import { extname as extname10 } from "path";
61281
62165
 
61282
62166
  // src/sast/rules/c.ts
61283
62167
  var cRules = [
@@ -62143,17 +63027,17 @@ var SEVERITY_ORDER = {
62143
63027
  };
62144
63028
  function shouldSkipFile(filePath) {
62145
63029
  try {
62146
- const stats = fs40.statSync(filePath);
63030
+ const stats = fs41.statSync(filePath);
62147
63031
  if (stats.size > MAX_FILE_SIZE_BYTES6) {
62148
63032
  return { skip: true, reason: "file too large" };
62149
63033
  }
62150
63034
  if (stats.size === 0) {
62151
63035
  return { skip: true, reason: "empty file" };
62152
63036
  }
62153
- const fd = fs40.openSync(filePath, "r");
63037
+ const fd = fs41.openSync(filePath, "r");
62154
63038
  const buffer = Buffer.alloc(8192);
62155
- const bytesRead = fs40.readSync(fd, buffer, 0, 8192, 0);
62156
- fs40.closeSync(fd);
63039
+ const bytesRead = fs41.readSync(fd, buffer, 0, 8192, 0);
63040
+ fs41.closeSync(fd);
62157
63041
  if (bytesRead > 0) {
62158
63042
  let nullCount = 0;
62159
63043
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -62192,7 +63076,7 @@ function countBySeverity(findings) {
62192
63076
  }
62193
63077
  function scanFileWithTierA(filePath, language) {
62194
63078
  try {
62195
- const content = fs40.readFileSync(filePath, "utf-8");
63079
+ const content = fs41.readFileSync(filePath, "utf-8");
62196
63080
  const findings = executeRulesSync(filePath, content, language);
62197
63081
  return findings.map((f) => ({
62198
63082
  rule_id: f.rule_id,
@@ -62239,8 +63123,8 @@ async function sastScan(input, directory, config3) {
62239
63123
  _filesSkipped++;
62240
63124
  continue;
62241
63125
  }
62242
- const resolvedPath = path51.isAbsolute(filePath) ? filePath : path51.resolve(directory, filePath);
62243
- if (!fs40.existsSync(resolvedPath)) {
63126
+ const resolvedPath = path52.isAbsolute(filePath) ? filePath : path52.resolve(directory, filePath);
63127
+ if (!fs41.existsSync(resolvedPath)) {
62244
63128
  _filesSkipped++;
62245
63129
  continue;
62246
63130
  }
@@ -62249,7 +63133,7 @@ async function sastScan(input, directory, config3) {
62249
63133
  _filesSkipped++;
62250
63134
  continue;
62251
63135
  }
62252
- const ext = extname9(resolvedPath).toLowerCase();
63136
+ const ext = extname10(resolvedPath).toLowerCase();
62253
63137
  const profile = getProfileForFile(resolvedPath);
62254
63138
  const langDef = getLanguageForExtension(ext);
62255
63139
  if (!profile && !langDef) {
@@ -62438,18 +63322,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
62438
63322
  let resolved;
62439
63323
  const isWinAbs = isWindowsAbsolutePath(inputPath);
62440
63324
  if (isWinAbs) {
62441
- resolved = path52.win32.resolve(inputPath);
62442
- } else if (path52.isAbsolute(inputPath)) {
62443
- resolved = path52.resolve(inputPath);
63325
+ resolved = path53.win32.resolve(inputPath);
63326
+ } else if (path53.isAbsolute(inputPath)) {
63327
+ resolved = path53.resolve(inputPath);
62444
63328
  } else {
62445
- resolved = path52.resolve(baseDir, inputPath);
63329
+ resolved = path53.resolve(baseDir, inputPath);
62446
63330
  }
62447
- const workspaceResolved = path52.resolve(workspaceDir);
63331
+ const workspaceResolved = path53.resolve(workspaceDir);
62448
63332
  let relative6;
62449
63333
  if (isWinAbs) {
62450
- relative6 = path52.win32.relative(workspaceResolved, resolved);
63334
+ relative6 = path53.win32.relative(workspaceResolved, resolved);
62451
63335
  } else {
62452
- relative6 = path52.relative(workspaceResolved, resolved);
63336
+ relative6 = path53.relative(workspaceResolved, resolved);
62453
63337
  }
62454
63338
  if (relative6.startsWith("..")) {
62455
63339
  return "path traversal detected";
@@ -62510,13 +63394,13 @@ async function runLintWrapped(files, directory, _config) {
62510
63394
  }
62511
63395
  async function runLintOnFiles(linter, files, workspaceDir) {
62512
63396
  const isWindows = process.platform === "win32";
62513
- const binDir = path52.join(workspaceDir, "node_modules", ".bin");
63397
+ const binDir = path53.join(workspaceDir, "node_modules", ".bin");
62514
63398
  const validatedFiles = [];
62515
63399
  for (const file3 of files) {
62516
63400
  if (typeof file3 !== "string") {
62517
63401
  continue;
62518
63402
  }
62519
- const resolvedPath = path52.resolve(file3);
63403
+ const resolvedPath = path53.resolve(file3);
62520
63404
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
62521
63405
  if (validationError) {
62522
63406
  continue;
@@ -62534,10 +63418,10 @@ async function runLintOnFiles(linter, files, workspaceDir) {
62534
63418
  }
62535
63419
  let command;
62536
63420
  if (linter === "biome") {
62537
- const biomeBin = isWindows ? path52.join(binDir, "biome.EXE") : path52.join(binDir, "biome");
63421
+ const biomeBin = isWindows ? path53.join(binDir, "biome.EXE") : path53.join(binDir, "biome");
62538
63422
  command = [biomeBin, "check", ...validatedFiles];
62539
63423
  } else {
62540
- const eslintBin = isWindows ? path52.join(binDir, "eslint.cmd") : path52.join(binDir, "eslint");
63424
+ const eslintBin = isWindows ? path53.join(binDir, "eslint.cmd") : path53.join(binDir, "eslint");
62541
63425
  command = [eslintBin, ...validatedFiles];
62542
63426
  }
62543
63427
  try {
@@ -62674,7 +63558,7 @@ async function runSecretscanWithFiles(files, directory) {
62674
63558
  skippedFiles++;
62675
63559
  continue;
62676
63560
  }
62677
- const resolvedPath = path52.resolve(file3);
63561
+ const resolvedPath = path53.resolve(file3);
62678
63562
  const validationError = validatePath(resolvedPath, directory, directory);
62679
63563
  if (validationError) {
62680
63564
  skippedFiles++;
@@ -62692,14 +63576,14 @@ async function runSecretscanWithFiles(files, directory) {
62692
63576
  };
62693
63577
  }
62694
63578
  for (const file3 of validatedFiles) {
62695
- const ext = path52.extname(file3).toLowerCase();
63579
+ const ext = path53.extname(file3).toLowerCase();
62696
63580
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
62697
63581
  skippedFiles++;
62698
63582
  continue;
62699
63583
  }
62700
63584
  let stat2;
62701
63585
  try {
62702
- stat2 = fs41.statSync(file3);
63586
+ stat2 = fs42.statSync(file3);
62703
63587
  } catch {
62704
63588
  skippedFiles++;
62705
63589
  continue;
@@ -62710,7 +63594,7 @@ async function runSecretscanWithFiles(files, directory) {
62710
63594
  }
62711
63595
  let content;
62712
63596
  try {
62713
- const buffer = fs41.readFileSync(file3);
63597
+ const buffer = fs42.readFileSync(file3);
62714
63598
  if (buffer.includes(0)) {
62715
63599
  skippedFiles++;
62716
63600
  continue;
@@ -62851,7 +63735,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
62851
63735
  warn(`pre_check_batch: Invalid file path: ${file3}`);
62852
63736
  continue;
62853
63737
  }
62854
- changedFiles.push(path52.resolve(directory, file3));
63738
+ changedFiles.push(path53.resolve(directory, file3));
62855
63739
  }
62856
63740
  if (changedFiles.length === 0) {
62857
63741
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -63022,7 +63906,7 @@ var pre_check_batch = createSwarmTool({
63022
63906
  };
63023
63907
  return JSON.stringify(errorResult, null, 2);
63024
63908
  }
63025
- const resolvedDirectory = path52.resolve(typedArgs.directory);
63909
+ const resolvedDirectory = path53.resolve(typedArgs.directory);
63026
63910
  const workspaceAnchor = resolvedDirectory;
63027
63911
  const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
63028
63912
  if (dirError) {
@@ -63128,10 +64012,72 @@ ${paginatedContent}`;
63128
64012
  });
63129
64013
  // src/tools/save-plan.ts
63130
64014
  init_tool();
64015
+ import * as fs44 from "fs";
64016
+ import * as path55 from "path";
64017
+
64018
+ // src/parallel/file-locks.ts
64019
+ import * as fs43 from "fs";
64020
+ import * as path54 from "path";
64021
+ var LOCKS_DIR = ".swarm/locks";
64022
+ var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
64023
+ function getLockFilePath(directory, filePath) {
64024
+ if (filePath.includes("..")) {
64025
+ throw new Error("Invalid file path: path traversal not allowed");
64026
+ }
64027
+ const hash3 = Buffer.from(filePath).toString("base64").replace(/[/+=]/g, "_");
64028
+ return path54.join(directory, LOCKS_DIR, `${hash3}.lock`);
64029
+ }
64030
+ function tryAcquireLock(directory, filePath, agent, taskId) {
64031
+ const lockPath = getLockFilePath(directory, filePath);
64032
+ const locksDir = path54.dirname(lockPath);
64033
+ if (!fs43.existsSync(locksDir)) {
64034
+ fs43.mkdirSync(locksDir, { recursive: true });
64035
+ }
64036
+ if (fs43.existsSync(lockPath)) {
64037
+ try {
64038
+ const existingLock = JSON.parse(fs43.readFileSync(lockPath, "utf-8"));
64039
+ if (Date.now() > existingLock.expiresAt) {
64040
+ fs43.unlinkSync(lockPath);
64041
+ } else {
64042
+ return { acquired: false, existing: existingLock };
64043
+ }
64044
+ } catch {
64045
+ fs43.unlinkSync(lockPath);
64046
+ }
64047
+ }
64048
+ const lock = {
64049
+ filePath,
64050
+ agent,
64051
+ taskId,
64052
+ timestamp: new Date().toISOString(),
64053
+ expiresAt: Date.now() + LOCK_TIMEOUT_MS
64054
+ };
64055
+ const tempPath = `${lockPath}.tmp`;
64056
+ fs43.writeFileSync(tempPath, JSON.stringify(lock, null, 2), "utf-8");
64057
+ fs43.renameSync(tempPath, lockPath);
64058
+ return { acquired: true, lock };
64059
+ }
64060
+ function releaseLock(directory, filePath, taskId) {
64061
+ const lockPath = getLockFilePath(directory, filePath);
64062
+ if (!fs43.existsSync(lockPath)) {
64063
+ return true;
64064
+ }
64065
+ try {
64066
+ const lock = JSON.parse(fs43.readFileSync(lockPath, "utf-8"));
64067
+ if (lock.taskId === taskId) {
64068
+ fs43.unlinkSync(lockPath);
64069
+ return true;
64070
+ }
64071
+ return false;
64072
+ } catch {
64073
+ fs43.unlinkSync(lockPath);
64074
+ return true;
64075
+ }
64076
+ }
64077
+
64078
+ // src/tools/save-plan.ts
63131
64079
  init_manager2();
63132
64080
  init_create_tool();
63133
- import * as fs42 from "fs";
63134
- import * as path53 from "path";
63135
64081
  function detectPlaceholderContent(args2) {
63136
64082
  const issues = [];
63137
64083
  const placeholderPattern = /^\[\w[\w\s]*\]$/;
@@ -63232,25 +64178,42 @@ async function executeSavePlan(args2, fallbackDir) {
63232
64178
  };
63233
64179
  const tasksCount = plan.phases.reduce((acc, phase) => acc + phase.tasks.length, 0);
63234
64180
  const dir = targetWorkspace;
64181
+ const lockTaskId = `save-plan-${Date.now()}`;
64182
+ const planFilePath = "plan.json";
63235
64183
  try {
63236
- await savePlan(dir, plan);
64184
+ const lockResult = tryAcquireLock(dir, planFilePath, "architect", lockTaskId);
64185
+ if (!lockResult.acquired) {
64186
+ return {
64187
+ success: false,
64188
+ message: `Plan write blocked: file is locked by ${lockResult.existing?.agent ?? "another agent"} (task: ${lockResult.existing?.taskId ?? "unknown"})`,
64189
+ errors: [
64190
+ "Concurrent plan write detected \u2014 retry after the current write completes"
64191
+ ],
64192
+ recovery_guidance: "Wait a moment and retry save_plan. The lock will expire automatically if the holding agent fails."
64193
+ };
64194
+ }
63237
64195
  try {
63238
- const markerPath = path53.join(dir, ".swarm", ".plan-write-marker");
63239
- const marker = JSON.stringify({
63240
- source: "save_plan",
63241
- timestamp: new Date().toISOString(),
64196
+ await savePlan(dir, plan);
64197
+ try {
64198
+ const markerPath = path55.join(dir, ".swarm", ".plan-write-marker");
64199
+ const marker = JSON.stringify({
64200
+ source: "save_plan",
64201
+ timestamp: new Date().toISOString(),
64202
+ phases_count: plan.phases.length,
64203
+ tasks_count: tasksCount
64204
+ });
64205
+ await fs44.promises.writeFile(markerPath, marker, "utf8");
64206
+ } catch {}
64207
+ return {
64208
+ success: true,
64209
+ message: "Plan saved successfully",
64210
+ plan_path: path55.join(dir, ".swarm", "plan.json"),
63242
64211
  phases_count: plan.phases.length,
63243
64212
  tasks_count: tasksCount
63244
- });
63245
- await fs42.promises.writeFile(markerPath, marker, "utf8");
63246
- } catch {}
63247
- return {
63248
- success: true,
63249
- message: "Plan saved successfully",
63250
- plan_path: path53.join(dir, ".swarm", "plan.json"),
63251
- phases_count: plan.phases.length,
63252
- tasks_count: tasksCount
63253
- };
64213
+ };
64214
+ } finally {
64215
+ releaseLock(dir, planFilePath, lockTaskId);
64216
+ }
63254
64217
  } catch (error93) {
63255
64218
  return {
63256
64219
  success: false,
@@ -63285,8 +64248,8 @@ var save_plan = createSwarmTool({
63285
64248
  // src/tools/sbom-generate.ts
63286
64249
  init_dist();
63287
64250
  init_manager();
63288
- import * as fs43 from "fs";
63289
- import * as path54 from "path";
64251
+ import * as fs45 from "fs";
64252
+ import * as path56 from "path";
63290
64253
 
63291
64254
  // src/sbom/detectors/index.ts
63292
64255
  init_utils();
@@ -64132,9 +65095,9 @@ function findManifestFiles(rootDir) {
64132
65095
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
64133
65096
  function searchDir(dir) {
64134
65097
  try {
64135
- const entries = fs43.readdirSync(dir, { withFileTypes: true });
65098
+ const entries = fs45.readdirSync(dir, { withFileTypes: true });
64136
65099
  for (const entry of entries) {
64137
- const fullPath = path54.join(dir, entry.name);
65100
+ const fullPath = path56.join(dir, entry.name);
64138
65101
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
64139
65102
  continue;
64140
65103
  }
@@ -64143,7 +65106,7 @@ function findManifestFiles(rootDir) {
64143
65106
  } else if (entry.isFile()) {
64144
65107
  for (const pattern of patterns) {
64145
65108
  if (simpleGlobToRegex(pattern).test(entry.name)) {
64146
- manifestFiles.push(path54.relative(rootDir, fullPath));
65109
+ manifestFiles.push(path56.relative(rootDir, fullPath));
64147
65110
  break;
64148
65111
  }
64149
65112
  }
@@ -64159,13 +65122,13 @@ function findManifestFilesInDirs(directories, workingDir) {
64159
65122
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
64160
65123
  for (const dir of directories) {
64161
65124
  try {
64162
- const entries = fs43.readdirSync(dir, { withFileTypes: true });
65125
+ const entries = fs45.readdirSync(dir, { withFileTypes: true });
64163
65126
  for (const entry of entries) {
64164
- const fullPath = path54.join(dir, entry.name);
65127
+ const fullPath = path56.join(dir, entry.name);
64165
65128
  if (entry.isFile()) {
64166
65129
  for (const pattern of patterns) {
64167
65130
  if (simpleGlobToRegex(pattern).test(entry.name)) {
64168
- found.push(path54.relative(workingDir, fullPath));
65131
+ found.push(path56.relative(workingDir, fullPath));
64169
65132
  break;
64170
65133
  }
64171
65134
  }
@@ -64178,11 +65141,11 @@ function findManifestFilesInDirs(directories, workingDir) {
64178
65141
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
64179
65142
  const dirs = new Set;
64180
65143
  for (const file3 of changedFiles) {
64181
- let currentDir = path54.dirname(file3);
65144
+ let currentDir = path56.dirname(file3);
64182
65145
  while (true) {
64183
- if (currentDir && currentDir !== "." && currentDir !== path54.sep) {
64184
- dirs.add(path54.join(workingDir, currentDir));
64185
- const parent = path54.dirname(currentDir);
65146
+ if (currentDir && currentDir !== "." && currentDir !== path56.sep) {
65147
+ dirs.add(path56.join(workingDir, currentDir));
65148
+ const parent = path56.dirname(currentDir);
64186
65149
  if (parent === currentDir)
64187
65150
  break;
64188
65151
  currentDir = parent;
@@ -64196,7 +65159,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
64196
65159
  }
64197
65160
  function ensureOutputDir(outputDir) {
64198
65161
  try {
64199
- fs43.mkdirSync(outputDir, { recursive: true });
65162
+ fs45.mkdirSync(outputDir, { recursive: true });
64200
65163
  } catch (error93) {
64201
65164
  if (!error93 || error93.code !== "EEXIST") {
64202
65165
  throw error93;
@@ -64266,7 +65229,7 @@ var sbom_generate = createSwarmTool({
64266
65229
  const changedFiles = obj.changed_files;
64267
65230
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
64268
65231
  const workingDir = directory;
64269
- const outputDir = path54.isAbsolute(relativeOutputDir) ? relativeOutputDir : path54.join(workingDir, relativeOutputDir);
65232
+ const outputDir = path56.isAbsolute(relativeOutputDir) ? relativeOutputDir : path56.join(workingDir, relativeOutputDir);
64270
65233
  let manifestFiles = [];
64271
65234
  if (scope === "all") {
64272
65235
  manifestFiles = findManifestFiles(workingDir);
@@ -64289,11 +65252,11 @@ var sbom_generate = createSwarmTool({
64289
65252
  const processedFiles = [];
64290
65253
  for (const manifestFile of manifestFiles) {
64291
65254
  try {
64292
- const fullPath = path54.isAbsolute(manifestFile) ? manifestFile : path54.join(workingDir, manifestFile);
64293
- if (!fs43.existsSync(fullPath)) {
65255
+ const fullPath = path56.isAbsolute(manifestFile) ? manifestFile : path56.join(workingDir, manifestFile);
65256
+ if (!fs45.existsSync(fullPath)) {
64294
65257
  continue;
64295
65258
  }
64296
- const content = fs43.readFileSync(fullPath, "utf-8");
65259
+ const content = fs45.readFileSync(fullPath, "utf-8");
64297
65260
  const components = detectComponents(manifestFile, content);
64298
65261
  processedFiles.push(manifestFile);
64299
65262
  if (components.length > 0) {
@@ -64306,8 +65269,8 @@ var sbom_generate = createSwarmTool({
64306
65269
  const bom = generateCycloneDX(allComponents);
64307
65270
  const bomJson = serializeCycloneDX(bom);
64308
65271
  const filename = generateSbomFilename();
64309
- const outputPath = path54.join(outputDir, filename);
64310
- fs43.writeFileSync(outputPath, bomJson, "utf-8");
65272
+ const outputPath = path56.join(outputDir, filename);
65273
+ fs45.writeFileSync(outputPath, bomJson, "utf-8");
64311
65274
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
64312
65275
  try {
64313
65276
  const timestamp = new Date().toISOString();
@@ -64349,8 +65312,8 @@ var sbom_generate = createSwarmTool({
64349
65312
  // src/tools/schema-drift.ts
64350
65313
  init_dist();
64351
65314
  init_create_tool();
64352
- import * as fs44 from "fs";
64353
- import * as path55 from "path";
65315
+ import * as fs46 from "fs";
65316
+ import * as path57 from "path";
64354
65317
  var SPEC_CANDIDATES = [
64355
65318
  "openapi.json",
64356
65319
  "openapi.yaml",
@@ -64382,28 +65345,28 @@ function normalizePath2(p) {
64382
65345
  }
64383
65346
  function discoverSpecFile(cwd, specFileArg) {
64384
65347
  if (specFileArg) {
64385
- const resolvedPath = path55.resolve(cwd, specFileArg);
64386
- const normalizedCwd = cwd.endsWith(path55.sep) ? cwd : cwd + path55.sep;
65348
+ const resolvedPath = path57.resolve(cwd, specFileArg);
65349
+ const normalizedCwd = cwd.endsWith(path57.sep) ? cwd : cwd + path57.sep;
64387
65350
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
64388
65351
  throw new Error("Invalid spec_file: path traversal detected");
64389
65352
  }
64390
- const ext = path55.extname(resolvedPath).toLowerCase();
65353
+ const ext = path57.extname(resolvedPath).toLowerCase();
64391
65354
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
64392
65355
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
64393
65356
  }
64394
- const stats = fs44.statSync(resolvedPath);
65357
+ const stats = fs46.statSync(resolvedPath);
64395
65358
  if (stats.size > MAX_SPEC_SIZE) {
64396
65359
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
64397
65360
  }
64398
- if (!fs44.existsSync(resolvedPath)) {
65361
+ if (!fs46.existsSync(resolvedPath)) {
64399
65362
  throw new Error(`Spec file not found: ${resolvedPath}`);
64400
65363
  }
64401
65364
  return resolvedPath;
64402
65365
  }
64403
65366
  for (const candidate of SPEC_CANDIDATES) {
64404
- const candidatePath = path55.resolve(cwd, candidate);
64405
- if (fs44.existsSync(candidatePath)) {
64406
- const stats = fs44.statSync(candidatePath);
65367
+ const candidatePath = path57.resolve(cwd, candidate);
65368
+ if (fs46.existsSync(candidatePath)) {
65369
+ const stats = fs46.statSync(candidatePath);
64407
65370
  if (stats.size <= MAX_SPEC_SIZE) {
64408
65371
  return candidatePath;
64409
65372
  }
@@ -64412,8 +65375,8 @@ function discoverSpecFile(cwd, specFileArg) {
64412
65375
  return null;
64413
65376
  }
64414
65377
  function parseSpec(specFile) {
64415
- const content = fs44.readFileSync(specFile, "utf-8");
64416
- const ext = path55.extname(specFile).toLowerCase();
65378
+ const content = fs46.readFileSync(specFile, "utf-8");
65379
+ const ext = path57.extname(specFile).toLowerCase();
64417
65380
  if (ext === ".json") {
64418
65381
  return parseJsonSpec(content);
64419
65382
  }
@@ -64484,12 +65447,12 @@ function extractRoutes(cwd) {
64484
65447
  function walkDir(dir) {
64485
65448
  let entries;
64486
65449
  try {
64487
- entries = fs44.readdirSync(dir, { withFileTypes: true });
65450
+ entries = fs46.readdirSync(dir, { withFileTypes: true });
64488
65451
  } catch {
64489
65452
  return;
64490
65453
  }
64491
65454
  for (const entry of entries) {
64492
- const fullPath = path55.join(dir, entry.name);
65455
+ const fullPath = path57.join(dir, entry.name);
64493
65456
  if (entry.isSymbolicLink()) {
64494
65457
  continue;
64495
65458
  }
@@ -64499,7 +65462,7 @@ function extractRoutes(cwd) {
64499
65462
  }
64500
65463
  walkDir(fullPath);
64501
65464
  } else if (entry.isFile()) {
64502
- const ext = path55.extname(entry.name).toLowerCase();
65465
+ const ext = path57.extname(entry.name).toLowerCase();
64503
65466
  const baseName = entry.name.toLowerCase();
64504
65467
  if (![".ts", ".js", ".mjs"].includes(ext)) {
64505
65468
  continue;
@@ -64517,7 +65480,7 @@ function extractRoutes(cwd) {
64517
65480
  }
64518
65481
  function extractRoutesFromFile(filePath) {
64519
65482
  const routes = [];
64520
- const content = fs44.readFileSync(filePath, "utf-8");
65483
+ const content = fs46.readFileSync(filePath, "utf-8");
64521
65484
  const lines = content.split(/\r?\n/);
64522
65485
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
64523
65486
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -64668,8 +65631,8 @@ init_secretscan();
64668
65631
  // src/tools/symbols.ts
64669
65632
  init_tool();
64670
65633
  init_create_tool();
64671
- import * as fs45 from "fs";
64672
- import * as path56 from "path";
65634
+ import * as fs47 from "fs";
65635
+ import * as path58 from "path";
64673
65636
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
64674
65637
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
64675
65638
  function containsControlCharacters(str) {
@@ -64698,11 +65661,11 @@ function containsWindowsAttacks(str) {
64698
65661
  }
64699
65662
  function isPathInWorkspace(filePath, workspace) {
64700
65663
  try {
64701
- const resolvedPath = path56.resolve(workspace, filePath);
64702
- const realWorkspace = fs45.realpathSync(workspace);
64703
- const realResolvedPath = fs45.realpathSync(resolvedPath);
64704
- const relativePath = path56.relative(realWorkspace, realResolvedPath);
64705
- if (relativePath.startsWith("..") || path56.isAbsolute(relativePath)) {
65664
+ const resolvedPath = path58.resolve(workspace, filePath);
65665
+ const realWorkspace = fs47.realpathSync(workspace);
65666
+ const realResolvedPath = fs47.realpathSync(resolvedPath);
65667
+ const relativePath = path58.relative(realWorkspace, realResolvedPath);
65668
+ if (relativePath.startsWith("..") || path58.isAbsolute(relativePath)) {
64706
65669
  return false;
64707
65670
  }
64708
65671
  return true;
@@ -64714,17 +65677,17 @@ function validatePathForRead(filePath, workspace) {
64714
65677
  return isPathInWorkspace(filePath, workspace);
64715
65678
  }
64716
65679
  function extractTSSymbols(filePath, cwd) {
64717
- const fullPath = path56.join(cwd, filePath);
65680
+ const fullPath = path58.join(cwd, filePath);
64718
65681
  if (!validatePathForRead(fullPath, cwd)) {
64719
65682
  return [];
64720
65683
  }
64721
65684
  let content;
64722
65685
  try {
64723
- const stats = fs45.statSync(fullPath);
65686
+ const stats = fs47.statSync(fullPath);
64724
65687
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
64725
65688
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
64726
65689
  }
64727
- content = fs45.readFileSync(fullPath, "utf-8");
65690
+ content = fs47.readFileSync(fullPath, "utf-8");
64728
65691
  } catch {
64729
65692
  return [];
64730
65693
  }
@@ -64866,17 +65829,17 @@ function extractTSSymbols(filePath, cwd) {
64866
65829
  });
64867
65830
  }
64868
65831
  function extractPythonSymbols(filePath, cwd) {
64869
- const fullPath = path56.join(cwd, filePath);
65832
+ const fullPath = path58.join(cwd, filePath);
64870
65833
  if (!validatePathForRead(fullPath, cwd)) {
64871
65834
  return [];
64872
65835
  }
64873
65836
  let content;
64874
65837
  try {
64875
- const stats = fs45.statSync(fullPath);
65838
+ const stats = fs47.statSync(fullPath);
64876
65839
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
64877
65840
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
64878
65841
  }
64879
- content = fs45.readFileSync(fullPath, "utf-8");
65842
+ content = fs47.readFileSync(fullPath, "utf-8");
64880
65843
  } catch {
64881
65844
  return [];
64882
65845
  }
@@ -64949,7 +65912,7 @@ var symbols = createSwarmTool({
64949
65912
  }, null, 2);
64950
65913
  }
64951
65914
  const cwd = directory;
64952
- const ext = path56.extname(file3);
65915
+ const ext = path58.extname(file3);
64953
65916
  if (containsControlCharacters(file3)) {
64954
65917
  return JSON.stringify({
64955
65918
  file: file3,
@@ -65020,8 +65983,8 @@ init_test_runner();
65020
65983
  init_dist();
65021
65984
  init_utils();
65022
65985
  init_create_tool();
65023
- import * as fs46 from "fs";
65024
- import * as path57 from "path";
65986
+ import * as fs48 from "fs";
65987
+ import * as path59 from "path";
65025
65988
  var MAX_TEXT_LENGTH = 200;
65026
65989
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
65027
65990
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -65092,9 +66055,9 @@ function validatePathsInput(paths, cwd) {
65092
66055
  return { error: "paths contains path traversal", resolvedPath: null };
65093
66056
  }
65094
66057
  try {
65095
- const resolvedPath = path57.resolve(paths);
65096
- const normalizedCwd = path57.resolve(cwd);
65097
- const normalizedResolved = path57.resolve(resolvedPath);
66058
+ const resolvedPath = path59.resolve(paths);
66059
+ const normalizedCwd = path59.resolve(cwd);
66060
+ const normalizedResolved = path59.resolve(resolvedPath);
65098
66061
  if (!normalizedResolved.startsWith(normalizedCwd)) {
65099
66062
  return {
65100
66063
  error: "paths must be within the current working directory",
@@ -65110,13 +66073,13 @@ function validatePathsInput(paths, cwd) {
65110
66073
  }
65111
66074
  }
65112
66075
  function isSupportedExtension(filePath) {
65113
- const ext = path57.extname(filePath).toLowerCase();
66076
+ const ext = path59.extname(filePath).toLowerCase();
65114
66077
  return SUPPORTED_EXTENSIONS2.has(ext);
65115
66078
  }
65116
66079
  function findSourceFiles2(dir, files = []) {
65117
66080
  let entries;
65118
66081
  try {
65119
- entries = fs46.readdirSync(dir);
66082
+ entries = fs48.readdirSync(dir);
65120
66083
  } catch {
65121
66084
  return files;
65122
66085
  }
@@ -65125,10 +66088,10 @@ function findSourceFiles2(dir, files = []) {
65125
66088
  if (SKIP_DIRECTORIES4.has(entry)) {
65126
66089
  continue;
65127
66090
  }
65128
- const fullPath = path57.join(dir, entry);
66091
+ const fullPath = path59.join(dir, entry);
65129
66092
  let stat2;
65130
66093
  try {
65131
- stat2 = fs46.statSync(fullPath);
66094
+ stat2 = fs48.statSync(fullPath);
65132
66095
  } catch {
65133
66096
  continue;
65134
66097
  }
@@ -65221,7 +66184,7 @@ var todo_extract = createSwarmTool({
65221
66184
  return JSON.stringify(errorResult, null, 2);
65222
66185
  }
65223
66186
  const scanPath = resolvedPath;
65224
- if (!fs46.existsSync(scanPath)) {
66187
+ if (!fs48.existsSync(scanPath)) {
65225
66188
  const errorResult = {
65226
66189
  error: `path not found: ${pathsInput}`,
65227
66190
  total: 0,
@@ -65231,13 +66194,13 @@ var todo_extract = createSwarmTool({
65231
66194
  return JSON.stringify(errorResult, null, 2);
65232
66195
  }
65233
66196
  const filesToScan = [];
65234
- const stat2 = fs46.statSync(scanPath);
66197
+ const stat2 = fs48.statSync(scanPath);
65235
66198
  if (stat2.isFile()) {
65236
66199
  if (isSupportedExtension(scanPath)) {
65237
66200
  filesToScan.push(scanPath);
65238
66201
  } else {
65239
66202
  const errorResult = {
65240
- error: `unsupported file extension: ${path57.extname(scanPath)}`,
66203
+ error: `unsupported file extension: ${path59.extname(scanPath)}`,
65241
66204
  total: 0,
65242
66205
  byPriority: { high: 0, medium: 0, low: 0 },
65243
66206
  entries: []
@@ -65250,11 +66213,11 @@ var todo_extract = createSwarmTool({
65250
66213
  const allEntries = [];
65251
66214
  for (const filePath of filesToScan) {
65252
66215
  try {
65253
- const fileStat = fs46.statSync(filePath);
66216
+ const fileStat = fs48.statSync(filePath);
65254
66217
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
65255
66218
  continue;
65256
66219
  }
65257
- const content = fs46.readFileSync(filePath, "utf-8");
66220
+ const content = fs48.readFileSync(filePath, "utf-8");
65258
66221
  const entries = parseTodoComments(content, filePath, tagsSet);
65259
66222
  allEntries.push(...entries);
65260
66223
  } catch {}
@@ -65283,18 +66246,18 @@ var todo_extract = createSwarmTool({
65283
66246
  init_tool();
65284
66247
  init_schema();
65285
66248
  init_gate_evidence();
65286
- import * as fs48 from "fs";
65287
- import * as path59 from "path";
66249
+ import * as fs50 from "fs";
66250
+ import * as path61 from "path";
65288
66251
 
65289
66252
  // src/hooks/diff-scope.ts
65290
- import * as fs47 from "fs";
65291
- import * as path58 from "path";
66253
+ import * as fs49 from "fs";
66254
+ import * as path60 from "path";
65292
66255
  function getDeclaredScope(taskId, directory) {
65293
66256
  try {
65294
- const planPath = path58.join(directory, ".swarm", "plan.json");
65295
- if (!fs47.existsSync(planPath))
66257
+ const planPath = path60.join(directory, ".swarm", "plan.json");
66258
+ if (!fs49.existsSync(planPath))
65296
66259
  return null;
65297
- const raw = fs47.readFileSync(planPath, "utf-8");
66260
+ const raw = fs49.readFileSync(planPath, "utf-8");
65298
66261
  const plan = JSON.parse(raw);
65299
66262
  for (const phase of plan.phases ?? []) {
65300
66263
  for (const task of phase.tasks ?? []) {
@@ -65407,7 +66370,7 @@ var TIER_3_PATTERNS = [
65407
66370
  ];
65408
66371
  function matchesTier3Pattern(files) {
65409
66372
  for (const file3 of files) {
65410
- const fileName = path59.basename(file3);
66373
+ const fileName = path61.basename(file3);
65411
66374
  for (const pattern of TIER_3_PATTERNS) {
65412
66375
  if (pattern.test(fileName)) {
65413
66376
  return true;
@@ -65421,8 +66384,8 @@ function checkReviewerGate(taskId, workingDirectory) {
65421
66384
  if (hasActiveTurboMode()) {
65422
66385
  const resolvedDir2 = workingDirectory;
65423
66386
  try {
65424
- const planPath = path59.join(resolvedDir2, ".swarm", "plan.json");
65425
- const planRaw = fs48.readFileSync(planPath, "utf-8");
66387
+ const planPath = path61.join(resolvedDir2, ".swarm", "plan.json");
66388
+ const planRaw = fs50.readFileSync(planPath, "utf-8");
65426
66389
  const plan = JSON.parse(planRaw);
65427
66390
  for (const planPhase of plan.phases ?? []) {
65428
66391
  for (const task of planPhase.tasks ?? []) {
@@ -65488,8 +66451,8 @@ function checkReviewerGate(taskId, workingDirectory) {
65488
66451
  }
65489
66452
  try {
65490
66453
  const resolvedDir2 = workingDirectory;
65491
- const planPath = path59.join(resolvedDir2, ".swarm", "plan.json");
65492
- const planRaw = fs48.readFileSync(planPath, "utf-8");
66454
+ const planPath = path61.join(resolvedDir2, ".swarm", "plan.json");
66455
+ const planRaw = fs50.readFileSync(planPath, "utf-8");
65493
66456
  const plan = JSON.parse(planRaw);
65494
66457
  for (const planPhase of plan.phases ?? []) {
65495
66458
  for (const task of planPhase.tasks ?? []) {
@@ -65671,8 +66634,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
65671
66634
  };
65672
66635
  }
65673
66636
  }
65674
- normalizedDir = path59.normalize(args2.working_directory);
65675
- const pathParts = normalizedDir.split(path59.sep);
66637
+ normalizedDir = path61.normalize(args2.working_directory);
66638
+ const pathParts = normalizedDir.split(path61.sep);
65676
66639
  if (pathParts.includes("..")) {
65677
66640
  return {
65678
66641
  success: false,
@@ -65682,11 +66645,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
65682
66645
  ]
65683
66646
  };
65684
66647
  }
65685
- const resolvedDir = path59.resolve(normalizedDir);
66648
+ const resolvedDir = path61.resolve(normalizedDir);
65686
66649
  try {
65687
- const realPath = fs48.realpathSync(resolvedDir);
65688
- const planPath = path59.join(realPath, ".swarm", "plan.json");
65689
- if (!fs48.existsSync(planPath)) {
66650
+ const realPath = fs50.realpathSync(resolvedDir);
66651
+ const planPath = path61.join(realPath, ".swarm", "plan.json");
66652
+ if (!fs50.existsSync(planPath)) {
65690
66653
  return {
65691
66654
  success: false,
65692
66655
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -65907,10 +66870,11 @@ var OpenCodeSwarm = async (ctx) => {
65907
66870
  let statusArtifact;
65908
66871
  if (automationConfig.mode !== "manual") {
65909
66872
  automationManager = createAutomationManager(automationConfig);
66873
+ automationManager.start();
65910
66874
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
65911
66875
  preflightTriggerManager = new PTM(automationConfig);
65912
66876
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
65913
- const swarmDir = path60.resolve(ctx.directory, ".swarm");
66877
+ const swarmDir = path62.resolve(ctx.directory, ".swarm");
65914
66878
  statusArtifact = new ASA(swarmDir);
65915
66879
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
65916
66880
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -65954,9 +66918,16 @@ var OpenCodeSwarm = async (ctx) => {
65954
66918
  });
65955
66919
  }
65956
66920
  }
66921
+ const cleanupAutomation = () => {
66922
+ automationManager?.stop();
66923
+ };
66924
+ process.on("exit", cleanupAutomation);
66925
+ process.on("SIGINT", cleanupAutomation);
66926
+ process.on("SIGTERM", cleanupAutomation);
65957
66927
  log("Automation framework initialized", {
65958
66928
  mode: automationConfig.mode,
65959
66929
  enabled: automationManager?.isEnabled(),
66930
+ running: automationManager?.isActive(),
65960
66931
  preflightEnabled: preflightTriggerManager?.isEnabled()
65961
66932
  });
65962
66933
  }
@@ -66221,6 +67192,7 @@ var OpenCodeSwarm = async (ctx) => {
66221
67192
  }
66222
67193
  await guardrailsHooks.toolBefore(input, output);
66223
67194
  await scopeGuardHook.toolBefore(input, output);
67195
+ await delegationGateHooks.toolBefore(input, output);
66224
67196
  if (swarmState.lastBudgetPct >= 50) {
66225
67197
  const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
66226
67198
  if (!pressureSession.contextPressureWarningSent) {
@@ -66254,6 +67226,37 @@ var OpenCodeSwarm = async (ctx) => {
66254
67226
  await safeHook(delegationGateHooks.toolAfter)(input, output);
66255
67227
  if (_dbg)
66256
67228
  console.error(`[DIAG] toolAfter delegationGate done tool=${_toolName}`);
67229
+ if (isTaskTool && typeof output.output === "string") {
67230
+ try {
67231
+ const adversarialMatches = detectAdversarialPatterns(output.output);
67232
+ if (adversarialMatches.length > 0) {
67233
+ const sessionId = input.sessionID;
67234
+ const session = swarmState.agentSessions.get(sessionId);
67235
+ if (session) {
67236
+ session.pendingAdvisoryMessages ??= [];
67237
+ session.pendingAdvisoryMessages.push(`ADVERSARIAL PATTERN DETECTED: ${adversarialMatches.map((p) => p.pattern).join(", ")}. Review agent output for potential prompt injection or gate bypass.`);
67238
+ }
67239
+ if ("adversarialPatternDetected" in telemetry) {
67240
+ telemetry.adversarialPatternDetected(input.sessionID, adversarialMatches);
67241
+ }
67242
+ }
67243
+ } catch {}
67244
+ }
67245
+ try {
67246
+ recordToolCall(normalizedTool, input.args);
67247
+ } catch {}
67248
+ try {
67249
+ const spiralMatch = await detectDebuggingSpiral(ctx.directory);
67250
+ if (spiralMatch) {
67251
+ const taskId = swarmState.agentSessions.get(input.sessionID)?.currentTaskId ?? "unknown";
67252
+ const spiralResult = await handleDebuggingSpiral(spiralMatch, taskId, ctx.directory);
67253
+ const session = swarmState.agentSessions.get(input.sessionID);
67254
+ if (session) {
67255
+ session.pendingAdvisoryMessages ??= [];
67256
+ session.pendingAdvisoryMessages.push(spiralResult.message);
67257
+ }
67258
+ }
67259
+ } catch {}
66257
67260
  if (knowledgeCuratorHook)
66258
67261
  await safeHook(knowledgeCuratorHook)(input, output);
66259
67262
  if (hivePromoterHook)
@@ -66275,8 +67278,9 @@ var OpenCodeSwarm = async (ctx) => {
66275
67278
  await slopDetectorHook.toolAfter(input, output);
66276
67279
  if (incrementalVerifyHook)
66277
67280
  await incrementalVerifyHook.toolAfter(input, output);
66278
- if (compactionServiceHook)
66279
- await compactionServiceHook.toolAfter(input, output);
67281
+ }
67282
+ if (execMode !== "fast" && compactionServiceHook) {
67283
+ await compactionServiceHook.toolAfter(input, output);
66280
67284
  }
66281
67285
  const toolOutputConfig = config3.tool_output;
66282
67286
  if (toolOutputConfig && toolOutputConfig.truncation_enabled !== false && typeof output.output === "string") {