opencode-swarm 6.28.0 → 6.29.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
@@ -14217,14 +14217,16 @@ var init_evidence_schema = __esm(() => {
14217
14217
  });
14218
14218
  RetrospectiveEvidenceSchema = BaseEvidenceSchema.extend({
14219
14219
  type: exports_external.literal("retrospective"),
14220
- phase_number: exports_external.number().int().min(0),
14221
- total_tool_calls: exports_external.number().int().min(0),
14222
- coder_revisions: exports_external.number().int().min(0),
14223
- reviewer_rejections: exports_external.number().int().min(0),
14224
- test_failures: exports_external.number().int().min(0),
14225
- security_findings: exports_external.number().int().min(0),
14226
- integration_issues: exports_external.number().int().min(0),
14227
- task_count: exports_external.number().int().min(1),
14220
+ phase_number: exports_external.number().int().min(0).max(99),
14221
+ total_tool_calls: exports_external.number().int().min(0).max(9999),
14222
+ coder_revisions: exports_external.number().int().min(0).max(999),
14223
+ reviewer_rejections: exports_external.number().int().min(0).max(999),
14224
+ loop_detections: exports_external.number().int().min(0).max(9999).optional(),
14225
+ circuit_breaker_trips: exports_external.number().int().min(0).max(9999).optional(),
14226
+ test_failures: exports_external.number().int().min(0).max(9999),
14227
+ security_findings: exports_external.number().int().min(0).max(999),
14228
+ integration_issues: exports_external.number().int().min(0).max(999),
14229
+ task_count: exports_external.number().int().min(1).max(9999),
14228
14230
  task_complexity: exports_external.enum(["trivial", "simple", "moderate", "complex"]),
14229
14231
  top_rejection_reasons: exports_external.array(exports_external.string()).default([]),
14230
14232
  lessons_learned: exports_external.array(exports_external.string()).max(5).default([]),
@@ -14435,7 +14437,7 @@ function resolveGuardrailsConfig(config2, agentName) {
14435
14437
  };
14436
14438
  return resolved;
14437
14439
  }
14438
- var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, PluginConfigSchema;
14440
+ var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PluginConfigSchema;
14439
14441
  var init_schema = __esm(() => {
14440
14442
  init_zod();
14441
14443
  init_constants();
@@ -14872,6 +14874,25 @@ var init_schema = __esm(() => {
14872
14874
  suppress_warnings: exports_external.boolean().default(true),
14873
14875
  drift_inject_max_chars: exports_external.number().min(100).max(2000).default(500)
14874
14876
  });
14877
+ SlopDetectorConfigSchema = exports_external.object({
14878
+ enabled: exports_external.boolean().default(true),
14879
+ classThreshold: exports_external.number().int().min(1).default(3),
14880
+ commentStripThreshold: exports_external.number().int().min(1).default(5),
14881
+ diffLineThreshold: exports_external.number().int().min(10).default(200)
14882
+ });
14883
+ IncrementalVerifyConfigSchema = exports_external.object({
14884
+ enabled: exports_external.boolean().default(true),
14885
+ command: exports_external.string().nullable().default(null),
14886
+ timeoutMs: exports_external.number().int().min(1000).max(300000).default(30000),
14887
+ triggerAgents: exports_external.array(exports_external.string()).default(["coder"])
14888
+ });
14889
+ CompactionConfigSchema = exports_external.object({
14890
+ enabled: exports_external.boolean().default(true),
14891
+ observationThreshold: exports_external.number().min(1).max(99).default(40),
14892
+ reflectionThreshold: exports_external.number().min(1).max(99).default(60),
14893
+ emergencyThreshold: exports_external.number().min(1).max(99).default(80),
14894
+ preserveLastNTurns: exports_external.number().int().min(1).default(5)
14895
+ });
14875
14896
  PluginConfigSchema = exports_external.object({
14876
14897
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
14877
14898
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
@@ -14905,7 +14926,10 @@ var init_schema = __esm(() => {
14905
14926
  truncation_enabled: exports_external.boolean().default(true),
14906
14927
  max_lines: exports_external.number().min(10).max(500).default(150),
14907
14928
  per_tool: exports_external.record(exports_external.string(), exports_external.number()).optional()
14908
- }).optional()
14929
+ }).optional(),
14930
+ slop_detector: SlopDetectorConfigSchema.optional(),
14931
+ incremental_verify: IncrementalVerifyConfigSchema.optional(),
14932
+ compaction_service: CompactionConfigSchema.optional()
14909
14933
  });
14910
14934
  });
14911
14935
 
@@ -17112,11 +17136,12 @@ class PreflightTriggerManager {
17112
17136
  this.unsubscribe = this.eventBus.subscribe("preflight.requested", async (event) => {
17113
17137
  if (this.preflightHandler) {
17114
17138
  const request = event.payload;
17139
+ let timeoutHandle;
17115
17140
  try {
17116
17141
  await Promise.race([
17117
17142
  this.preflightHandler(request),
17118
17143
  new Promise((_, reject) => {
17119
- setTimeout(() => {
17144
+ timeoutHandle = setTimeout(() => {
17120
17145
  reject(new Error(`Preflight handler timed out after ${HANDLER_TIMEOUT_MS}ms`));
17121
17146
  }, HANDLER_TIMEOUT_MS);
17122
17147
  })
@@ -17127,6 +17152,8 @@ class PreflightTriggerManager {
17127
17152
  phase: request.currentPhase,
17128
17153
  errorType: error49 instanceof Error ? error49.name : "unknown"
17129
17154
  });
17155
+ } finally {
17156
+ clearTimeout(timeoutHandle);
17130
17157
  }
17131
17158
  }
17132
17159
  });
@@ -33386,7 +33413,15 @@ function detectAdditionalLinter(cwd) {
33386
33413
  return "rubocop";
33387
33414
  return null;
33388
33415
  }
33389
- async function detectAvailableLinter() {
33416
+ async function detectAvailableLinter(directory) {
33417
+ const DETECT_TIMEOUT = 2000;
33418
+ const projectDir = directory ?? process.cwd();
33419
+ const isWindows = process.platform === "win32";
33420
+ const biomeBin = isWindows ? path21.join(projectDir, "node_modules", ".bin", "biome.EXE") : path21.join(projectDir, "node_modules", ".bin", "biome");
33421
+ const eslintBin = isWindows ? path21.join(projectDir, "node_modules", ".bin", "eslint.cmd") : path21.join(projectDir, "node_modules", ".bin", "eslint");
33422
+ return _detectAvailableLinter(projectDir, biomeBin, eslintBin);
33423
+ }
33424
+ async function _detectAvailableLinter(projectDir, biomeBin, eslintBin) {
33390
33425
  const DETECT_TIMEOUT = 2000;
33391
33426
  try {
33392
33427
  const biomeProc = Bun.spawn(["npx", "biome", "--version"], {
@@ -33398,7 +33433,7 @@ async function detectAvailableLinter() {
33398
33433
  const result = await Promise.race([biomeExit, timeout]);
33399
33434
  if (result === "timeout") {
33400
33435
  biomeProc.kill();
33401
- } else if (biomeProc.exitCode === 0) {
33436
+ } else if (biomeProc.exitCode === 0 && fs9.existsSync(biomeBin)) {
33402
33437
  return "biome";
33403
33438
  }
33404
33439
  } catch {}
@@ -33412,7 +33447,7 @@ async function detectAvailableLinter() {
33412
33447
  const result = await Promise.race([eslintExit, timeout]);
33413
33448
  if (result === "timeout") {
33414
33449
  eslintProc.kill();
33415
- } else if (eslintProc.exitCode === 0) {
33450
+ } else if (eslintProc.exitCode === 0 && fs9.existsSync(eslintBin)) {
33416
33451
  return "eslint";
33417
33452
  }
33418
33453
  } catch {}
@@ -33564,7 +33599,7 @@ var init_lint = __esm(() => {
33564
33599
  }
33565
33600
  const { mode } = args2;
33566
33601
  const cwd = directory;
33567
- const linter = await detectAvailableLinter();
33602
+ const linter = await detectAvailableLinter(directory);
33568
33603
  if (linter) {
33569
33604
  const result = await runLint(linter, mode, directory);
33570
33605
  return JSON.stringify(result, null, 2);
@@ -34181,10 +34216,7 @@ var init_secretscan = __esm(() => {
34181
34216
  const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
34182
34217
  const excludeGlobs = [];
34183
34218
  const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
34184
- const allUserPatterns = [
34185
- ...exclude ?? [],
34186
- ...ignoreFilePatterns
34187
- ];
34219
+ const allUserPatterns = [...exclude ?? [], ...ignoreFilePatterns];
34188
34220
  for (const exc of allUserPatterns) {
34189
34221
  if (exc.length === 0)
34190
34222
  continue;
@@ -34427,7 +34459,7 @@ function detectMinitest(cwd) {
34427
34459
  return fs11.existsSync(path23.join(cwd, "test")) && (fs11.existsSync(path23.join(cwd, "Gemfile")) || fs11.existsSync(path23.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
34428
34460
  }
34429
34461
  async function detectTestFramework(cwd) {
34430
- const baseDir = cwd || process.cwd();
34462
+ const baseDir = cwd;
34431
34463
  try {
34432
34464
  const packageJsonPath = path23.join(baseDir, "package.json");
34433
34465
  if (fs11.existsSync(packageJsonPath)) {
@@ -37569,11 +37601,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
37569
37601
  throw toThrow;
37570
37602
  }, "quit_");
37571
37603
  var scriptDirectory = "";
37572
- function locateFile(path43) {
37604
+ function locateFile(path45) {
37573
37605
  if (Module["locateFile"]) {
37574
- return Module["locateFile"](path43, scriptDirectory);
37606
+ return Module["locateFile"](path45, scriptDirectory);
37575
37607
  }
37576
- return scriptDirectory + path43;
37608
+ return scriptDirectory + path45;
37577
37609
  }
37578
37610
  __name(locateFile, "locateFile");
37579
37611
  var readAsync, readBinary;
@@ -39321,7 +39353,7 @@ var init_runtime = __esm(() => {
39321
39353
  });
39322
39354
 
39323
39355
  // src/index.ts
39324
- import * as path52 from "path";
39356
+ import * as path55 from "path";
39325
39357
 
39326
39358
  // src/agents/index.ts
39327
39359
  init_config();
@@ -39340,6 +39372,7 @@ var swarmState = {
39340
39372
  activeAgent: new Map,
39341
39373
  delegationChains: new Map,
39342
39374
  pendingEvents: 0,
39375
+ lastBudgetPct: 0,
39343
39376
  agentSessions: new Map
39344
39377
  };
39345
39378
  function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, directory) {
@@ -39384,7 +39417,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
39384
39417
  lastScopeViolation: null,
39385
39418
  scopeViolationDetected: false,
39386
39419
  modifiedFilesThisCoderTask: [],
39387
- turboMode: false
39420
+ turboMode: false,
39421
+ loopDetectionWindow: []
39388
39422
  };
39389
39423
  swarmState.agentSessions.set(sessionId, sessionState);
39390
39424
  swarmState.activeAgent.set(sessionId, agentName);
@@ -39486,6 +39520,9 @@ function ensureAgentSession(sessionId, agentName, directory) {
39486
39520
  if (session.turboMode === undefined) {
39487
39521
  session.turboMode = false;
39488
39522
  }
39523
+ if (session.loopDetectionWindow === undefined) {
39524
+ session.loopDetectionWindow = [];
39525
+ }
39489
39526
  session.lastToolCallTime = now;
39490
39527
  return session;
39491
39528
  }
@@ -47570,6 +47607,173 @@ No plan content available. Start by creating a .swarm/plan.md file.
47570
47607
  // src/services/status-service.ts
47571
47608
  init_utils2();
47572
47609
  init_manager2();
47610
+
47611
+ // src/services/context-budget-service.ts
47612
+ init_utils2();
47613
+ function validateDirectory(directory) {
47614
+ if (!directory || directory.trim() === "") {
47615
+ throw new Error("Invalid directory: empty");
47616
+ }
47617
+ if (/\.\.[/\\]/.test(directory)) {
47618
+ throw new Error("Invalid directory: path traversal detected");
47619
+ }
47620
+ if (directory.startsWith("/") || directory.startsWith("\\")) {
47621
+ throw new Error("Invalid directory: absolute path");
47622
+ }
47623
+ if (/^[A-Za-z]:[\\/]/.test(directory)) {
47624
+ throw new Error("Invalid directory: Windows absolute path");
47625
+ }
47626
+ }
47627
+ var DEFAULT_CONTEXT_BUDGET_CONFIG = {
47628
+ enabled: true,
47629
+ budgetTokens: 40000,
47630
+ warningPct: 70,
47631
+ criticalPct: 90,
47632
+ warningMode: "once",
47633
+ warningIntervalTurns: 20
47634
+ };
47635
+ var COST_PER_1K_TOKENS = 0.003;
47636
+ function estimateTokens2(text) {
47637
+ if (!text || typeof text !== "string") {
47638
+ return 0;
47639
+ }
47640
+ return Math.ceil(text.length / 3.5);
47641
+ }
47642
+ async function readBudgetState(directory) {
47643
+ const content = await readSwarmFileAsync(directory, "session/budget-state.json");
47644
+ if (!content) {
47645
+ return null;
47646
+ }
47647
+ try {
47648
+ return JSON.parse(content);
47649
+ } catch {
47650
+ return null;
47651
+ }
47652
+ }
47653
+ async function writeBudgetState(directory, state) {
47654
+ const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
47655
+ const content = JSON.stringify(state, null, 2);
47656
+ await Bun.write(resolvedPath, content);
47657
+ }
47658
+ async function countEvents(directory) {
47659
+ const content = await readSwarmFileAsync(directory, "events.jsonl");
47660
+ if (!content) {
47661
+ return 0;
47662
+ }
47663
+ const lines = content.split(`
47664
+ `).filter((line) => line.trim().length > 0);
47665
+ return lines.length;
47666
+ }
47667
+ async function getPlanCursorContent(directory) {
47668
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
47669
+ if (!planContent) {
47670
+ return "";
47671
+ }
47672
+ const lines = planContent.split(`
47673
+ `);
47674
+ const cursorLines = [];
47675
+ let inCurrentSection = false;
47676
+ for (const line of lines) {
47677
+ if (line.includes("in_progress") || line.includes("**Current**")) {
47678
+ inCurrentSection = true;
47679
+ }
47680
+ if (inCurrentSection) {
47681
+ cursorLines.push(line);
47682
+ if (cursorLines.length > 30) {
47683
+ break;
47684
+ }
47685
+ }
47686
+ }
47687
+ return cursorLines.join(`
47688
+ `) || planContent.substring(0, 1000);
47689
+ }
47690
+ async function getContextBudgetReport(directory, assembledSystemPrompt, config3) {
47691
+ validateDirectory(directory);
47692
+ const timestamp = new Date().toISOString();
47693
+ const systemPromptTokens = estimateTokens2(assembledSystemPrompt);
47694
+ const planCursorContent = await getPlanCursorContent(directory);
47695
+ const planCursorTokens = estimateTokens2(planCursorContent);
47696
+ const knowledgeContent = await readSwarmFileAsync(directory, "knowledge.jsonl");
47697
+ const knowledgeTokens = estimateTokens2(knowledgeContent || "");
47698
+ const runMemoryContent = await readSwarmFileAsync(directory, "run-memory.jsonl");
47699
+ const runMemoryTokens = estimateTokens2(runMemoryContent || "");
47700
+ const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
47701
+ const handoffTokens = estimateTokens2(handoffContent || "");
47702
+ const contextMdContent = await readSwarmFileAsync(directory, "context.md");
47703
+ const contextMdTokens = estimateTokens2(contextMdContent || "");
47704
+ const swarmTotalTokens = systemPromptTokens + planCursorTokens + knowledgeTokens + runMemoryTokens + handoffTokens + contextMdTokens;
47705
+ const estimatedTurnCount = await countEvents(directory);
47706
+ const budgetPct = swarmTotalTokens / config3.budgetTokens * 100;
47707
+ let status;
47708
+ let recommendation = null;
47709
+ if (budgetPct < config3.warningPct) {
47710
+ status = "ok";
47711
+ } else if (budgetPct < config3.criticalPct) {
47712
+ status = "warning";
47713
+ recommendation = "Consider wrapping up current phase and running /swarm handoff before starting new work.";
47714
+ } else {
47715
+ status = "critical";
47716
+ recommendation = "Run /swarm handoff and start a new session to avoid cost escalation.";
47717
+ }
47718
+ const estimatedSessionTokens = swarmTotalTokens * Math.max(1, estimatedTurnCount);
47719
+ return {
47720
+ timestamp,
47721
+ systemPromptTokens,
47722
+ planCursorTokens,
47723
+ knowledgeTokens,
47724
+ runMemoryTokens,
47725
+ handoffTokens,
47726
+ contextMdTokens,
47727
+ swarmTotalTokens,
47728
+ estimatedTurnCount,
47729
+ estimatedSessionTokens,
47730
+ budgetPct,
47731
+ status,
47732
+ recommendation
47733
+ };
47734
+ }
47735
+ async function formatBudgetWarning(report, directory, config3) {
47736
+ validateDirectory(directory);
47737
+ if (report.status === "ok") {
47738
+ return null;
47739
+ }
47740
+ if (!directory || directory.trim() === "") {
47741
+ return formatWarningMessage(report);
47742
+ }
47743
+ const budgetState = await readBudgetState(directory);
47744
+ const state = budgetState || {
47745
+ warningFiredAtTurn: null,
47746
+ criticalFiredAtTurn: null,
47747
+ lastInjectedAtTurn: null
47748
+ };
47749
+ const currentTurn = report.estimatedTurnCount;
47750
+ if (report.status === "warning") {
47751
+ if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
47752
+ return null;
47753
+ }
47754
+ if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
47755
+ return null;
47756
+ }
47757
+ state.warningFiredAtTurn = currentTurn;
47758
+ state.lastInjectedAtTurn = currentTurn;
47759
+ await writeBudgetState(directory, state);
47760
+ } else if (report.status === "critical") {
47761
+ state.criticalFiredAtTurn = currentTurn;
47762
+ state.lastInjectedAtTurn = currentTurn;
47763
+ }
47764
+ return formatWarningMessage(report);
47765
+ }
47766
+ function formatWarningMessage(report) {
47767
+ const budgetPctStr = report.budgetPct.toFixed(1);
47768
+ const tokensPerTurn = report.swarmTotalTokens.toLocaleString();
47769
+ if (report.status === "warning") {
47770
+ return `[CONTEXT BUDGET: ${budgetPctStr}% \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Consider wrapping current phase and running /swarm handoff before starting new work.]`;
47771
+ }
47772
+ const costPerTurn = (report.swarmTotalTokens / 1000 * COST_PER_1K_TOKENS).toFixed(3);
47773
+ return `[CONTEXT BUDGET: ${budgetPctStr}% CRITICAL \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Run /swarm handoff and start a new session to avoid cost escalation. Estimated session cost scaling: ~$${costPerTurn}/turn at current context size.]`;
47774
+ }
47775
+
47776
+ // src/services/status-service.ts
47573
47777
  async function getStatusData(directory, agents) {
47574
47778
  const plan = await loadPlan(directory);
47575
47779
  if (plan && plan.migration_status !== "migration_failed") {
@@ -47591,7 +47795,10 @@ async function getStatusData(directory, agents) {
47591
47795
  totalTasks: totalTasks2,
47592
47796
  agentCount: agentCount2,
47593
47797
  isLegacy: false,
47594
- turboMode: hasActiveTurboMode()
47798
+ turboMode: hasActiveTurboMode(),
47799
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47800
+ compactionCount: 0,
47801
+ lastSnapshotAt: null
47595
47802
  };
47596
47803
  }
47597
47804
  const planContent = await readSwarmFileAsync(directory, "plan.md");
@@ -47603,7 +47810,10 @@ async function getStatusData(directory, agents) {
47603
47810
  totalTasks: 0,
47604
47811
  agentCount: Object.keys(agents).length,
47605
47812
  isLegacy: true,
47606
- turboMode: hasActiveTurboMode()
47813
+ turboMode: hasActiveTurboMode(),
47814
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47815
+ compactionCount: 0,
47816
+ lastSnapshotAt: null
47607
47817
  };
47608
47818
  }
47609
47819
  const currentPhase = extractCurrentPhase(planContent) || "Unknown";
@@ -47618,7 +47828,10 @@ async function getStatusData(directory, agents) {
47618
47828
  totalTasks,
47619
47829
  agentCount,
47620
47830
  isLegacy: true,
47621
- turboMode: hasActiveTurboMode()
47831
+ turboMode: hasActiveTurboMode(),
47832
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47833
+ compactionCount: 0,
47834
+ lastSnapshotAt: null
47622
47835
  };
47623
47836
  }
47624
47837
  function formatStatusMarkdown(status) {
@@ -47632,6 +47845,18 @@ function formatStatusMarkdown(status) {
47632
47845
  if (status.turboMode) {
47633
47846
  lines.push("", `**TURBO MODE**: active`);
47634
47847
  }
47848
+ if (status.contextBudgetPct !== null && status.contextBudgetPct > 0) {
47849
+ const pct = status.contextBudgetPct.toFixed(1);
47850
+ const budgetTokens = DEFAULT_CONTEXT_BUDGET_CONFIG.budgetTokens;
47851
+ const est = Math.round(status.contextBudgetPct / 100 * budgetTokens);
47852
+ lines.push("", `**Context**: ${pct}% used (est. ${est.toLocaleString()} / ${budgetTokens.toLocaleString()} tokens)`);
47853
+ if (status.compactionCount > 0) {
47854
+ lines.push(`**Compaction events**: ${status.compactionCount} triggered`);
47855
+ }
47856
+ if (status.lastSnapshotAt) {
47857
+ lines.push(`**Last snapshot**: ${status.lastSnapshotAt}`);
47858
+ }
47859
+ }
47635
47860
  return lines.join(`
47636
47861
  `);
47637
47862
  }
@@ -47726,6 +47951,132 @@ async function executeWriteRetro(args2, directory) {
47726
47951
  message: "Invalid task_count: must be a positive integer >= 1"
47727
47952
  }, null, 2);
47728
47953
  }
47954
+ if (!Number.isInteger(args2.total_tool_calls) || args2.total_tool_calls < 0) {
47955
+ return JSON.stringify({
47956
+ success: false,
47957
+ phase,
47958
+ message: "Invalid total_tool_calls: must be a non-negative integer"
47959
+ }, null, 2);
47960
+ }
47961
+ if (!Number.isInteger(args2.coder_revisions) || args2.coder_revisions < 0) {
47962
+ return JSON.stringify({
47963
+ success: false,
47964
+ phase,
47965
+ message: "Invalid coder_revisions: must be a non-negative integer"
47966
+ }, null, 2);
47967
+ }
47968
+ if (!Number.isInteger(args2.reviewer_rejections) || args2.reviewer_rejections < 0) {
47969
+ return JSON.stringify({
47970
+ success: false,
47971
+ phase,
47972
+ message: "Invalid reviewer_rejections: must be a non-negative integer"
47973
+ }, null, 2);
47974
+ }
47975
+ if (!Number.isInteger(args2.test_failures) || args2.test_failures < 0) {
47976
+ return JSON.stringify({
47977
+ success: false,
47978
+ phase,
47979
+ message: "Invalid test_failures: must be a non-negative integer"
47980
+ }, null, 2);
47981
+ }
47982
+ if (!Number.isInteger(args2.security_findings) || args2.security_findings < 0) {
47983
+ return JSON.stringify({
47984
+ success: false,
47985
+ phase,
47986
+ message: "Invalid security_findings: must be a non-negative integer"
47987
+ }, null, 2);
47988
+ }
47989
+ if (!Number.isInteger(args2.integration_issues) || args2.integration_issues < 0) {
47990
+ return JSON.stringify({
47991
+ success: false,
47992
+ phase,
47993
+ message: "Invalid integration_issues: must be a non-negative integer"
47994
+ }, null, 2);
47995
+ }
47996
+ if (args2.loop_detections !== undefined && (!Number.isInteger(args2.loop_detections) || args2.loop_detections < 0)) {
47997
+ return JSON.stringify({
47998
+ success: false,
47999
+ phase,
48000
+ message: "Invalid loop_detections: must be a non-negative integer"
48001
+ }, null, 2);
48002
+ }
48003
+ if (args2.circuit_breaker_trips !== undefined && (!Number.isInteger(args2.circuit_breaker_trips) || args2.circuit_breaker_trips < 0)) {
48004
+ return JSON.stringify({
48005
+ success: false,
48006
+ phase,
48007
+ message: "Invalid circuit_breaker_trips: must be a non-negative integer"
48008
+ }, null, 2);
48009
+ }
48010
+ if (args2.phase > 99) {
48011
+ return JSON.stringify({
48012
+ success: false,
48013
+ phase,
48014
+ message: "Invalid phase: must be <= 99"
48015
+ }, null, 2);
48016
+ }
48017
+ if (args2.task_count > 9999) {
48018
+ return JSON.stringify({
48019
+ success: false,
48020
+ phase,
48021
+ message: "Invalid task_count: must be <= 9999"
48022
+ }, null, 2);
48023
+ }
48024
+ if (args2.total_tool_calls > 9999) {
48025
+ return JSON.stringify({
48026
+ success: false,
48027
+ phase,
48028
+ message: "Invalid total_tool_calls: must be <= 9999"
48029
+ }, null, 2);
48030
+ }
48031
+ if (args2.coder_revisions > 999) {
48032
+ return JSON.stringify({
48033
+ success: false,
48034
+ phase,
48035
+ message: "Invalid coder_revisions: must be <= 999"
48036
+ }, null, 2);
48037
+ }
48038
+ if (args2.reviewer_rejections > 999) {
48039
+ return JSON.stringify({
48040
+ success: false,
48041
+ phase,
48042
+ message: "Invalid reviewer_rejections: must be <= 999"
48043
+ }, null, 2);
48044
+ }
48045
+ if (args2.loop_detections !== undefined && args2.loop_detections > 9999) {
48046
+ return JSON.stringify({
48047
+ success: false,
48048
+ phase,
48049
+ message: "Invalid loop_detections: must be <= 9999"
48050
+ }, null, 2);
48051
+ }
48052
+ if (args2.circuit_breaker_trips !== undefined && args2.circuit_breaker_trips > 9999) {
48053
+ return JSON.stringify({
48054
+ success: false,
48055
+ phase,
48056
+ message: "Invalid circuit_breaker_trips: must be <= 9999"
48057
+ }, null, 2);
48058
+ }
48059
+ if (args2.test_failures > 9999) {
48060
+ return JSON.stringify({
48061
+ success: false,
48062
+ phase,
48063
+ message: "Invalid test_failures: must be <= 9999"
48064
+ }, null, 2);
48065
+ }
48066
+ if (args2.security_findings > 999) {
48067
+ return JSON.stringify({
48068
+ success: false,
48069
+ phase,
48070
+ message: "Invalid security_findings: must be <= 999"
48071
+ }, null, 2);
48072
+ }
48073
+ if (args2.integration_issues > 999) {
48074
+ return JSON.stringify({
48075
+ success: false,
48076
+ phase,
48077
+ message: "Invalid integration_issues: must be <= 999"
48078
+ }, null, 2);
48079
+ }
47729
48080
  const summary = args2.summary;
47730
48081
  if (typeof summary !== "string" || summary.trim().length === 0) {
47731
48082
  return JSON.stringify({
@@ -47747,6 +48098,8 @@ async function executeWriteRetro(args2, directory) {
47747
48098
  total_tool_calls: args2.total_tool_calls,
47748
48099
  coder_revisions: args2.coder_revisions,
47749
48100
  reviewer_rejections: args2.reviewer_rejections,
48101
+ loop_detections: args2.loop_detections,
48102
+ circuit_breaker_trips: args2.circuit_breaker_trips,
47750
48103
  test_failures: args2.test_failures,
47751
48104
  security_findings: args2.security_findings,
47752
48105
  integration_issues: args2.integration_issues,
@@ -47776,16 +48129,18 @@ async function executeWriteRetro(args2, directory) {
47776
48129
  var write_retro = createSwarmTool({
47777
48130
  description: "Write a retrospective evidence bundle for a completed phase. " + "Accepts flat retro fields and writes a correctly-wrapped EvidenceBundle to " + ".swarm/evidence/retro-{phase}/evidence.json. " + "Use this instead of manually writing retro JSON to avoid schema validation failures in phase_complete.",
47778
48131
  args: {
47779
- phase: tool.schema.number().int().positive().describe("The phase number being completed (e.g., 1, 2, 3)"),
48132
+ phase: tool.schema.number().int().positive().max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
47780
48133
  summary: tool.schema.string().describe("Human-readable summary of the phase"),
47781
- task_count: tool.schema.number().int().min(1).describe("Count of tasks completed in this phase"),
48134
+ task_count: tool.schema.number().int().min(1).max(9999).describe("Count of tasks completed in this phase"),
47782
48135
  task_complexity: tool.schema.enum(["trivial", "simple", "moderate", "complex"]).describe("Complexity level of the completed tasks"),
47783
- total_tool_calls: tool.schema.number().int().min(0).describe("Total number of tool calls in this phase"),
47784
- coder_revisions: tool.schema.number().int().min(0).describe("Number of coder revisions made"),
47785
- reviewer_rejections: tool.schema.number().int().min(0).describe("Number of reviewer rejections received"),
47786
- test_failures: tool.schema.number().int().min(0).describe("Number of test failures encountered"),
47787
- security_findings: tool.schema.number().int().min(0).describe("Number of security findings"),
47788
- integration_issues: tool.schema.number().int().min(0).describe("Number of integration issues"),
48136
+ total_tool_calls: tool.schema.number().int().min(0).max(9999).describe("Total number of tool calls in this phase"),
48137
+ coder_revisions: tool.schema.number().int().min(0).max(999).describe("Number of coder revisions made"),
48138
+ reviewer_rejections: tool.schema.number().int().min(0).max(999).describe("Number of reviewer rejections received"),
48139
+ loop_detections: tool.schema.number().int().min(0).max(9999).optional().describe("Number of loop detection events in this phase"),
48140
+ circuit_breaker_trips: tool.schema.number().int().min(0).max(9999).optional().describe("Number of circuit breaker trips in this phase"),
48141
+ test_failures: tool.schema.number().int().min(0).max(9999).describe("Number of test failures encountered"),
48142
+ security_findings: tool.schema.number().int().min(0).max(999).describe("Number of security findings"),
48143
+ integration_issues: tool.schema.number().int().min(0).max(999).describe("Number of integration issues"),
47789
48144
  lessons_learned: tool.schema.array(tool.schema.string()).max(5).optional().describe("Key lessons learned from this phase (max 5)"),
47790
48145
  top_rejection_reasons: tool.schema.array(tool.schema.string()).optional().describe("Top reasons for reviewer rejections"),
47791
48146
  task_id: tool.schema.string().optional().describe("Optional custom task ID (defaults to retro-{phase})"),
@@ -47802,6 +48157,8 @@ var write_retro = createSwarmTool({
47802
48157
  total_tool_calls: Number(args2.total_tool_calls),
47803
48158
  coder_revisions: Number(args2.coder_revisions),
47804
48159
  reviewer_rejections: Number(args2.reviewer_rejections),
48160
+ loop_detections: args2.loop_detections != null ? Number(args2.loop_detections) : undefined,
48161
+ circuit_breaker_trips: args2.circuit_breaker_trips != null ? Number(args2.circuit_breaker_trips) : undefined,
47805
48162
  test_failures: Number(args2.test_failures),
47806
48163
  security_findings: Number(args2.security_findings),
47807
48164
  integration_issues: Number(args2.integration_issues),
@@ -48687,6 +49044,48 @@ init_schema();
48687
49044
  init_manager2();
48688
49045
  import * as path27 from "path";
48689
49046
  init_utils();
49047
+
49048
+ // src/hooks/loop-detector.ts
49049
+ function hashDelegation(toolName, args2) {
49050
+ const targetAgent = typeof args2?.subagent_type === "string" ? args2.subagent_type : "unknown";
49051
+ const firstArgKey = args2 != null ? Object.keys(args2)[0] ?? "noargs" : "noargs";
49052
+ return `${toolName}:${targetAgent}:${firstArgKey}`;
49053
+ }
49054
+ function detectLoop(sessionId, toolName, args2) {
49055
+ if (toolName !== "Task") {
49056
+ return { looping: false, count: 0, pattern: "" };
49057
+ }
49058
+ const session = swarmState.agentSessions.get(sessionId);
49059
+ if (!session) {
49060
+ return { looping: false, count: 0, pattern: "" };
49061
+ }
49062
+ if (!session.loopDetectionWindow) {
49063
+ session.loopDetectionWindow = [];
49064
+ }
49065
+ const argsRecord = args2 != null && typeof args2 === "object" && !Array.isArray(args2) ? args2 : undefined;
49066
+ const hash3 = hashDelegation(toolName, argsRecord);
49067
+ const now = Date.now();
49068
+ session.loopDetectionWindow.push({ hash: hash3, timestamp: now });
49069
+ if (session.loopDetectionWindow.length > 10) {
49070
+ session.loopDetectionWindow.shift();
49071
+ }
49072
+ const window2 = session.loopDetectionWindow;
49073
+ let consecutiveCount = 0;
49074
+ for (let i2 = window2.length - 1;i2 >= 0; i2--) {
49075
+ if (window2[i2].hash === hash3) {
49076
+ consecutiveCount++;
49077
+ } else {
49078
+ break;
49079
+ }
49080
+ }
49081
+ return {
49082
+ looping: consecutiveCount >= 3,
49083
+ count: consecutiveCount,
49084
+ pattern: hash3
49085
+ };
49086
+ }
49087
+
49088
+ // src/hooks/guardrails.ts
48690
49089
  var storedInputArgs = new Map;
48691
49090
  function getStoredInputArgs(callID) {
48692
49091
  return storedInputArgs.get(callID);
@@ -48794,7 +49193,10 @@ function isAgentDelegation(toolName, args2) {
48794
49193
  }
48795
49194
  const subagentType = argsObj.subagent_type;
48796
49195
  if (typeof subagentType === "string") {
48797
- return { isDelegation: true, targetAgent: stripKnownSwarmPrefix(subagentType) };
49196
+ return {
49197
+ isDelegation: true,
49198
+ targetAgent: stripKnownSwarmPrefix(subagentType)
49199
+ };
48798
49200
  }
48799
49201
  return { isDelegation: false, targetAgent: null };
48800
49202
  }
@@ -48861,6 +49263,37 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
48861
49263
  }
48862
49264
  }
48863
49265
  }
49266
+ if (input.tool === "Task") {
49267
+ const loopArgs = output.args;
49268
+ const loopResult = detectLoop(input.sessionID, input.tool, loopArgs);
49269
+ if (loopResult.count >= 5) {
49270
+ throw new Error(`CIRCUIT BREAKER: Delegation loop detected (${loopResult.count} identical patterns). Session paused. Ask the user for guidance.`);
49271
+ } else if (loopResult.count === 3) {
49272
+ const agentName2 = typeof loopArgs?.subagent_type === "string" ? loopArgs.subagent_type : "agent";
49273
+ const loopSession = swarmState.agentSessions.get(input.sessionID);
49274
+ if (loopSession) {
49275
+ loopSession.loopWarningPending = {
49276
+ agent: agentName2,
49277
+ message: `LOOP DETECTED: You have delegated to ${agentName2} with the same pattern 3 times. Change your approach \u2014 try a different agent, different instructions, or escalate to the user.`,
49278
+ timestamp: Date.now()
49279
+ };
49280
+ }
49281
+ }
49282
+ }
49283
+ if (input.tool === "bash" || input.tool === "shell") {
49284
+ const bashArgs = output.args;
49285
+ const cmd = (typeof bashArgs?.command === "string" ? bashArgs.command : "").trim();
49286
+ const testRunnerPrefixPattern = /^(bun\s+test|npm\s+test|npx\s+vitest|bunx\s+vitest)\b/;
49287
+ if (testRunnerPrefixPattern.test(cmd)) {
49288
+ const tokens = cmd.split(/\s+/);
49289
+ const runnerTokenCount = tokens[0] === "npx" || tokens[0] === "bunx" ? 3 : 2;
49290
+ const remainingTokens = tokens.slice(runnerTokenCount);
49291
+ const hasFileArg = remainingTokens.some((token) => token.length > 0 && !token.startsWith("-") && (token.includes("/") || token.includes("\\") || token.endsWith(".ts") || token.endsWith(".js") || token.endsWith(".tsx") || token.endsWith(".jsx") || token.endsWith(".mts") || token.endsWith(".mjs")));
49292
+ if (!hasFileArg) {
49293
+ throw new Error("BLOCKED: Full test suite execution is not allowed in-session. Run a specific test file instead: bun test path/to/file.test.ts");
49294
+ }
49295
+ }
49296
+ }
48864
49297
  if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
48865
49298
  const args2 = output.args;
48866
49299
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
@@ -49205,6 +49638,21 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
49205
49638
  const activeAgent = swarmState.activeAgent.get(sessionId);
49206
49639
  const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
49207
49640
  const systemMessages = messages.filter((msg) => msg.info?.role === "system");
49641
+ if (isArchitectSession && session?.loopWarningPending) {
49642
+ const pending = session.loopWarningPending;
49643
+ session.loopWarningPending = undefined;
49644
+ const loopSystemMsg = systemMessages[0];
49645
+ if (loopSystemMsg) {
49646
+ const loopTextPart = (loopSystemMsg.parts ?? []).find((part) => part.type === "text" && typeof part.text === "string");
49647
+ if (loopTextPart && !loopTextPart.text.includes("LOOP DETECTED")) {
49648
+ loopTextPart.text = `[LOOP WARNING]
49649
+ ${pending.message}
49650
+ [/LOOP WARNING]
49651
+
49652
+ ` + loopTextPart.text;
49653
+ }
49654
+ }
49655
+ }
49208
49656
  if (isArchitectSession && session && session.architectWriteCount > session.selfCodingWarnedAtCount) {
49209
49657
  let targetSystemMessage = systemMessages[0];
49210
49658
  if (!targetSystemMessage) {
@@ -50508,165 +50956,6 @@ function formatDriftForContext(result) {
50508
50956
 
50509
50957
  // src/services/index.ts
50510
50958
  init_config_doctor();
50511
-
50512
- // src/services/context-budget-service.ts
50513
- init_utils2();
50514
- function validateDirectory(directory) {
50515
- if (!directory || directory.trim() === "") {
50516
- throw new Error("Invalid directory: empty");
50517
- }
50518
- if (/\.\.[/\\]/.test(directory)) {
50519
- throw new Error("Invalid directory: path traversal detected");
50520
- }
50521
- if (directory.startsWith("/") || directory.startsWith("\\")) {
50522
- throw new Error("Invalid directory: absolute path");
50523
- }
50524
- if (/^[A-Za-z]:[\\/]/.test(directory)) {
50525
- throw new Error("Invalid directory: Windows absolute path");
50526
- }
50527
- }
50528
- var COST_PER_1K_TOKENS = 0.003;
50529
- function estimateTokens2(text) {
50530
- if (!text || typeof text !== "string") {
50531
- return 0;
50532
- }
50533
- return Math.ceil(text.length / 3.5);
50534
- }
50535
- async function readBudgetState(directory) {
50536
- const content = await readSwarmFileAsync(directory, "session/budget-state.json");
50537
- if (!content) {
50538
- return null;
50539
- }
50540
- try {
50541
- return JSON.parse(content);
50542
- } catch {
50543
- return null;
50544
- }
50545
- }
50546
- async function writeBudgetState(directory, state) {
50547
- const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
50548
- const content = JSON.stringify(state, null, 2);
50549
- await Bun.write(resolvedPath, content);
50550
- }
50551
- async function countEvents(directory) {
50552
- const content = await readSwarmFileAsync(directory, "events.jsonl");
50553
- if (!content) {
50554
- return 0;
50555
- }
50556
- const lines = content.split(`
50557
- `).filter((line) => line.trim().length > 0);
50558
- return lines.length;
50559
- }
50560
- async function getPlanCursorContent(directory) {
50561
- const planContent = await readSwarmFileAsync(directory, "plan.md");
50562
- if (!planContent) {
50563
- return "";
50564
- }
50565
- const lines = planContent.split(`
50566
- `);
50567
- const cursorLines = [];
50568
- let inCurrentSection = false;
50569
- for (const line of lines) {
50570
- if (line.includes("in_progress") || line.includes("**Current**")) {
50571
- inCurrentSection = true;
50572
- }
50573
- if (inCurrentSection) {
50574
- cursorLines.push(line);
50575
- if (cursorLines.length > 30) {
50576
- break;
50577
- }
50578
- }
50579
- }
50580
- return cursorLines.join(`
50581
- `) || planContent.substring(0, 1000);
50582
- }
50583
- async function getContextBudgetReport(directory, assembledSystemPrompt, config3) {
50584
- validateDirectory(directory);
50585
- const timestamp = new Date().toISOString();
50586
- const systemPromptTokens = estimateTokens2(assembledSystemPrompt);
50587
- const planCursorContent = await getPlanCursorContent(directory);
50588
- const planCursorTokens = estimateTokens2(planCursorContent);
50589
- const knowledgeContent = await readSwarmFileAsync(directory, "knowledge.jsonl");
50590
- const knowledgeTokens = estimateTokens2(knowledgeContent || "");
50591
- const runMemoryContent = await readSwarmFileAsync(directory, "run-memory.jsonl");
50592
- const runMemoryTokens = estimateTokens2(runMemoryContent || "");
50593
- const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
50594
- const handoffTokens = estimateTokens2(handoffContent || "");
50595
- const contextMdContent = await readSwarmFileAsync(directory, "context.md");
50596
- const contextMdTokens = estimateTokens2(contextMdContent || "");
50597
- const swarmTotalTokens = systemPromptTokens + planCursorTokens + knowledgeTokens + runMemoryTokens + handoffTokens + contextMdTokens;
50598
- const estimatedTurnCount = await countEvents(directory);
50599
- const budgetPct = swarmTotalTokens / config3.budgetTokens * 100;
50600
- let status;
50601
- let recommendation = null;
50602
- if (budgetPct < config3.warningPct) {
50603
- status = "ok";
50604
- } else if (budgetPct < config3.criticalPct) {
50605
- status = "warning";
50606
- recommendation = "Consider wrapping up current phase and running /swarm handoff before starting new work.";
50607
- } else {
50608
- status = "critical";
50609
- recommendation = "Run /swarm handoff and start a new session to avoid cost escalation.";
50610
- }
50611
- const estimatedSessionTokens = swarmTotalTokens * Math.max(1, estimatedTurnCount);
50612
- return {
50613
- timestamp,
50614
- systemPromptTokens,
50615
- planCursorTokens,
50616
- knowledgeTokens,
50617
- runMemoryTokens,
50618
- handoffTokens,
50619
- contextMdTokens,
50620
- swarmTotalTokens,
50621
- estimatedTurnCount,
50622
- estimatedSessionTokens,
50623
- budgetPct,
50624
- status,
50625
- recommendation
50626
- };
50627
- }
50628
- async function formatBudgetWarning(report, directory, config3) {
50629
- validateDirectory(directory);
50630
- if (report.status === "ok") {
50631
- return null;
50632
- }
50633
- if (!directory || directory.trim() === "") {
50634
- return formatWarningMessage(report);
50635
- }
50636
- const budgetState = await readBudgetState(directory);
50637
- const state = budgetState || {
50638
- warningFiredAtTurn: null,
50639
- criticalFiredAtTurn: null,
50640
- lastInjectedAtTurn: null
50641
- };
50642
- const currentTurn = report.estimatedTurnCount;
50643
- if (report.status === "warning") {
50644
- if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
50645
- return null;
50646
- }
50647
- if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
50648
- return null;
50649
- }
50650
- state.warningFiredAtTurn = currentTurn;
50651
- state.lastInjectedAtTurn = currentTurn;
50652
- await writeBudgetState(directory, state);
50653
- } else if (report.status === "critical") {
50654
- state.criticalFiredAtTurn = currentTurn;
50655
- state.lastInjectedAtTurn = currentTurn;
50656
- }
50657
- return formatWarningMessage(report);
50658
- }
50659
- function formatWarningMessage(report) {
50660
- const budgetPctStr = report.budgetPct.toFixed(1);
50661
- const tokensPerTurn = report.swarmTotalTokens.toLocaleString();
50662
- if (report.status === "warning") {
50663
- return `[CONTEXT BUDGET: ${budgetPctStr}% \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Consider wrapping current phase and running /swarm handoff before starting new work.]`;
50664
- }
50665
- const costPerTurn = (report.swarmTotalTokens / 1000 * COST_PER_1K_TOKENS).toFixed(3);
50666
- return `[CONTEXT BUDGET: ${budgetPctStr}% CRITICAL \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Run /swarm handoff and start a new session to avoid cost escalation. Estimated session cost scaling: ~$${costPerTurn}/turn at current context size.]`;
50667
- }
50668
-
50669
- // src/services/index.ts
50670
50959
  init_evidence_summary_service();
50671
50960
  init_preflight_integration();
50672
50961
  init_preflight_service();
@@ -51253,6 +51542,7 @@ ${handoffBlock}`);
51253
51542
  const assembledSystemPrompt = output.system.join(`
51254
51543
  `);
51255
51544
  const budgetReport = await getContextBudgetReport(directory, assembledSystemPrompt, contextBudgetConfig);
51545
+ swarmState.lastBudgetPct = budgetReport.budgetPct;
51256
51546
  const budgetWarning = await formatBudgetWarning(budgetReport, directory, contextBudgetConfig);
51257
51547
  if (budgetWarning) {
51258
51548
  const sessionId_cb = _input.sessionID;
@@ -51654,6 +51944,7 @@ ${handoffBlock}`;
51654
51944
  const assembledSystemPrompt_b = output.system.join(`
51655
51945
  `);
51656
51946
  const budgetReport_b = await getContextBudgetReport(directory, assembledSystemPrompt_b, contextBudgetConfig_b);
51947
+ swarmState.lastBudgetPct = budgetReport_b.budgetPct;
51657
51948
  const budgetWarning_b = await formatBudgetWarning(budgetReport_b, directory, contextBudgetConfig_b);
51658
51949
  if (budgetWarning_b) {
51659
51950
  const sessionId_cb_b = _input.sessionID;
@@ -52016,10 +52307,92 @@ function createDarkMatterDetectorHook(directory) {
52016
52307
  return safeHook(hook);
52017
52308
  }
52018
52309
 
52310
+ // src/hooks/incremental-verify.ts
52311
+ import * as fs19 from "fs";
52312
+ import * as path31 from "path";
52313
+ function detectTypecheckCommand(projectDir) {
52314
+ const pkgPath = path31.join(projectDir, "package.json");
52315
+ if (!fs19.existsSync(pkgPath))
52316
+ return null;
52317
+ try {
52318
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
52319
+ const scripts = pkg.scripts;
52320
+ if (scripts?.typecheck)
52321
+ return ["bun", "run", "typecheck"];
52322
+ if (scripts?.["type-check"])
52323
+ return ["bun", "run", "type-check"];
52324
+ const deps = {
52325
+ ...pkg.dependencies,
52326
+ ...pkg.devDependencies
52327
+ };
52328
+ if (!deps?.typescript && !fs19.existsSync(path31.join(projectDir, "tsconfig.json"))) {
52329
+ return null;
52330
+ }
52331
+ return ["npx", "tsc", "--noEmit"];
52332
+ } catch {
52333
+ return null;
52334
+ }
52335
+ }
52336
+ async function runWithTimeout(command, cwd, timeoutMs) {
52337
+ try {
52338
+ const proc = Bun.spawn(command, {
52339
+ cwd,
52340
+ stdout: "pipe",
52341
+ stderr: "pipe"
52342
+ });
52343
+ const timeoutHandle = setTimeout(() => {
52344
+ try {
52345
+ proc.kill();
52346
+ } catch {}
52347
+ }, timeoutMs);
52348
+ try {
52349
+ const [exitCode, stderr] = await Promise.all([
52350
+ proc.exited,
52351
+ new Response(proc.stderr).text()
52352
+ ]);
52353
+ return { exitCode, stderr };
52354
+ } finally {
52355
+ clearTimeout(timeoutHandle);
52356
+ }
52357
+ } catch {
52358
+ return null;
52359
+ }
52360
+ }
52361
+ function createIncrementalVerifyHook(config3, projectDir, injectMessage) {
52362
+ return {
52363
+ toolAfter: async (input, output) => {
52364
+ if (!config3.enabled)
52365
+ return;
52366
+ if (input.tool !== "Task")
52367
+ return;
52368
+ const args2 = input.args ?? output.args;
52369
+ const subagentType = typeof args2?.subagent_type === "string" ? args2.subagent_type : "";
52370
+ const agentName = subagentType.replace(/^[^_]+_/, "");
52371
+ if (!config3.triggerAgents.includes(agentName) && !config3.triggerAgents.includes(subagentType)) {
52372
+ return;
52373
+ }
52374
+ const command = config3.command != null ? config3.command.split(" ") : detectTypecheckCommand(projectDir);
52375
+ if (!command)
52376
+ return;
52377
+ const result = await runWithTimeout(command, projectDir, config3.timeoutMs);
52378
+ if (result === null) {
52379
+ return;
52380
+ }
52381
+ if (result.exitCode === 0) {
52382
+ injectMessage(input.sessionID, "POST-CODER CHECK PASSED: No type errors.");
52383
+ } else {
52384
+ const errorSummary = result.stderr.slice(0, 800);
52385
+ injectMessage(input.sessionID, `POST-CODER CHECK FAILED: Type errors detected after coder delegation. Address these before proceeding.
52386
+ ${errorSummary}`);
52387
+ }
52388
+ }
52389
+ };
52390
+ }
52391
+
52019
52392
  // src/hooks/knowledge-reader.ts
52020
- import { existsSync as existsSync18 } from "fs";
52393
+ import { existsSync as existsSync19 } from "fs";
52021
52394
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
52022
- import * as path31 from "path";
52395
+ import * as path32 from "path";
52023
52396
  var JACCARD_THRESHOLD = 0.6;
52024
52397
  var HIVE_TIER_BOOST = 0.05;
52025
52398
  var SAME_PROJECT_PENALTY = -0.05;
@@ -52067,15 +52440,15 @@ function inferCategoriesFromPhase(phaseDescription) {
52067
52440
  return ["process", "tooling"];
52068
52441
  }
52069
52442
  async function recordLessonsShown(directory, lessonIds, currentPhase) {
52070
- const shownFile = path31.join(directory, ".swarm", ".knowledge-shown.json");
52443
+ const shownFile = path32.join(directory, ".swarm", ".knowledge-shown.json");
52071
52444
  try {
52072
52445
  let shownData = {};
52073
- if (existsSync18(shownFile)) {
52446
+ if (existsSync19(shownFile)) {
52074
52447
  const content = await readFile5(shownFile, "utf-8");
52075
52448
  shownData = JSON.parse(content);
52076
52449
  }
52077
52450
  shownData[currentPhase] = lessonIds;
52078
- await mkdir4(path31.dirname(shownFile), { recursive: true });
52451
+ await mkdir4(path32.dirname(shownFile), { recursive: true });
52079
52452
  await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
52080
52453
  } catch {
52081
52454
  console.warn("[swarm] Knowledge: failed to record shown lessons");
@@ -52170,9 +52543,9 @@ async function readMergedKnowledge(directory, config3, context) {
52170
52543
  return topN;
52171
52544
  }
52172
52545
  async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
52173
- const shownFile = path31.join(directory, ".swarm", ".knowledge-shown.json");
52546
+ const shownFile = path32.join(directory, ".swarm", ".knowledge-shown.json");
52174
52547
  try {
52175
- if (!existsSync18(shownFile)) {
52548
+ if (!existsSync19(shownFile)) {
52176
52549
  return;
52177
52550
  }
52178
52551
  const content = await readFile5(shownFile, "utf-8");
@@ -52642,12 +53015,12 @@ Use this data to avoid repeating known failure patterns.`;
52642
53015
  // src/hooks/curator-drift.ts
52643
53016
  init_event_bus();
52644
53017
  init_utils2();
52645
- import * as fs19 from "fs";
52646
- import * as path32 from "path";
53018
+ import * as fs20 from "fs";
53019
+ import * as path33 from "path";
52647
53020
  var DRIFT_REPORT_PREFIX = "drift-report-phase-";
52648
53021
  async function readPriorDriftReports(directory) {
52649
- const swarmDir = path32.join(directory, ".swarm");
52650
- const entries = await fs19.promises.readdir(swarmDir).catch(() => null);
53022
+ const swarmDir = path33.join(directory, ".swarm");
53023
+ const entries = await fs20.promises.readdir(swarmDir).catch(() => null);
52651
53024
  if (entries === null)
52652
53025
  return [];
52653
53026
  const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
@@ -52673,10 +53046,10 @@ async function readPriorDriftReports(directory) {
52673
53046
  async function writeDriftReport(directory, report) {
52674
53047
  const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
52675
53048
  const filePath = validateSwarmPath(directory, filename);
52676
- const swarmDir = path32.dirname(filePath);
52677
- await fs19.promises.mkdir(swarmDir, { recursive: true });
53049
+ const swarmDir = path33.dirname(filePath);
53050
+ await fs20.promises.mkdir(swarmDir, { recursive: true });
52678
53051
  try {
52679
- await fs19.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
53052
+ await fs20.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
52680
53053
  } catch (err2) {
52681
53054
  throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
52682
53055
  }
@@ -52926,9 +53299,143 @@ ${cachedInjectionText}`;
52926
53299
  });
52927
53300
  }
52928
53301
 
53302
+ // src/hooks/slop-detector.ts
53303
+ var WRITE_EDIT_TOOLS = new Set([
53304
+ "write",
53305
+ "edit",
53306
+ "apply_patch",
53307
+ "create_file"
53308
+ ]);
53309
+ function countMatches(text, pattern) {
53310
+ return (text.match(pattern) ?? []).length;
53311
+ }
53312
+ function checkAbstractionBloat(content, threshold) {
53313
+ const newClasses = countMatches(content, /^\+.*\bclass\s+\w+/gm);
53314
+ if (newClasses >= threshold) {
53315
+ return {
53316
+ type: "abstraction_bloat",
53317
+ detail: `${newClasses} new class declarations added (threshold: ${threshold}). Consider whether all abstractions are necessary.`
53318
+ };
53319
+ }
53320
+ return null;
53321
+ }
53322
+ function checkCommentStrip(content, threshold) {
53323
+ const removedComments = countMatches(content, /^-\s*\/[/*]/gm);
53324
+ const addedComments = countMatches(content, /^\+\s*\/[/*]/gm);
53325
+ if (removedComments >= threshold && addedComments === 0) {
53326
+ return {
53327
+ type: "comment_strip",
53328
+ detail: `${removedComments} comment lines removed and 0 added. Verify comments were not documenting important behaviour.`
53329
+ };
53330
+ }
53331
+ return null;
53332
+ }
53333
+ function checkBoilerplateExplosion(content, taskDescription, threshold) {
53334
+ const addedLines = countMatches(content, /^\+[^+]/gm);
53335
+ const isSmallTask = /\b(fix|patch|update|tweak|adjust|correct|remove|rename|change)\b/i.test(taskDescription);
53336
+ if (isSmallTask && addedLines >= threshold) {
53337
+ return {
53338
+ type: "boilerplate_explosion",
53339
+ detail: `${addedLines} lines added for a "${taskDescription.slice(0, 40)}" task (threshold: ${threshold}). Review for scope creep.`
53340
+ };
53341
+ }
53342
+ return null;
53343
+ }
53344
+ async function checkDeadExports(content, projectDir, startTime) {
53345
+ const exportMatches = content.matchAll(/^(?:export)\s+(?:function|class|const|type|interface)\s+(\w{3,})/gm);
53346
+ const newExports = [];
53347
+ for (const match of exportMatches) {
53348
+ if (match[1])
53349
+ newExports.push(match[1]);
53350
+ }
53351
+ if (newExports.length === 0)
53352
+ return null;
53353
+ const deadExports = [];
53354
+ for (const name2 of newExports) {
53355
+ if (Date.now() - startTime > 480)
53356
+ break;
53357
+ try {
53358
+ const importPattern = new RegExp(`\\bimport\\b[^;]*\\b${name2}\\b`, "g");
53359
+ const glob = new Bun.Glob(`src/**/*.ts`);
53360
+ let found = false;
53361
+ for await (const file3 of glob.scan(projectDir)) {
53362
+ if (found || Date.now() - startTime > 480)
53363
+ break;
53364
+ try {
53365
+ const text = await Bun.file(`${projectDir}/${file3}`).text();
53366
+ if (importPattern.test(text))
53367
+ found = true;
53368
+ importPattern.lastIndex = 0;
53369
+ } catch {}
53370
+ }
53371
+ if (!found)
53372
+ deadExports.push(name2);
53373
+ } catch {}
53374
+ }
53375
+ if (deadExports.length === 0)
53376
+ return null;
53377
+ return {
53378
+ type: "dead_export",
53379
+ detail: `New exports not found in any import: ${deadExports.slice(0, 3).join(", ")}. Verify these are intentionally exported.`
53380
+ };
53381
+ }
53382
+ function createSlopDetectorHook(config3, projectDir, injectSystemMessage) {
53383
+ return {
53384
+ toolAfter: async (input, output) => {
53385
+ if (!config3.enabled)
53386
+ return;
53387
+ if (!WRITE_EDIT_TOOLS.has(input.tool.toLowerCase()))
53388
+ return;
53389
+ const args2 = output.args;
53390
+ const content = (() => {
53391
+ if (typeof args2?.content === "string")
53392
+ return args2.content;
53393
+ if (typeof args2?.newString === "string")
53394
+ return args2.newString;
53395
+ if (typeof args2?.patch === "string")
53396
+ return args2.patch;
53397
+ if (typeof args2?.file_text === "string")
53398
+ return args2.file_text;
53399
+ return "";
53400
+ })();
53401
+ if (!content || content.length < 10)
53402
+ return;
53403
+ const taskDescription = typeof args2?.description === "string" ? args2.description : typeof args2?.task === "string" ? args2.task : "";
53404
+ const startTime = Date.now();
53405
+ const findings = [];
53406
+ try {
53407
+ const bloat = checkAbstractionBloat(content, config3.classThreshold);
53408
+ if (bloat)
53409
+ findings.push(bloat);
53410
+ const strip = checkCommentStrip(content, config3.commentStripThreshold);
53411
+ if (strip)
53412
+ findings.push(strip);
53413
+ const explosion = checkBoilerplateExplosion(content, taskDescription, config3.diffLineThreshold);
53414
+ if (explosion)
53415
+ findings.push(explosion);
53416
+ } catch {}
53417
+ if (Date.now() - startTime < 400) {
53418
+ try {
53419
+ const dead = await checkDeadExports(content, projectDir, startTime);
53420
+ if (dead)
53421
+ findings.push(dead);
53422
+ } catch {}
53423
+ }
53424
+ if (findings.length === 0)
53425
+ return;
53426
+ const findingText = findings.map((f) => ` \u2022 ${f.type}: ${f.detail}`).join(`
53427
+ `);
53428
+ const message = `SLOP CHECK: ${findings.length} potential issue(s) detected after ${input.tool}:
53429
+ ${findingText}
53430
+ Review before proceeding.`;
53431
+ injectSystemMessage(input.sessionID, message);
53432
+ }
53433
+ };
53434
+ }
53435
+
52929
53436
  // src/hooks/steering-consumed.ts
52930
53437
  init_utils2();
52931
- import * as fs20 from "fs";
53438
+ import * as fs21 from "fs";
52932
53439
  function recordSteeringConsumed(directory, directiveId) {
52933
53440
  try {
52934
53441
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -52937,7 +53444,7 @@ function recordSteeringConsumed(directory, directiveId) {
52937
53444
  directiveId,
52938
53445
  timestamp: new Date().toISOString()
52939
53446
  };
52940
- fs20.appendFileSync(eventsPath, `${JSON.stringify(event)}
53447
+ fs21.appendFileSync(eventsPath, `${JSON.stringify(event)}
52941
53448
  `, "utf-8");
52942
53449
  } catch {}
52943
53450
  }
@@ -52977,12 +53484,93 @@ function createSteeringConsumedHook(directory) {
52977
53484
  return safeHook(hook);
52978
53485
  }
52979
53486
 
53487
+ // src/services/compaction-service.ts
53488
+ import * as fs22 from "fs";
53489
+ import * as path34 from "path";
53490
+ function makeInitialState() {
53491
+ return {
53492
+ lastObservationAt: 0,
53493
+ lastReflectionAt: 0,
53494
+ lastEmergencyAt: 0,
53495
+ observationCount: 0,
53496
+ reflectionCount: 0,
53497
+ emergencyCount: 0
53498
+ };
53499
+ }
53500
+ function appendSnapshot(directory, tier, budgetPct, message) {
53501
+ try {
53502
+ const snapshotPath = path34.join(directory, ".swarm", "context-snapshot.md");
53503
+ const timestamp = new Date().toISOString();
53504
+ const entry = `
53505
+ ## [${tier.toUpperCase()}] ${timestamp} \u2014 ${budgetPct.toFixed(1)}% used
53506
+ ${message}
53507
+ `;
53508
+ fs22.appendFileSync(snapshotPath, entry, "utf-8");
53509
+ } catch {}
53510
+ }
53511
+ function buildObservationMessage(budgetPct) {
53512
+ return `[CONTEXT COMPACTION \u2014 OBSERVATION TIER]
53513
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. Initiating observation compaction.
53514
+ ` + `INSTRUCTIONS: Summarise the key decisions made so far, files changed, errors resolved, ` + `and the current task state. Discard verbose tool outputs and raw file reads. ` + `Preserve: plan task ID, agent verdicts, file paths touched, unresolved blockers.
53515
+ ` + `[/CONTEXT COMPACTION]`;
53516
+ }
53517
+ function buildReflectionMessage(budgetPct) {
53518
+ return `[CONTEXT COMPACTION \u2014 REFLECTION TIER]
53519
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. Initiating reflection compaction.
53520
+ ` + `INSTRUCTIONS: Re-summarise into a tighter format. Discard completed task details ` + `and resolved errors. Retain ONLY: current phase tasks remaining, open blockers, ` + `last 3 reviewer/test verdicts, and active file scope.
53521
+ ` + `[/CONTEXT COMPACTION]`;
53522
+ }
53523
+ function buildEmergencyMessage(budgetPct, preserveLastN) {
53524
+ return `[CONTEXT COMPACTION \u2014 EMERGENCY TIER]
53525
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. EMERGENCY compaction required.
53526
+ ` + `INSTRUCTIONS: Retain ONLY the system prompt, the current task context, and the ` + `last ${preserveLastN} conversation turns. Discard everything else. ` + `If you cannot complete the current task in the remaining context, escalate to the user.
53527
+ ` + `[/CONTEXT COMPACTION]`;
53528
+ }
53529
+ function createCompactionService(config3, directory, injectMessage) {
53530
+ const state = makeInitialState();
53531
+ return {
53532
+ toolAfter: async (_input, _output) => {
53533
+ if (!config3.enabled)
53534
+ return;
53535
+ const budgetPct = swarmState.lastBudgetPct ?? 0;
53536
+ if (budgetPct <= 0)
53537
+ return;
53538
+ const sessionId = _input.sessionID;
53539
+ try {
53540
+ if (budgetPct >= config3.emergencyThreshold && budgetPct > state.lastEmergencyAt + 5) {
53541
+ state.lastEmergencyAt = budgetPct;
53542
+ state.emergencyCount++;
53543
+ const msg = buildEmergencyMessage(budgetPct, config3.preserveLastNTurns);
53544
+ appendSnapshot(directory, "emergency", budgetPct, msg);
53545
+ injectMessage(sessionId, msg);
53546
+ return;
53547
+ }
53548
+ if (budgetPct >= config3.reflectionThreshold && budgetPct > state.lastReflectionAt + 5) {
53549
+ state.lastReflectionAt = budgetPct;
53550
+ state.reflectionCount++;
53551
+ const msg = buildReflectionMessage(budgetPct);
53552
+ appendSnapshot(directory, "reflection", budgetPct, msg);
53553
+ injectMessage(sessionId, msg);
53554
+ return;
53555
+ }
53556
+ if (budgetPct >= config3.observationThreshold && budgetPct > state.lastObservationAt + 5) {
53557
+ state.lastObservationAt = budgetPct;
53558
+ state.observationCount++;
53559
+ const msg = buildObservationMessage(budgetPct);
53560
+ appendSnapshot(directory, "observation", budgetPct, msg);
53561
+ injectMessage(sessionId, msg);
53562
+ }
53563
+ } catch {}
53564
+ }
53565
+ };
53566
+ }
53567
+
52980
53568
  // src/index.ts
52981
53569
  init_config_doctor();
52982
53570
 
52983
53571
  // src/session/snapshot-reader.ts
52984
53572
  init_utils2();
52985
- import path33 from "path";
53573
+ import path35 from "path";
52986
53574
  var VALID_TASK_WORKFLOW_STATES = [
52987
53575
  "idle",
52988
53576
  "coder_delegated",
@@ -53107,7 +53695,7 @@ function rehydrateState(snapshot) {
53107
53695
  async function reconcileTaskStatesFromPlan(directory) {
53108
53696
  let raw;
53109
53697
  try {
53110
- raw = await Bun.file(path33.join(directory, ".swarm/plan.json")).text();
53698
+ raw = await Bun.file(path35.join(directory, ".swarm/plan.json")).text();
53111
53699
  } catch {
53112
53700
  return;
53113
53701
  }
@@ -53329,8 +53917,8 @@ var build_check = createSwarmTool({
53329
53917
  // src/tools/check-gate-status.ts
53330
53918
  init_dist();
53331
53919
  init_create_tool();
53332
- import * as fs21 from "fs";
53333
- import * as path34 from "path";
53920
+ import * as fs23 from "fs";
53921
+ import * as path36 from "path";
53334
53922
  var EVIDENCE_DIR = ".swarm/evidence";
53335
53923
  var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
53336
53924
  function isValidTaskId3(taskId) {
@@ -53347,18 +53935,18 @@ function isValidTaskId3(taskId) {
53347
53935
  return TASK_ID_PATTERN2.test(taskId);
53348
53936
  }
53349
53937
  function isPathWithinSwarm(filePath, workspaceRoot) {
53350
- const normalizedWorkspace = path34.resolve(workspaceRoot);
53351
- const swarmPath = path34.join(normalizedWorkspace, ".swarm", "evidence");
53352
- const normalizedPath = path34.resolve(filePath);
53938
+ const normalizedWorkspace = path36.resolve(workspaceRoot);
53939
+ const swarmPath = path36.join(normalizedWorkspace, ".swarm", "evidence");
53940
+ const normalizedPath = path36.resolve(filePath);
53353
53941
  return normalizedPath.startsWith(swarmPath);
53354
53942
  }
53355
53943
  function readEvidenceFile(evidencePath) {
53356
- if (!fs21.existsSync(evidencePath)) {
53944
+ if (!fs23.existsSync(evidencePath)) {
53357
53945
  return null;
53358
53946
  }
53359
53947
  let content;
53360
53948
  try {
53361
- content = fs21.readFileSync(evidencePath, "utf-8");
53949
+ content = fs23.readFileSync(evidencePath, "utf-8");
53362
53950
  } catch {
53363
53951
  return null;
53364
53952
  }
@@ -53410,7 +53998,7 @@ var check_gate_status = createSwarmTool({
53410
53998
  };
53411
53999
  return JSON.stringify(errorResult, null, 2);
53412
54000
  }
53413
- const evidencePath = path34.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
54001
+ const evidencePath = path36.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
53414
54002
  if (!isPathWithinSwarm(evidencePath, directory)) {
53415
54003
  const errorResult = {
53416
54004
  taskId: taskIdInput,
@@ -53470,8 +54058,8 @@ var check_gate_status = createSwarmTool({
53470
54058
  init_tool();
53471
54059
  init_create_tool();
53472
54060
  import { spawnSync } from "child_process";
53473
- import * as fs22 from "fs";
53474
- import * as path35 from "path";
54061
+ import * as fs24 from "fs";
54062
+ import * as path37 from "path";
53475
54063
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
53476
54064
  var MAX_LABEL_LENGTH = 100;
53477
54065
  var GIT_TIMEOUT_MS = 30000;
@@ -53522,13 +54110,13 @@ function validateLabel(label) {
53522
54110
  return null;
53523
54111
  }
53524
54112
  function getCheckpointLogPath(directory) {
53525
- return path35.join(directory, CHECKPOINT_LOG_PATH);
54113
+ return path37.join(directory, CHECKPOINT_LOG_PATH);
53526
54114
  }
53527
54115
  function readCheckpointLog(directory) {
53528
54116
  const logPath = getCheckpointLogPath(directory);
53529
54117
  try {
53530
- if (fs22.existsSync(logPath)) {
53531
- const content = fs22.readFileSync(logPath, "utf-8");
54118
+ if (fs24.existsSync(logPath)) {
54119
+ const content = fs24.readFileSync(logPath, "utf-8");
53532
54120
  const parsed = JSON.parse(content);
53533
54121
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
53534
54122
  return { version: 1, checkpoints: [] };
@@ -53540,13 +54128,13 @@ function readCheckpointLog(directory) {
53540
54128
  }
53541
54129
  function writeCheckpointLog(log2, directory) {
53542
54130
  const logPath = getCheckpointLogPath(directory);
53543
- const dir = path35.dirname(logPath);
53544
- if (!fs22.existsSync(dir)) {
53545
- fs22.mkdirSync(dir, { recursive: true });
54131
+ const dir = path37.dirname(logPath);
54132
+ if (!fs24.existsSync(dir)) {
54133
+ fs24.mkdirSync(dir, { recursive: true });
53546
54134
  }
53547
54135
  const tempPath = `${logPath}.tmp`;
53548
- fs22.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
53549
- fs22.renameSync(tempPath, logPath);
54136
+ fs24.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
54137
+ fs24.renameSync(tempPath, logPath);
53550
54138
  }
53551
54139
  function gitExec(args2) {
53552
54140
  const result = spawnSync("git", args2, {
@@ -53747,8 +54335,8 @@ var checkpoint = createSwarmTool({
53747
54335
  // src/tools/complexity-hotspots.ts
53748
54336
  init_dist();
53749
54337
  init_create_tool();
53750
- import * as fs23 from "fs";
53751
- import * as path36 from "path";
54338
+ import * as fs25 from "fs";
54339
+ import * as path38 from "path";
53752
54340
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
53753
54341
  var DEFAULT_DAYS = 90;
53754
54342
  var DEFAULT_TOP_N = 20;
@@ -53877,11 +54465,11 @@ function estimateComplexity(content) {
53877
54465
  }
53878
54466
  function getComplexityForFile(filePath) {
53879
54467
  try {
53880
- const stat2 = fs23.statSync(filePath);
54468
+ const stat2 = fs25.statSync(filePath);
53881
54469
  if (stat2.size > MAX_FILE_SIZE_BYTES2) {
53882
54470
  return null;
53883
54471
  }
53884
- const content = fs23.readFileSync(filePath, "utf-8");
54472
+ const content = fs25.readFileSync(filePath, "utf-8");
53885
54473
  return estimateComplexity(content);
53886
54474
  } catch {
53887
54475
  return null;
@@ -53892,7 +54480,7 @@ async function analyzeHotspots(days, topN, extensions, directory) {
53892
54480
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
53893
54481
  const filteredChurn = new Map;
53894
54482
  for (const [file3, count] of churnMap) {
53895
- const ext = path36.extname(file3).toLowerCase();
54483
+ const ext = path38.extname(file3).toLowerCase();
53896
54484
  if (extSet.has(ext)) {
53897
54485
  filteredChurn.set(file3, count);
53898
54486
  }
@@ -53902,8 +54490,8 @@ async function analyzeHotspots(days, topN, extensions, directory) {
53902
54490
  let analyzedFiles = 0;
53903
54491
  for (const [file3, churnCount] of filteredChurn) {
53904
54492
  let fullPath = file3;
53905
- if (!fs23.existsSync(fullPath)) {
53906
- fullPath = path36.join(cwd, file3);
54493
+ if (!fs25.existsSync(fullPath)) {
54494
+ fullPath = path38.join(cwd, file3);
53907
54495
  }
53908
54496
  const complexity = getComplexityForFile(fullPath);
53909
54497
  if (complexity !== null) {
@@ -54050,8 +54638,8 @@ var complexity_hotspots = createSwarmTool({
54050
54638
  });
54051
54639
  // src/tools/declare-scope.ts
54052
54640
  init_tool();
54053
- import * as fs24 from "fs";
54054
- import * as path37 from "path";
54641
+ import * as fs26 from "fs";
54642
+ import * as path39 from "path";
54055
54643
  init_create_tool();
54056
54644
  function validateTaskIdFormat(taskId) {
54057
54645
  const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
@@ -54130,8 +54718,8 @@ async function executeDeclareScope(args2, fallbackDir) {
54130
54718
  };
54131
54719
  }
54132
54720
  }
54133
- normalizedDir = path37.normalize(args2.working_directory);
54134
- const pathParts = normalizedDir.split(path37.sep);
54721
+ normalizedDir = path39.normalize(args2.working_directory);
54722
+ const pathParts = normalizedDir.split(path39.sep);
54135
54723
  if (pathParts.includes("..")) {
54136
54724
  return {
54137
54725
  success: false,
@@ -54141,11 +54729,11 @@ async function executeDeclareScope(args2, fallbackDir) {
54141
54729
  ]
54142
54730
  };
54143
54731
  }
54144
- const resolvedDir = path37.resolve(normalizedDir);
54732
+ const resolvedDir = path39.resolve(normalizedDir);
54145
54733
  try {
54146
- const realPath = fs24.realpathSync(resolvedDir);
54147
- const planPath2 = path37.join(realPath, ".swarm", "plan.json");
54148
- if (!fs24.existsSync(planPath2)) {
54734
+ const realPath = fs26.realpathSync(resolvedDir);
54735
+ const planPath2 = path39.join(realPath, ".swarm", "plan.json");
54736
+ if (!fs26.existsSync(planPath2)) {
54149
54737
  return {
54150
54738
  success: false,
54151
54739
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -54165,8 +54753,8 @@ async function executeDeclareScope(args2, fallbackDir) {
54165
54753
  }
54166
54754
  }
54167
54755
  const directory = normalizedDir ?? fallbackDir ?? process.cwd();
54168
- const planPath = path37.resolve(directory, ".swarm", "plan.json");
54169
- if (!fs24.existsSync(planPath)) {
54756
+ const planPath = path39.resolve(directory, ".swarm", "plan.json");
54757
+ if (!fs26.existsSync(planPath)) {
54170
54758
  return {
54171
54759
  success: false,
54172
54760
  message: "No plan found",
@@ -54175,7 +54763,7 @@ async function executeDeclareScope(args2, fallbackDir) {
54175
54763
  }
54176
54764
  let planContent;
54177
54765
  try {
54178
- planContent = JSON.parse(fs24.readFileSync(planPath, "utf-8"));
54766
+ planContent = JSON.parse(fs26.readFileSync(planPath, "utf-8"));
54179
54767
  } catch {
54180
54768
  return {
54181
54769
  success: false,
@@ -54255,20 +54843,20 @@ function validateBase(base) {
54255
54843
  function validatePaths(paths) {
54256
54844
  if (!paths)
54257
54845
  return null;
54258
- for (const path38 of paths) {
54259
- if (!path38 || path38.length === 0) {
54846
+ for (const path40 of paths) {
54847
+ if (!path40 || path40.length === 0) {
54260
54848
  return "empty path not allowed";
54261
54849
  }
54262
- if (path38.length > MAX_PATH_LENGTH) {
54850
+ if (path40.length > MAX_PATH_LENGTH) {
54263
54851
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
54264
54852
  }
54265
- if (SHELL_METACHARACTERS2.test(path38)) {
54853
+ if (SHELL_METACHARACTERS2.test(path40)) {
54266
54854
  return "path contains shell metacharacters";
54267
54855
  }
54268
- if (path38.startsWith("-")) {
54856
+ if (path40.startsWith("-")) {
54269
54857
  return 'path cannot start with "-" (option-like arguments not allowed)';
54270
54858
  }
54271
- if (CONTROL_CHAR_PATTERN2.test(path38)) {
54859
+ if (CONTROL_CHAR_PATTERN2.test(path40)) {
54272
54860
  return "path contains control characters";
54273
54861
  }
54274
54862
  }
@@ -54348,8 +54936,8 @@ var diff = tool({
54348
54936
  if (parts2.length >= 3) {
54349
54937
  const additions = parseInt(parts2[0], 10) || 0;
54350
54938
  const deletions = parseInt(parts2[1], 10) || 0;
54351
- const path38 = parts2[2];
54352
- files.push({ path: path38, additions, deletions });
54939
+ const path40 = parts2[2];
54940
+ files.push({ path: path40, additions, deletions });
54353
54941
  }
54354
54942
  }
54355
54943
  const contractChanges = [];
@@ -54578,8 +55166,8 @@ Use these as DOMAIN values when delegating to @sme.`;
54578
55166
  // src/tools/evidence-check.ts
54579
55167
  init_dist();
54580
55168
  init_create_tool();
54581
- import * as fs25 from "fs";
54582
- import * as path38 from "path";
55169
+ import * as fs27 from "fs";
55170
+ import * as path40 from "path";
54583
55171
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
54584
55172
  var MAX_EVIDENCE_FILES = 1000;
54585
55173
  var EVIDENCE_DIR2 = ".swarm/evidence";
@@ -54609,9 +55197,9 @@ function validateRequiredTypes(input) {
54609
55197
  return null;
54610
55198
  }
54611
55199
  function isPathWithinSwarm2(filePath, cwd) {
54612
- const normalizedCwd = path38.resolve(cwd);
54613
- const swarmPath = path38.join(normalizedCwd, ".swarm");
54614
- const normalizedPath = path38.resolve(filePath);
55200
+ const normalizedCwd = path40.resolve(cwd);
55201
+ const swarmPath = path40.join(normalizedCwd, ".swarm");
55202
+ const normalizedPath = path40.resolve(filePath);
54615
55203
  return normalizedPath.startsWith(swarmPath);
54616
55204
  }
54617
55205
  function parseCompletedTasks(planContent) {
@@ -54627,12 +55215,12 @@ function parseCompletedTasks(planContent) {
54627
55215
  }
54628
55216
  function readEvidenceFiles(evidenceDir, _cwd) {
54629
55217
  const evidence = [];
54630
- if (!fs25.existsSync(evidenceDir) || !fs25.statSync(evidenceDir).isDirectory()) {
55218
+ if (!fs27.existsSync(evidenceDir) || !fs27.statSync(evidenceDir).isDirectory()) {
54631
55219
  return evidence;
54632
55220
  }
54633
55221
  let files;
54634
55222
  try {
54635
- files = fs25.readdirSync(evidenceDir);
55223
+ files = fs27.readdirSync(evidenceDir);
54636
55224
  } catch {
54637
55225
  return evidence;
54638
55226
  }
@@ -54641,14 +55229,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54641
55229
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
54642
55230
  continue;
54643
55231
  }
54644
- const filePath = path38.join(evidenceDir, filename);
55232
+ const filePath = path40.join(evidenceDir, filename);
54645
55233
  try {
54646
- const resolvedPath = path38.resolve(filePath);
54647
- const evidenceDirResolved = path38.resolve(evidenceDir);
55234
+ const resolvedPath = path40.resolve(filePath);
55235
+ const evidenceDirResolved = path40.resolve(evidenceDir);
54648
55236
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
54649
55237
  continue;
54650
55238
  }
54651
- const stat2 = fs25.lstatSync(filePath);
55239
+ const stat2 = fs27.lstatSync(filePath);
54652
55240
  if (!stat2.isFile()) {
54653
55241
  continue;
54654
55242
  }
@@ -54657,7 +55245,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54657
55245
  }
54658
55246
  let fileStat;
54659
55247
  try {
54660
- fileStat = fs25.statSync(filePath);
55248
+ fileStat = fs27.statSync(filePath);
54661
55249
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
54662
55250
  continue;
54663
55251
  }
@@ -54666,7 +55254,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54666
55254
  }
54667
55255
  let content;
54668
55256
  try {
54669
- content = fs25.readFileSync(filePath, "utf-8");
55257
+ content = fs27.readFileSync(filePath, "utf-8");
54670
55258
  } catch {
54671
55259
  continue;
54672
55260
  }
@@ -54762,7 +55350,7 @@ var evidence_check = createSwarmTool({
54762
55350
  return JSON.stringify(errorResult, null, 2);
54763
55351
  }
54764
55352
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
54765
- const planPath = path38.join(cwd, PLAN_FILE);
55353
+ const planPath = path40.join(cwd, PLAN_FILE);
54766
55354
  if (!isPathWithinSwarm2(planPath, cwd)) {
54767
55355
  const errorResult = {
54768
55356
  error: "plan file path validation failed",
@@ -54776,7 +55364,7 @@ var evidence_check = createSwarmTool({
54776
55364
  }
54777
55365
  let planContent;
54778
55366
  try {
54779
- planContent = fs25.readFileSync(planPath, "utf-8");
55367
+ planContent = fs27.readFileSync(planPath, "utf-8");
54780
55368
  } catch {
54781
55369
  const result2 = {
54782
55370
  message: "No completed tasks found in plan.",
@@ -54794,7 +55382,7 @@ var evidence_check = createSwarmTool({
54794
55382
  };
54795
55383
  return JSON.stringify(result2, null, 2);
54796
55384
  }
54797
- const evidenceDir = path38.join(cwd, EVIDENCE_DIR2);
55385
+ const evidenceDir = path40.join(cwd, EVIDENCE_DIR2);
54798
55386
  const evidence = readEvidenceFiles(evidenceDir, cwd);
54799
55387
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
54800
55388
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -54811,8 +55399,8 @@ var evidence_check = createSwarmTool({
54811
55399
  // src/tools/file-extractor.ts
54812
55400
  init_tool();
54813
55401
  init_create_tool();
54814
- import * as fs26 from "fs";
54815
- import * as path39 from "path";
55402
+ import * as fs28 from "fs";
55403
+ import * as path41 from "path";
54816
55404
  var EXT_MAP = {
54817
55405
  python: ".py",
54818
55406
  py: ".py",
@@ -54874,8 +55462,8 @@ var extract_code_blocks = createSwarmTool({
54874
55462
  execute: async (args2, directory) => {
54875
55463
  const { content, output_dir, prefix } = args2;
54876
55464
  const targetDir = output_dir || directory;
54877
- if (!fs26.existsSync(targetDir)) {
54878
- fs26.mkdirSync(targetDir, { recursive: true });
55465
+ if (!fs28.existsSync(targetDir)) {
55466
+ fs28.mkdirSync(targetDir, { recursive: true });
54879
55467
  }
54880
55468
  if (!content) {
54881
55469
  return "Error: content is required";
@@ -54893,16 +55481,16 @@ var extract_code_blocks = createSwarmTool({
54893
55481
  if (prefix) {
54894
55482
  filename = `${prefix}_${filename}`;
54895
55483
  }
54896
- let filepath = path39.join(targetDir, filename);
54897
- const base = path39.basename(filepath, path39.extname(filepath));
54898
- const ext = path39.extname(filepath);
55484
+ let filepath = path41.join(targetDir, filename);
55485
+ const base = path41.basename(filepath, path41.extname(filepath));
55486
+ const ext = path41.extname(filepath);
54899
55487
  let counter = 1;
54900
- while (fs26.existsSync(filepath)) {
54901
- filepath = path39.join(targetDir, `${base}_${counter}${ext}`);
55488
+ while (fs28.existsSync(filepath)) {
55489
+ filepath = path41.join(targetDir, `${base}_${counter}${ext}`);
54902
55490
  counter++;
54903
55491
  }
54904
55492
  try {
54905
- fs26.writeFileSync(filepath, code.trim(), "utf-8");
55493
+ fs28.writeFileSync(filepath, code.trim(), "utf-8");
54906
55494
  savedFiles.push(filepath);
54907
55495
  } catch (error93) {
54908
55496
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -55015,8 +55603,8 @@ var gitingest = tool({
55015
55603
  });
55016
55604
  // src/tools/imports.ts
55017
55605
  init_dist();
55018
- import * as fs27 from "fs";
55019
- import * as path40 from "path";
55606
+ import * as fs29 from "fs";
55607
+ import * as path42 from "path";
55020
55608
  var MAX_FILE_PATH_LENGTH2 = 500;
55021
55609
  var MAX_SYMBOL_LENGTH = 256;
55022
55610
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -55070,7 +55658,7 @@ function validateSymbolInput(symbol3) {
55070
55658
  return null;
55071
55659
  }
55072
55660
  function isBinaryFile2(filePath, buffer) {
55073
- const ext = path40.extname(filePath).toLowerCase();
55661
+ const ext = path42.extname(filePath).toLowerCase();
55074
55662
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
55075
55663
  return false;
55076
55664
  }
@@ -55094,15 +55682,15 @@ function parseImports(content, targetFile, targetSymbol) {
55094
55682
  const imports = [];
55095
55683
  let _resolvedTarget;
55096
55684
  try {
55097
- _resolvedTarget = path40.resolve(targetFile);
55685
+ _resolvedTarget = path42.resolve(targetFile);
55098
55686
  } catch {
55099
55687
  _resolvedTarget = targetFile;
55100
55688
  }
55101
- const targetBasename = path40.basename(targetFile, path40.extname(targetFile));
55689
+ const targetBasename = path42.basename(targetFile, path42.extname(targetFile));
55102
55690
  const targetWithExt = targetFile;
55103
55691
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
55104
- const normalizedTargetWithExt = path40.normalize(targetWithExt).replace(/\\/g, "/");
55105
- const normalizedTargetWithoutExt = path40.normalize(targetWithoutExt).replace(/\\/g, "/");
55692
+ const normalizedTargetWithExt = path42.normalize(targetWithExt).replace(/\\/g, "/");
55693
+ const normalizedTargetWithoutExt = path42.normalize(targetWithoutExt).replace(/\\/g, "/");
55106
55694
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
55107
55695
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
55108
55696
  const modulePath = match[1] || match[2] || match[3];
@@ -55125,9 +55713,9 @@ function parseImports(content, targetFile, targetSymbol) {
55125
55713
  }
55126
55714
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
55127
55715
  let isMatch = false;
55128
- const _targetDir = path40.dirname(targetFile);
55129
- const targetExt = path40.extname(targetFile);
55130
- const targetBasenameNoExt = path40.basename(targetFile, targetExt);
55716
+ const _targetDir = path42.dirname(targetFile);
55717
+ const targetExt = path42.extname(targetFile);
55718
+ const targetBasenameNoExt = path42.basename(targetFile, targetExt);
55131
55719
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
55132
55720
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
55133
55721
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -55184,7 +55772,7 @@ var SKIP_DIRECTORIES2 = new Set([
55184
55772
  function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
55185
55773
  let entries;
55186
55774
  try {
55187
- entries = fs27.readdirSync(dir);
55775
+ entries = fs29.readdirSync(dir);
55188
55776
  } catch (e) {
55189
55777
  stats.fileErrors.push({
55190
55778
  path: dir,
@@ -55195,13 +55783,13 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
55195
55783
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
55196
55784
  for (const entry of entries) {
55197
55785
  if (SKIP_DIRECTORIES2.has(entry)) {
55198
- stats.skippedDirs.push(path40.join(dir, entry));
55786
+ stats.skippedDirs.push(path42.join(dir, entry));
55199
55787
  continue;
55200
55788
  }
55201
- const fullPath = path40.join(dir, entry);
55789
+ const fullPath = path42.join(dir, entry);
55202
55790
  let stat2;
55203
55791
  try {
55204
- stat2 = fs27.statSync(fullPath);
55792
+ stat2 = fs29.statSync(fullPath);
55205
55793
  } catch (e) {
55206
55794
  stats.fileErrors.push({
55207
55795
  path: fullPath,
@@ -55212,7 +55800,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
55212
55800
  if (stat2.isDirectory()) {
55213
55801
  findSourceFiles(fullPath, files, stats);
55214
55802
  } else if (stat2.isFile()) {
55215
- const ext = path40.extname(fullPath).toLowerCase();
55803
+ const ext = path42.extname(fullPath).toLowerCase();
55216
55804
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
55217
55805
  files.push(fullPath);
55218
55806
  }
@@ -55268,8 +55856,8 @@ var imports = tool({
55268
55856
  return JSON.stringify(errorResult, null, 2);
55269
55857
  }
55270
55858
  try {
55271
- const targetFile = path40.resolve(file3);
55272
- if (!fs27.existsSync(targetFile)) {
55859
+ const targetFile = path42.resolve(file3);
55860
+ if (!fs29.existsSync(targetFile)) {
55273
55861
  const errorResult = {
55274
55862
  error: `target file not found: ${file3}`,
55275
55863
  target: file3,
@@ -55279,7 +55867,7 @@ var imports = tool({
55279
55867
  };
55280
55868
  return JSON.stringify(errorResult, null, 2);
55281
55869
  }
55282
- const targetStat = fs27.statSync(targetFile);
55870
+ const targetStat = fs29.statSync(targetFile);
55283
55871
  if (!targetStat.isFile()) {
55284
55872
  const errorResult = {
55285
55873
  error: "target must be a file, not a directory",
@@ -55290,7 +55878,7 @@ var imports = tool({
55290
55878
  };
55291
55879
  return JSON.stringify(errorResult, null, 2);
55292
55880
  }
55293
- const baseDir = path40.dirname(targetFile);
55881
+ const baseDir = path42.dirname(targetFile);
55294
55882
  const scanStats = {
55295
55883
  skippedDirs: [],
55296
55884
  skippedFiles: 0,
@@ -55305,12 +55893,12 @@ var imports = tool({
55305
55893
  if (consumers.length >= MAX_CONSUMERS)
55306
55894
  break;
55307
55895
  try {
55308
- const stat2 = fs27.statSync(filePath);
55896
+ const stat2 = fs29.statSync(filePath);
55309
55897
  if (stat2.size > MAX_FILE_SIZE_BYTES4) {
55310
55898
  skippedFileCount++;
55311
55899
  continue;
55312
55900
  }
55313
- const buffer = fs27.readFileSync(filePath);
55901
+ const buffer = fs29.readFileSync(filePath);
55314
55902
  if (isBinaryFile2(filePath, buffer)) {
55315
55903
  skippedFileCount++;
55316
55904
  continue;
@@ -55375,7 +55963,7 @@ var imports = tool({
55375
55963
  });
55376
55964
  // src/tools/knowledge-query.ts
55377
55965
  init_dist();
55378
- import { existsSync as existsSync26 } from "fs";
55966
+ import { existsSync as existsSync27 } from "fs";
55379
55967
  init_create_tool();
55380
55968
  var DEFAULT_LIMIT = 10;
55381
55969
  var MAX_LESSON_LENGTH = 200;
@@ -55445,14 +56033,14 @@ function validateLimit(limit) {
55445
56033
  }
55446
56034
  async function readSwarmKnowledge(directory) {
55447
56035
  const swarmPath = resolveSwarmKnowledgePath(directory);
55448
- if (!existsSync26(swarmPath)) {
56036
+ if (!existsSync27(swarmPath)) {
55449
56037
  return [];
55450
56038
  }
55451
56039
  return readKnowledge(swarmPath);
55452
56040
  }
55453
56041
  async function readHiveKnowledge() {
55454
56042
  const hivePath = resolveHiveKnowledgePath();
55455
- if (!existsSync26(hivePath)) {
56043
+ if (!existsSync27(hivePath)) {
55456
56044
  return [];
55457
56045
  }
55458
56046
  return readKnowledge(hivePath);
@@ -55611,8 +56199,8 @@ init_dist();
55611
56199
  init_config();
55612
56200
  init_schema();
55613
56201
  init_manager();
55614
- import * as fs28 from "fs";
55615
- import * as path41 from "path";
56202
+ import * as fs30 from "fs";
56203
+ import * as path43 from "path";
55616
56204
  init_utils2();
55617
56205
  init_create_tool();
55618
56206
  function safeWarn(message, error93) {
@@ -55807,7 +56395,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55807
56395
  }
55808
56396
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
55809
56397
  try {
55810
- const projectName = path41.basename(dir);
56398
+ const projectName = path43.basename(dir);
55811
56399
  const knowledgeConfig = {
55812
56400
  enabled: true,
55813
56401
  swarm_max_entries: 100,
@@ -55855,7 +56443,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55855
56443
  if (agentsMissing.length > 0) {
55856
56444
  try {
55857
56445
  const planPath = validateSwarmPath(dir, "plan.json");
55858
- const planRaw = fs28.readFileSync(planPath, "utf-8");
56446
+ const planRaw = fs30.readFileSync(planPath, "utf-8");
55859
56447
  const plan = JSON.parse(planRaw);
55860
56448
  const targetPhase = plan.phases.find((p) => p.id === phase);
55861
56449
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -55896,7 +56484,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55896
56484
  };
55897
56485
  try {
55898
56486
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
55899
- fs28.appendFileSync(eventsPath, `${JSON.stringify(event)}
56487
+ fs30.appendFileSync(eventsPath, `${JSON.stringify(event)}
55900
56488
  `, "utf-8");
55901
56489
  } catch (writeError) {
55902
56490
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -55915,12 +56503,12 @@ async function executePhaseComplete(args2, workingDirectory) {
55915
56503
  }
55916
56504
  try {
55917
56505
  const planPath = validateSwarmPath(dir, "plan.json");
55918
- const planJson = fs28.readFileSync(planPath, "utf-8");
56506
+ const planJson = fs30.readFileSync(planPath, "utf-8");
55919
56507
  const plan = JSON.parse(planJson);
55920
56508
  const phaseObj = plan.phases.find((p) => p.id === phase);
55921
56509
  if (phaseObj) {
55922
56510
  phaseObj.status = "completed";
55923
- fs28.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
56511
+ fs30.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
55924
56512
  `, "utf-8");
55925
56513
  }
55926
56514
  } catch (error93) {
@@ -55970,8 +56558,8 @@ init_dist();
55970
56558
  init_discovery();
55971
56559
  init_utils();
55972
56560
  init_create_tool();
55973
- import * as fs29 from "fs";
55974
- import * as path42 from "path";
56561
+ import * as fs31 from "fs";
56562
+ import * as path44 from "path";
55975
56563
  var MAX_OUTPUT_BYTES5 = 52428800;
55976
56564
  var AUDIT_TIMEOUT_MS = 120000;
55977
56565
  function isValidEcosystem(value) {
@@ -55989,28 +56577,28 @@ function validateArgs3(args2) {
55989
56577
  function detectEcosystems(directory) {
55990
56578
  const ecosystems = [];
55991
56579
  const cwd = directory;
55992
- if (fs29.existsSync(path42.join(cwd, "package.json"))) {
56580
+ if (fs31.existsSync(path44.join(cwd, "package.json"))) {
55993
56581
  ecosystems.push("npm");
55994
56582
  }
55995
- if (fs29.existsSync(path42.join(cwd, "pyproject.toml")) || fs29.existsSync(path42.join(cwd, "requirements.txt"))) {
56583
+ if (fs31.existsSync(path44.join(cwd, "pyproject.toml")) || fs31.existsSync(path44.join(cwd, "requirements.txt"))) {
55996
56584
  ecosystems.push("pip");
55997
56585
  }
55998
- if (fs29.existsSync(path42.join(cwd, "Cargo.toml"))) {
56586
+ if (fs31.existsSync(path44.join(cwd, "Cargo.toml"))) {
55999
56587
  ecosystems.push("cargo");
56000
56588
  }
56001
- if (fs29.existsSync(path42.join(cwd, "go.mod"))) {
56589
+ if (fs31.existsSync(path44.join(cwd, "go.mod"))) {
56002
56590
  ecosystems.push("go");
56003
56591
  }
56004
56592
  try {
56005
- const files = fs29.readdirSync(cwd);
56593
+ const files = fs31.readdirSync(cwd);
56006
56594
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
56007
56595
  ecosystems.push("dotnet");
56008
56596
  }
56009
56597
  } catch {}
56010
- if (fs29.existsSync(path42.join(cwd, "Gemfile")) || fs29.existsSync(path42.join(cwd, "Gemfile.lock"))) {
56598
+ if (fs31.existsSync(path44.join(cwd, "Gemfile")) || fs31.existsSync(path44.join(cwd, "Gemfile.lock"))) {
56011
56599
  ecosystems.push("ruby");
56012
56600
  }
56013
- if (fs29.existsSync(path42.join(cwd, "pubspec.yaml"))) {
56601
+ if (fs31.existsSync(path44.join(cwd, "pubspec.yaml"))) {
56014
56602
  ecosystems.push("dart");
56015
56603
  }
56016
56604
  return ecosystems;
@@ -57072,8 +57660,8 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
57072
57660
  ]);
57073
57661
  // src/tools/pre-check-batch.ts
57074
57662
  init_dist();
57075
- import * as fs32 from "fs";
57076
- import * as path45 from "path";
57663
+ import * as fs34 from "fs";
57664
+ import * as path47 from "path";
57077
57665
 
57078
57666
  // node_modules/yocto-queue/index.js
57079
57667
  class Node2 {
@@ -57240,8 +57828,8 @@ init_lint();
57240
57828
  init_manager();
57241
57829
 
57242
57830
  // src/quality/metrics.ts
57243
- import * as fs30 from "fs";
57244
- import * as path43 from "path";
57831
+ import * as fs32 from "fs";
57832
+ import * as path45 from "path";
57245
57833
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
57246
57834
  var MIN_DUPLICATION_LINES = 10;
57247
57835
  function estimateCyclomaticComplexity(content) {
@@ -57279,11 +57867,11 @@ function estimateCyclomaticComplexity(content) {
57279
57867
  }
57280
57868
  function getComplexityForFile2(filePath) {
57281
57869
  try {
57282
- const stat2 = fs30.statSync(filePath);
57870
+ const stat2 = fs32.statSync(filePath);
57283
57871
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
57284
57872
  return null;
57285
57873
  }
57286
- const content = fs30.readFileSync(filePath, "utf-8");
57874
+ const content = fs32.readFileSync(filePath, "utf-8");
57287
57875
  return estimateCyclomaticComplexity(content);
57288
57876
  } catch {
57289
57877
  return null;
@@ -57293,8 +57881,8 @@ async function computeComplexityDelta(files, workingDir) {
57293
57881
  let totalComplexity = 0;
57294
57882
  const analyzedFiles = [];
57295
57883
  for (const file3 of files) {
57296
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57297
- if (!fs30.existsSync(fullPath)) {
57884
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
57885
+ if (!fs32.existsSync(fullPath)) {
57298
57886
  continue;
57299
57887
  }
57300
57888
  const complexity = getComplexityForFile2(fullPath);
@@ -57415,8 +58003,8 @@ function countGoExports(content) {
57415
58003
  }
57416
58004
  function getExportCountForFile(filePath) {
57417
58005
  try {
57418
- const content = fs30.readFileSync(filePath, "utf-8");
57419
- const ext = path43.extname(filePath).toLowerCase();
58006
+ const content = fs32.readFileSync(filePath, "utf-8");
58007
+ const ext = path45.extname(filePath).toLowerCase();
57420
58008
  switch (ext) {
57421
58009
  case ".ts":
57422
58010
  case ".tsx":
@@ -57442,8 +58030,8 @@ async function computePublicApiDelta(files, workingDir) {
57442
58030
  let totalExports = 0;
57443
58031
  const analyzedFiles = [];
57444
58032
  for (const file3 of files) {
57445
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57446
- if (!fs30.existsSync(fullPath)) {
58033
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
58034
+ if (!fs32.existsSync(fullPath)) {
57447
58035
  continue;
57448
58036
  }
57449
58037
  const exports = getExportCountForFile(fullPath);
@@ -57476,16 +58064,16 @@ async function computeDuplicationRatio(files, workingDir) {
57476
58064
  let duplicateLines = 0;
57477
58065
  const analyzedFiles = [];
57478
58066
  for (const file3 of files) {
57479
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57480
- if (!fs30.existsSync(fullPath)) {
58067
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
58068
+ if (!fs32.existsSync(fullPath)) {
57481
58069
  continue;
57482
58070
  }
57483
58071
  try {
57484
- const stat2 = fs30.statSync(fullPath);
58072
+ const stat2 = fs32.statSync(fullPath);
57485
58073
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
57486
58074
  continue;
57487
58075
  }
57488
- const content = fs30.readFileSync(fullPath, "utf-8");
58076
+ const content = fs32.readFileSync(fullPath, "utf-8");
57489
58077
  const lines = content.split(`
57490
58078
  `).filter((line) => line.trim().length > 0);
57491
58079
  if (lines.length < MIN_DUPLICATION_LINES) {
@@ -57509,8 +58097,8 @@ function countCodeLines(content) {
57509
58097
  return lines.length;
57510
58098
  }
57511
58099
  function isTestFile(filePath) {
57512
- const basename8 = path43.basename(filePath);
57513
- const _ext = path43.extname(filePath).toLowerCase();
58100
+ const basename8 = path45.basename(filePath);
58101
+ const _ext = path45.extname(filePath).toLowerCase();
57514
58102
  const testPatterns = [
57515
58103
  ".test.",
57516
58104
  ".spec.",
@@ -57591,8 +58179,8 @@ function matchGlobSegment(globSegments, pathSegments) {
57591
58179
  }
57592
58180
  return gIndex === globSegments.length && pIndex === pathSegments.length;
57593
58181
  }
57594
- function matchesGlobSegment(path44, glob) {
57595
- const normalizedPath = path44.replace(/\\/g, "/");
58182
+ function matchesGlobSegment(path46, glob) {
58183
+ const normalizedPath = path46.replace(/\\/g, "/");
57596
58184
  const normalizedGlob = glob.replace(/\\/g, "/");
57597
58185
  if (normalizedPath.includes("//")) {
57598
58186
  return false;
@@ -57623,8 +58211,8 @@ function simpleGlobToRegex2(glob) {
57623
58211
  function hasGlobstar(glob) {
57624
58212
  return glob.includes("**");
57625
58213
  }
57626
- function globMatches(path44, glob) {
57627
- const normalizedPath = path44.replace(/\\/g, "/");
58214
+ function globMatches(path46, glob) {
58215
+ const normalizedPath = path46.replace(/\\/g, "/");
57628
58216
  if (!glob || glob === "") {
57629
58217
  if (normalizedPath.includes("//")) {
57630
58218
  return false;
@@ -57660,31 +58248,31 @@ function shouldExcludeFile(filePath, excludeGlobs) {
57660
58248
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
57661
58249
  let testLines = 0;
57662
58250
  let codeLines = 0;
57663
- const srcDir = path43.join(workingDir, "src");
57664
- if (fs30.existsSync(srcDir)) {
58251
+ const srcDir = path45.join(workingDir, "src");
58252
+ if (fs32.existsSync(srcDir)) {
57665
58253
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
57666
58254
  codeLines += lines;
57667
58255
  });
57668
58256
  }
57669
58257
  const possibleSrcDirs = ["lib", "app", "source", "core"];
57670
58258
  for (const dir of possibleSrcDirs) {
57671
- const dirPath = path43.join(workingDir, dir);
57672
- if (fs30.existsSync(dirPath)) {
58259
+ const dirPath = path45.join(workingDir, dir);
58260
+ if (fs32.existsSync(dirPath)) {
57673
58261
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
57674
58262
  codeLines += lines;
57675
58263
  });
57676
58264
  }
57677
58265
  }
57678
- const testsDir = path43.join(workingDir, "tests");
57679
- if (fs30.existsSync(testsDir)) {
58266
+ const testsDir = path45.join(workingDir, "tests");
58267
+ if (fs32.existsSync(testsDir)) {
57680
58268
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
57681
58269
  testLines += lines;
57682
58270
  });
57683
58271
  }
57684
58272
  const possibleTestDirs = ["test", "__tests__", "specs"];
57685
58273
  for (const dir of possibleTestDirs) {
57686
- const dirPath = path43.join(workingDir, dir);
57687
- if (fs30.existsSync(dirPath) && dirPath !== testsDir) {
58274
+ const dirPath = path45.join(workingDir, dir);
58275
+ if (fs32.existsSync(dirPath) && dirPath !== testsDir) {
57688
58276
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
57689
58277
  testLines += lines;
57690
58278
  });
@@ -57696,9 +58284,9 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
57696
58284
  }
57697
58285
  async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
57698
58286
  try {
57699
- const entries = fs30.readdirSync(dirPath, { withFileTypes: true });
58287
+ const entries = fs32.readdirSync(dirPath, { withFileTypes: true });
57700
58288
  for (const entry of entries) {
57701
- const fullPath = path43.join(dirPath, entry.name);
58289
+ const fullPath = path45.join(dirPath, entry.name);
57702
58290
  if (entry.isDirectory()) {
57703
58291
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
57704
58292
  continue;
@@ -57706,7 +58294,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
57706
58294
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
57707
58295
  } else if (entry.isFile()) {
57708
58296
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
57709
- const ext = path43.extname(entry.name).toLowerCase();
58297
+ const ext = path45.extname(entry.name).toLowerCase();
57710
58298
  const validExts = [
57711
58299
  ".ts",
57712
58300
  ".tsx",
@@ -57742,7 +58330,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
57742
58330
  continue;
57743
58331
  }
57744
58332
  try {
57745
- const content = fs30.readFileSync(fullPath, "utf-8");
58333
+ const content = fs32.readFileSync(fullPath, "utf-8");
57746
58334
  const lines = countCodeLines(content);
57747
58335
  callback(lines);
57748
58336
  } catch {}
@@ -57956,8 +58544,8 @@ async function qualityBudget(input, directory) {
57956
58544
  init_dist();
57957
58545
  init_manager();
57958
58546
  init_detector();
57959
- import * as fs31 from "fs";
57960
- import * as path44 from "path";
58547
+ import * as fs33 from "fs";
58548
+ import * as path46 from "path";
57961
58549
  import { extname as extname9 } from "path";
57962
58550
 
57963
58551
  // src/sast/rules/c.ts
@@ -58824,17 +59412,17 @@ var SEVERITY_ORDER = {
58824
59412
  };
58825
59413
  function shouldSkipFile(filePath) {
58826
59414
  try {
58827
- const stats = fs31.statSync(filePath);
59415
+ const stats = fs33.statSync(filePath);
58828
59416
  if (stats.size > MAX_FILE_SIZE_BYTES6) {
58829
59417
  return { skip: true, reason: "file too large" };
58830
59418
  }
58831
59419
  if (stats.size === 0) {
58832
59420
  return { skip: true, reason: "empty file" };
58833
59421
  }
58834
- const fd = fs31.openSync(filePath, "r");
59422
+ const fd = fs33.openSync(filePath, "r");
58835
59423
  const buffer = Buffer.alloc(8192);
58836
- const bytesRead = fs31.readSync(fd, buffer, 0, 8192, 0);
58837
- fs31.closeSync(fd);
59424
+ const bytesRead = fs33.readSync(fd, buffer, 0, 8192, 0);
59425
+ fs33.closeSync(fd);
58838
59426
  if (bytesRead > 0) {
58839
59427
  let nullCount = 0;
58840
59428
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -58873,7 +59461,7 @@ function countBySeverity(findings) {
58873
59461
  }
58874
59462
  function scanFileWithTierA(filePath, language) {
58875
59463
  try {
58876
- const content = fs31.readFileSync(filePath, "utf-8");
59464
+ const content = fs33.readFileSync(filePath, "utf-8");
58877
59465
  const findings = executeRulesSync(filePath, content, language);
58878
59466
  return findings.map((f) => ({
58879
59467
  rule_id: f.rule_id,
@@ -58920,8 +59508,8 @@ async function sastScan(input, directory, config3) {
58920
59508
  _filesSkipped++;
58921
59509
  continue;
58922
59510
  }
58923
- const resolvedPath = path44.isAbsolute(filePath) ? filePath : path44.resolve(directory, filePath);
58924
- if (!fs31.existsSync(resolvedPath)) {
59511
+ const resolvedPath = path46.isAbsolute(filePath) ? filePath : path46.resolve(directory, filePath);
59512
+ if (!fs33.existsSync(resolvedPath)) {
58925
59513
  _filesSkipped++;
58926
59514
  continue;
58927
59515
  }
@@ -59119,18 +59707,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
59119
59707
  let resolved;
59120
59708
  const isWinAbs = isWindowsAbsolutePath(inputPath);
59121
59709
  if (isWinAbs) {
59122
- resolved = path45.win32.resolve(inputPath);
59123
- } else if (path45.isAbsolute(inputPath)) {
59124
- resolved = path45.resolve(inputPath);
59710
+ resolved = path47.win32.resolve(inputPath);
59711
+ } else if (path47.isAbsolute(inputPath)) {
59712
+ resolved = path47.resolve(inputPath);
59125
59713
  } else {
59126
- resolved = path45.resolve(baseDir, inputPath);
59714
+ resolved = path47.resolve(baseDir, inputPath);
59127
59715
  }
59128
- const workspaceResolved = path45.resolve(workspaceDir);
59716
+ const workspaceResolved = path47.resolve(workspaceDir);
59129
59717
  let relative5;
59130
59718
  if (isWinAbs) {
59131
- relative5 = path45.win32.relative(workspaceResolved, resolved);
59719
+ relative5 = path47.win32.relative(workspaceResolved, resolved);
59132
59720
  } else {
59133
- relative5 = path45.relative(workspaceResolved, resolved);
59721
+ relative5 = path47.relative(workspaceResolved, resolved);
59134
59722
  }
59135
59723
  if (relative5.startsWith("..")) {
59136
59724
  return "path traversal detected";
@@ -59150,7 +59738,7 @@ function validateDirectory3(dir, workspaceDir) {
59150
59738
  }
59151
59739
  return null;
59152
59740
  }
59153
- async function runWithTimeout(promise3, timeoutMs) {
59741
+ async function runWithTimeout2(promise3, timeoutMs) {
59154
59742
  const timeoutPromise = new Promise((_, reject) => {
59155
59743
  setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
59156
59744
  });
@@ -59159,7 +59747,7 @@ async function runWithTimeout(promise3, timeoutMs) {
59159
59747
  async function runLintWrapped(files, directory, _config) {
59160
59748
  const start2 = process.hrtime.bigint();
59161
59749
  try {
59162
- const linter = await detectAvailableLinter();
59750
+ const linter = await detectAvailableLinter(directory);
59163
59751
  if (!linter) {
59164
59752
  return {
59165
59753
  ran: false,
@@ -59175,7 +59763,7 @@ async function runLintWrapped(files, directory, _config) {
59175
59763
  duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
59176
59764
  };
59177
59765
  }
59178
- const result = await runWithTimeout(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
59766
+ const result = await runWithTimeout2(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
59179
59767
  return {
59180
59768
  ran: true,
59181
59769
  result,
@@ -59191,13 +59779,13 @@ async function runLintWrapped(files, directory, _config) {
59191
59779
  }
59192
59780
  async function runLintOnFiles(linter, files, workspaceDir) {
59193
59781
  const isWindows = process.platform === "win32";
59194
- const binDir = path45.join(workspaceDir, "node_modules", ".bin");
59782
+ const binDir = path47.join(workspaceDir, "node_modules", ".bin");
59195
59783
  const validatedFiles = [];
59196
59784
  for (const file3 of files) {
59197
59785
  if (typeof file3 !== "string") {
59198
59786
  continue;
59199
59787
  }
59200
- const resolvedPath = path45.resolve(file3);
59788
+ const resolvedPath = path47.resolve(file3);
59201
59789
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
59202
59790
  if (validationError) {
59203
59791
  continue;
@@ -59215,10 +59803,10 @@ async function runLintOnFiles(linter, files, workspaceDir) {
59215
59803
  }
59216
59804
  let command;
59217
59805
  if (linter === "biome") {
59218
- const biomeBin = isWindows ? path45.join(binDir, "biome.EXE") : path45.join(binDir, "biome");
59806
+ const biomeBin = isWindows ? path47.join(binDir, "biome.EXE") : path47.join(binDir, "biome");
59219
59807
  command = [biomeBin, "check", ...validatedFiles];
59220
59808
  } else {
59221
- const eslintBin = isWindows ? path45.join(binDir, "eslint.cmd") : path45.join(binDir, "eslint");
59809
+ const eslintBin = isWindows ? path47.join(binDir, "eslint.cmd") : path47.join(binDir, "eslint");
59222
59810
  command = [eslintBin, ...validatedFiles];
59223
59811
  }
59224
59812
  try {
@@ -59269,14 +59857,14 @@ async function runSecretscanWrapped(files, directory, _config) {
59269
59857
  const start2 = process.hrtime.bigint();
59270
59858
  try {
59271
59859
  if (files && files.length > 0) {
59272
- const result2 = await runWithTimeout(runSecretscanWithFiles(files, directory), TOOL_TIMEOUT_MS);
59860
+ const result2 = await runWithTimeout2(runSecretscanWithFiles(files, directory), TOOL_TIMEOUT_MS);
59273
59861
  return {
59274
59862
  ran: true,
59275
59863
  result: result2,
59276
59864
  duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
59277
59865
  };
59278
59866
  }
59279
- const result = await runWithTimeout(runSecretscan(directory), TOOL_TIMEOUT_MS);
59867
+ const result = await runWithTimeout2(runSecretscan(directory), TOOL_TIMEOUT_MS);
59280
59868
  return {
59281
59869
  ran: true,
59282
59870
  result,
@@ -59355,7 +59943,7 @@ async function runSecretscanWithFiles(files, directory) {
59355
59943
  skippedFiles++;
59356
59944
  continue;
59357
59945
  }
59358
- const resolvedPath = path45.resolve(file3);
59946
+ const resolvedPath = path47.resolve(file3);
59359
59947
  const validationError = validatePath(resolvedPath, directory, directory);
59360
59948
  if (validationError) {
59361
59949
  skippedFiles++;
@@ -59373,14 +59961,14 @@ async function runSecretscanWithFiles(files, directory) {
59373
59961
  };
59374
59962
  }
59375
59963
  for (const file3 of validatedFiles) {
59376
- const ext = path45.extname(file3).toLowerCase();
59964
+ const ext = path47.extname(file3).toLowerCase();
59377
59965
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
59378
59966
  skippedFiles++;
59379
59967
  continue;
59380
59968
  }
59381
59969
  let stat2;
59382
59970
  try {
59383
- stat2 = fs32.statSync(file3);
59971
+ stat2 = fs34.statSync(file3);
59384
59972
  } catch {
59385
59973
  skippedFiles++;
59386
59974
  continue;
@@ -59391,7 +59979,7 @@ async function runSecretscanWithFiles(files, directory) {
59391
59979
  }
59392
59980
  let content;
59393
59981
  try {
59394
- const buffer = fs32.readFileSync(file3);
59982
+ const buffer = fs34.readFileSync(file3);
59395
59983
  if (buffer.includes(0)) {
59396
59984
  skippedFiles++;
59397
59985
  continue;
@@ -59460,7 +60048,7 @@ async function runSecretscanWithFiles(files, directory) {
59460
60048
  async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
59461
60049
  const start2 = process.hrtime.bigint();
59462
60050
  try {
59463
- const result = await runWithTimeout(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
60051
+ const result = await runWithTimeout2(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
59464
60052
  return {
59465
60053
  ran: true,
59466
60054
  result,
@@ -59477,7 +60065,7 @@ async function runSastScanWrapped(changedFiles, directory, severityThreshold, co
59477
60065
  async function runQualityBudgetWrapped(changedFiles, directory, _config) {
59478
60066
  const start2 = process.hrtime.bigint();
59479
60067
  try {
59480
- const result = await runWithTimeout(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
60068
+ const result = await runWithTimeout2(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
59481
60069
  return {
59482
60070
  ran: true,
59483
60071
  result,
@@ -59532,7 +60120,7 @@ async function runPreCheckBatch(input, workspaceDir) {
59532
60120
  warn(`pre_check_batch: Invalid file path: ${file3}`);
59533
60121
  continue;
59534
60122
  }
59535
- changedFiles.push(path45.resolve(directory, file3));
60123
+ changedFiles.push(path47.resolve(directory, file3));
59536
60124
  }
59537
60125
  if (changedFiles.length === 0) {
59538
60126
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -59683,7 +60271,7 @@ var pre_check_batch = createSwarmTool({
59683
60271
  };
59684
60272
  return JSON.stringify(errorResult, null, 2);
59685
60273
  }
59686
- const resolvedDirectory = path45.resolve(typedArgs.directory);
60274
+ const resolvedDirectory = path47.resolve(typedArgs.directory);
59687
60275
  const workspaceAnchor = resolvedDirectory;
59688
60276
  const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
59689
60277
  if (dirError) {
@@ -59790,8 +60378,8 @@ ${paginatedContent}`;
59790
60378
  init_tool();
59791
60379
  init_manager2();
59792
60380
  init_create_tool();
59793
- import * as fs33 from "fs";
59794
- import * as path46 from "path";
60381
+ import * as fs35 from "fs";
60382
+ import * as path48 from "path";
59795
60383
  function detectPlaceholderContent(args2) {
59796
60384
  const issues = [];
59797
60385
  const placeholderPattern = /^\[\w[\w\s]*\]$/;
@@ -59895,19 +60483,19 @@ async function executeSavePlan(args2, fallbackDir) {
59895
60483
  try {
59896
60484
  await savePlan(dir, plan);
59897
60485
  try {
59898
- const markerPath = path46.join(dir, ".swarm", ".plan-write-marker");
60486
+ const markerPath = path48.join(dir, ".swarm", ".plan-write-marker");
59899
60487
  const marker = JSON.stringify({
59900
60488
  source: "save_plan",
59901
60489
  timestamp: new Date().toISOString(),
59902
60490
  phases_count: plan.phases.length,
59903
60491
  tasks_count: tasksCount
59904
60492
  });
59905
- await fs33.promises.writeFile(markerPath, marker, "utf8");
60493
+ await fs35.promises.writeFile(markerPath, marker, "utf8");
59906
60494
  } catch {}
59907
60495
  return {
59908
60496
  success: true,
59909
60497
  message: "Plan saved successfully",
59910
- plan_path: path46.join(dir, ".swarm", "plan.json"),
60498
+ plan_path: path48.join(dir, ".swarm", "plan.json"),
59911
60499
  phases_count: plan.phases.length,
59912
60500
  tasks_count: tasksCount
59913
60501
  };
@@ -59945,8 +60533,8 @@ var save_plan = createSwarmTool({
59945
60533
  // src/tools/sbom-generate.ts
59946
60534
  init_dist();
59947
60535
  init_manager();
59948
- import * as fs34 from "fs";
59949
- import * as path47 from "path";
60536
+ import * as fs36 from "fs";
60537
+ import * as path49 from "path";
59950
60538
 
59951
60539
  // src/sbom/detectors/index.ts
59952
60540
  init_utils();
@@ -60792,9 +61380,9 @@ function findManifestFiles(rootDir) {
60792
61380
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
60793
61381
  function searchDir(dir) {
60794
61382
  try {
60795
- const entries = fs34.readdirSync(dir, { withFileTypes: true });
61383
+ const entries = fs36.readdirSync(dir, { withFileTypes: true });
60796
61384
  for (const entry of entries) {
60797
- const fullPath = path47.join(dir, entry.name);
61385
+ const fullPath = path49.join(dir, entry.name);
60798
61386
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
60799
61387
  continue;
60800
61388
  }
@@ -60803,7 +61391,7 @@ function findManifestFiles(rootDir) {
60803
61391
  } else if (entry.isFile()) {
60804
61392
  for (const pattern of patterns) {
60805
61393
  if (simpleGlobToRegex(pattern).test(entry.name)) {
60806
- manifestFiles.push(path47.relative(rootDir, fullPath));
61394
+ manifestFiles.push(path49.relative(rootDir, fullPath));
60807
61395
  break;
60808
61396
  }
60809
61397
  }
@@ -60819,13 +61407,13 @@ function findManifestFilesInDirs(directories, workingDir) {
60819
61407
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
60820
61408
  for (const dir of directories) {
60821
61409
  try {
60822
- const entries = fs34.readdirSync(dir, { withFileTypes: true });
61410
+ const entries = fs36.readdirSync(dir, { withFileTypes: true });
60823
61411
  for (const entry of entries) {
60824
- const fullPath = path47.join(dir, entry.name);
61412
+ const fullPath = path49.join(dir, entry.name);
60825
61413
  if (entry.isFile()) {
60826
61414
  for (const pattern of patterns) {
60827
61415
  if (simpleGlobToRegex(pattern).test(entry.name)) {
60828
- found.push(path47.relative(workingDir, fullPath));
61416
+ found.push(path49.relative(workingDir, fullPath));
60829
61417
  break;
60830
61418
  }
60831
61419
  }
@@ -60838,11 +61426,11 @@ function findManifestFilesInDirs(directories, workingDir) {
60838
61426
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
60839
61427
  const dirs = new Set;
60840
61428
  for (const file3 of changedFiles) {
60841
- let currentDir = path47.dirname(file3);
61429
+ let currentDir = path49.dirname(file3);
60842
61430
  while (true) {
60843
- if (currentDir && currentDir !== "." && currentDir !== path47.sep) {
60844
- dirs.add(path47.join(workingDir, currentDir));
60845
- const parent = path47.dirname(currentDir);
61431
+ if (currentDir && currentDir !== "." && currentDir !== path49.sep) {
61432
+ dirs.add(path49.join(workingDir, currentDir));
61433
+ const parent = path49.dirname(currentDir);
60846
61434
  if (parent === currentDir)
60847
61435
  break;
60848
61436
  currentDir = parent;
@@ -60856,7 +61444,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
60856
61444
  }
60857
61445
  function ensureOutputDir(outputDir) {
60858
61446
  try {
60859
- fs34.mkdirSync(outputDir, { recursive: true });
61447
+ fs36.mkdirSync(outputDir, { recursive: true });
60860
61448
  } catch (error93) {
60861
61449
  if (!error93 || error93.code !== "EEXIST") {
60862
61450
  throw error93;
@@ -60926,7 +61514,7 @@ var sbom_generate = createSwarmTool({
60926
61514
  const changedFiles = obj.changed_files;
60927
61515
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
60928
61516
  const workingDir = directory;
60929
- const outputDir = path47.isAbsolute(relativeOutputDir) ? relativeOutputDir : path47.join(workingDir, relativeOutputDir);
61517
+ const outputDir = path49.isAbsolute(relativeOutputDir) ? relativeOutputDir : path49.join(workingDir, relativeOutputDir);
60930
61518
  let manifestFiles = [];
60931
61519
  if (scope === "all") {
60932
61520
  manifestFiles = findManifestFiles(workingDir);
@@ -60949,11 +61537,11 @@ var sbom_generate = createSwarmTool({
60949
61537
  const processedFiles = [];
60950
61538
  for (const manifestFile of manifestFiles) {
60951
61539
  try {
60952
- const fullPath = path47.isAbsolute(manifestFile) ? manifestFile : path47.join(workingDir, manifestFile);
60953
- if (!fs34.existsSync(fullPath)) {
61540
+ const fullPath = path49.isAbsolute(manifestFile) ? manifestFile : path49.join(workingDir, manifestFile);
61541
+ if (!fs36.existsSync(fullPath)) {
60954
61542
  continue;
60955
61543
  }
60956
- const content = fs34.readFileSync(fullPath, "utf-8");
61544
+ const content = fs36.readFileSync(fullPath, "utf-8");
60957
61545
  const components = detectComponents(manifestFile, content);
60958
61546
  processedFiles.push(manifestFile);
60959
61547
  if (components.length > 0) {
@@ -60966,8 +61554,8 @@ var sbom_generate = createSwarmTool({
60966
61554
  const bom = generateCycloneDX(allComponents);
60967
61555
  const bomJson = serializeCycloneDX(bom);
60968
61556
  const filename = generateSbomFilename();
60969
- const outputPath = path47.join(outputDir, filename);
60970
- fs34.writeFileSync(outputPath, bomJson, "utf-8");
61557
+ const outputPath = path49.join(outputDir, filename);
61558
+ fs36.writeFileSync(outputPath, bomJson, "utf-8");
60971
61559
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
60972
61560
  try {
60973
61561
  const timestamp = new Date().toISOString();
@@ -61009,8 +61597,8 @@ var sbom_generate = createSwarmTool({
61009
61597
  // src/tools/schema-drift.ts
61010
61598
  init_dist();
61011
61599
  init_create_tool();
61012
- import * as fs35 from "fs";
61013
- import * as path48 from "path";
61600
+ import * as fs37 from "fs";
61601
+ import * as path50 from "path";
61014
61602
  var SPEC_CANDIDATES = [
61015
61603
  "openapi.json",
61016
61604
  "openapi.yaml",
@@ -61042,28 +61630,28 @@ function normalizePath2(p) {
61042
61630
  }
61043
61631
  function discoverSpecFile(cwd, specFileArg) {
61044
61632
  if (specFileArg) {
61045
- const resolvedPath = path48.resolve(cwd, specFileArg);
61046
- const normalizedCwd = cwd.endsWith(path48.sep) ? cwd : cwd + path48.sep;
61633
+ const resolvedPath = path50.resolve(cwd, specFileArg);
61634
+ const normalizedCwd = cwd.endsWith(path50.sep) ? cwd : cwd + path50.sep;
61047
61635
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
61048
61636
  throw new Error("Invalid spec_file: path traversal detected");
61049
61637
  }
61050
- const ext = path48.extname(resolvedPath).toLowerCase();
61638
+ const ext = path50.extname(resolvedPath).toLowerCase();
61051
61639
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
61052
61640
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
61053
61641
  }
61054
- const stats = fs35.statSync(resolvedPath);
61642
+ const stats = fs37.statSync(resolvedPath);
61055
61643
  if (stats.size > MAX_SPEC_SIZE) {
61056
61644
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
61057
61645
  }
61058
- if (!fs35.existsSync(resolvedPath)) {
61646
+ if (!fs37.existsSync(resolvedPath)) {
61059
61647
  throw new Error(`Spec file not found: ${resolvedPath}`);
61060
61648
  }
61061
61649
  return resolvedPath;
61062
61650
  }
61063
61651
  for (const candidate of SPEC_CANDIDATES) {
61064
- const candidatePath = path48.resolve(cwd, candidate);
61065
- if (fs35.existsSync(candidatePath)) {
61066
- const stats = fs35.statSync(candidatePath);
61652
+ const candidatePath = path50.resolve(cwd, candidate);
61653
+ if (fs37.existsSync(candidatePath)) {
61654
+ const stats = fs37.statSync(candidatePath);
61067
61655
  if (stats.size <= MAX_SPEC_SIZE) {
61068
61656
  return candidatePath;
61069
61657
  }
@@ -61072,8 +61660,8 @@ function discoverSpecFile(cwd, specFileArg) {
61072
61660
  return null;
61073
61661
  }
61074
61662
  function parseSpec(specFile) {
61075
- const content = fs35.readFileSync(specFile, "utf-8");
61076
- const ext = path48.extname(specFile).toLowerCase();
61663
+ const content = fs37.readFileSync(specFile, "utf-8");
61664
+ const ext = path50.extname(specFile).toLowerCase();
61077
61665
  if (ext === ".json") {
61078
61666
  return parseJsonSpec(content);
61079
61667
  }
@@ -61144,12 +61732,12 @@ function extractRoutes(cwd) {
61144
61732
  function walkDir(dir) {
61145
61733
  let entries;
61146
61734
  try {
61147
- entries = fs35.readdirSync(dir, { withFileTypes: true });
61735
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
61148
61736
  } catch {
61149
61737
  return;
61150
61738
  }
61151
61739
  for (const entry of entries) {
61152
- const fullPath = path48.join(dir, entry.name);
61740
+ const fullPath = path50.join(dir, entry.name);
61153
61741
  if (entry.isSymbolicLink()) {
61154
61742
  continue;
61155
61743
  }
@@ -61159,7 +61747,7 @@ function extractRoutes(cwd) {
61159
61747
  }
61160
61748
  walkDir(fullPath);
61161
61749
  } else if (entry.isFile()) {
61162
- const ext = path48.extname(entry.name).toLowerCase();
61750
+ const ext = path50.extname(entry.name).toLowerCase();
61163
61751
  const baseName = entry.name.toLowerCase();
61164
61752
  if (![".ts", ".js", ".mjs"].includes(ext)) {
61165
61753
  continue;
@@ -61177,7 +61765,7 @@ function extractRoutes(cwd) {
61177
61765
  }
61178
61766
  function extractRoutesFromFile(filePath) {
61179
61767
  const routes = [];
61180
- const content = fs35.readFileSync(filePath, "utf-8");
61768
+ const content = fs37.readFileSync(filePath, "utf-8");
61181
61769
  const lines = content.split(/\r?\n/);
61182
61770
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
61183
61771
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -61328,8 +61916,8 @@ init_secretscan();
61328
61916
  // src/tools/symbols.ts
61329
61917
  init_tool();
61330
61918
  init_create_tool();
61331
- import * as fs36 from "fs";
61332
- import * as path49 from "path";
61919
+ import * as fs38 from "fs";
61920
+ import * as path51 from "path";
61333
61921
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
61334
61922
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
61335
61923
  function containsControlCharacters(str) {
@@ -61358,11 +61946,11 @@ function containsWindowsAttacks(str) {
61358
61946
  }
61359
61947
  function isPathInWorkspace(filePath, workspace) {
61360
61948
  try {
61361
- const resolvedPath = path49.resolve(workspace, filePath);
61362
- const realWorkspace = fs36.realpathSync(workspace);
61363
- const realResolvedPath = fs36.realpathSync(resolvedPath);
61364
- const relativePath = path49.relative(realWorkspace, realResolvedPath);
61365
- if (relativePath.startsWith("..") || path49.isAbsolute(relativePath)) {
61949
+ const resolvedPath = path51.resolve(workspace, filePath);
61950
+ const realWorkspace = fs38.realpathSync(workspace);
61951
+ const realResolvedPath = fs38.realpathSync(resolvedPath);
61952
+ const relativePath = path51.relative(realWorkspace, realResolvedPath);
61953
+ if (relativePath.startsWith("..") || path51.isAbsolute(relativePath)) {
61366
61954
  return false;
61367
61955
  }
61368
61956
  return true;
@@ -61374,17 +61962,17 @@ function validatePathForRead(filePath, workspace) {
61374
61962
  return isPathInWorkspace(filePath, workspace);
61375
61963
  }
61376
61964
  function extractTSSymbols(filePath, cwd) {
61377
- const fullPath = path49.join(cwd, filePath);
61965
+ const fullPath = path51.join(cwd, filePath);
61378
61966
  if (!validatePathForRead(fullPath, cwd)) {
61379
61967
  return [];
61380
61968
  }
61381
61969
  let content;
61382
61970
  try {
61383
- const stats = fs36.statSync(fullPath);
61971
+ const stats = fs38.statSync(fullPath);
61384
61972
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
61385
61973
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
61386
61974
  }
61387
- content = fs36.readFileSync(fullPath, "utf-8");
61975
+ content = fs38.readFileSync(fullPath, "utf-8");
61388
61976
  } catch {
61389
61977
  return [];
61390
61978
  }
@@ -61526,17 +62114,17 @@ function extractTSSymbols(filePath, cwd) {
61526
62114
  });
61527
62115
  }
61528
62116
  function extractPythonSymbols(filePath, cwd) {
61529
- const fullPath = path49.join(cwd, filePath);
62117
+ const fullPath = path51.join(cwd, filePath);
61530
62118
  if (!validatePathForRead(fullPath, cwd)) {
61531
62119
  return [];
61532
62120
  }
61533
62121
  let content;
61534
62122
  try {
61535
- const stats = fs36.statSync(fullPath);
62123
+ const stats = fs38.statSync(fullPath);
61536
62124
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
61537
62125
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
61538
62126
  }
61539
- content = fs36.readFileSync(fullPath, "utf-8");
62127
+ content = fs38.readFileSync(fullPath, "utf-8");
61540
62128
  } catch {
61541
62129
  return [];
61542
62130
  }
@@ -61609,7 +62197,7 @@ var symbols = createSwarmTool({
61609
62197
  }, null, 2);
61610
62198
  }
61611
62199
  const cwd = directory;
61612
- const ext = path49.extname(file3);
62200
+ const ext = path51.extname(file3);
61613
62201
  if (containsControlCharacters(file3)) {
61614
62202
  return JSON.stringify({
61615
62203
  file: file3,
@@ -61680,8 +62268,8 @@ init_test_runner();
61680
62268
  init_dist();
61681
62269
  init_utils();
61682
62270
  init_create_tool();
61683
- import * as fs37 from "fs";
61684
- import * as path50 from "path";
62271
+ import * as fs39 from "fs";
62272
+ import * as path52 from "path";
61685
62273
  var MAX_TEXT_LENGTH = 200;
61686
62274
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
61687
62275
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -61752,9 +62340,9 @@ function validatePathsInput(paths, cwd) {
61752
62340
  return { error: "paths contains path traversal", resolvedPath: null };
61753
62341
  }
61754
62342
  try {
61755
- const resolvedPath = path50.resolve(paths);
61756
- const normalizedCwd = path50.resolve(cwd);
61757
- const normalizedResolved = path50.resolve(resolvedPath);
62343
+ const resolvedPath = path52.resolve(paths);
62344
+ const normalizedCwd = path52.resolve(cwd);
62345
+ const normalizedResolved = path52.resolve(resolvedPath);
61758
62346
  if (!normalizedResolved.startsWith(normalizedCwd)) {
61759
62347
  return {
61760
62348
  error: "paths must be within the current working directory",
@@ -61770,13 +62358,13 @@ function validatePathsInput(paths, cwd) {
61770
62358
  }
61771
62359
  }
61772
62360
  function isSupportedExtension(filePath) {
61773
- const ext = path50.extname(filePath).toLowerCase();
62361
+ const ext = path52.extname(filePath).toLowerCase();
61774
62362
  return SUPPORTED_EXTENSIONS2.has(ext);
61775
62363
  }
61776
62364
  function findSourceFiles2(dir, files = []) {
61777
62365
  let entries;
61778
62366
  try {
61779
- entries = fs37.readdirSync(dir);
62367
+ entries = fs39.readdirSync(dir);
61780
62368
  } catch {
61781
62369
  return files;
61782
62370
  }
@@ -61785,10 +62373,10 @@ function findSourceFiles2(dir, files = []) {
61785
62373
  if (SKIP_DIRECTORIES3.has(entry)) {
61786
62374
  continue;
61787
62375
  }
61788
- const fullPath = path50.join(dir, entry);
62376
+ const fullPath = path52.join(dir, entry);
61789
62377
  let stat2;
61790
62378
  try {
61791
- stat2 = fs37.statSync(fullPath);
62379
+ stat2 = fs39.statSync(fullPath);
61792
62380
  } catch {
61793
62381
  continue;
61794
62382
  }
@@ -61881,7 +62469,7 @@ var todo_extract = createSwarmTool({
61881
62469
  return JSON.stringify(errorResult, null, 2);
61882
62470
  }
61883
62471
  const scanPath = resolvedPath;
61884
- if (!fs37.existsSync(scanPath)) {
62472
+ if (!fs39.existsSync(scanPath)) {
61885
62473
  const errorResult = {
61886
62474
  error: `path not found: ${pathsInput}`,
61887
62475
  total: 0,
@@ -61891,13 +62479,13 @@ var todo_extract = createSwarmTool({
61891
62479
  return JSON.stringify(errorResult, null, 2);
61892
62480
  }
61893
62481
  const filesToScan = [];
61894
- const stat2 = fs37.statSync(scanPath);
62482
+ const stat2 = fs39.statSync(scanPath);
61895
62483
  if (stat2.isFile()) {
61896
62484
  if (isSupportedExtension(scanPath)) {
61897
62485
  filesToScan.push(scanPath);
61898
62486
  } else {
61899
62487
  const errorResult = {
61900
- error: `unsupported file extension: ${path50.extname(scanPath)}`,
62488
+ error: `unsupported file extension: ${path52.extname(scanPath)}`,
61901
62489
  total: 0,
61902
62490
  byPriority: { high: 0, medium: 0, low: 0 },
61903
62491
  entries: []
@@ -61910,11 +62498,11 @@ var todo_extract = createSwarmTool({
61910
62498
  const allEntries = [];
61911
62499
  for (const filePath of filesToScan) {
61912
62500
  try {
61913
- const fileStat = fs37.statSync(filePath);
62501
+ const fileStat = fs39.statSync(filePath);
61914
62502
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
61915
62503
  continue;
61916
62504
  }
61917
- const content = fs37.readFileSync(filePath, "utf-8");
62505
+ const content = fs39.readFileSync(filePath, "utf-8");
61918
62506
  const entries = parseTodoComments(content, filePath, tagsSet);
61919
62507
  allEntries.push(...entries);
61920
62508
  } catch {}
@@ -61942,9 +62530,95 @@ var todo_extract = createSwarmTool({
61942
62530
  // src/tools/update-task-status.ts
61943
62531
  init_tool();
61944
62532
  init_schema();
62533
+ import * as fs41 from "fs";
62534
+ import * as path54 from "path";
62535
+
62536
+ // src/hooks/diff-scope.ts
62537
+ import * as fs40 from "fs";
62538
+ import * as path53 from "path";
62539
+ function getDeclaredScope(taskId, directory) {
62540
+ try {
62541
+ const planPath = path53.join(directory, ".swarm", "plan.json");
62542
+ if (!fs40.existsSync(planPath))
62543
+ return null;
62544
+ const raw = fs40.readFileSync(planPath, "utf-8");
62545
+ const plan = JSON.parse(raw);
62546
+ for (const phase of plan.phases ?? []) {
62547
+ for (const task of phase.tasks ?? []) {
62548
+ if (task.id !== taskId)
62549
+ continue;
62550
+ const ft = task.files_touched;
62551
+ if (Array.isArray(ft) && ft.length > 0) {
62552
+ return ft;
62553
+ }
62554
+ if (typeof ft === "string" && ft.length > 0) {
62555
+ return [ft];
62556
+ }
62557
+ return null;
62558
+ }
62559
+ }
62560
+ return null;
62561
+ } catch {
62562
+ return null;
62563
+ }
62564
+ }
62565
+ async function getChangedFiles(directory) {
62566
+ try {
62567
+ const proc = Bun.spawn(["git", "diff", "--name-only", "HEAD~1"], {
62568
+ cwd: directory,
62569
+ stdout: "pipe",
62570
+ stderr: "pipe"
62571
+ });
62572
+ const [exitCode, stdout] = await Promise.all([
62573
+ proc.exited,
62574
+ new Response(proc.stdout).text()
62575
+ ]);
62576
+ if (exitCode === 0) {
62577
+ return stdout.trim().split(`
62578
+ `).map((f) => f.trim()).filter((f) => f.length > 0);
62579
+ }
62580
+ const proc2 = Bun.spawn(["git", "diff", "--name-only", "HEAD"], {
62581
+ cwd: directory,
62582
+ stdout: "pipe",
62583
+ stderr: "pipe"
62584
+ });
62585
+ const [exitCode2, stdout2] = await Promise.all([
62586
+ proc2.exited,
62587
+ new Response(proc2.stdout).text()
62588
+ ]);
62589
+ if (exitCode2 === 0) {
62590
+ return stdout2.trim().split(`
62591
+ `).map((f) => f.trim()).filter((f) => f.length > 0);
62592
+ }
62593
+ return null;
62594
+ } catch {
62595
+ return null;
62596
+ }
62597
+ }
62598
+ async function validateDiffScope(taskId, directory) {
62599
+ try {
62600
+ const declaredScope = getDeclaredScope(taskId, directory);
62601
+ if (!declaredScope)
62602
+ return null;
62603
+ const changedFiles = await getChangedFiles(directory);
62604
+ if (!changedFiles)
62605
+ return null;
62606
+ const normalise = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
62607
+ const normScope = new Set(declaredScope.map(normalise));
62608
+ const undeclared = changedFiles.map(normalise).filter((f) => !normScope.has(f));
62609
+ if (undeclared.length === 0)
62610
+ return null;
62611
+ const scopeStr = declaredScope.join(", ");
62612
+ const undeclaredStr = undeclared.slice(0, 5).join(", ");
62613
+ const extra = undeclared.length > 5 ? ` (+${undeclared.length - 5} more)` : "";
62614
+ return `SCOPE WARNING: Task ${taskId} declared scope [${scopeStr}] but also modified [${undeclaredStr}${extra}]. Reviewer should verify these changes are intentional.`;
62615
+ } catch {
62616
+ return null;
62617
+ }
62618
+ }
62619
+
62620
+ // src/tools/update-task-status.ts
61945
62621
  init_manager2();
61946
- import * as fs38 from "fs";
61947
- import * as path51 from "path";
61948
62622
  init_create_tool();
61949
62623
  var VALID_STATUSES2 = [
61950
62624
  "pending",
@@ -61979,7 +62653,7 @@ var TIER_3_PATTERNS = [
61979
62653
  ];
61980
62654
  function matchesTier3Pattern(files) {
61981
62655
  for (const file3 of files) {
61982
- const fileName = path51.basename(file3);
62656
+ const fileName = path54.basename(file3);
61983
62657
  for (const pattern of TIER_3_PATTERNS) {
61984
62658
  if (pattern.test(fileName)) {
61985
62659
  return true;
@@ -62001,8 +62675,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62001
62675
  if (hasActiveTurboMode2()) {
62002
62676
  const resolvedDir2 = workingDirectory ?? process.cwd();
62003
62677
  try {
62004
- const planPath = path51.join(resolvedDir2, ".swarm", "plan.json");
62005
- const planRaw = fs38.readFileSync(planPath, "utf-8");
62678
+ const planPath = path54.join(resolvedDir2, ".swarm", "plan.json");
62679
+ const planRaw = fs41.readFileSync(planPath, "utf-8");
62006
62680
  const plan = JSON.parse(planRaw);
62007
62681
  for (const planPhase of plan.phases ?? []) {
62008
62682
  for (const task of planPhase.tasks ?? []) {
@@ -62021,8 +62695,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62021
62695
  }
62022
62696
  const resolvedDir = workingDirectory ?? process.cwd();
62023
62697
  try {
62024
- const evidencePath = path51.join(resolvedDir, ".swarm", "evidence", `${taskId}.json`);
62025
- const raw = fs38.readFileSync(evidencePath, "utf-8");
62698
+ const evidencePath = path54.join(resolvedDir, ".swarm", "evidence", `${taskId}.json`);
62699
+ const raw = fs41.readFileSync(evidencePath, "utf-8");
62026
62700
  const evidence = JSON.parse(raw);
62027
62701
  if (evidence?.required_gates && Array.isArray(evidence.required_gates) && evidence?.gates) {
62028
62702
  const allGatesMet = evidence.required_gates.every((gate) => evidence.gates[gate] != null);
@@ -62062,8 +62736,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62062
62736
  }
62063
62737
  try {
62064
62738
  const resolvedDir2 = workingDirectory ?? process.cwd();
62065
- const planPath = path51.join(resolvedDir2, ".swarm", "plan.json");
62066
- const planRaw = fs38.readFileSync(planPath, "utf-8");
62739
+ const planPath = path54.join(resolvedDir2, ".swarm", "plan.json");
62740
+ const planRaw = fs41.readFileSync(planPath, "utf-8");
62067
62741
  const plan = JSON.parse(planRaw);
62068
62742
  for (const planPhase of plan.phases ?? []) {
62069
62743
  for (const task of planPhase.tasks ?? []) {
@@ -62121,6 +62795,17 @@ function checkReviewerGate(taskId, workingDirectory) {
62121
62795
  return { blocked: false, reason: "" };
62122
62796
  }
62123
62797
  }
62798
+ async function checkReviewerGateWithScope(taskId, workingDirectory) {
62799
+ const result = checkReviewerGate(taskId, workingDirectory);
62800
+ const scopeWarning = await validateDiffScope(taskId, workingDirectory ?? process.cwd()).catch(() => null);
62801
+ if (!scopeWarning)
62802
+ return result;
62803
+ return {
62804
+ ...result,
62805
+ reason: result.reason ? `${result.reason}
62806
+ ${scopeWarning}` : scopeWarning
62807
+ };
62808
+ }
62124
62809
  function recoverTaskStateFromDelegations(taskId) {
62125
62810
  let hasReviewer = false;
62126
62811
  let hasTestEngineer = false;
@@ -62233,8 +62918,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62233
62918
  };
62234
62919
  }
62235
62920
  }
62236
- normalizedDir = path51.normalize(args2.working_directory);
62237
- const pathParts = normalizedDir.split(path51.sep);
62921
+ normalizedDir = path54.normalize(args2.working_directory);
62922
+ const pathParts = normalizedDir.split(path54.sep);
62238
62923
  if (pathParts.includes("..")) {
62239
62924
  return {
62240
62925
  success: false,
@@ -62244,11 +62929,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62244
62929
  ]
62245
62930
  };
62246
62931
  }
62247
- const resolvedDir = path51.resolve(normalizedDir);
62932
+ const resolvedDir = path54.resolve(normalizedDir);
62248
62933
  try {
62249
- const realPath = fs38.realpathSync(resolvedDir);
62250
- const planPath = path51.join(realPath, ".swarm", "plan.json");
62251
- if (!fs38.existsSync(planPath)) {
62934
+ const realPath = fs41.realpathSync(resolvedDir);
62935
+ const planPath = path54.join(realPath, ".swarm", "plan.json");
62936
+ if (!fs41.existsSync(planPath)) {
62252
62937
  return {
62253
62938
  success: false,
62254
62939
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -62272,7 +62957,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62272
62957
  }
62273
62958
  if (args2.status === "completed") {
62274
62959
  recoverTaskStateFromDelegations(args2.task_id);
62275
- const reviewerCheck = checkReviewerGate(args2.task_id, directory);
62960
+ const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
62276
62961
  if (reviewerCheck.blocked) {
62277
62962
  return {
62278
62963
  success: false,
@@ -62395,6 +63080,31 @@ var OpenCodeSwarm = async (ctx) => {
62395
63080
  const steeringConsumedHook = createSteeringConsumedHook(ctx.directory);
62396
63081
  const coChangeSuggesterHook = createCoChangeSuggesterHook(ctx.directory);
62397
63082
  const darkMatterDetectorHook = createDarkMatterDetectorHook(ctx.directory);
63083
+ const slopDetectorHook = config3.slop_detector?.enabled !== false ? createSlopDetectorHook(config3.slop_detector ?? {
63084
+ enabled: true,
63085
+ classThreshold: 3,
63086
+ commentStripThreshold: 5,
63087
+ diffLineThreshold: 200
63088
+ }, ctx.directory, (_sessionId, message) => {
63089
+ console.warn(`[slop-detector] ${message}`);
63090
+ }) : null;
63091
+ const incrementalVerifyHook = config3.incremental_verify?.enabled !== false ? createIncrementalVerifyHook(config3.incremental_verify ?? {
63092
+ enabled: true,
63093
+ command: null,
63094
+ timeoutMs: 30000,
63095
+ triggerAgents: ["coder"]
63096
+ }, ctx.directory, (_sessionId, message) => {
63097
+ console.warn(`[incremental-verify] ${message}`);
63098
+ }) : null;
63099
+ const compactionServiceHook = config3.compaction_service?.enabled !== false ? createCompactionService(config3.compaction_service ?? {
63100
+ enabled: true,
63101
+ observationThreshold: 40,
63102
+ reflectionThreshold: 60,
63103
+ emergencyThreshold: 80,
63104
+ preserveLastNTurns: 5
63105
+ }, ctx.directory, (_sessionId, message) => {
63106
+ console.warn(`[compaction-service] ${message}`);
63107
+ }) : null;
62398
63108
  const snapshotWriterHook = createSnapshotWriterHook(ctx.directory);
62399
63109
  const automationConfig = AutomationConfigSchema.parse(config3.automation ?? {});
62400
63110
  let automationManager;
@@ -62405,7 +63115,7 @@ var OpenCodeSwarm = async (ctx) => {
62405
63115
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
62406
63116
  preflightTriggerManager = new PTM(automationConfig);
62407
63117
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
62408
- const swarmDir = path52.resolve(ctx.directory, ".swarm");
63118
+ const swarmDir = path55.resolve(ctx.directory, ".swarm");
62409
63119
  statusArtifact = new ASA(swarmDir);
62410
63120
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
62411
63121
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -62667,6 +63377,13 @@ var OpenCodeSwarm = async (ctx) => {
62667
63377
  }
62668
63378
  }
62669
63379
  await guardrailsHooks.toolBefore(input, output);
63380
+ if (swarmState.lastBudgetPct >= 50) {
63381
+ const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
63382
+ if (!pressureSession.contextPressureWarningSent) {
63383
+ pressureSession.contextPressureWarningSent = true;
63384
+ console.warn(`[context-pressure] CONTEXT PRESSURE: ${swarmState.lastBudgetPct.toFixed(1)}% of context window estimated used. Prioritize completing the current task.`);
63385
+ }
63386
+ }
62670
63387
  await safeHook(activityHooks.toolBefore)(input, output);
62671
63388
  },
62672
63389
  "tool.execute.after": async (input, output) => {
@@ -62682,6 +63399,12 @@ var OpenCodeSwarm = async (ctx) => {
62682
63399
  await safeHook(darkMatterDetectorHook)(input, output);
62683
63400
  await snapshotWriterHook(input, output);
62684
63401
  await toolSummarizerHook?.(input, output);
63402
+ if (slopDetectorHook)
63403
+ await slopDetectorHook.toolAfter(input, output);
63404
+ if (incrementalVerifyHook)
63405
+ await incrementalVerifyHook.toolAfter(input, output);
63406
+ if (compactionServiceHook)
63407
+ await compactionServiceHook.toolAfter(input, output);
62685
63408
  const toolOutputConfig = config3.tool_output;
62686
63409
  if (toolOutputConfig && toolOutputConfig.truncation_enabled !== false && typeof output.output === "string") {
62687
63410
  const skipTools = [