opencode-swarm 6.28.1 → 6.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
  });
@@ -34189,10 +34216,7 @@ var init_secretscan = __esm(() => {
34189
34216
  const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
34190
34217
  const excludeGlobs = [];
34191
34218
  const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
34192
- const allUserPatterns = [
34193
- ...exclude ?? [],
34194
- ...ignoreFilePatterns
34195
- ];
34219
+ const allUserPatterns = [...exclude ?? [], ...ignoreFilePatterns];
34196
34220
  for (const exc of allUserPatterns) {
34197
34221
  if (exc.length === 0)
34198
34222
  continue;
@@ -34435,7 +34459,7 @@ function detectMinitest(cwd) {
34435
34459
  return fs11.existsSync(path23.join(cwd, "test")) && (fs11.existsSync(path23.join(cwd, "Gemfile")) || fs11.existsSync(path23.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
34436
34460
  }
34437
34461
  async function detectTestFramework(cwd) {
34438
- const baseDir = cwd || process.cwd();
34462
+ const baseDir = cwd;
34439
34463
  try {
34440
34464
  const packageJsonPath = path23.join(baseDir, "package.json");
34441
34465
  if (fs11.existsSync(packageJsonPath)) {
@@ -35918,7 +35942,7 @@ __export(exports_gate_evidence, {
35918
35942
  DEFAULT_REQUIRED_GATES: () => DEFAULT_REQUIRED_GATES
35919
35943
  });
35920
35944
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync13, renameSync as renameSync7, unlinkSync as unlinkSync4 } from "fs";
35921
- import * as path28 from "path";
35945
+ import * as path29 from "path";
35922
35946
  function isValidTaskId2(taskId) {
35923
35947
  if (!taskId)
35924
35948
  return false;
@@ -35965,10 +35989,10 @@ function expandRequiredGates(existingGates, newAgentType) {
35965
35989
  return combined.sort();
35966
35990
  }
35967
35991
  function getEvidenceDir(directory) {
35968
- return path28.join(directory, ".swarm", "evidence");
35992
+ return path29.join(directory, ".swarm", "evidence");
35969
35993
  }
35970
35994
  function getEvidencePath(directory, taskId) {
35971
- return path28.join(getEvidenceDir(directory), `${taskId}.json`);
35995
+ return path29.join(getEvidenceDir(directory), `${taskId}.json`);
35972
35996
  }
35973
35997
  function readExisting(evidencePath) {
35974
35998
  try {
@@ -36082,10 +36106,10 @@ function createPreflightIntegration(config3) {
36082
36106
  });
36083
36107
  const report = await runPreflight(directory, request.currentPhase, preflightConfig);
36084
36108
  if (statusArtifact) {
36085
- const state = report.overall === "pass" ? "success" : "failure";
36086
- statusArtifact.recordOutcome(state, request.currentPhase, report.message);
36109
+ const state2 = report.overall === "pass" ? "success" : "failure";
36110
+ statusArtifact.recordOutcome(state2, request.currentPhase, report.message);
36087
36111
  console.log("[PreflightIntegration] Status artifact updated", {
36088
- state,
36112
+ state: state2,
36089
36113
  phase: request.currentPhase,
36090
36114
  message: report.message
36091
36115
  });
@@ -37577,11 +37601,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
37577
37601
  throw toThrow;
37578
37602
  }, "quit_");
37579
37603
  var scriptDirectory = "";
37580
- function locateFile(path43) {
37604
+ function locateFile(path45) {
37581
37605
  if (Module["locateFile"]) {
37582
- return Module["locateFile"](path43, scriptDirectory);
37606
+ return Module["locateFile"](path45, scriptDirectory);
37583
37607
  }
37584
- return scriptDirectory + path43;
37608
+ return scriptDirectory + path45;
37585
37609
  }
37586
37610
  __name(locateFile, "locateFile");
37587
37611
  var readAsync, readBinary;
@@ -39329,7 +39353,7 @@ var init_runtime = __esm(() => {
39329
39353
  });
39330
39354
 
39331
39355
  // src/index.ts
39332
- import * as path52 from "path";
39356
+ import * as path55 from "path";
39333
39357
 
39334
39358
  // src/agents/index.ts
39335
39359
  init_config();
@@ -39348,6 +39372,7 @@ var swarmState = {
39348
39372
  activeAgent: new Map,
39349
39373
  delegationChains: new Map,
39350
39374
  pendingEvents: 0,
39375
+ lastBudgetPct: 0,
39351
39376
  agentSessions: new Map
39352
39377
  };
39353
39378
  function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, directory) {
@@ -39392,7 +39417,9 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
39392
39417
  lastScopeViolation: null,
39393
39418
  scopeViolationDetected: false,
39394
39419
  modifiedFilesThisCoderTask: [],
39395
- turboMode: false
39420
+ turboMode: false,
39421
+ loopDetectionWindow: [],
39422
+ pendingAdvisoryMessages: []
39396
39423
  };
39397
39424
  swarmState.agentSessions.set(sessionId, sessionState);
39398
39425
  swarmState.activeAgent.set(sessionId, agentName);
@@ -39494,6 +39521,12 @@ function ensureAgentSession(sessionId, agentName, directory) {
39494
39521
  if (session.turboMode === undefined) {
39495
39522
  session.turboMode = false;
39496
39523
  }
39524
+ if (session.loopDetectionWindow === undefined) {
39525
+ session.loopDetectionWindow = [];
39526
+ }
39527
+ if (session.pendingAdvisoryMessages === undefined) {
39528
+ session.pendingAdvisoryMessages = [];
39529
+ }
39497
39530
  session.lastToolCallTime = now;
39498
39531
  return session;
39499
39532
  }
@@ -47578,6 +47611,264 @@ No plan content available. Start by creating a .swarm/plan.md file.
47578
47611
  // src/services/status-service.ts
47579
47612
  init_utils2();
47580
47613
  init_manager2();
47614
+
47615
+ // src/services/compaction-service.ts
47616
+ import * as fs15 from "fs";
47617
+ import * as path27 from "path";
47618
+ function makeInitialState() {
47619
+ return {
47620
+ lastObservationAt: 0,
47621
+ lastReflectionAt: 0,
47622
+ lastEmergencyAt: 0,
47623
+ observationCount: 0,
47624
+ reflectionCount: 0,
47625
+ emergencyCount: 0,
47626
+ lastSnapshotAt: null
47627
+ };
47628
+ }
47629
+ var state = makeInitialState();
47630
+ function appendSnapshot(directory, tier, budgetPct, message) {
47631
+ try {
47632
+ const snapshotPath = path27.join(directory, ".swarm", "context-snapshot.md");
47633
+ const timestamp = new Date().toISOString();
47634
+ const entry = `
47635
+ ## [${tier.toUpperCase()}] ${timestamp} \u2014 ${budgetPct.toFixed(1)}% used
47636
+ ${message}
47637
+ `;
47638
+ fs15.appendFileSync(snapshotPath, entry, "utf-8");
47639
+ } catch {}
47640
+ }
47641
+ function buildObservationMessage(budgetPct) {
47642
+ return `[CONTEXT COMPACTION \u2014 OBSERVATION TIER]
47643
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. Initiating observation compaction.
47644
+ ` + `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.
47645
+ ` + `[/CONTEXT COMPACTION]`;
47646
+ }
47647
+ function buildReflectionMessage(budgetPct) {
47648
+ return `[CONTEXT COMPACTION \u2014 REFLECTION TIER]
47649
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. Initiating reflection compaction.
47650
+ ` + `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.
47651
+ ` + `[/CONTEXT COMPACTION]`;
47652
+ }
47653
+ function buildEmergencyMessage(budgetPct, preserveLastN) {
47654
+ return `[CONTEXT COMPACTION \u2014 EMERGENCY TIER]
47655
+ ` + `Context window is ${budgetPct.toFixed(1)}% used. EMERGENCY compaction required.
47656
+ ` + `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.
47657
+ ` + `[/CONTEXT COMPACTION]`;
47658
+ }
47659
+ function createCompactionService(config3, directory, injectMessage) {
47660
+ return {
47661
+ toolAfter: async (_input, _output) => {
47662
+ if (!config3.enabled)
47663
+ return;
47664
+ const budgetPct = swarmState.lastBudgetPct ?? 0;
47665
+ if (budgetPct <= 0)
47666
+ return;
47667
+ const sessionId = _input.sessionID;
47668
+ try {
47669
+ if (budgetPct >= config3.emergencyThreshold && budgetPct > state.lastEmergencyAt + 5) {
47670
+ state.lastEmergencyAt = budgetPct;
47671
+ state.emergencyCount++;
47672
+ const msg = buildEmergencyMessage(budgetPct, config3.preserveLastNTurns);
47673
+ appendSnapshot(directory, "emergency", budgetPct, msg);
47674
+ state.lastSnapshotAt = new Date().toISOString();
47675
+ injectMessage(sessionId, msg);
47676
+ return;
47677
+ }
47678
+ if (budgetPct >= config3.reflectionThreshold && budgetPct > state.lastReflectionAt + 5) {
47679
+ state.lastReflectionAt = budgetPct;
47680
+ state.reflectionCount++;
47681
+ const msg = buildReflectionMessage(budgetPct);
47682
+ appendSnapshot(directory, "reflection", budgetPct, msg);
47683
+ state.lastSnapshotAt = new Date().toISOString();
47684
+ injectMessage(sessionId, msg);
47685
+ return;
47686
+ }
47687
+ if (budgetPct >= config3.observationThreshold && budgetPct > state.lastObservationAt + 5) {
47688
+ state.lastObservationAt = budgetPct;
47689
+ state.observationCount++;
47690
+ const msg = buildObservationMessage(budgetPct);
47691
+ appendSnapshot(directory, "observation", budgetPct, msg);
47692
+ state.lastSnapshotAt = new Date().toISOString();
47693
+ injectMessage(sessionId, msg);
47694
+ }
47695
+ } catch {}
47696
+ }
47697
+ };
47698
+ }
47699
+ function getCompactionMetrics() {
47700
+ return {
47701
+ compactionCount: state.observationCount + state.reflectionCount + state.emergencyCount,
47702
+ lastSnapshotAt: state.lastSnapshotAt
47703
+ };
47704
+ }
47705
+
47706
+ // src/services/context-budget-service.ts
47707
+ init_utils2();
47708
+ function validateDirectory(directory) {
47709
+ if (!directory || directory.trim() === "") {
47710
+ throw new Error("Invalid directory: empty");
47711
+ }
47712
+ if (/\.\.[/\\]/.test(directory)) {
47713
+ throw new Error("Invalid directory: path traversal detected");
47714
+ }
47715
+ if (directory.startsWith("/") || directory.startsWith("\\")) {
47716
+ throw new Error("Invalid directory: absolute path");
47717
+ }
47718
+ if (/^[A-Za-z]:[\\/]/.test(directory)) {
47719
+ throw new Error("Invalid directory: Windows absolute path");
47720
+ }
47721
+ }
47722
+ var DEFAULT_CONTEXT_BUDGET_CONFIG = {
47723
+ enabled: true,
47724
+ budgetTokens: 40000,
47725
+ warningPct: 70,
47726
+ criticalPct: 90,
47727
+ warningMode: "once",
47728
+ warningIntervalTurns: 20
47729
+ };
47730
+ var COST_PER_1K_TOKENS = 0.003;
47731
+ function estimateTokens2(text) {
47732
+ if (!text || typeof text !== "string") {
47733
+ return 0;
47734
+ }
47735
+ return Math.ceil(text.length / 3.5);
47736
+ }
47737
+ async function readBudgetState(directory) {
47738
+ const content = await readSwarmFileAsync(directory, "session/budget-state.json");
47739
+ if (!content) {
47740
+ return null;
47741
+ }
47742
+ try {
47743
+ return JSON.parse(content);
47744
+ } catch {
47745
+ return null;
47746
+ }
47747
+ }
47748
+ async function writeBudgetState(directory, state2) {
47749
+ const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
47750
+ const content = JSON.stringify(state2, null, 2);
47751
+ await Bun.write(resolvedPath, content);
47752
+ }
47753
+ async function countEvents(directory) {
47754
+ const content = await readSwarmFileAsync(directory, "events.jsonl");
47755
+ if (!content) {
47756
+ return 0;
47757
+ }
47758
+ const lines = content.split(`
47759
+ `).filter((line) => line.trim().length > 0);
47760
+ return lines.length;
47761
+ }
47762
+ async function getPlanCursorContent(directory) {
47763
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
47764
+ if (!planContent) {
47765
+ return "";
47766
+ }
47767
+ const lines = planContent.split(`
47768
+ `);
47769
+ const cursorLines = [];
47770
+ let inCurrentSection = false;
47771
+ for (const line of lines) {
47772
+ if (line.includes("in_progress") || line.includes("**Current**")) {
47773
+ inCurrentSection = true;
47774
+ }
47775
+ if (inCurrentSection) {
47776
+ cursorLines.push(line);
47777
+ if (cursorLines.length > 30) {
47778
+ break;
47779
+ }
47780
+ }
47781
+ }
47782
+ return cursorLines.join(`
47783
+ `) || planContent.substring(0, 1000);
47784
+ }
47785
+ async function getContextBudgetReport(directory, assembledSystemPrompt, config3) {
47786
+ validateDirectory(directory);
47787
+ const timestamp = new Date().toISOString();
47788
+ const systemPromptTokens = estimateTokens2(assembledSystemPrompt);
47789
+ const planCursorContent = await getPlanCursorContent(directory);
47790
+ const planCursorTokens = estimateTokens2(planCursorContent);
47791
+ const knowledgeContent = await readSwarmFileAsync(directory, "knowledge.jsonl");
47792
+ const knowledgeTokens = estimateTokens2(knowledgeContent || "");
47793
+ const runMemoryContent = await readSwarmFileAsync(directory, "run-memory.jsonl");
47794
+ const runMemoryTokens = estimateTokens2(runMemoryContent || "");
47795
+ const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
47796
+ const handoffTokens = estimateTokens2(handoffContent || "");
47797
+ const contextMdContent = await readSwarmFileAsync(directory, "context.md");
47798
+ const contextMdTokens = estimateTokens2(contextMdContent || "");
47799
+ const swarmTotalTokens = systemPromptTokens + planCursorTokens + knowledgeTokens + runMemoryTokens + handoffTokens + contextMdTokens;
47800
+ const estimatedTurnCount = await countEvents(directory);
47801
+ const budgetPct = swarmTotalTokens / config3.budgetTokens * 100;
47802
+ let status;
47803
+ let recommendation = null;
47804
+ if (budgetPct < config3.warningPct) {
47805
+ status = "ok";
47806
+ } else if (budgetPct < config3.criticalPct) {
47807
+ status = "warning";
47808
+ recommendation = "Consider wrapping up current phase and running /swarm handoff before starting new work.";
47809
+ } else {
47810
+ status = "critical";
47811
+ recommendation = "Run /swarm handoff and start a new session to avoid cost escalation.";
47812
+ }
47813
+ const estimatedSessionTokens = swarmTotalTokens * Math.max(1, estimatedTurnCount);
47814
+ return {
47815
+ timestamp,
47816
+ systemPromptTokens,
47817
+ planCursorTokens,
47818
+ knowledgeTokens,
47819
+ runMemoryTokens,
47820
+ handoffTokens,
47821
+ contextMdTokens,
47822
+ swarmTotalTokens,
47823
+ estimatedTurnCount,
47824
+ estimatedSessionTokens,
47825
+ budgetPct,
47826
+ status,
47827
+ recommendation
47828
+ };
47829
+ }
47830
+ async function formatBudgetWarning(report, directory, config3) {
47831
+ validateDirectory(directory);
47832
+ if (report.status === "ok") {
47833
+ return null;
47834
+ }
47835
+ if (!directory || directory.trim() === "") {
47836
+ return formatWarningMessage(report);
47837
+ }
47838
+ const budgetState = await readBudgetState(directory);
47839
+ const state2 = budgetState || {
47840
+ warningFiredAtTurn: null,
47841
+ criticalFiredAtTurn: null,
47842
+ lastInjectedAtTurn: null
47843
+ };
47844
+ const currentTurn = report.estimatedTurnCount;
47845
+ if (report.status === "warning") {
47846
+ if (config3.warningMode === "once" && state2.warningFiredAtTurn !== null) {
47847
+ return null;
47848
+ }
47849
+ if (config3.warningMode === "interval" && state2.warningFiredAtTurn !== null && currentTurn - state2.warningFiredAtTurn < config3.warningIntervalTurns) {
47850
+ return null;
47851
+ }
47852
+ state2.warningFiredAtTurn = currentTurn;
47853
+ state2.lastInjectedAtTurn = currentTurn;
47854
+ await writeBudgetState(directory, state2);
47855
+ } else if (report.status === "critical") {
47856
+ state2.criticalFiredAtTurn = currentTurn;
47857
+ state2.lastInjectedAtTurn = currentTurn;
47858
+ }
47859
+ return formatWarningMessage(report);
47860
+ }
47861
+ function formatWarningMessage(report) {
47862
+ const budgetPctStr = report.budgetPct.toFixed(1);
47863
+ const tokensPerTurn = report.swarmTotalTokens.toLocaleString();
47864
+ if (report.status === "warning") {
47865
+ return `[CONTEXT BUDGET: ${budgetPctStr}% \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Consider wrapping current phase and running /swarm handoff before starting new work.]`;
47866
+ }
47867
+ const costPerTurn = (report.swarmTotalTokens / 1000 * COST_PER_1K_TOKENS).toFixed(3);
47868
+ 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.]`;
47869
+ }
47870
+
47871
+ // src/services/status-service.ts
47581
47872
  async function getStatusData(directory, agents) {
47582
47873
  const plan = await loadPlan(directory);
47583
47874
  if (plan && plan.migration_status !== "migration_failed") {
@@ -47592,6 +47883,7 @@ async function getStatusData(directory, agents) {
47592
47883
  }
47593
47884
  }
47594
47885
  const agentCount2 = Object.keys(agents).length;
47886
+ const metrics2 = getCompactionMetrics();
47595
47887
  return {
47596
47888
  hasPlan: true,
47597
47889
  currentPhase: currentPhase2,
@@ -47599,11 +47891,15 @@ async function getStatusData(directory, agents) {
47599
47891
  totalTasks: totalTasks2,
47600
47892
  agentCount: agentCount2,
47601
47893
  isLegacy: false,
47602
- turboMode: hasActiveTurboMode()
47894
+ turboMode: hasActiveTurboMode(),
47895
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47896
+ compactionCount: metrics2.compactionCount,
47897
+ lastSnapshotAt: metrics2.lastSnapshotAt
47603
47898
  };
47604
47899
  }
47605
47900
  const planContent = await readSwarmFileAsync(directory, "plan.md");
47606
47901
  if (!planContent) {
47902
+ const metrics2 = getCompactionMetrics();
47607
47903
  return {
47608
47904
  hasPlan: false,
47609
47905
  currentPhase: "Unknown",
@@ -47611,7 +47907,10 @@ async function getStatusData(directory, agents) {
47611
47907
  totalTasks: 0,
47612
47908
  agentCount: Object.keys(agents).length,
47613
47909
  isLegacy: true,
47614
- turboMode: hasActiveTurboMode()
47910
+ turboMode: hasActiveTurboMode(),
47911
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47912
+ compactionCount: metrics2.compactionCount,
47913
+ lastSnapshotAt: metrics2.lastSnapshotAt
47615
47914
  };
47616
47915
  }
47617
47916
  const currentPhase = extractCurrentPhase(planContent) || "Unknown";
@@ -47619,6 +47918,7 @@ async function getStatusData(directory, agents) {
47619
47918
  const incompleteTasks = (planContent.match(/^- \[ \]/gm) || []).length;
47620
47919
  const totalTasks = completedTasks + incompleteTasks;
47621
47920
  const agentCount = Object.keys(agents).length;
47921
+ const metrics = getCompactionMetrics();
47622
47922
  return {
47623
47923
  hasPlan: true,
47624
47924
  currentPhase,
@@ -47626,7 +47926,10 @@ async function getStatusData(directory, agents) {
47626
47926
  totalTasks,
47627
47927
  agentCount,
47628
47928
  isLegacy: true,
47629
- turboMode: hasActiveTurboMode()
47929
+ turboMode: hasActiveTurboMode(),
47930
+ contextBudgetPct: swarmState.lastBudgetPct > 0 ? swarmState.lastBudgetPct : null,
47931
+ compactionCount: metrics.compactionCount,
47932
+ lastSnapshotAt: metrics.lastSnapshotAt
47630
47933
  };
47631
47934
  }
47632
47935
  function formatStatusMarkdown(status) {
@@ -47640,6 +47943,18 @@ function formatStatusMarkdown(status) {
47640
47943
  if (status.turboMode) {
47641
47944
  lines.push("", `**TURBO MODE**: active`);
47642
47945
  }
47946
+ if (status.contextBudgetPct !== null && status.contextBudgetPct > 0) {
47947
+ const pct = status.contextBudgetPct.toFixed(1);
47948
+ const budgetTokens = DEFAULT_CONTEXT_BUDGET_CONFIG.budgetTokens;
47949
+ const est = Math.round(status.contextBudgetPct / 100 * budgetTokens);
47950
+ lines.push("", `**Context**: ${pct}% used (est. ${est.toLocaleString()} / ${budgetTokens.toLocaleString()} tokens)`);
47951
+ if (status.compactionCount > 0) {
47952
+ lines.push(`**Compaction events**: ${status.compactionCount} triggered`);
47953
+ }
47954
+ if (status.lastSnapshotAt) {
47955
+ lines.push(`**Last snapshot**: ${status.lastSnapshotAt}`);
47956
+ }
47957
+ }
47643
47958
  return lines.join(`
47644
47959
  `);
47645
47960
  }
@@ -47734,6 +48049,132 @@ async function executeWriteRetro(args2, directory) {
47734
48049
  message: "Invalid task_count: must be a positive integer >= 1"
47735
48050
  }, null, 2);
47736
48051
  }
48052
+ if (!Number.isInteger(args2.total_tool_calls) || args2.total_tool_calls < 0) {
48053
+ return JSON.stringify({
48054
+ success: false,
48055
+ phase,
48056
+ message: "Invalid total_tool_calls: must be a non-negative integer"
48057
+ }, null, 2);
48058
+ }
48059
+ if (!Number.isInteger(args2.coder_revisions) || args2.coder_revisions < 0) {
48060
+ return JSON.stringify({
48061
+ success: false,
48062
+ phase,
48063
+ message: "Invalid coder_revisions: must be a non-negative integer"
48064
+ }, null, 2);
48065
+ }
48066
+ if (!Number.isInteger(args2.reviewer_rejections) || args2.reviewer_rejections < 0) {
48067
+ return JSON.stringify({
48068
+ success: false,
48069
+ phase,
48070
+ message: "Invalid reviewer_rejections: must be a non-negative integer"
48071
+ }, null, 2);
48072
+ }
48073
+ if (!Number.isInteger(args2.test_failures) || args2.test_failures < 0) {
48074
+ return JSON.stringify({
48075
+ success: false,
48076
+ phase,
48077
+ message: "Invalid test_failures: must be a non-negative integer"
48078
+ }, null, 2);
48079
+ }
48080
+ if (!Number.isInteger(args2.security_findings) || args2.security_findings < 0) {
48081
+ return JSON.stringify({
48082
+ success: false,
48083
+ phase,
48084
+ message: "Invalid security_findings: must be a non-negative integer"
48085
+ }, null, 2);
48086
+ }
48087
+ if (!Number.isInteger(args2.integration_issues) || args2.integration_issues < 0) {
48088
+ return JSON.stringify({
48089
+ success: false,
48090
+ phase,
48091
+ message: "Invalid integration_issues: must be a non-negative integer"
48092
+ }, null, 2);
48093
+ }
48094
+ if (args2.loop_detections !== undefined && (!Number.isInteger(args2.loop_detections) || args2.loop_detections < 0)) {
48095
+ return JSON.stringify({
48096
+ success: false,
48097
+ phase,
48098
+ message: "Invalid loop_detections: must be a non-negative integer"
48099
+ }, null, 2);
48100
+ }
48101
+ if (args2.circuit_breaker_trips !== undefined && (!Number.isInteger(args2.circuit_breaker_trips) || args2.circuit_breaker_trips < 0)) {
48102
+ return JSON.stringify({
48103
+ success: false,
48104
+ phase,
48105
+ message: "Invalid circuit_breaker_trips: must be a non-negative integer"
48106
+ }, null, 2);
48107
+ }
48108
+ if (args2.phase > 99) {
48109
+ return JSON.stringify({
48110
+ success: false,
48111
+ phase,
48112
+ message: "Invalid phase: must be <= 99"
48113
+ }, null, 2);
48114
+ }
48115
+ if (args2.task_count > 9999) {
48116
+ return JSON.stringify({
48117
+ success: false,
48118
+ phase,
48119
+ message: "Invalid task_count: must be <= 9999"
48120
+ }, null, 2);
48121
+ }
48122
+ if (args2.total_tool_calls > 9999) {
48123
+ return JSON.stringify({
48124
+ success: false,
48125
+ phase,
48126
+ message: "Invalid total_tool_calls: must be <= 9999"
48127
+ }, null, 2);
48128
+ }
48129
+ if (args2.coder_revisions > 999) {
48130
+ return JSON.stringify({
48131
+ success: false,
48132
+ phase,
48133
+ message: "Invalid coder_revisions: must be <= 999"
48134
+ }, null, 2);
48135
+ }
48136
+ if (args2.reviewer_rejections > 999) {
48137
+ return JSON.stringify({
48138
+ success: false,
48139
+ phase,
48140
+ message: "Invalid reviewer_rejections: must be <= 999"
48141
+ }, null, 2);
48142
+ }
48143
+ if (args2.loop_detections !== undefined && args2.loop_detections > 9999) {
48144
+ return JSON.stringify({
48145
+ success: false,
48146
+ phase,
48147
+ message: "Invalid loop_detections: must be <= 9999"
48148
+ }, null, 2);
48149
+ }
48150
+ if (args2.circuit_breaker_trips !== undefined && args2.circuit_breaker_trips > 9999) {
48151
+ return JSON.stringify({
48152
+ success: false,
48153
+ phase,
48154
+ message: "Invalid circuit_breaker_trips: must be <= 9999"
48155
+ }, null, 2);
48156
+ }
48157
+ if (args2.test_failures > 9999) {
48158
+ return JSON.stringify({
48159
+ success: false,
48160
+ phase,
48161
+ message: "Invalid test_failures: must be <= 9999"
48162
+ }, null, 2);
48163
+ }
48164
+ if (args2.security_findings > 999) {
48165
+ return JSON.stringify({
48166
+ success: false,
48167
+ phase,
48168
+ message: "Invalid security_findings: must be <= 999"
48169
+ }, null, 2);
48170
+ }
48171
+ if (args2.integration_issues > 999) {
48172
+ return JSON.stringify({
48173
+ success: false,
48174
+ phase,
48175
+ message: "Invalid integration_issues: must be <= 999"
48176
+ }, null, 2);
48177
+ }
47737
48178
  const summary = args2.summary;
47738
48179
  if (typeof summary !== "string" || summary.trim().length === 0) {
47739
48180
  return JSON.stringify({
@@ -47755,6 +48196,8 @@ async function executeWriteRetro(args2, directory) {
47755
48196
  total_tool_calls: args2.total_tool_calls,
47756
48197
  coder_revisions: args2.coder_revisions,
47757
48198
  reviewer_rejections: args2.reviewer_rejections,
48199
+ loop_detections: args2.loop_detections,
48200
+ circuit_breaker_trips: args2.circuit_breaker_trips,
47758
48201
  test_failures: args2.test_failures,
47759
48202
  security_findings: args2.security_findings,
47760
48203
  integration_issues: args2.integration_issues,
@@ -47784,16 +48227,18 @@ async function executeWriteRetro(args2, directory) {
47784
48227
  var write_retro = createSwarmTool({
47785
48228
  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.",
47786
48229
  args: {
47787
- phase: tool.schema.number().int().positive().describe("The phase number being completed (e.g., 1, 2, 3)"),
48230
+ phase: tool.schema.number().int().positive().max(99).describe("The phase number being completed (e.g., 1, 2, 3)"),
47788
48231
  summary: tool.schema.string().describe("Human-readable summary of the phase"),
47789
- task_count: tool.schema.number().int().min(1).describe("Count of tasks completed in this phase"),
48232
+ task_count: tool.schema.number().int().min(1).max(9999).describe("Count of tasks completed in this phase"),
47790
48233
  task_complexity: tool.schema.enum(["trivial", "simple", "moderate", "complex"]).describe("Complexity level of the completed tasks"),
47791
- total_tool_calls: tool.schema.number().int().min(0).describe("Total number of tool calls in this phase"),
47792
- coder_revisions: tool.schema.number().int().min(0).describe("Number of coder revisions made"),
47793
- reviewer_rejections: tool.schema.number().int().min(0).describe("Number of reviewer rejections received"),
47794
- test_failures: tool.schema.number().int().min(0).describe("Number of test failures encountered"),
47795
- security_findings: tool.schema.number().int().min(0).describe("Number of security findings"),
47796
- integration_issues: tool.schema.number().int().min(0).describe("Number of integration issues"),
48234
+ total_tool_calls: tool.schema.number().int().min(0).max(9999).describe("Total number of tool calls in this phase"),
48235
+ coder_revisions: tool.schema.number().int().min(0).max(999).describe("Number of coder revisions made"),
48236
+ reviewer_rejections: tool.schema.number().int().min(0).max(999).describe("Number of reviewer rejections received"),
48237
+ loop_detections: tool.schema.number().int().min(0).max(9999).optional().describe("Number of loop detection events in this phase"),
48238
+ circuit_breaker_trips: tool.schema.number().int().min(0).max(9999).optional().describe("Number of circuit breaker trips in this phase"),
48239
+ test_failures: tool.schema.number().int().min(0).max(9999).describe("Number of test failures encountered"),
48240
+ security_findings: tool.schema.number().int().min(0).max(999).describe("Number of security findings"),
48241
+ integration_issues: tool.schema.number().int().min(0).max(999).describe("Number of integration issues"),
47797
48242
  lessons_learned: tool.schema.array(tool.schema.string()).max(5).optional().describe("Key lessons learned from this phase (max 5)"),
47798
48243
  top_rejection_reasons: tool.schema.array(tool.schema.string()).optional().describe("Top reasons for reviewer rejections"),
47799
48244
  task_id: tool.schema.string().optional().describe("Optional custom task ID (defaults to retro-{phase})"),
@@ -47810,6 +48255,8 @@ var write_retro = createSwarmTool({
47810
48255
  total_tool_calls: Number(args2.total_tool_calls),
47811
48256
  coder_revisions: Number(args2.coder_revisions),
47812
48257
  reviewer_rejections: Number(args2.reviewer_rejections),
48258
+ loop_detections: args2.loop_detections != null ? Number(args2.loop_detections) : undefined,
48259
+ circuit_breaker_trips: args2.circuit_breaker_trips != null ? Number(args2.circuit_breaker_trips) : undefined,
47813
48260
  test_failures: Number(args2.test_failures),
47814
48261
  security_findings: Number(args2.security_findings),
47815
48262
  integration_issues: Number(args2.integration_issues),
@@ -48112,11 +48559,11 @@ async function doFlush(directory) {
48112
48559
  const activitySection = renderActivitySection();
48113
48560
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
48114
48561
  const flushedCount = swarmState.pendingEvents;
48115
- const path27 = `${directory}/.swarm/context.md`;
48116
- const tempPath = `${path27}.tmp`;
48562
+ const path28 = `${directory}/.swarm/context.md`;
48563
+ const tempPath = `${path28}.tmp`;
48117
48564
  try {
48118
48565
  await Bun.write(tempPath, updated);
48119
- renameSync6(tempPath, path27);
48566
+ renameSync6(tempPath, path28);
48120
48567
  } catch (writeError) {
48121
48568
  try {
48122
48569
  unlinkSync3(tempPath);
@@ -48686,15 +49133,57 @@ function maskToolOutput(msg, _threshold) {
48686
49133
  }
48687
49134
  // src/hooks/delegation-gate.ts
48688
49135
  init_schema();
48689
- import * as fs15 from "fs";
48690
- import * as path29 from "path";
49136
+ import * as fs16 from "fs";
49137
+ import * as path30 from "path";
48691
49138
 
48692
49139
  // src/hooks/guardrails.ts
48693
49140
  init_constants();
48694
49141
  init_schema();
48695
49142
  init_manager2();
48696
- import * as path27 from "path";
49143
+ import * as path28 from "path";
48697
49144
  init_utils();
49145
+
49146
+ // src/hooks/loop-detector.ts
49147
+ function hashDelegation(toolName, args2) {
49148
+ const targetAgent = typeof args2?.subagent_type === "string" ? args2.subagent_type : "unknown";
49149
+ const firstArgKey = args2 != null ? Object.keys(args2)[0] ?? "noargs" : "noargs";
49150
+ return `${toolName}:${targetAgent}:${firstArgKey}`;
49151
+ }
49152
+ function detectLoop(sessionId, toolName, args2) {
49153
+ if (toolName !== "Task") {
49154
+ return { looping: false, count: 0, pattern: "" };
49155
+ }
49156
+ const session = swarmState.agentSessions.get(sessionId);
49157
+ if (!session) {
49158
+ return { looping: false, count: 0, pattern: "" };
49159
+ }
49160
+ if (!session.loopDetectionWindow) {
49161
+ session.loopDetectionWindow = [];
49162
+ }
49163
+ const argsRecord = args2 != null && typeof args2 === "object" && !Array.isArray(args2) ? args2 : undefined;
49164
+ const hash3 = hashDelegation(toolName, argsRecord);
49165
+ const now = Date.now();
49166
+ session.loopDetectionWindow.push({ hash: hash3, timestamp: now });
49167
+ if (session.loopDetectionWindow.length > 10) {
49168
+ session.loopDetectionWindow.shift();
49169
+ }
49170
+ const window2 = session.loopDetectionWindow;
49171
+ let consecutiveCount = 0;
49172
+ for (let i2 = window2.length - 1;i2 >= 0; i2--) {
49173
+ if (window2[i2].hash === hash3) {
49174
+ consecutiveCount++;
49175
+ } else {
49176
+ break;
49177
+ }
49178
+ }
49179
+ return {
49180
+ looping: consecutiveCount >= 3,
49181
+ count: consecutiveCount,
49182
+ pattern: hash3
49183
+ };
49184
+ }
49185
+
49186
+ // src/hooks/guardrails.ts
48698
49187
  var storedInputArgs = new Map;
48699
49188
  function getStoredInputArgs(callID) {
48700
49189
  return storedInputArgs.get(callID);
@@ -48744,10 +49233,10 @@ function isArchitect(sessionId) {
48744
49233
  function isOutsideSwarmDir(filePath, directory) {
48745
49234
  if (!filePath)
48746
49235
  return false;
48747
- const swarmDir = path27.resolve(directory, ".swarm");
48748
- const resolved = path27.resolve(directory, filePath);
48749
- const relative4 = path27.relative(swarmDir, resolved);
48750
- return relative4.startsWith("..") || path27.isAbsolute(relative4);
49236
+ const swarmDir = path28.resolve(directory, ".swarm");
49237
+ const resolved = path28.resolve(directory, filePath);
49238
+ const relative4 = path28.relative(swarmDir, resolved);
49239
+ return relative4.startsWith("..") || path28.isAbsolute(relative4);
48751
49240
  }
48752
49241
  function isSourceCodePath(filePath) {
48753
49242
  if (!filePath)
@@ -48802,7 +49291,10 @@ function isAgentDelegation(toolName, args2) {
48802
49291
  }
48803
49292
  const subagentType = argsObj.subagent_type;
48804
49293
  if (typeof subagentType === "string") {
48805
- return { isDelegation: true, targetAgent: stripKnownSwarmPrefix(subagentType) };
49294
+ return {
49295
+ isDelegation: true,
49296
+ targetAgent: stripKnownSwarmPrefix(subagentType)
49297
+ };
48806
49298
  }
48807
49299
  return { isDelegation: false, targetAgent: null };
48808
49300
  }
@@ -48811,13 +49303,13 @@ function getCurrentTaskId(sessionId) {
48811
49303
  return session?.currentTaskId ?? `${sessionId}:unknown`;
48812
49304
  }
48813
49305
  function isInDeclaredScope(filePath, scopeEntries) {
48814
- const resolvedFile = path27.resolve(filePath);
49306
+ const resolvedFile = path28.resolve(filePath);
48815
49307
  return scopeEntries.some((scope) => {
48816
- const resolvedScope = path27.resolve(scope);
49308
+ const resolvedScope = path28.resolve(scope);
48817
49309
  if (resolvedFile === resolvedScope)
48818
49310
  return true;
48819
- const rel = path27.relative(resolvedScope, resolvedFile);
48820
- return rel.length > 0 && !rel.startsWith("..") && !path27.isAbsolute(rel);
49311
+ const rel = path28.relative(resolvedScope, resolvedFile);
49312
+ return rel.length > 0 && !rel.startsWith("..") && !path28.isAbsolute(rel);
48821
49313
  });
48822
49314
  }
48823
49315
  function createGuardrailsHooks(directoryOrConfig, config3) {
@@ -48869,13 +49361,44 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
48869
49361
  }
48870
49362
  }
48871
49363
  }
49364
+ if (input.tool === "Task") {
49365
+ const loopArgs = output.args;
49366
+ const loopResult = detectLoop(input.sessionID, input.tool, loopArgs);
49367
+ if (loopResult.count >= 5) {
49368
+ throw new Error(`CIRCUIT BREAKER: Delegation loop detected (${loopResult.count} identical patterns). Session paused. Ask the user for guidance.`);
49369
+ } else if (loopResult.count === 3) {
49370
+ const agentName2 = typeof loopArgs?.subagent_type === "string" ? loopArgs.subagent_type : "agent";
49371
+ const loopSession = swarmState.agentSessions.get(input.sessionID);
49372
+ if (loopSession) {
49373
+ loopSession.loopWarningPending = {
49374
+ agent: agentName2,
49375
+ 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.`,
49376
+ timestamp: Date.now()
49377
+ };
49378
+ }
49379
+ }
49380
+ }
49381
+ if (input.tool === "bash" || input.tool === "shell") {
49382
+ const bashArgs = output.args;
49383
+ const cmd = (typeof bashArgs?.command === "string" ? bashArgs.command : "").trim();
49384
+ const testRunnerPrefixPattern = /^(bun\s+test|npm\s+test|npx\s+vitest|bunx\s+vitest)\b/;
49385
+ if (testRunnerPrefixPattern.test(cmd)) {
49386
+ const tokens = cmd.split(/\s+/);
49387
+ const runnerTokenCount = tokens[0] === "npx" || tokens[0] === "bunx" ? 3 : 2;
49388
+ const remainingTokens = tokens.slice(runnerTokenCount);
49389
+ 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")));
49390
+ if (!hasFileArg) {
49391
+ 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");
49392
+ }
49393
+ }
49394
+ }
48872
49395
  if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
48873
49396
  const args2 = output.args;
48874
49397
  const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
48875
49398
  if (typeof targetPath === "string" && targetPath.length > 0) {
48876
- const resolvedTarget = path27.resolve(directory, targetPath).toLowerCase();
48877
- const planMdPath = path27.resolve(directory, ".swarm", "plan.md").toLowerCase();
48878
- const planJsonPath = path27.resolve(directory, ".swarm", "plan.json").toLowerCase();
49399
+ const resolvedTarget = path28.resolve(directory, targetPath).toLowerCase();
49400
+ const planMdPath = path28.resolve(directory, ".swarm", "plan.md").toLowerCase();
49401
+ const planJsonPath = path28.resolve(directory, ".swarm", "plan.json").toLowerCase();
48879
49402
  if (resolvedTarget === planMdPath || resolvedTarget === planJsonPath) {
48880
49403
  throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
48881
49404
  }
@@ -48924,9 +49447,9 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
48924
49447
  }
48925
49448
  }
48926
49449
  for (const p of paths) {
48927
- const resolvedP = path27.resolve(directory, p);
48928
- const planMdPath = path27.resolve(directory, ".swarm", "plan.md").toLowerCase();
48929
- const planJsonPath = path27.resolve(directory, ".swarm", "plan.json").toLowerCase();
49450
+ const resolvedP = path28.resolve(directory, p);
49451
+ const planMdPath = path28.resolve(directory, ".swarm", "plan.md").toLowerCase();
49452
+ const planJsonPath = path28.resolve(directory, ".swarm", "plan.json").toLowerCase();
48930
49453
  if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
48931
49454
  throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
48932
49455
  }
@@ -48946,7 +49469,7 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
48946
49469
  }
48947
49470
  }
48948
49471
  }
48949
- if (typeof targetPath === "string" && targetPath.length > 0 && isOutsideSwarmDir(targetPath, directory) && isSourceCodePath(path27.relative(directory, path27.resolve(directory, targetPath)))) {
49472
+ if (typeof targetPath === "string" && targetPath.length > 0 && isOutsideSwarmDir(targetPath, directory) && isSourceCodePath(path28.relative(directory, path28.resolve(directory, targetPath)))) {
48950
49473
  const session2 = swarmState.agentSessions.get(input.sessionID);
48951
49474
  if (session2) {
48952
49475
  session2.architectWriteCount++;
@@ -49213,6 +49736,45 @@ function createGuardrailsHooks(directoryOrConfig, config3) {
49213
49736
  const activeAgent = swarmState.activeAgent.get(sessionId);
49214
49737
  const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
49215
49738
  const systemMessages = messages.filter((msg) => msg.info?.role === "system");
49739
+ if (isArchitectSession && session?.loopWarningPending) {
49740
+ const pending = session.loopWarningPending;
49741
+ session.loopWarningPending = undefined;
49742
+ const loopSystemMsg = systemMessages[0];
49743
+ if (loopSystemMsg) {
49744
+ const loopTextPart = (loopSystemMsg.parts ?? []).find((part) => part.type === "text" && typeof part.text === "string");
49745
+ if (loopTextPart && !loopTextPart.text.includes("LOOP DETECTED")) {
49746
+ loopTextPart.text = `[LOOP WARNING]
49747
+ ${pending.message}
49748
+ [/LOOP WARNING]
49749
+
49750
+ ` + loopTextPart.text;
49751
+ }
49752
+ }
49753
+ }
49754
+ if (isArchitectSession && (session?.pendingAdvisoryMessages?.length ?? 0) > 0) {
49755
+ const advisories = session.pendingAdvisoryMessages;
49756
+ let targetMsg = systemMessages[0];
49757
+ if (!targetMsg) {
49758
+ const newMsg = {
49759
+ info: { role: "system" },
49760
+ parts: [{ type: "text", text: "" }]
49761
+ };
49762
+ messages.unshift(newMsg);
49763
+ targetMsg = newMsg;
49764
+ }
49765
+ const textPart2 = (targetMsg.parts ?? []).find((part) => part.type === "text" && typeof part.text === "string");
49766
+ if (textPart2) {
49767
+ const joined = advisories.join(`
49768
+ ---
49769
+ `);
49770
+ textPart2.text = `[ADVISORIES]
49771
+ ${joined}
49772
+ [/ADVISORIES]
49773
+
49774
+ ` + textPart2.text;
49775
+ }
49776
+ session.pendingAdvisoryMessages = [];
49777
+ }
49216
49778
  if (isArchitectSession && session && session.architectWriteCount > session.selfCodingWarnedAtCount) {
49217
49779
  let targetSystemMessage = systemMessages[0];
49218
49780
  if (!targetSystemMessage) {
@@ -49440,13 +50002,13 @@ function getEvidenceTaskId(session, directory) {
49440
50002
  if (typeof directory !== "string" || directory.length === 0) {
49441
50003
  return null;
49442
50004
  }
49443
- const resolvedDirectory = path29.resolve(directory);
49444
- const planPath = path29.join(resolvedDirectory, ".swarm", "plan.json");
49445
- const resolvedPlanPath = path29.resolve(planPath);
49446
- if (!resolvedPlanPath.startsWith(resolvedDirectory + path29.sep) && resolvedPlanPath !== resolvedDirectory) {
50005
+ const resolvedDirectory = path30.resolve(directory);
50006
+ const planPath = path30.join(resolvedDirectory, ".swarm", "plan.json");
50007
+ const resolvedPlanPath = path30.resolve(planPath);
50008
+ if (!resolvedPlanPath.startsWith(resolvedDirectory + path30.sep) && resolvedPlanPath !== resolvedDirectory) {
49447
50009
  return null;
49448
50010
  }
49449
- const planContent = fs15.readFileSync(resolvedPlanPath, "utf-8");
50011
+ const planContent = fs16.readFileSync(resolvedPlanPath, "utf-8");
49450
50012
  const plan = JSON.parse(planContent);
49451
50013
  if (!plan || !Array.isArray(plan.phases)) {
49452
50014
  return null;
@@ -49504,23 +50066,23 @@ function createDelegationGateHook(config3, directory) {
49504
50066
  if (targetAgent === "test_engineer")
49505
50067
  hasTestEngineer = true;
49506
50068
  if (targetAgent === "reviewer" && session.taskWorkflowStates) {
49507
- for (const [taskId, state] of session.taskWorkflowStates) {
49508
- if (state === "coder_delegated" || state === "pre_check_passed") {
50069
+ for (const [taskId, state2] of session.taskWorkflowStates) {
50070
+ if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
49509
50071
  try {
49510
50072
  advanceTaskState(session, taskId, "reviewer_run");
49511
50073
  } catch (err2) {
49512
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50074
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49513
50075
  }
49514
50076
  }
49515
50077
  }
49516
50078
  }
49517
50079
  if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
49518
- for (const [taskId, state] of session.taskWorkflowStates) {
49519
- if (state === "reviewer_run") {
50080
+ for (const [taskId, state2] of session.taskWorkflowStates) {
50081
+ if (state2 === "reviewer_run") {
49520
50082
  try {
49521
50083
  advanceTaskState(session, taskId, "tests_run");
49522
50084
  } catch (err2) {
49523
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50085
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49524
50086
  }
49525
50087
  }
49526
50088
  }
@@ -49536,12 +50098,12 @@ function createDelegationGateHook(config3, directory) {
49536
50098
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
49537
50099
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
49538
50100
  }
49539
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
49540
- if (state === "coder_delegated" || state === "pre_check_passed") {
50101
+ for (const [taskId, state2] of otherSession.taskWorkflowStates) {
50102
+ if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
49541
50103
  try {
49542
50104
  advanceTaskState(otherSession, taskId, "reviewer_run");
49543
50105
  } catch (err2) {
49544
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50106
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49545
50107
  }
49546
50108
  }
49547
50109
  }
@@ -49551,12 +50113,12 @@ function createDelegationGateHook(config3, directory) {
49551
50113
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
49552
50114
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
49553
50115
  }
49554
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
49555
- if (state === "reviewer_run") {
50116
+ for (const [taskId, state2] of otherSession.taskWorkflowStates) {
50117
+ if (state2 === "reviewer_run") {
49556
50118
  try {
49557
50119
  advanceTaskState(otherSession, taskId, "tests_run");
49558
50120
  } catch (err2) {
49559
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50121
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49560
50122
  }
49561
50123
  }
49562
50124
  }
@@ -49620,23 +50182,23 @@ function createDelegationGateHook(config3, directory) {
49620
50182
  session.qaSkipTaskIds = [];
49621
50183
  }
49622
50184
  if (hasReviewer && session.taskWorkflowStates) {
49623
- for (const [taskId, state] of session.taskWorkflowStates) {
49624
- if (state === "coder_delegated" || state === "pre_check_passed") {
50185
+ for (const [taskId, state2] of session.taskWorkflowStates) {
50186
+ if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
49625
50187
  try {
49626
50188
  advanceTaskState(session, taskId, "reviewer_run");
49627
50189
  } catch (err2) {
49628
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50190
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49629
50191
  }
49630
50192
  }
49631
50193
  }
49632
50194
  }
49633
50195
  if (hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
49634
- for (const [taskId, state] of session.taskWorkflowStates) {
49635
- if (state === "reviewer_run") {
50196
+ for (const [taskId, state2] of session.taskWorkflowStates) {
50197
+ if (state2 === "reviewer_run") {
49636
50198
  try {
49637
50199
  advanceTaskState(session, taskId, "tests_run");
49638
50200
  } catch (err2) {
49639
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50201
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49640
50202
  }
49641
50203
  }
49642
50204
  }
@@ -49651,12 +50213,12 @@ function createDelegationGateHook(config3, directory) {
49651
50213
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
49652
50214
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
49653
50215
  }
49654
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
49655
- if (state === "coder_delegated" || state === "pre_check_passed") {
50216
+ for (const [taskId, state2] of otherSession.taskWorkflowStates) {
50217
+ if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
49656
50218
  try {
49657
50219
  advanceTaskState(otherSession, taskId, "reviewer_run");
49658
50220
  } catch (err2) {
49659
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50221
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49660
50222
  }
49661
50223
  }
49662
50224
  }
@@ -49672,12 +50234,12 @@ function createDelegationGateHook(config3, directory) {
49672
50234
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
49673
50235
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
49674
50236
  }
49675
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
49676
- if (state === "reviewer_run") {
50237
+ for (const [taskId, state2] of otherSession.taskWorkflowStates) {
50238
+ if (state2 === "reviewer_run") {
49677
50239
  try {
49678
50240
  advanceTaskState(otherSession, taskId, "tests_run");
49679
50241
  } catch (err2) {
49680
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
50242
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
49681
50243
  }
49682
50244
  }
49683
50245
  }
@@ -49923,7 +50485,7 @@ ${warningLines.join(`
49923
50485
  }
49924
50486
  // src/hooks/delegation-sanitizer.ts
49925
50487
  init_utils2();
49926
- import * as fs16 from "fs";
50488
+ import * as fs17 from "fs";
49927
50489
  var SANITIZATION_PATTERNS = [
49928
50490
  /\b\d+(st|nd|rd|th)\s+(attempt|try|time)\b/gi,
49929
50491
  /\b(5th|fifth|final|last)\s+attempt\b/gi,
@@ -49994,7 +50556,7 @@ function createDelegationSanitizerHook(directory) {
49994
50556
  stripped_patterns: result.stripped,
49995
50557
  timestamp: new Date().toISOString()
49996
50558
  };
49997
- fs16.appendFileSync(eventsPath, `${JSON.stringify(event)}
50559
+ fs17.appendFileSync(eventsPath, `${JSON.stringify(event)}
49998
50560
  `, "utf-8");
49999
50561
  } catch {}
50000
50562
  }
@@ -50244,13 +50806,13 @@ init_schema();
50244
50806
  init_manager();
50245
50807
  init_detector();
50246
50808
  init_manager2();
50247
- import * as fs18 from "fs";
50809
+ import * as fs19 from "fs";
50248
50810
 
50249
50811
  // src/services/decision-drift-analyzer.ts
50250
50812
  init_utils2();
50251
50813
  init_manager2();
50252
- import * as fs17 from "fs";
50253
- import * as path30 from "path";
50814
+ import * as fs18 from "fs";
50815
+ import * as path31 from "path";
50254
50816
  var DEFAULT_DRIFT_CONFIG = {
50255
50817
  staleThresholdPhases: 1,
50256
50818
  detectContradictions: true,
@@ -50404,11 +50966,11 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
50404
50966
  currentPhase = legacyPhase;
50405
50967
  }
50406
50968
  }
50407
- const contextPath = path30.join(directory, ".swarm", "context.md");
50969
+ const contextPath = path31.join(directory, ".swarm", "context.md");
50408
50970
  let contextContent = "";
50409
50971
  try {
50410
- if (fs17.existsSync(contextPath)) {
50411
- contextContent = fs17.readFileSync(contextPath, "utf-8");
50972
+ if (fs18.existsSync(contextPath)) {
50973
+ contextContent = fs18.readFileSync(contextPath, "utf-8");
50412
50974
  }
50413
50975
  } catch {
50414
50976
  return {
@@ -50516,165 +51078,6 @@ function formatDriftForContext(result) {
50516
51078
 
50517
51079
  // src/services/index.ts
50518
51080
  init_config_doctor();
50519
-
50520
- // src/services/context-budget-service.ts
50521
- init_utils2();
50522
- function validateDirectory(directory) {
50523
- if (!directory || directory.trim() === "") {
50524
- throw new Error("Invalid directory: empty");
50525
- }
50526
- if (/\.\.[/\\]/.test(directory)) {
50527
- throw new Error("Invalid directory: path traversal detected");
50528
- }
50529
- if (directory.startsWith("/") || directory.startsWith("\\")) {
50530
- throw new Error("Invalid directory: absolute path");
50531
- }
50532
- if (/^[A-Za-z]:[\\/]/.test(directory)) {
50533
- throw new Error("Invalid directory: Windows absolute path");
50534
- }
50535
- }
50536
- var COST_PER_1K_TOKENS = 0.003;
50537
- function estimateTokens2(text) {
50538
- if (!text || typeof text !== "string") {
50539
- return 0;
50540
- }
50541
- return Math.ceil(text.length / 3.5);
50542
- }
50543
- async function readBudgetState(directory) {
50544
- const content = await readSwarmFileAsync(directory, "session/budget-state.json");
50545
- if (!content) {
50546
- return null;
50547
- }
50548
- try {
50549
- return JSON.parse(content);
50550
- } catch {
50551
- return null;
50552
- }
50553
- }
50554
- async function writeBudgetState(directory, state) {
50555
- const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
50556
- const content = JSON.stringify(state, null, 2);
50557
- await Bun.write(resolvedPath, content);
50558
- }
50559
- async function countEvents(directory) {
50560
- const content = await readSwarmFileAsync(directory, "events.jsonl");
50561
- if (!content) {
50562
- return 0;
50563
- }
50564
- const lines = content.split(`
50565
- `).filter((line) => line.trim().length > 0);
50566
- return lines.length;
50567
- }
50568
- async function getPlanCursorContent(directory) {
50569
- const planContent = await readSwarmFileAsync(directory, "plan.md");
50570
- if (!planContent) {
50571
- return "";
50572
- }
50573
- const lines = planContent.split(`
50574
- `);
50575
- const cursorLines = [];
50576
- let inCurrentSection = false;
50577
- for (const line of lines) {
50578
- if (line.includes("in_progress") || line.includes("**Current**")) {
50579
- inCurrentSection = true;
50580
- }
50581
- if (inCurrentSection) {
50582
- cursorLines.push(line);
50583
- if (cursorLines.length > 30) {
50584
- break;
50585
- }
50586
- }
50587
- }
50588
- return cursorLines.join(`
50589
- `) || planContent.substring(0, 1000);
50590
- }
50591
- async function getContextBudgetReport(directory, assembledSystemPrompt, config3) {
50592
- validateDirectory(directory);
50593
- const timestamp = new Date().toISOString();
50594
- const systemPromptTokens = estimateTokens2(assembledSystemPrompt);
50595
- const planCursorContent = await getPlanCursorContent(directory);
50596
- const planCursorTokens = estimateTokens2(planCursorContent);
50597
- const knowledgeContent = await readSwarmFileAsync(directory, "knowledge.jsonl");
50598
- const knowledgeTokens = estimateTokens2(knowledgeContent || "");
50599
- const runMemoryContent = await readSwarmFileAsync(directory, "run-memory.jsonl");
50600
- const runMemoryTokens = estimateTokens2(runMemoryContent || "");
50601
- const handoffContent = await readSwarmFileAsync(directory, "handoff.md");
50602
- const handoffTokens = estimateTokens2(handoffContent || "");
50603
- const contextMdContent = await readSwarmFileAsync(directory, "context.md");
50604
- const contextMdTokens = estimateTokens2(contextMdContent || "");
50605
- const swarmTotalTokens = systemPromptTokens + planCursorTokens + knowledgeTokens + runMemoryTokens + handoffTokens + contextMdTokens;
50606
- const estimatedTurnCount = await countEvents(directory);
50607
- const budgetPct = swarmTotalTokens / config3.budgetTokens * 100;
50608
- let status;
50609
- let recommendation = null;
50610
- if (budgetPct < config3.warningPct) {
50611
- status = "ok";
50612
- } else if (budgetPct < config3.criticalPct) {
50613
- status = "warning";
50614
- recommendation = "Consider wrapping up current phase and running /swarm handoff before starting new work.";
50615
- } else {
50616
- status = "critical";
50617
- recommendation = "Run /swarm handoff and start a new session to avoid cost escalation.";
50618
- }
50619
- const estimatedSessionTokens = swarmTotalTokens * Math.max(1, estimatedTurnCount);
50620
- return {
50621
- timestamp,
50622
- systemPromptTokens,
50623
- planCursorTokens,
50624
- knowledgeTokens,
50625
- runMemoryTokens,
50626
- handoffTokens,
50627
- contextMdTokens,
50628
- swarmTotalTokens,
50629
- estimatedTurnCount,
50630
- estimatedSessionTokens,
50631
- budgetPct,
50632
- status,
50633
- recommendation
50634
- };
50635
- }
50636
- async function formatBudgetWarning(report, directory, config3) {
50637
- validateDirectory(directory);
50638
- if (report.status === "ok") {
50639
- return null;
50640
- }
50641
- if (!directory || directory.trim() === "") {
50642
- return formatWarningMessage(report);
50643
- }
50644
- const budgetState = await readBudgetState(directory);
50645
- const state = budgetState || {
50646
- warningFiredAtTurn: null,
50647
- criticalFiredAtTurn: null,
50648
- lastInjectedAtTurn: null
50649
- };
50650
- const currentTurn = report.estimatedTurnCount;
50651
- if (report.status === "warning") {
50652
- if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
50653
- return null;
50654
- }
50655
- if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
50656
- return null;
50657
- }
50658
- state.warningFiredAtTurn = currentTurn;
50659
- state.lastInjectedAtTurn = currentTurn;
50660
- await writeBudgetState(directory, state);
50661
- } else if (report.status === "critical") {
50662
- state.criticalFiredAtTurn = currentTurn;
50663
- state.lastInjectedAtTurn = currentTurn;
50664
- }
50665
- return formatWarningMessage(report);
50666
- }
50667
- function formatWarningMessage(report) {
50668
- const budgetPctStr = report.budgetPct.toFixed(1);
50669
- const tokensPerTurn = report.swarmTotalTokens.toLocaleString();
50670
- if (report.status === "warning") {
50671
- return `[CONTEXT BUDGET: ${budgetPctStr}% \u2014 swarm injecting ~${tokensPerTurn} tokens/turn. Consider wrapping current phase and running /swarm handoff before starting new work.]`;
50672
- }
50673
- const costPerTurn = (report.swarmTotalTokens / 1000 * COST_PER_1K_TOKENS).toFixed(3);
50674
- 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.]`;
50675
- }
50676
-
50677
- // src/services/index.ts
50678
51081
  init_evidence_summary_service();
50679
51082
  init_preflight_integration();
50680
51083
  init_preflight_service();
@@ -51061,11 +51464,11 @@ function createSystemEnhancerHook(config3, directory) {
51061
51464
  if (handoffContent) {
51062
51465
  const handoffPath = validateSwarmPath(directory, "handoff.md");
51063
51466
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
51064
- if (fs18.existsSync(consumedPath)) {
51467
+ if (fs19.existsSync(consumedPath)) {
51065
51468
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
51066
- fs18.unlinkSync(consumedPath);
51469
+ fs19.unlinkSync(consumedPath);
51067
51470
  }
51068
- fs18.renameSync(handoffPath, consumedPath);
51471
+ fs19.renameSync(handoffPath, consumedPath);
51069
51472
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
51070
51473
  The previous model's session ended. Here is your starting context:
51071
51474
 
@@ -51261,6 +51664,7 @@ ${handoffBlock}`);
51261
51664
  const assembledSystemPrompt = output.system.join(`
51262
51665
  `);
51263
51666
  const budgetReport = await getContextBudgetReport(directory, assembledSystemPrompt, contextBudgetConfig);
51667
+ swarmState.lastBudgetPct = budgetReport.budgetPct;
51264
51668
  const budgetWarning = await formatBudgetWarning(budgetReport, directory, contextBudgetConfig);
51265
51669
  if (budgetWarning) {
51266
51670
  const sessionId_cb = _input.sessionID;
@@ -51344,11 +51748,11 @@ ${budgetWarning}`);
51344
51748
  if (handoffContent) {
51345
51749
  const handoffPath = validateSwarmPath(directory, "handoff.md");
51346
51750
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
51347
- if (fs18.existsSync(consumedPath)) {
51751
+ if (fs19.existsSync(consumedPath)) {
51348
51752
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
51349
- fs18.unlinkSync(consumedPath);
51753
+ fs19.unlinkSync(consumedPath);
51350
51754
  }
51351
- fs18.renameSync(handoffPath, consumedPath);
51755
+ fs19.renameSync(handoffPath, consumedPath);
51352
51756
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
51353
51757
  The previous model's session ended. Here is your starting context:
51354
51758
 
@@ -51662,6 +52066,7 @@ ${handoffBlock}`;
51662
52066
  const assembledSystemPrompt_b = output.system.join(`
51663
52067
  `);
51664
52068
  const budgetReport_b = await getContextBudgetReport(directory, assembledSystemPrompt_b, contextBudgetConfig_b);
52069
+ swarmState.lastBudgetPct = budgetReport_b.budgetPct;
51665
52070
  const budgetWarning_b = await formatBudgetWarning(budgetReport_b, directory, contextBudgetConfig_b);
51666
52071
  if (budgetWarning_b) {
51667
52072
  const sessionId_cb_b = _input.sessionID;
@@ -52024,10 +52429,92 @@ function createDarkMatterDetectorHook(directory) {
52024
52429
  return safeHook(hook);
52025
52430
  }
52026
52431
 
52432
+ // src/hooks/incremental-verify.ts
52433
+ import * as fs20 from "fs";
52434
+ import * as path32 from "path";
52435
+ function detectTypecheckCommand(projectDir) {
52436
+ const pkgPath = path32.join(projectDir, "package.json");
52437
+ if (!fs20.existsSync(pkgPath))
52438
+ return null;
52439
+ try {
52440
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf8"));
52441
+ const scripts = pkg.scripts;
52442
+ if (scripts?.typecheck)
52443
+ return ["bun", "run", "typecheck"];
52444
+ if (scripts?.["type-check"])
52445
+ return ["bun", "run", "type-check"];
52446
+ const deps = {
52447
+ ...pkg.dependencies,
52448
+ ...pkg.devDependencies
52449
+ };
52450
+ if (!deps?.typescript && !fs20.existsSync(path32.join(projectDir, "tsconfig.json"))) {
52451
+ return null;
52452
+ }
52453
+ return ["npx", "tsc", "--noEmit"];
52454
+ } catch {
52455
+ return null;
52456
+ }
52457
+ }
52458
+ async function runWithTimeout(command, cwd, timeoutMs) {
52459
+ try {
52460
+ const proc = Bun.spawn(command, {
52461
+ cwd,
52462
+ stdout: "pipe",
52463
+ stderr: "pipe"
52464
+ });
52465
+ const timeoutHandle = setTimeout(() => {
52466
+ try {
52467
+ proc.kill();
52468
+ } catch {}
52469
+ }, timeoutMs);
52470
+ try {
52471
+ const [exitCode, stderr] = await Promise.all([
52472
+ proc.exited,
52473
+ new Response(proc.stderr).text()
52474
+ ]);
52475
+ return { exitCode, stderr };
52476
+ } finally {
52477
+ clearTimeout(timeoutHandle);
52478
+ }
52479
+ } catch {
52480
+ return null;
52481
+ }
52482
+ }
52483
+ function createIncrementalVerifyHook(config3, projectDir, injectMessage) {
52484
+ return {
52485
+ toolAfter: async (input, output) => {
52486
+ if (!config3.enabled)
52487
+ return;
52488
+ if (input.tool !== "Task")
52489
+ return;
52490
+ const args2 = input.args ?? output.args;
52491
+ const subagentType = typeof args2?.subagent_type === "string" ? args2.subagent_type : "";
52492
+ const agentName = subagentType.replace(/^[^_]+_/, "");
52493
+ if (!config3.triggerAgents.includes(agentName) && !config3.triggerAgents.includes(subagentType)) {
52494
+ return;
52495
+ }
52496
+ const command = config3.command != null ? config3.command.split(" ") : detectTypecheckCommand(projectDir);
52497
+ if (!command)
52498
+ return;
52499
+ const result = await runWithTimeout(command, projectDir, config3.timeoutMs);
52500
+ if (result === null) {
52501
+ return;
52502
+ }
52503
+ if (result.exitCode === 0) {
52504
+ injectMessage(input.sessionID, "POST-CODER CHECK PASSED: No type errors.");
52505
+ } else {
52506
+ const errorSummary = result.stderr.slice(0, 800);
52507
+ injectMessage(input.sessionID, `POST-CODER CHECK FAILED: Type errors detected after coder delegation. Address these before proceeding.
52508
+ ${errorSummary}`);
52509
+ }
52510
+ }
52511
+ };
52512
+ }
52513
+
52027
52514
  // src/hooks/knowledge-reader.ts
52028
- import { existsSync as existsSync18 } from "fs";
52515
+ import { existsSync as existsSync19 } from "fs";
52029
52516
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
52030
- import * as path31 from "path";
52517
+ import * as path33 from "path";
52031
52518
  var JACCARD_THRESHOLD = 0.6;
52032
52519
  var HIVE_TIER_BOOST = 0.05;
52033
52520
  var SAME_PROJECT_PENALTY = -0.05;
@@ -52075,15 +52562,15 @@ function inferCategoriesFromPhase(phaseDescription) {
52075
52562
  return ["process", "tooling"];
52076
52563
  }
52077
52564
  async function recordLessonsShown(directory, lessonIds, currentPhase) {
52078
- const shownFile = path31.join(directory, ".swarm", ".knowledge-shown.json");
52565
+ const shownFile = path33.join(directory, ".swarm", ".knowledge-shown.json");
52079
52566
  try {
52080
52567
  let shownData = {};
52081
- if (existsSync18(shownFile)) {
52568
+ if (existsSync19(shownFile)) {
52082
52569
  const content = await readFile5(shownFile, "utf-8");
52083
52570
  shownData = JSON.parse(content);
52084
52571
  }
52085
52572
  shownData[currentPhase] = lessonIds;
52086
- await mkdir4(path31.dirname(shownFile), { recursive: true });
52573
+ await mkdir4(path33.dirname(shownFile), { recursive: true });
52087
52574
  await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
52088
52575
  } catch {
52089
52576
  console.warn("[swarm] Knowledge: failed to record shown lessons");
@@ -52178,9 +52665,9 @@ async function readMergedKnowledge(directory, config3, context) {
52178
52665
  return topN;
52179
52666
  }
52180
52667
  async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
52181
- const shownFile = path31.join(directory, ".swarm", ".knowledge-shown.json");
52668
+ const shownFile = path33.join(directory, ".swarm", ".knowledge-shown.json");
52182
52669
  try {
52183
- if (!existsSync18(shownFile)) {
52670
+ if (!existsSync19(shownFile)) {
52184
52671
  return;
52185
52672
  }
52186
52673
  const content = await readFile5(shownFile, "utf-8");
@@ -52650,12 +53137,12 @@ Use this data to avoid repeating known failure patterns.`;
52650
53137
  // src/hooks/curator-drift.ts
52651
53138
  init_event_bus();
52652
53139
  init_utils2();
52653
- import * as fs19 from "fs";
52654
- import * as path32 from "path";
53140
+ import * as fs21 from "fs";
53141
+ import * as path34 from "path";
52655
53142
  var DRIFT_REPORT_PREFIX = "drift-report-phase-";
52656
53143
  async function readPriorDriftReports(directory) {
52657
- const swarmDir = path32.join(directory, ".swarm");
52658
- const entries = await fs19.promises.readdir(swarmDir).catch(() => null);
53144
+ const swarmDir = path34.join(directory, ".swarm");
53145
+ const entries = await fs21.promises.readdir(swarmDir).catch(() => null);
52659
53146
  if (entries === null)
52660
53147
  return [];
52661
53148
  const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
@@ -52681,10 +53168,10 @@ async function readPriorDriftReports(directory) {
52681
53168
  async function writeDriftReport(directory, report) {
52682
53169
  const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
52683
53170
  const filePath = validateSwarmPath(directory, filename);
52684
- const swarmDir = path32.dirname(filePath);
52685
- await fs19.promises.mkdir(swarmDir, { recursive: true });
53171
+ const swarmDir = path34.dirname(filePath);
53172
+ await fs21.promises.mkdir(swarmDir, { recursive: true });
52686
53173
  try {
52687
- await fs19.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
53174
+ await fs21.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
52688
53175
  } catch (err2) {
52689
53176
  throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
52690
53177
  }
@@ -52934,9 +53421,143 @@ ${cachedInjectionText}`;
52934
53421
  });
52935
53422
  }
52936
53423
 
53424
+ // src/hooks/slop-detector.ts
53425
+ var WRITE_EDIT_TOOLS = new Set([
53426
+ "write",
53427
+ "edit",
53428
+ "apply_patch",
53429
+ "create_file"
53430
+ ]);
53431
+ function countMatches(text, pattern) {
53432
+ return (text.match(pattern) ?? []).length;
53433
+ }
53434
+ function checkAbstractionBloat(content, threshold) {
53435
+ const newClasses = countMatches(content, /^\+.*\bclass\s+\w+/gm);
53436
+ if (newClasses >= threshold) {
53437
+ return {
53438
+ type: "abstraction_bloat",
53439
+ detail: `${newClasses} new class declarations added (threshold: ${threshold}). Consider whether all abstractions are necessary.`
53440
+ };
53441
+ }
53442
+ return null;
53443
+ }
53444
+ function checkCommentStrip(content, threshold) {
53445
+ const removedComments = countMatches(content, /^-\s*\/[/*]/gm);
53446
+ const addedComments = countMatches(content, /^\+\s*\/[/*]/gm);
53447
+ if (removedComments >= threshold && addedComments === 0) {
53448
+ return {
53449
+ type: "comment_strip",
53450
+ detail: `${removedComments} comment lines removed and 0 added. Verify comments were not documenting important behaviour.`
53451
+ };
53452
+ }
53453
+ return null;
53454
+ }
53455
+ function checkBoilerplateExplosion(content, taskDescription, threshold) {
53456
+ const addedLines = countMatches(content, /^\+[^+]/gm);
53457
+ const isSmallTask = /\b(fix|patch|update|tweak|adjust|correct|remove|rename|change)\b/i.test(taskDescription);
53458
+ if (isSmallTask && addedLines >= threshold) {
53459
+ return {
53460
+ type: "boilerplate_explosion",
53461
+ detail: `${addedLines} lines added for a "${taskDescription.slice(0, 40)}" task (threshold: ${threshold}). Review for scope creep.`
53462
+ };
53463
+ }
53464
+ return null;
53465
+ }
53466
+ async function checkDeadExports(content, projectDir, startTime) {
53467
+ const exportMatches = content.matchAll(/^(?:export)\s+(?:function|class|const|type|interface)\s+(\w{3,})/gm);
53468
+ const newExports = [];
53469
+ for (const match of exportMatches) {
53470
+ if (match[1])
53471
+ newExports.push(match[1]);
53472
+ }
53473
+ if (newExports.length === 0)
53474
+ return null;
53475
+ const deadExports = [];
53476
+ for (const name2 of newExports) {
53477
+ if (Date.now() - startTime > 480)
53478
+ break;
53479
+ try {
53480
+ const importPattern = new RegExp(`\\bimport\\b[^;]*\\b${name2}\\b`, "g");
53481
+ const glob = new Bun.Glob(`src/**/*.ts`);
53482
+ let found = false;
53483
+ for await (const file3 of glob.scan(projectDir)) {
53484
+ if (found || Date.now() - startTime > 480)
53485
+ break;
53486
+ try {
53487
+ const text = await Bun.file(`${projectDir}/${file3}`).text();
53488
+ if (importPattern.test(text))
53489
+ found = true;
53490
+ importPattern.lastIndex = 0;
53491
+ } catch {}
53492
+ }
53493
+ if (!found)
53494
+ deadExports.push(name2);
53495
+ } catch {}
53496
+ }
53497
+ if (deadExports.length === 0)
53498
+ return null;
53499
+ return {
53500
+ type: "dead_export",
53501
+ detail: `New exports not found in any import: ${deadExports.slice(0, 3).join(", ")}. Verify these are intentionally exported.`
53502
+ };
53503
+ }
53504
+ function createSlopDetectorHook(config3, projectDir, injectSystemMessage) {
53505
+ return {
53506
+ toolAfter: async (input, output) => {
53507
+ if (!config3.enabled)
53508
+ return;
53509
+ if (!WRITE_EDIT_TOOLS.has(input.tool.toLowerCase()))
53510
+ return;
53511
+ const args2 = output.args;
53512
+ const content = (() => {
53513
+ if (typeof args2?.content === "string")
53514
+ return args2.content;
53515
+ if (typeof args2?.newString === "string")
53516
+ return args2.newString;
53517
+ if (typeof args2?.patch === "string")
53518
+ return args2.patch;
53519
+ if (typeof args2?.file_text === "string")
53520
+ return args2.file_text;
53521
+ return "";
53522
+ })();
53523
+ if (!content || content.length < 10)
53524
+ return;
53525
+ const taskDescription = typeof args2?.description === "string" ? args2.description : typeof args2?.task === "string" ? args2.task : "";
53526
+ const startTime = Date.now();
53527
+ const findings = [];
53528
+ try {
53529
+ const bloat = checkAbstractionBloat(content, config3.classThreshold);
53530
+ if (bloat)
53531
+ findings.push(bloat);
53532
+ const strip = checkCommentStrip(content, config3.commentStripThreshold);
53533
+ if (strip)
53534
+ findings.push(strip);
53535
+ const explosion = checkBoilerplateExplosion(content, taskDescription, config3.diffLineThreshold);
53536
+ if (explosion)
53537
+ findings.push(explosion);
53538
+ } catch {}
53539
+ if (Date.now() - startTime < 400) {
53540
+ try {
53541
+ const dead = await checkDeadExports(content, projectDir, startTime);
53542
+ if (dead)
53543
+ findings.push(dead);
53544
+ } catch {}
53545
+ }
53546
+ if (findings.length === 0)
53547
+ return;
53548
+ const findingText = findings.map((f) => ` \u2022 ${f.type}: ${f.detail}`).join(`
53549
+ `);
53550
+ const message = `SLOP CHECK: ${findings.length} potential issue(s) detected after ${input.tool}:
53551
+ ${findingText}
53552
+ Review before proceeding.`;
53553
+ injectSystemMessage(input.sessionID, message);
53554
+ }
53555
+ };
53556
+ }
53557
+
52937
53558
  // src/hooks/steering-consumed.ts
52938
53559
  init_utils2();
52939
- import * as fs20 from "fs";
53560
+ import * as fs22 from "fs";
52940
53561
  function recordSteeringConsumed(directory, directiveId) {
52941
53562
  try {
52942
53563
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -52945,7 +53566,7 @@ function recordSteeringConsumed(directory, directiveId) {
52945
53566
  directiveId,
52946
53567
  timestamp: new Date().toISOString()
52947
53568
  };
52948
- fs20.appendFileSync(eventsPath, `${JSON.stringify(event)}
53569
+ fs22.appendFileSync(eventsPath, `${JSON.stringify(event)}
52949
53570
  `, "utf-8");
52950
53571
  } catch {}
52951
53572
  }
@@ -52990,7 +53611,7 @@ init_config_doctor();
52990
53611
 
52991
53612
  // src/session/snapshot-reader.ts
52992
53613
  init_utils2();
52993
- import path33 from "path";
53614
+ import path35 from "path";
52994
53615
  var VALID_TASK_WORKFLOW_STATES = [
52995
53616
  "idle",
52996
53617
  "coder_delegated",
@@ -53115,7 +53736,7 @@ function rehydrateState(snapshot) {
53115
53736
  async function reconcileTaskStatesFromPlan(directory) {
53116
53737
  let raw;
53117
53738
  try {
53118
- raw = await Bun.file(path33.join(directory, ".swarm/plan.json")).text();
53739
+ raw = await Bun.file(path35.join(directory, ".swarm/plan.json")).text();
53119
53740
  } catch {
53120
53741
  return;
53121
53742
  }
@@ -53337,8 +53958,8 @@ var build_check = createSwarmTool({
53337
53958
  // src/tools/check-gate-status.ts
53338
53959
  init_dist();
53339
53960
  init_create_tool();
53340
- import * as fs21 from "fs";
53341
- import * as path34 from "path";
53961
+ import * as fs23 from "fs";
53962
+ import * as path36 from "path";
53342
53963
  var EVIDENCE_DIR = ".swarm/evidence";
53343
53964
  var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
53344
53965
  function isValidTaskId3(taskId) {
@@ -53355,18 +53976,18 @@ function isValidTaskId3(taskId) {
53355
53976
  return TASK_ID_PATTERN2.test(taskId);
53356
53977
  }
53357
53978
  function isPathWithinSwarm(filePath, workspaceRoot) {
53358
- const normalizedWorkspace = path34.resolve(workspaceRoot);
53359
- const swarmPath = path34.join(normalizedWorkspace, ".swarm", "evidence");
53360
- const normalizedPath = path34.resolve(filePath);
53979
+ const normalizedWorkspace = path36.resolve(workspaceRoot);
53980
+ const swarmPath = path36.join(normalizedWorkspace, ".swarm", "evidence");
53981
+ const normalizedPath = path36.resolve(filePath);
53361
53982
  return normalizedPath.startsWith(swarmPath);
53362
53983
  }
53363
53984
  function readEvidenceFile(evidencePath) {
53364
- if (!fs21.existsSync(evidencePath)) {
53985
+ if (!fs23.existsSync(evidencePath)) {
53365
53986
  return null;
53366
53987
  }
53367
53988
  let content;
53368
53989
  try {
53369
- content = fs21.readFileSync(evidencePath, "utf-8");
53990
+ content = fs23.readFileSync(evidencePath, "utf-8");
53370
53991
  } catch {
53371
53992
  return null;
53372
53993
  }
@@ -53418,7 +54039,7 @@ var check_gate_status = createSwarmTool({
53418
54039
  };
53419
54040
  return JSON.stringify(errorResult, null, 2);
53420
54041
  }
53421
- const evidencePath = path34.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
54042
+ const evidencePath = path36.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
53422
54043
  if (!isPathWithinSwarm(evidencePath, directory)) {
53423
54044
  const errorResult = {
53424
54045
  taskId: taskIdInput,
@@ -53478,8 +54099,8 @@ var check_gate_status = createSwarmTool({
53478
54099
  init_tool();
53479
54100
  init_create_tool();
53480
54101
  import { spawnSync } from "child_process";
53481
- import * as fs22 from "fs";
53482
- import * as path35 from "path";
54102
+ import * as fs24 from "fs";
54103
+ import * as path37 from "path";
53483
54104
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
53484
54105
  var MAX_LABEL_LENGTH = 100;
53485
54106
  var GIT_TIMEOUT_MS = 30000;
@@ -53530,13 +54151,13 @@ function validateLabel(label) {
53530
54151
  return null;
53531
54152
  }
53532
54153
  function getCheckpointLogPath(directory) {
53533
- return path35.join(directory, CHECKPOINT_LOG_PATH);
54154
+ return path37.join(directory, CHECKPOINT_LOG_PATH);
53534
54155
  }
53535
54156
  function readCheckpointLog(directory) {
53536
54157
  const logPath = getCheckpointLogPath(directory);
53537
54158
  try {
53538
- if (fs22.existsSync(logPath)) {
53539
- const content = fs22.readFileSync(logPath, "utf-8");
54159
+ if (fs24.existsSync(logPath)) {
54160
+ const content = fs24.readFileSync(logPath, "utf-8");
53540
54161
  const parsed = JSON.parse(content);
53541
54162
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
53542
54163
  return { version: 1, checkpoints: [] };
@@ -53548,13 +54169,13 @@ function readCheckpointLog(directory) {
53548
54169
  }
53549
54170
  function writeCheckpointLog(log2, directory) {
53550
54171
  const logPath = getCheckpointLogPath(directory);
53551
- const dir = path35.dirname(logPath);
53552
- if (!fs22.existsSync(dir)) {
53553
- fs22.mkdirSync(dir, { recursive: true });
54172
+ const dir = path37.dirname(logPath);
54173
+ if (!fs24.existsSync(dir)) {
54174
+ fs24.mkdirSync(dir, { recursive: true });
53554
54175
  }
53555
54176
  const tempPath = `${logPath}.tmp`;
53556
- fs22.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
53557
- fs22.renameSync(tempPath, logPath);
54177
+ fs24.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
54178
+ fs24.renameSync(tempPath, logPath);
53558
54179
  }
53559
54180
  function gitExec(args2) {
53560
54181
  const result = spawnSync("git", args2, {
@@ -53755,8 +54376,8 @@ var checkpoint = createSwarmTool({
53755
54376
  // src/tools/complexity-hotspots.ts
53756
54377
  init_dist();
53757
54378
  init_create_tool();
53758
- import * as fs23 from "fs";
53759
- import * as path36 from "path";
54379
+ import * as fs25 from "fs";
54380
+ import * as path38 from "path";
53760
54381
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
53761
54382
  var DEFAULT_DAYS = 90;
53762
54383
  var DEFAULT_TOP_N = 20;
@@ -53885,11 +54506,11 @@ function estimateComplexity(content) {
53885
54506
  }
53886
54507
  function getComplexityForFile(filePath) {
53887
54508
  try {
53888
- const stat2 = fs23.statSync(filePath);
54509
+ const stat2 = fs25.statSync(filePath);
53889
54510
  if (stat2.size > MAX_FILE_SIZE_BYTES2) {
53890
54511
  return null;
53891
54512
  }
53892
- const content = fs23.readFileSync(filePath, "utf-8");
54513
+ const content = fs25.readFileSync(filePath, "utf-8");
53893
54514
  return estimateComplexity(content);
53894
54515
  } catch {
53895
54516
  return null;
@@ -53900,7 +54521,7 @@ async function analyzeHotspots(days, topN, extensions, directory) {
53900
54521
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
53901
54522
  const filteredChurn = new Map;
53902
54523
  for (const [file3, count] of churnMap) {
53903
- const ext = path36.extname(file3).toLowerCase();
54524
+ const ext = path38.extname(file3).toLowerCase();
53904
54525
  if (extSet.has(ext)) {
53905
54526
  filteredChurn.set(file3, count);
53906
54527
  }
@@ -53910,8 +54531,8 @@ async function analyzeHotspots(days, topN, extensions, directory) {
53910
54531
  let analyzedFiles = 0;
53911
54532
  for (const [file3, churnCount] of filteredChurn) {
53912
54533
  let fullPath = file3;
53913
- if (!fs23.existsSync(fullPath)) {
53914
- fullPath = path36.join(cwd, file3);
54534
+ if (!fs25.existsSync(fullPath)) {
54535
+ fullPath = path38.join(cwd, file3);
53915
54536
  }
53916
54537
  const complexity = getComplexityForFile(fullPath);
53917
54538
  if (complexity !== null) {
@@ -54058,8 +54679,8 @@ var complexity_hotspots = createSwarmTool({
54058
54679
  });
54059
54680
  // src/tools/declare-scope.ts
54060
54681
  init_tool();
54061
- import * as fs24 from "fs";
54062
- import * as path37 from "path";
54682
+ import * as fs26 from "fs";
54683
+ import * as path39 from "path";
54063
54684
  init_create_tool();
54064
54685
  function validateTaskIdFormat(taskId) {
54065
54686
  const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
@@ -54138,8 +54759,8 @@ async function executeDeclareScope(args2, fallbackDir) {
54138
54759
  };
54139
54760
  }
54140
54761
  }
54141
- normalizedDir = path37.normalize(args2.working_directory);
54142
- const pathParts = normalizedDir.split(path37.sep);
54762
+ normalizedDir = path39.normalize(args2.working_directory);
54763
+ const pathParts = normalizedDir.split(path39.sep);
54143
54764
  if (pathParts.includes("..")) {
54144
54765
  return {
54145
54766
  success: false,
@@ -54149,11 +54770,11 @@ async function executeDeclareScope(args2, fallbackDir) {
54149
54770
  ]
54150
54771
  };
54151
54772
  }
54152
- const resolvedDir = path37.resolve(normalizedDir);
54773
+ const resolvedDir = path39.resolve(normalizedDir);
54153
54774
  try {
54154
- const realPath = fs24.realpathSync(resolvedDir);
54155
- const planPath2 = path37.join(realPath, ".swarm", "plan.json");
54156
- if (!fs24.existsSync(planPath2)) {
54775
+ const realPath = fs26.realpathSync(resolvedDir);
54776
+ const planPath2 = path39.join(realPath, ".swarm", "plan.json");
54777
+ if (!fs26.existsSync(planPath2)) {
54157
54778
  return {
54158
54779
  success: false,
54159
54780
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -54173,8 +54794,8 @@ async function executeDeclareScope(args2, fallbackDir) {
54173
54794
  }
54174
54795
  }
54175
54796
  const directory = normalizedDir ?? fallbackDir ?? process.cwd();
54176
- const planPath = path37.resolve(directory, ".swarm", "plan.json");
54177
- if (!fs24.existsSync(planPath)) {
54797
+ const planPath = path39.resolve(directory, ".swarm", "plan.json");
54798
+ if (!fs26.existsSync(planPath)) {
54178
54799
  return {
54179
54800
  success: false,
54180
54801
  message: "No plan found",
@@ -54183,7 +54804,7 @@ async function executeDeclareScope(args2, fallbackDir) {
54183
54804
  }
54184
54805
  let planContent;
54185
54806
  try {
54186
- planContent = JSON.parse(fs24.readFileSync(planPath, "utf-8"));
54807
+ planContent = JSON.parse(fs26.readFileSync(planPath, "utf-8"));
54187
54808
  } catch {
54188
54809
  return {
54189
54810
  success: false,
@@ -54263,20 +54884,20 @@ function validateBase(base) {
54263
54884
  function validatePaths(paths) {
54264
54885
  if (!paths)
54265
54886
  return null;
54266
- for (const path38 of paths) {
54267
- if (!path38 || path38.length === 0) {
54887
+ for (const path40 of paths) {
54888
+ if (!path40 || path40.length === 0) {
54268
54889
  return "empty path not allowed";
54269
54890
  }
54270
- if (path38.length > MAX_PATH_LENGTH) {
54891
+ if (path40.length > MAX_PATH_LENGTH) {
54271
54892
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
54272
54893
  }
54273
- if (SHELL_METACHARACTERS2.test(path38)) {
54894
+ if (SHELL_METACHARACTERS2.test(path40)) {
54274
54895
  return "path contains shell metacharacters";
54275
54896
  }
54276
- if (path38.startsWith("-")) {
54897
+ if (path40.startsWith("-")) {
54277
54898
  return 'path cannot start with "-" (option-like arguments not allowed)';
54278
54899
  }
54279
- if (CONTROL_CHAR_PATTERN2.test(path38)) {
54900
+ if (CONTROL_CHAR_PATTERN2.test(path40)) {
54280
54901
  return "path contains control characters";
54281
54902
  }
54282
54903
  }
@@ -54356,8 +54977,8 @@ var diff = tool({
54356
54977
  if (parts2.length >= 3) {
54357
54978
  const additions = parseInt(parts2[0], 10) || 0;
54358
54979
  const deletions = parseInt(parts2[1], 10) || 0;
54359
- const path38 = parts2[2];
54360
- files.push({ path: path38, additions, deletions });
54980
+ const path40 = parts2[2];
54981
+ files.push({ path: path40, additions, deletions });
54361
54982
  }
54362
54983
  }
54363
54984
  const contractChanges = [];
@@ -54586,8 +55207,8 @@ Use these as DOMAIN values when delegating to @sme.`;
54586
55207
  // src/tools/evidence-check.ts
54587
55208
  init_dist();
54588
55209
  init_create_tool();
54589
- import * as fs25 from "fs";
54590
- import * as path38 from "path";
55210
+ import * as fs27 from "fs";
55211
+ import * as path40 from "path";
54591
55212
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
54592
55213
  var MAX_EVIDENCE_FILES = 1000;
54593
55214
  var EVIDENCE_DIR2 = ".swarm/evidence";
@@ -54617,9 +55238,9 @@ function validateRequiredTypes(input) {
54617
55238
  return null;
54618
55239
  }
54619
55240
  function isPathWithinSwarm2(filePath, cwd) {
54620
- const normalizedCwd = path38.resolve(cwd);
54621
- const swarmPath = path38.join(normalizedCwd, ".swarm");
54622
- const normalizedPath = path38.resolve(filePath);
55241
+ const normalizedCwd = path40.resolve(cwd);
55242
+ const swarmPath = path40.join(normalizedCwd, ".swarm");
55243
+ const normalizedPath = path40.resolve(filePath);
54623
55244
  return normalizedPath.startsWith(swarmPath);
54624
55245
  }
54625
55246
  function parseCompletedTasks(planContent) {
@@ -54635,12 +55256,12 @@ function parseCompletedTasks(planContent) {
54635
55256
  }
54636
55257
  function readEvidenceFiles(evidenceDir, _cwd) {
54637
55258
  const evidence = [];
54638
- if (!fs25.existsSync(evidenceDir) || !fs25.statSync(evidenceDir).isDirectory()) {
55259
+ if (!fs27.existsSync(evidenceDir) || !fs27.statSync(evidenceDir).isDirectory()) {
54639
55260
  return evidence;
54640
55261
  }
54641
55262
  let files;
54642
55263
  try {
54643
- files = fs25.readdirSync(evidenceDir);
55264
+ files = fs27.readdirSync(evidenceDir);
54644
55265
  } catch {
54645
55266
  return evidence;
54646
55267
  }
@@ -54649,14 +55270,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54649
55270
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
54650
55271
  continue;
54651
55272
  }
54652
- const filePath = path38.join(evidenceDir, filename);
55273
+ const filePath = path40.join(evidenceDir, filename);
54653
55274
  try {
54654
- const resolvedPath = path38.resolve(filePath);
54655
- const evidenceDirResolved = path38.resolve(evidenceDir);
55275
+ const resolvedPath = path40.resolve(filePath);
55276
+ const evidenceDirResolved = path40.resolve(evidenceDir);
54656
55277
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
54657
55278
  continue;
54658
55279
  }
54659
- const stat2 = fs25.lstatSync(filePath);
55280
+ const stat2 = fs27.lstatSync(filePath);
54660
55281
  if (!stat2.isFile()) {
54661
55282
  continue;
54662
55283
  }
@@ -54665,7 +55286,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54665
55286
  }
54666
55287
  let fileStat;
54667
55288
  try {
54668
- fileStat = fs25.statSync(filePath);
55289
+ fileStat = fs27.statSync(filePath);
54669
55290
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
54670
55291
  continue;
54671
55292
  }
@@ -54674,7 +55295,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
54674
55295
  }
54675
55296
  let content;
54676
55297
  try {
54677
- content = fs25.readFileSync(filePath, "utf-8");
55298
+ content = fs27.readFileSync(filePath, "utf-8");
54678
55299
  } catch {
54679
55300
  continue;
54680
55301
  }
@@ -54770,7 +55391,7 @@ var evidence_check = createSwarmTool({
54770
55391
  return JSON.stringify(errorResult, null, 2);
54771
55392
  }
54772
55393
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
54773
- const planPath = path38.join(cwd, PLAN_FILE);
55394
+ const planPath = path40.join(cwd, PLAN_FILE);
54774
55395
  if (!isPathWithinSwarm2(planPath, cwd)) {
54775
55396
  const errorResult = {
54776
55397
  error: "plan file path validation failed",
@@ -54784,7 +55405,7 @@ var evidence_check = createSwarmTool({
54784
55405
  }
54785
55406
  let planContent;
54786
55407
  try {
54787
- planContent = fs25.readFileSync(planPath, "utf-8");
55408
+ planContent = fs27.readFileSync(planPath, "utf-8");
54788
55409
  } catch {
54789
55410
  const result2 = {
54790
55411
  message: "No completed tasks found in plan.",
@@ -54802,7 +55423,7 @@ var evidence_check = createSwarmTool({
54802
55423
  };
54803
55424
  return JSON.stringify(result2, null, 2);
54804
55425
  }
54805
- const evidenceDir = path38.join(cwd, EVIDENCE_DIR2);
55426
+ const evidenceDir = path40.join(cwd, EVIDENCE_DIR2);
54806
55427
  const evidence = readEvidenceFiles(evidenceDir, cwd);
54807
55428
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
54808
55429
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -54819,8 +55440,8 @@ var evidence_check = createSwarmTool({
54819
55440
  // src/tools/file-extractor.ts
54820
55441
  init_tool();
54821
55442
  init_create_tool();
54822
- import * as fs26 from "fs";
54823
- import * as path39 from "path";
55443
+ import * as fs28 from "fs";
55444
+ import * as path41 from "path";
54824
55445
  var EXT_MAP = {
54825
55446
  python: ".py",
54826
55447
  py: ".py",
@@ -54882,8 +55503,8 @@ var extract_code_blocks = createSwarmTool({
54882
55503
  execute: async (args2, directory) => {
54883
55504
  const { content, output_dir, prefix } = args2;
54884
55505
  const targetDir = output_dir || directory;
54885
- if (!fs26.existsSync(targetDir)) {
54886
- fs26.mkdirSync(targetDir, { recursive: true });
55506
+ if (!fs28.existsSync(targetDir)) {
55507
+ fs28.mkdirSync(targetDir, { recursive: true });
54887
55508
  }
54888
55509
  if (!content) {
54889
55510
  return "Error: content is required";
@@ -54901,16 +55522,16 @@ var extract_code_blocks = createSwarmTool({
54901
55522
  if (prefix) {
54902
55523
  filename = `${prefix}_${filename}`;
54903
55524
  }
54904
- let filepath = path39.join(targetDir, filename);
54905
- const base = path39.basename(filepath, path39.extname(filepath));
54906
- const ext = path39.extname(filepath);
55525
+ let filepath = path41.join(targetDir, filename);
55526
+ const base = path41.basename(filepath, path41.extname(filepath));
55527
+ const ext = path41.extname(filepath);
54907
55528
  let counter = 1;
54908
- while (fs26.existsSync(filepath)) {
54909
- filepath = path39.join(targetDir, `${base}_${counter}${ext}`);
55529
+ while (fs28.existsSync(filepath)) {
55530
+ filepath = path41.join(targetDir, `${base}_${counter}${ext}`);
54910
55531
  counter++;
54911
55532
  }
54912
55533
  try {
54913
- fs26.writeFileSync(filepath, code.trim(), "utf-8");
55534
+ fs28.writeFileSync(filepath, code.trim(), "utf-8");
54914
55535
  savedFiles.push(filepath);
54915
55536
  } catch (error93) {
54916
55537
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -55023,8 +55644,8 @@ var gitingest = tool({
55023
55644
  });
55024
55645
  // src/tools/imports.ts
55025
55646
  init_dist();
55026
- import * as fs27 from "fs";
55027
- import * as path40 from "path";
55647
+ import * as fs29 from "fs";
55648
+ import * as path42 from "path";
55028
55649
  var MAX_FILE_PATH_LENGTH2 = 500;
55029
55650
  var MAX_SYMBOL_LENGTH = 256;
55030
55651
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -55078,7 +55699,7 @@ function validateSymbolInput(symbol3) {
55078
55699
  return null;
55079
55700
  }
55080
55701
  function isBinaryFile2(filePath, buffer) {
55081
- const ext = path40.extname(filePath).toLowerCase();
55702
+ const ext = path42.extname(filePath).toLowerCase();
55082
55703
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
55083
55704
  return false;
55084
55705
  }
@@ -55102,15 +55723,15 @@ function parseImports(content, targetFile, targetSymbol) {
55102
55723
  const imports = [];
55103
55724
  let _resolvedTarget;
55104
55725
  try {
55105
- _resolvedTarget = path40.resolve(targetFile);
55726
+ _resolvedTarget = path42.resolve(targetFile);
55106
55727
  } catch {
55107
55728
  _resolvedTarget = targetFile;
55108
55729
  }
55109
- const targetBasename = path40.basename(targetFile, path40.extname(targetFile));
55730
+ const targetBasename = path42.basename(targetFile, path42.extname(targetFile));
55110
55731
  const targetWithExt = targetFile;
55111
55732
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
55112
- const normalizedTargetWithExt = path40.normalize(targetWithExt).replace(/\\/g, "/");
55113
- const normalizedTargetWithoutExt = path40.normalize(targetWithoutExt).replace(/\\/g, "/");
55733
+ const normalizedTargetWithExt = path42.normalize(targetWithExt).replace(/\\/g, "/");
55734
+ const normalizedTargetWithoutExt = path42.normalize(targetWithoutExt).replace(/\\/g, "/");
55114
55735
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
55115
55736
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
55116
55737
  const modulePath = match[1] || match[2] || match[3];
@@ -55133,9 +55754,9 @@ function parseImports(content, targetFile, targetSymbol) {
55133
55754
  }
55134
55755
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
55135
55756
  let isMatch = false;
55136
- const _targetDir = path40.dirname(targetFile);
55137
- const targetExt = path40.extname(targetFile);
55138
- const targetBasenameNoExt = path40.basename(targetFile, targetExt);
55757
+ const _targetDir = path42.dirname(targetFile);
55758
+ const targetExt = path42.extname(targetFile);
55759
+ const targetBasenameNoExt = path42.basename(targetFile, targetExt);
55139
55760
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
55140
55761
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
55141
55762
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -55192,7 +55813,7 @@ var SKIP_DIRECTORIES2 = new Set([
55192
55813
  function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
55193
55814
  let entries;
55194
55815
  try {
55195
- entries = fs27.readdirSync(dir);
55816
+ entries = fs29.readdirSync(dir);
55196
55817
  } catch (e) {
55197
55818
  stats.fileErrors.push({
55198
55819
  path: dir,
@@ -55203,13 +55824,13 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
55203
55824
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
55204
55825
  for (const entry of entries) {
55205
55826
  if (SKIP_DIRECTORIES2.has(entry)) {
55206
- stats.skippedDirs.push(path40.join(dir, entry));
55827
+ stats.skippedDirs.push(path42.join(dir, entry));
55207
55828
  continue;
55208
55829
  }
55209
- const fullPath = path40.join(dir, entry);
55830
+ const fullPath = path42.join(dir, entry);
55210
55831
  let stat2;
55211
55832
  try {
55212
- stat2 = fs27.statSync(fullPath);
55833
+ stat2 = fs29.statSync(fullPath);
55213
55834
  } catch (e) {
55214
55835
  stats.fileErrors.push({
55215
55836
  path: fullPath,
@@ -55220,7 +55841,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
55220
55841
  if (stat2.isDirectory()) {
55221
55842
  findSourceFiles(fullPath, files, stats);
55222
55843
  } else if (stat2.isFile()) {
55223
- const ext = path40.extname(fullPath).toLowerCase();
55844
+ const ext = path42.extname(fullPath).toLowerCase();
55224
55845
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
55225
55846
  files.push(fullPath);
55226
55847
  }
@@ -55276,8 +55897,8 @@ var imports = tool({
55276
55897
  return JSON.stringify(errorResult, null, 2);
55277
55898
  }
55278
55899
  try {
55279
- const targetFile = path40.resolve(file3);
55280
- if (!fs27.existsSync(targetFile)) {
55900
+ const targetFile = path42.resolve(file3);
55901
+ if (!fs29.existsSync(targetFile)) {
55281
55902
  const errorResult = {
55282
55903
  error: `target file not found: ${file3}`,
55283
55904
  target: file3,
@@ -55287,7 +55908,7 @@ var imports = tool({
55287
55908
  };
55288
55909
  return JSON.stringify(errorResult, null, 2);
55289
55910
  }
55290
- const targetStat = fs27.statSync(targetFile);
55911
+ const targetStat = fs29.statSync(targetFile);
55291
55912
  if (!targetStat.isFile()) {
55292
55913
  const errorResult = {
55293
55914
  error: "target must be a file, not a directory",
@@ -55298,7 +55919,7 @@ var imports = tool({
55298
55919
  };
55299
55920
  return JSON.stringify(errorResult, null, 2);
55300
55921
  }
55301
- const baseDir = path40.dirname(targetFile);
55922
+ const baseDir = path42.dirname(targetFile);
55302
55923
  const scanStats = {
55303
55924
  skippedDirs: [],
55304
55925
  skippedFiles: 0,
@@ -55313,12 +55934,12 @@ var imports = tool({
55313
55934
  if (consumers.length >= MAX_CONSUMERS)
55314
55935
  break;
55315
55936
  try {
55316
- const stat2 = fs27.statSync(filePath);
55937
+ const stat2 = fs29.statSync(filePath);
55317
55938
  if (stat2.size > MAX_FILE_SIZE_BYTES4) {
55318
55939
  skippedFileCount++;
55319
55940
  continue;
55320
55941
  }
55321
- const buffer = fs27.readFileSync(filePath);
55942
+ const buffer = fs29.readFileSync(filePath);
55322
55943
  if (isBinaryFile2(filePath, buffer)) {
55323
55944
  skippedFileCount++;
55324
55945
  continue;
@@ -55383,7 +56004,7 @@ var imports = tool({
55383
56004
  });
55384
56005
  // src/tools/knowledge-query.ts
55385
56006
  init_dist();
55386
- import { existsSync as existsSync26 } from "fs";
56007
+ import { existsSync as existsSync27 } from "fs";
55387
56008
  init_create_tool();
55388
56009
  var DEFAULT_LIMIT = 10;
55389
56010
  var MAX_LESSON_LENGTH = 200;
@@ -55453,14 +56074,14 @@ function validateLimit(limit) {
55453
56074
  }
55454
56075
  async function readSwarmKnowledge(directory) {
55455
56076
  const swarmPath = resolveSwarmKnowledgePath(directory);
55456
- if (!existsSync26(swarmPath)) {
56077
+ if (!existsSync27(swarmPath)) {
55457
56078
  return [];
55458
56079
  }
55459
56080
  return readKnowledge(swarmPath);
55460
56081
  }
55461
56082
  async function readHiveKnowledge() {
55462
56083
  const hivePath = resolveHiveKnowledgePath();
55463
- if (!existsSync26(hivePath)) {
56084
+ if (!existsSync27(hivePath)) {
55464
56085
  return [];
55465
56086
  }
55466
56087
  return readKnowledge(hivePath);
@@ -55619,8 +56240,8 @@ init_dist();
55619
56240
  init_config();
55620
56241
  init_schema();
55621
56242
  init_manager();
55622
- import * as fs28 from "fs";
55623
- import * as path41 from "path";
56243
+ import * as fs30 from "fs";
56244
+ import * as path43 from "path";
55624
56245
  init_utils2();
55625
56246
  init_create_tool();
55626
56247
  function safeWarn(message, error93) {
@@ -55815,7 +56436,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55815
56436
  }
55816
56437
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
55817
56438
  try {
55818
- const projectName = path41.basename(dir);
56439
+ const projectName = path43.basename(dir);
55819
56440
  const knowledgeConfig = {
55820
56441
  enabled: true,
55821
56442
  swarm_max_entries: 100,
@@ -55863,7 +56484,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55863
56484
  if (agentsMissing.length > 0) {
55864
56485
  try {
55865
56486
  const planPath = validateSwarmPath(dir, "plan.json");
55866
- const planRaw = fs28.readFileSync(planPath, "utf-8");
56487
+ const planRaw = fs30.readFileSync(planPath, "utf-8");
55867
56488
  const plan = JSON.parse(planRaw);
55868
56489
  const targetPhase = plan.phases.find((p) => p.id === phase);
55869
56490
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -55904,7 +56525,7 @@ async function executePhaseComplete(args2, workingDirectory) {
55904
56525
  };
55905
56526
  try {
55906
56527
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
55907
- fs28.appendFileSync(eventsPath, `${JSON.stringify(event)}
56528
+ fs30.appendFileSync(eventsPath, `${JSON.stringify(event)}
55908
56529
  `, "utf-8");
55909
56530
  } catch (writeError) {
55910
56531
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -55923,12 +56544,12 @@ async function executePhaseComplete(args2, workingDirectory) {
55923
56544
  }
55924
56545
  try {
55925
56546
  const planPath = validateSwarmPath(dir, "plan.json");
55926
- const planJson = fs28.readFileSync(planPath, "utf-8");
56547
+ const planJson = fs30.readFileSync(planPath, "utf-8");
55927
56548
  const plan = JSON.parse(planJson);
55928
56549
  const phaseObj = plan.phases.find((p) => p.id === phase);
55929
56550
  if (phaseObj) {
55930
56551
  phaseObj.status = "completed";
55931
- fs28.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
56552
+ fs30.writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}
55932
56553
  `, "utf-8");
55933
56554
  }
55934
56555
  } catch (error93) {
@@ -55978,8 +56599,8 @@ init_dist();
55978
56599
  init_discovery();
55979
56600
  init_utils();
55980
56601
  init_create_tool();
55981
- import * as fs29 from "fs";
55982
- import * as path42 from "path";
56602
+ import * as fs31 from "fs";
56603
+ import * as path44 from "path";
55983
56604
  var MAX_OUTPUT_BYTES5 = 52428800;
55984
56605
  var AUDIT_TIMEOUT_MS = 120000;
55985
56606
  function isValidEcosystem(value) {
@@ -55997,28 +56618,28 @@ function validateArgs3(args2) {
55997
56618
  function detectEcosystems(directory) {
55998
56619
  const ecosystems = [];
55999
56620
  const cwd = directory;
56000
- if (fs29.existsSync(path42.join(cwd, "package.json"))) {
56621
+ if (fs31.existsSync(path44.join(cwd, "package.json"))) {
56001
56622
  ecosystems.push("npm");
56002
56623
  }
56003
- if (fs29.existsSync(path42.join(cwd, "pyproject.toml")) || fs29.existsSync(path42.join(cwd, "requirements.txt"))) {
56624
+ if (fs31.existsSync(path44.join(cwd, "pyproject.toml")) || fs31.existsSync(path44.join(cwd, "requirements.txt"))) {
56004
56625
  ecosystems.push("pip");
56005
56626
  }
56006
- if (fs29.existsSync(path42.join(cwd, "Cargo.toml"))) {
56627
+ if (fs31.existsSync(path44.join(cwd, "Cargo.toml"))) {
56007
56628
  ecosystems.push("cargo");
56008
56629
  }
56009
- if (fs29.existsSync(path42.join(cwd, "go.mod"))) {
56630
+ if (fs31.existsSync(path44.join(cwd, "go.mod"))) {
56010
56631
  ecosystems.push("go");
56011
56632
  }
56012
56633
  try {
56013
- const files = fs29.readdirSync(cwd);
56634
+ const files = fs31.readdirSync(cwd);
56014
56635
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
56015
56636
  ecosystems.push("dotnet");
56016
56637
  }
56017
56638
  } catch {}
56018
- if (fs29.existsSync(path42.join(cwd, "Gemfile")) || fs29.existsSync(path42.join(cwd, "Gemfile.lock"))) {
56639
+ if (fs31.existsSync(path44.join(cwd, "Gemfile")) || fs31.existsSync(path44.join(cwd, "Gemfile.lock"))) {
56019
56640
  ecosystems.push("ruby");
56020
56641
  }
56021
- if (fs29.existsSync(path42.join(cwd, "pubspec.yaml"))) {
56642
+ if (fs31.existsSync(path44.join(cwd, "pubspec.yaml"))) {
56022
56643
  ecosystems.push("dart");
56023
56644
  }
56024
56645
  return ecosystems;
@@ -57080,8 +57701,8 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
57080
57701
  ]);
57081
57702
  // src/tools/pre-check-batch.ts
57082
57703
  init_dist();
57083
- import * as fs32 from "fs";
57084
- import * as path45 from "path";
57704
+ import * as fs34 from "fs";
57705
+ import * as path47 from "path";
57085
57706
 
57086
57707
  // node_modules/yocto-queue/index.js
57087
57708
  class Node2 {
@@ -57248,8 +57869,8 @@ init_lint();
57248
57869
  init_manager();
57249
57870
 
57250
57871
  // src/quality/metrics.ts
57251
- import * as fs30 from "fs";
57252
- import * as path43 from "path";
57872
+ import * as fs32 from "fs";
57873
+ import * as path45 from "path";
57253
57874
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
57254
57875
  var MIN_DUPLICATION_LINES = 10;
57255
57876
  function estimateCyclomaticComplexity(content) {
@@ -57287,11 +57908,11 @@ function estimateCyclomaticComplexity(content) {
57287
57908
  }
57288
57909
  function getComplexityForFile2(filePath) {
57289
57910
  try {
57290
- const stat2 = fs30.statSync(filePath);
57911
+ const stat2 = fs32.statSync(filePath);
57291
57912
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
57292
57913
  return null;
57293
57914
  }
57294
- const content = fs30.readFileSync(filePath, "utf-8");
57915
+ const content = fs32.readFileSync(filePath, "utf-8");
57295
57916
  return estimateCyclomaticComplexity(content);
57296
57917
  } catch {
57297
57918
  return null;
@@ -57301,8 +57922,8 @@ async function computeComplexityDelta(files, workingDir) {
57301
57922
  let totalComplexity = 0;
57302
57923
  const analyzedFiles = [];
57303
57924
  for (const file3 of files) {
57304
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57305
- if (!fs30.existsSync(fullPath)) {
57925
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
57926
+ if (!fs32.existsSync(fullPath)) {
57306
57927
  continue;
57307
57928
  }
57308
57929
  const complexity = getComplexityForFile2(fullPath);
@@ -57423,8 +58044,8 @@ function countGoExports(content) {
57423
58044
  }
57424
58045
  function getExportCountForFile(filePath) {
57425
58046
  try {
57426
- const content = fs30.readFileSync(filePath, "utf-8");
57427
- const ext = path43.extname(filePath).toLowerCase();
58047
+ const content = fs32.readFileSync(filePath, "utf-8");
58048
+ const ext = path45.extname(filePath).toLowerCase();
57428
58049
  switch (ext) {
57429
58050
  case ".ts":
57430
58051
  case ".tsx":
@@ -57450,8 +58071,8 @@ async function computePublicApiDelta(files, workingDir) {
57450
58071
  let totalExports = 0;
57451
58072
  const analyzedFiles = [];
57452
58073
  for (const file3 of files) {
57453
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57454
- if (!fs30.existsSync(fullPath)) {
58074
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
58075
+ if (!fs32.existsSync(fullPath)) {
57455
58076
  continue;
57456
58077
  }
57457
58078
  const exports = getExportCountForFile(fullPath);
@@ -57484,16 +58105,16 @@ async function computeDuplicationRatio(files, workingDir) {
57484
58105
  let duplicateLines = 0;
57485
58106
  const analyzedFiles = [];
57486
58107
  for (const file3 of files) {
57487
- const fullPath = path43.isAbsolute(file3) ? file3 : path43.join(workingDir, file3);
57488
- if (!fs30.existsSync(fullPath)) {
58108
+ const fullPath = path45.isAbsolute(file3) ? file3 : path45.join(workingDir, file3);
58109
+ if (!fs32.existsSync(fullPath)) {
57489
58110
  continue;
57490
58111
  }
57491
58112
  try {
57492
- const stat2 = fs30.statSync(fullPath);
58113
+ const stat2 = fs32.statSync(fullPath);
57493
58114
  if (stat2.size > MAX_FILE_SIZE_BYTES5) {
57494
58115
  continue;
57495
58116
  }
57496
- const content = fs30.readFileSync(fullPath, "utf-8");
58117
+ const content = fs32.readFileSync(fullPath, "utf-8");
57497
58118
  const lines = content.split(`
57498
58119
  `).filter((line) => line.trim().length > 0);
57499
58120
  if (lines.length < MIN_DUPLICATION_LINES) {
@@ -57517,8 +58138,8 @@ function countCodeLines(content) {
57517
58138
  return lines.length;
57518
58139
  }
57519
58140
  function isTestFile(filePath) {
57520
- const basename8 = path43.basename(filePath);
57521
- const _ext = path43.extname(filePath).toLowerCase();
58141
+ const basename8 = path45.basename(filePath);
58142
+ const _ext = path45.extname(filePath).toLowerCase();
57522
58143
  const testPatterns = [
57523
58144
  ".test.",
57524
58145
  ".spec.",
@@ -57599,8 +58220,8 @@ function matchGlobSegment(globSegments, pathSegments) {
57599
58220
  }
57600
58221
  return gIndex === globSegments.length && pIndex === pathSegments.length;
57601
58222
  }
57602
- function matchesGlobSegment(path44, glob) {
57603
- const normalizedPath = path44.replace(/\\/g, "/");
58223
+ function matchesGlobSegment(path46, glob) {
58224
+ const normalizedPath = path46.replace(/\\/g, "/");
57604
58225
  const normalizedGlob = glob.replace(/\\/g, "/");
57605
58226
  if (normalizedPath.includes("//")) {
57606
58227
  return false;
@@ -57631,8 +58252,8 @@ function simpleGlobToRegex2(glob) {
57631
58252
  function hasGlobstar(glob) {
57632
58253
  return glob.includes("**");
57633
58254
  }
57634
- function globMatches(path44, glob) {
57635
- const normalizedPath = path44.replace(/\\/g, "/");
58255
+ function globMatches(path46, glob) {
58256
+ const normalizedPath = path46.replace(/\\/g, "/");
57636
58257
  if (!glob || glob === "") {
57637
58258
  if (normalizedPath.includes("//")) {
57638
58259
  return false;
@@ -57668,31 +58289,31 @@ function shouldExcludeFile(filePath, excludeGlobs) {
57668
58289
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
57669
58290
  let testLines = 0;
57670
58291
  let codeLines = 0;
57671
- const srcDir = path43.join(workingDir, "src");
57672
- if (fs30.existsSync(srcDir)) {
58292
+ const srcDir = path45.join(workingDir, "src");
58293
+ if (fs32.existsSync(srcDir)) {
57673
58294
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
57674
58295
  codeLines += lines;
57675
58296
  });
57676
58297
  }
57677
58298
  const possibleSrcDirs = ["lib", "app", "source", "core"];
57678
58299
  for (const dir of possibleSrcDirs) {
57679
- const dirPath = path43.join(workingDir, dir);
57680
- if (fs30.existsSync(dirPath)) {
58300
+ const dirPath = path45.join(workingDir, dir);
58301
+ if (fs32.existsSync(dirPath)) {
57681
58302
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
57682
58303
  codeLines += lines;
57683
58304
  });
57684
58305
  }
57685
58306
  }
57686
- const testsDir = path43.join(workingDir, "tests");
57687
- if (fs30.existsSync(testsDir)) {
58307
+ const testsDir = path45.join(workingDir, "tests");
58308
+ if (fs32.existsSync(testsDir)) {
57688
58309
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
57689
58310
  testLines += lines;
57690
58311
  });
57691
58312
  }
57692
58313
  const possibleTestDirs = ["test", "__tests__", "specs"];
57693
58314
  for (const dir of possibleTestDirs) {
57694
- const dirPath = path43.join(workingDir, dir);
57695
- if (fs30.existsSync(dirPath) && dirPath !== testsDir) {
58315
+ const dirPath = path45.join(workingDir, dir);
58316
+ if (fs32.existsSync(dirPath) && dirPath !== testsDir) {
57696
58317
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
57697
58318
  testLines += lines;
57698
58319
  });
@@ -57704,9 +58325,9 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
57704
58325
  }
57705
58326
  async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTestScan, callback) {
57706
58327
  try {
57707
- const entries = fs30.readdirSync(dirPath, { withFileTypes: true });
58328
+ const entries = fs32.readdirSync(dirPath, { withFileTypes: true });
57708
58329
  for (const entry of entries) {
57709
- const fullPath = path43.join(dirPath, entry.name);
58330
+ const fullPath = path45.join(dirPath, entry.name);
57710
58331
  if (entry.isDirectory()) {
57711
58332
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
57712
58333
  continue;
@@ -57714,7 +58335,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
57714
58335
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
57715
58336
  } else if (entry.isFile()) {
57716
58337
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
57717
- const ext = path43.extname(entry.name).toLowerCase();
58338
+ const ext = path45.extname(entry.name).toLowerCase();
57718
58339
  const validExts = [
57719
58340
  ".ts",
57720
58341
  ".tsx",
@@ -57750,7 +58371,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
57750
58371
  continue;
57751
58372
  }
57752
58373
  try {
57753
- const content = fs30.readFileSync(fullPath, "utf-8");
58374
+ const content = fs32.readFileSync(fullPath, "utf-8");
57754
58375
  const lines = countCodeLines(content);
57755
58376
  callback(lines);
57756
58377
  } catch {}
@@ -57964,8 +58585,8 @@ async function qualityBudget(input, directory) {
57964
58585
  init_dist();
57965
58586
  init_manager();
57966
58587
  init_detector();
57967
- import * as fs31 from "fs";
57968
- import * as path44 from "path";
58588
+ import * as fs33 from "fs";
58589
+ import * as path46 from "path";
57969
58590
  import { extname as extname9 } from "path";
57970
58591
 
57971
58592
  // src/sast/rules/c.ts
@@ -58832,17 +59453,17 @@ var SEVERITY_ORDER = {
58832
59453
  };
58833
59454
  function shouldSkipFile(filePath) {
58834
59455
  try {
58835
- const stats = fs31.statSync(filePath);
59456
+ const stats = fs33.statSync(filePath);
58836
59457
  if (stats.size > MAX_FILE_SIZE_BYTES6) {
58837
59458
  return { skip: true, reason: "file too large" };
58838
59459
  }
58839
59460
  if (stats.size === 0) {
58840
59461
  return { skip: true, reason: "empty file" };
58841
59462
  }
58842
- const fd = fs31.openSync(filePath, "r");
59463
+ const fd = fs33.openSync(filePath, "r");
58843
59464
  const buffer = Buffer.alloc(8192);
58844
- const bytesRead = fs31.readSync(fd, buffer, 0, 8192, 0);
58845
- fs31.closeSync(fd);
59465
+ const bytesRead = fs33.readSync(fd, buffer, 0, 8192, 0);
59466
+ fs33.closeSync(fd);
58846
59467
  if (bytesRead > 0) {
58847
59468
  let nullCount = 0;
58848
59469
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -58881,7 +59502,7 @@ function countBySeverity(findings) {
58881
59502
  }
58882
59503
  function scanFileWithTierA(filePath, language) {
58883
59504
  try {
58884
- const content = fs31.readFileSync(filePath, "utf-8");
59505
+ const content = fs33.readFileSync(filePath, "utf-8");
58885
59506
  const findings = executeRulesSync(filePath, content, language);
58886
59507
  return findings.map((f) => ({
58887
59508
  rule_id: f.rule_id,
@@ -58928,8 +59549,8 @@ async function sastScan(input, directory, config3) {
58928
59549
  _filesSkipped++;
58929
59550
  continue;
58930
59551
  }
58931
- const resolvedPath = path44.isAbsolute(filePath) ? filePath : path44.resolve(directory, filePath);
58932
- if (!fs31.existsSync(resolvedPath)) {
59552
+ const resolvedPath = path46.isAbsolute(filePath) ? filePath : path46.resolve(directory, filePath);
59553
+ if (!fs33.existsSync(resolvedPath)) {
58933
59554
  _filesSkipped++;
58934
59555
  continue;
58935
59556
  }
@@ -59127,18 +59748,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
59127
59748
  let resolved;
59128
59749
  const isWinAbs = isWindowsAbsolutePath(inputPath);
59129
59750
  if (isWinAbs) {
59130
- resolved = path45.win32.resolve(inputPath);
59131
- } else if (path45.isAbsolute(inputPath)) {
59132
- resolved = path45.resolve(inputPath);
59751
+ resolved = path47.win32.resolve(inputPath);
59752
+ } else if (path47.isAbsolute(inputPath)) {
59753
+ resolved = path47.resolve(inputPath);
59133
59754
  } else {
59134
- resolved = path45.resolve(baseDir, inputPath);
59755
+ resolved = path47.resolve(baseDir, inputPath);
59135
59756
  }
59136
- const workspaceResolved = path45.resolve(workspaceDir);
59757
+ const workspaceResolved = path47.resolve(workspaceDir);
59137
59758
  let relative5;
59138
59759
  if (isWinAbs) {
59139
- relative5 = path45.win32.relative(workspaceResolved, resolved);
59760
+ relative5 = path47.win32.relative(workspaceResolved, resolved);
59140
59761
  } else {
59141
- relative5 = path45.relative(workspaceResolved, resolved);
59762
+ relative5 = path47.relative(workspaceResolved, resolved);
59142
59763
  }
59143
59764
  if (relative5.startsWith("..")) {
59144
59765
  return "path traversal detected";
@@ -59158,7 +59779,7 @@ function validateDirectory3(dir, workspaceDir) {
59158
59779
  }
59159
59780
  return null;
59160
59781
  }
59161
- async function runWithTimeout(promise3, timeoutMs) {
59782
+ async function runWithTimeout2(promise3, timeoutMs) {
59162
59783
  const timeoutPromise = new Promise((_, reject) => {
59163
59784
  setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
59164
59785
  });
@@ -59183,7 +59804,7 @@ async function runLintWrapped(files, directory, _config) {
59183
59804
  duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
59184
59805
  };
59185
59806
  }
59186
- const result = await runWithTimeout(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
59807
+ const result = await runWithTimeout2(runLint(linter, "check", directory), TOOL_TIMEOUT_MS);
59187
59808
  return {
59188
59809
  ran: true,
59189
59810
  result,
@@ -59199,13 +59820,13 @@ async function runLintWrapped(files, directory, _config) {
59199
59820
  }
59200
59821
  async function runLintOnFiles(linter, files, workspaceDir) {
59201
59822
  const isWindows = process.platform === "win32";
59202
- const binDir = path45.join(workspaceDir, "node_modules", ".bin");
59823
+ const binDir = path47.join(workspaceDir, "node_modules", ".bin");
59203
59824
  const validatedFiles = [];
59204
59825
  for (const file3 of files) {
59205
59826
  if (typeof file3 !== "string") {
59206
59827
  continue;
59207
59828
  }
59208
- const resolvedPath = path45.resolve(file3);
59829
+ const resolvedPath = path47.resolve(file3);
59209
59830
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
59210
59831
  if (validationError) {
59211
59832
  continue;
@@ -59223,10 +59844,10 @@ async function runLintOnFiles(linter, files, workspaceDir) {
59223
59844
  }
59224
59845
  let command;
59225
59846
  if (linter === "biome") {
59226
- const biomeBin = isWindows ? path45.join(binDir, "biome.EXE") : path45.join(binDir, "biome");
59847
+ const biomeBin = isWindows ? path47.join(binDir, "biome.EXE") : path47.join(binDir, "biome");
59227
59848
  command = [biomeBin, "check", ...validatedFiles];
59228
59849
  } else {
59229
- const eslintBin = isWindows ? path45.join(binDir, "eslint.cmd") : path45.join(binDir, "eslint");
59850
+ const eslintBin = isWindows ? path47.join(binDir, "eslint.cmd") : path47.join(binDir, "eslint");
59230
59851
  command = [eslintBin, ...validatedFiles];
59231
59852
  }
59232
59853
  try {
@@ -59277,14 +59898,14 @@ async function runSecretscanWrapped(files, directory, _config) {
59277
59898
  const start2 = process.hrtime.bigint();
59278
59899
  try {
59279
59900
  if (files && files.length > 0) {
59280
- const result2 = await runWithTimeout(runSecretscanWithFiles(files, directory), TOOL_TIMEOUT_MS);
59901
+ const result2 = await runWithTimeout2(runSecretscanWithFiles(files, directory), TOOL_TIMEOUT_MS);
59281
59902
  return {
59282
59903
  ran: true,
59283
59904
  result: result2,
59284
59905
  duration_ms: Number(process.hrtime.bigint() - start2) / 1e6
59285
59906
  };
59286
59907
  }
59287
- const result = await runWithTimeout(runSecretscan(directory), TOOL_TIMEOUT_MS);
59908
+ const result = await runWithTimeout2(runSecretscan(directory), TOOL_TIMEOUT_MS);
59288
59909
  return {
59289
59910
  ran: true,
59290
59911
  result,
@@ -59363,7 +59984,7 @@ async function runSecretscanWithFiles(files, directory) {
59363
59984
  skippedFiles++;
59364
59985
  continue;
59365
59986
  }
59366
- const resolvedPath = path45.resolve(file3);
59987
+ const resolvedPath = path47.resolve(file3);
59367
59988
  const validationError = validatePath(resolvedPath, directory, directory);
59368
59989
  if (validationError) {
59369
59990
  skippedFiles++;
@@ -59381,14 +60002,14 @@ async function runSecretscanWithFiles(files, directory) {
59381
60002
  };
59382
60003
  }
59383
60004
  for (const file3 of validatedFiles) {
59384
- const ext = path45.extname(file3).toLowerCase();
60005
+ const ext = path47.extname(file3).toLowerCase();
59385
60006
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
59386
60007
  skippedFiles++;
59387
60008
  continue;
59388
60009
  }
59389
60010
  let stat2;
59390
60011
  try {
59391
- stat2 = fs32.statSync(file3);
60012
+ stat2 = fs34.statSync(file3);
59392
60013
  } catch {
59393
60014
  skippedFiles++;
59394
60015
  continue;
@@ -59399,7 +60020,7 @@ async function runSecretscanWithFiles(files, directory) {
59399
60020
  }
59400
60021
  let content;
59401
60022
  try {
59402
- const buffer = fs32.readFileSync(file3);
60023
+ const buffer = fs34.readFileSync(file3);
59403
60024
  if (buffer.includes(0)) {
59404
60025
  skippedFiles++;
59405
60026
  continue;
@@ -59468,7 +60089,7 @@ async function runSecretscanWithFiles(files, directory) {
59468
60089
  async function runSastScanWrapped(changedFiles, directory, severityThreshold, config3) {
59469
60090
  const start2 = process.hrtime.bigint();
59470
60091
  try {
59471
- const result = await runWithTimeout(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
60092
+ const result = await runWithTimeout2(sastScan({ changed_files: changedFiles, severity_threshold: severityThreshold }, directory, config3), TOOL_TIMEOUT_MS);
59472
60093
  return {
59473
60094
  ran: true,
59474
60095
  result,
@@ -59485,7 +60106,7 @@ async function runSastScanWrapped(changedFiles, directory, severityThreshold, co
59485
60106
  async function runQualityBudgetWrapped(changedFiles, directory, _config) {
59486
60107
  const start2 = process.hrtime.bigint();
59487
60108
  try {
59488
- const result = await runWithTimeout(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
60109
+ const result = await runWithTimeout2(qualityBudget({ changed_files: changedFiles }, directory), TOOL_TIMEOUT_MS);
59489
60110
  return {
59490
60111
  ran: true,
59491
60112
  result,
@@ -59540,7 +60161,7 @@ async function runPreCheckBatch(input, workspaceDir) {
59540
60161
  warn(`pre_check_batch: Invalid file path: ${file3}`);
59541
60162
  continue;
59542
60163
  }
59543
- changedFiles.push(path45.resolve(directory, file3));
60164
+ changedFiles.push(path47.resolve(directory, file3));
59544
60165
  }
59545
60166
  if (changedFiles.length === 0) {
59546
60167
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -59691,7 +60312,7 @@ var pre_check_batch = createSwarmTool({
59691
60312
  };
59692
60313
  return JSON.stringify(errorResult, null, 2);
59693
60314
  }
59694
- const resolvedDirectory = path45.resolve(typedArgs.directory);
60315
+ const resolvedDirectory = path47.resolve(typedArgs.directory);
59695
60316
  const workspaceAnchor = resolvedDirectory;
59696
60317
  const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
59697
60318
  if (dirError) {
@@ -59798,8 +60419,8 @@ ${paginatedContent}`;
59798
60419
  init_tool();
59799
60420
  init_manager2();
59800
60421
  init_create_tool();
59801
- import * as fs33 from "fs";
59802
- import * as path46 from "path";
60422
+ import * as fs35 from "fs";
60423
+ import * as path48 from "path";
59803
60424
  function detectPlaceholderContent(args2) {
59804
60425
  const issues = [];
59805
60426
  const placeholderPattern = /^\[\w[\w\s]*\]$/;
@@ -59903,19 +60524,19 @@ async function executeSavePlan(args2, fallbackDir) {
59903
60524
  try {
59904
60525
  await savePlan(dir, plan);
59905
60526
  try {
59906
- const markerPath = path46.join(dir, ".swarm", ".plan-write-marker");
60527
+ const markerPath = path48.join(dir, ".swarm", ".plan-write-marker");
59907
60528
  const marker = JSON.stringify({
59908
60529
  source: "save_plan",
59909
60530
  timestamp: new Date().toISOString(),
59910
60531
  phases_count: plan.phases.length,
59911
60532
  tasks_count: tasksCount
59912
60533
  });
59913
- await fs33.promises.writeFile(markerPath, marker, "utf8");
60534
+ await fs35.promises.writeFile(markerPath, marker, "utf8");
59914
60535
  } catch {}
59915
60536
  return {
59916
60537
  success: true,
59917
60538
  message: "Plan saved successfully",
59918
- plan_path: path46.join(dir, ".swarm", "plan.json"),
60539
+ plan_path: path48.join(dir, ".swarm", "plan.json"),
59919
60540
  phases_count: plan.phases.length,
59920
60541
  tasks_count: tasksCount
59921
60542
  };
@@ -59953,8 +60574,8 @@ var save_plan = createSwarmTool({
59953
60574
  // src/tools/sbom-generate.ts
59954
60575
  init_dist();
59955
60576
  init_manager();
59956
- import * as fs34 from "fs";
59957
- import * as path47 from "path";
60577
+ import * as fs36 from "fs";
60578
+ import * as path49 from "path";
59958
60579
 
59959
60580
  // src/sbom/detectors/index.ts
59960
60581
  init_utils();
@@ -60648,8 +61269,8 @@ function parsePackageResolved(content) {
60648
61269
  const pins = resolved.pins || [];
60649
61270
  for (const pin of pins) {
60650
61271
  const identity = pin.identity || pin.package || "";
60651
- const state = pin.state || {};
60652
- const version3 = state.version || state.revision || "";
61272
+ const state2 = pin.state || {};
61273
+ const version3 = state2.version || state2.revision || "";
60653
61274
  let org = "";
60654
61275
  const location = pin.location || "";
60655
61276
  const orgMatch = location.match(/github\.com\/([^/]+)\//);
@@ -60800,9 +61421,9 @@ function findManifestFiles(rootDir) {
60800
61421
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
60801
61422
  function searchDir(dir) {
60802
61423
  try {
60803
- const entries = fs34.readdirSync(dir, { withFileTypes: true });
61424
+ const entries = fs36.readdirSync(dir, { withFileTypes: true });
60804
61425
  for (const entry of entries) {
60805
- const fullPath = path47.join(dir, entry.name);
61426
+ const fullPath = path49.join(dir, entry.name);
60806
61427
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
60807
61428
  continue;
60808
61429
  }
@@ -60811,7 +61432,7 @@ function findManifestFiles(rootDir) {
60811
61432
  } else if (entry.isFile()) {
60812
61433
  for (const pattern of patterns) {
60813
61434
  if (simpleGlobToRegex(pattern).test(entry.name)) {
60814
- manifestFiles.push(path47.relative(rootDir, fullPath));
61435
+ manifestFiles.push(path49.relative(rootDir, fullPath));
60815
61436
  break;
60816
61437
  }
60817
61438
  }
@@ -60827,13 +61448,13 @@ function findManifestFilesInDirs(directories, workingDir) {
60827
61448
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
60828
61449
  for (const dir of directories) {
60829
61450
  try {
60830
- const entries = fs34.readdirSync(dir, { withFileTypes: true });
61451
+ const entries = fs36.readdirSync(dir, { withFileTypes: true });
60831
61452
  for (const entry of entries) {
60832
- const fullPath = path47.join(dir, entry.name);
61453
+ const fullPath = path49.join(dir, entry.name);
60833
61454
  if (entry.isFile()) {
60834
61455
  for (const pattern of patterns) {
60835
61456
  if (simpleGlobToRegex(pattern).test(entry.name)) {
60836
- found.push(path47.relative(workingDir, fullPath));
61457
+ found.push(path49.relative(workingDir, fullPath));
60837
61458
  break;
60838
61459
  }
60839
61460
  }
@@ -60846,11 +61467,11 @@ function findManifestFilesInDirs(directories, workingDir) {
60846
61467
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
60847
61468
  const dirs = new Set;
60848
61469
  for (const file3 of changedFiles) {
60849
- let currentDir = path47.dirname(file3);
61470
+ let currentDir = path49.dirname(file3);
60850
61471
  while (true) {
60851
- if (currentDir && currentDir !== "." && currentDir !== path47.sep) {
60852
- dirs.add(path47.join(workingDir, currentDir));
60853
- const parent = path47.dirname(currentDir);
61472
+ if (currentDir && currentDir !== "." && currentDir !== path49.sep) {
61473
+ dirs.add(path49.join(workingDir, currentDir));
61474
+ const parent = path49.dirname(currentDir);
60854
61475
  if (parent === currentDir)
60855
61476
  break;
60856
61477
  currentDir = parent;
@@ -60864,7 +61485,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
60864
61485
  }
60865
61486
  function ensureOutputDir(outputDir) {
60866
61487
  try {
60867
- fs34.mkdirSync(outputDir, { recursive: true });
61488
+ fs36.mkdirSync(outputDir, { recursive: true });
60868
61489
  } catch (error93) {
60869
61490
  if (!error93 || error93.code !== "EEXIST") {
60870
61491
  throw error93;
@@ -60934,7 +61555,7 @@ var sbom_generate = createSwarmTool({
60934
61555
  const changedFiles = obj.changed_files;
60935
61556
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
60936
61557
  const workingDir = directory;
60937
- const outputDir = path47.isAbsolute(relativeOutputDir) ? relativeOutputDir : path47.join(workingDir, relativeOutputDir);
61558
+ const outputDir = path49.isAbsolute(relativeOutputDir) ? relativeOutputDir : path49.join(workingDir, relativeOutputDir);
60938
61559
  let manifestFiles = [];
60939
61560
  if (scope === "all") {
60940
61561
  manifestFiles = findManifestFiles(workingDir);
@@ -60957,11 +61578,11 @@ var sbom_generate = createSwarmTool({
60957
61578
  const processedFiles = [];
60958
61579
  for (const manifestFile of manifestFiles) {
60959
61580
  try {
60960
- const fullPath = path47.isAbsolute(manifestFile) ? manifestFile : path47.join(workingDir, manifestFile);
60961
- if (!fs34.existsSync(fullPath)) {
61581
+ const fullPath = path49.isAbsolute(manifestFile) ? manifestFile : path49.join(workingDir, manifestFile);
61582
+ if (!fs36.existsSync(fullPath)) {
60962
61583
  continue;
60963
61584
  }
60964
- const content = fs34.readFileSync(fullPath, "utf-8");
61585
+ const content = fs36.readFileSync(fullPath, "utf-8");
60965
61586
  const components = detectComponents(manifestFile, content);
60966
61587
  processedFiles.push(manifestFile);
60967
61588
  if (components.length > 0) {
@@ -60974,8 +61595,8 @@ var sbom_generate = createSwarmTool({
60974
61595
  const bom = generateCycloneDX(allComponents);
60975
61596
  const bomJson = serializeCycloneDX(bom);
60976
61597
  const filename = generateSbomFilename();
60977
- const outputPath = path47.join(outputDir, filename);
60978
- fs34.writeFileSync(outputPath, bomJson, "utf-8");
61598
+ const outputPath = path49.join(outputDir, filename);
61599
+ fs36.writeFileSync(outputPath, bomJson, "utf-8");
60979
61600
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
60980
61601
  try {
60981
61602
  const timestamp = new Date().toISOString();
@@ -61017,8 +61638,8 @@ var sbom_generate = createSwarmTool({
61017
61638
  // src/tools/schema-drift.ts
61018
61639
  init_dist();
61019
61640
  init_create_tool();
61020
- import * as fs35 from "fs";
61021
- import * as path48 from "path";
61641
+ import * as fs37 from "fs";
61642
+ import * as path50 from "path";
61022
61643
  var SPEC_CANDIDATES = [
61023
61644
  "openapi.json",
61024
61645
  "openapi.yaml",
@@ -61050,28 +61671,28 @@ function normalizePath2(p) {
61050
61671
  }
61051
61672
  function discoverSpecFile(cwd, specFileArg) {
61052
61673
  if (specFileArg) {
61053
- const resolvedPath = path48.resolve(cwd, specFileArg);
61054
- const normalizedCwd = cwd.endsWith(path48.sep) ? cwd : cwd + path48.sep;
61674
+ const resolvedPath = path50.resolve(cwd, specFileArg);
61675
+ const normalizedCwd = cwd.endsWith(path50.sep) ? cwd : cwd + path50.sep;
61055
61676
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
61056
61677
  throw new Error("Invalid spec_file: path traversal detected");
61057
61678
  }
61058
- const ext = path48.extname(resolvedPath).toLowerCase();
61679
+ const ext = path50.extname(resolvedPath).toLowerCase();
61059
61680
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
61060
61681
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
61061
61682
  }
61062
- const stats = fs35.statSync(resolvedPath);
61683
+ const stats = fs37.statSync(resolvedPath);
61063
61684
  if (stats.size > MAX_SPEC_SIZE) {
61064
61685
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
61065
61686
  }
61066
- if (!fs35.existsSync(resolvedPath)) {
61687
+ if (!fs37.existsSync(resolvedPath)) {
61067
61688
  throw new Error(`Spec file not found: ${resolvedPath}`);
61068
61689
  }
61069
61690
  return resolvedPath;
61070
61691
  }
61071
61692
  for (const candidate of SPEC_CANDIDATES) {
61072
- const candidatePath = path48.resolve(cwd, candidate);
61073
- if (fs35.existsSync(candidatePath)) {
61074
- const stats = fs35.statSync(candidatePath);
61693
+ const candidatePath = path50.resolve(cwd, candidate);
61694
+ if (fs37.existsSync(candidatePath)) {
61695
+ const stats = fs37.statSync(candidatePath);
61075
61696
  if (stats.size <= MAX_SPEC_SIZE) {
61076
61697
  return candidatePath;
61077
61698
  }
@@ -61080,8 +61701,8 @@ function discoverSpecFile(cwd, specFileArg) {
61080
61701
  return null;
61081
61702
  }
61082
61703
  function parseSpec(specFile) {
61083
- const content = fs35.readFileSync(specFile, "utf-8");
61084
- const ext = path48.extname(specFile).toLowerCase();
61704
+ const content = fs37.readFileSync(specFile, "utf-8");
61705
+ const ext = path50.extname(specFile).toLowerCase();
61085
61706
  if (ext === ".json") {
61086
61707
  return parseJsonSpec(content);
61087
61708
  }
@@ -61152,12 +61773,12 @@ function extractRoutes(cwd) {
61152
61773
  function walkDir(dir) {
61153
61774
  let entries;
61154
61775
  try {
61155
- entries = fs35.readdirSync(dir, { withFileTypes: true });
61776
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
61156
61777
  } catch {
61157
61778
  return;
61158
61779
  }
61159
61780
  for (const entry of entries) {
61160
- const fullPath = path48.join(dir, entry.name);
61781
+ const fullPath = path50.join(dir, entry.name);
61161
61782
  if (entry.isSymbolicLink()) {
61162
61783
  continue;
61163
61784
  }
@@ -61167,7 +61788,7 @@ function extractRoutes(cwd) {
61167
61788
  }
61168
61789
  walkDir(fullPath);
61169
61790
  } else if (entry.isFile()) {
61170
- const ext = path48.extname(entry.name).toLowerCase();
61791
+ const ext = path50.extname(entry.name).toLowerCase();
61171
61792
  const baseName = entry.name.toLowerCase();
61172
61793
  if (![".ts", ".js", ".mjs"].includes(ext)) {
61173
61794
  continue;
@@ -61185,7 +61806,7 @@ function extractRoutes(cwd) {
61185
61806
  }
61186
61807
  function extractRoutesFromFile(filePath) {
61187
61808
  const routes = [];
61188
- const content = fs35.readFileSync(filePath, "utf-8");
61809
+ const content = fs37.readFileSync(filePath, "utf-8");
61189
61810
  const lines = content.split(/\r?\n/);
61190
61811
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
61191
61812
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -61336,8 +61957,8 @@ init_secretscan();
61336
61957
  // src/tools/symbols.ts
61337
61958
  init_tool();
61338
61959
  init_create_tool();
61339
- import * as fs36 from "fs";
61340
- import * as path49 from "path";
61960
+ import * as fs38 from "fs";
61961
+ import * as path51 from "path";
61341
61962
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
61342
61963
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
61343
61964
  function containsControlCharacters(str) {
@@ -61366,11 +61987,11 @@ function containsWindowsAttacks(str) {
61366
61987
  }
61367
61988
  function isPathInWorkspace(filePath, workspace) {
61368
61989
  try {
61369
- const resolvedPath = path49.resolve(workspace, filePath);
61370
- const realWorkspace = fs36.realpathSync(workspace);
61371
- const realResolvedPath = fs36.realpathSync(resolvedPath);
61372
- const relativePath = path49.relative(realWorkspace, realResolvedPath);
61373
- if (relativePath.startsWith("..") || path49.isAbsolute(relativePath)) {
61990
+ const resolvedPath = path51.resolve(workspace, filePath);
61991
+ const realWorkspace = fs38.realpathSync(workspace);
61992
+ const realResolvedPath = fs38.realpathSync(resolvedPath);
61993
+ const relativePath = path51.relative(realWorkspace, realResolvedPath);
61994
+ if (relativePath.startsWith("..") || path51.isAbsolute(relativePath)) {
61374
61995
  return false;
61375
61996
  }
61376
61997
  return true;
@@ -61382,17 +62003,17 @@ function validatePathForRead(filePath, workspace) {
61382
62003
  return isPathInWorkspace(filePath, workspace);
61383
62004
  }
61384
62005
  function extractTSSymbols(filePath, cwd) {
61385
- const fullPath = path49.join(cwd, filePath);
62006
+ const fullPath = path51.join(cwd, filePath);
61386
62007
  if (!validatePathForRead(fullPath, cwd)) {
61387
62008
  return [];
61388
62009
  }
61389
62010
  let content;
61390
62011
  try {
61391
- const stats = fs36.statSync(fullPath);
62012
+ const stats = fs38.statSync(fullPath);
61392
62013
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
61393
62014
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
61394
62015
  }
61395
- content = fs36.readFileSync(fullPath, "utf-8");
62016
+ content = fs38.readFileSync(fullPath, "utf-8");
61396
62017
  } catch {
61397
62018
  return [];
61398
62019
  }
@@ -61534,17 +62155,17 @@ function extractTSSymbols(filePath, cwd) {
61534
62155
  });
61535
62156
  }
61536
62157
  function extractPythonSymbols(filePath, cwd) {
61537
- const fullPath = path49.join(cwd, filePath);
62158
+ const fullPath = path51.join(cwd, filePath);
61538
62159
  if (!validatePathForRead(fullPath, cwd)) {
61539
62160
  return [];
61540
62161
  }
61541
62162
  let content;
61542
62163
  try {
61543
- const stats = fs36.statSync(fullPath);
62164
+ const stats = fs38.statSync(fullPath);
61544
62165
  if (stats.size > MAX_FILE_SIZE_BYTES7) {
61545
62166
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES7})`);
61546
62167
  }
61547
- content = fs36.readFileSync(fullPath, "utf-8");
62168
+ content = fs38.readFileSync(fullPath, "utf-8");
61548
62169
  } catch {
61549
62170
  return [];
61550
62171
  }
@@ -61617,7 +62238,7 @@ var symbols = createSwarmTool({
61617
62238
  }, null, 2);
61618
62239
  }
61619
62240
  const cwd = directory;
61620
- const ext = path49.extname(file3);
62241
+ const ext = path51.extname(file3);
61621
62242
  if (containsControlCharacters(file3)) {
61622
62243
  return JSON.stringify({
61623
62244
  file: file3,
@@ -61688,8 +62309,8 @@ init_test_runner();
61688
62309
  init_dist();
61689
62310
  init_utils();
61690
62311
  init_create_tool();
61691
- import * as fs37 from "fs";
61692
- import * as path50 from "path";
62312
+ import * as fs39 from "fs";
62313
+ import * as path52 from "path";
61693
62314
  var MAX_TEXT_LENGTH = 200;
61694
62315
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
61695
62316
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -61760,9 +62381,9 @@ function validatePathsInput(paths, cwd) {
61760
62381
  return { error: "paths contains path traversal", resolvedPath: null };
61761
62382
  }
61762
62383
  try {
61763
- const resolvedPath = path50.resolve(paths);
61764
- const normalizedCwd = path50.resolve(cwd);
61765
- const normalizedResolved = path50.resolve(resolvedPath);
62384
+ const resolvedPath = path52.resolve(paths);
62385
+ const normalizedCwd = path52.resolve(cwd);
62386
+ const normalizedResolved = path52.resolve(resolvedPath);
61766
62387
  if (!normalizedResolved.startsWith(normalizedCwd)) {
61767
62388
  return {
61768
62389
  error: "paths must be within the current working directory",
@@ -61778,13 +62399,13 @@ function validatePathsInput(paths, cwd) {
61778
62399
  }
61779
62400
  }
61780
62401
  function isSupportedExtension(filePath) {
61781
- const ext = path50.extname(filePath).toLowerCase();
62402
+ const ext = path52.extname(filePath).toLowerCase();
61782
62403
  return SUPPORTED_EXTENSIONS2.has(ext);
61783
62404
  }
61784
62405
  function findSourceFiles2(dir, files = []) {
61785
62406
  let entries;
61786
62407
  try {
61787
- entries = fs37.readdirSync(dir);
62408
+ entries = fs39.readdirSync(dir);
61788
62409
  } catch {
61789
62410
  return files;
61790
62411
  }
@@ -61793,10 +62414,10 @@ function findSourceFiles2(dir, files = []) {
61793
62414
  if (SKIP_DIRECTORIES3.has(entry)) {
61794
62415
  continue;
61795
62416
  }
61796
- const fullPath = path50.join(dir, entry);
62417
+ const fullPath = path52.join(dir, entry);
61797
62418
  let stat2;
61798
62419
  try {
61799
- stat2 = fs37.statSync(fullPath);
62420
+ stat2 = fs39.statSync(fullPath);
61800
62421
  } catch {
61801
62422
  continue;
61802
62423
  }
@@ -61889,7 +62510,7 @@ var todo_extract = createSwarmTool({
61889
62510
  return JSON.stringify(errorResult, null, 2);
61890
62511
  }
61891
62512
  const scanPath = resolvedPath;
61892
- if (!fs37.existsSync(scanPath)) {
62513
+ if (!fs39.existsSync(scanPath)) {
61893
62514
  const errorResult = {
61894
62515
  error: `path not found: ${pathsInput}`,
61895
62516
  total: 0,
@@ -61899,13 +62520,13 @@ var todo_extract = createSwarmTool({
61899
62520
  return JSON.stringify(errorResult, null, 2);
61900
62521
  }
61901
62522
  const filesToScan = [];
61902
- const stat2 = fs37.statSync(scanPath);
62523
+ const stat2 = fs39.statSync(scanPath);
61903
62524
  if (stat2.isFile()) {
61904
62525
  if (isSupportedExtension(scanPath)) {
61905
62526
  filesToScan.push(scanPath);
61906
62527
  } else {
61907
62528
  const errorResult = {
61908
- error: `unsupported file extension: ${path50.extname(scanPath)}`,
62529
+ error: `unsupported file extension: ${path52.extname(scanPath)}`,
61909
62530
  total: 0,
61910
62531
  byPriority: { high: 0, medium: 0, low: 0 },
61911
62532
  entries: []
@@ -61918,11 +62539,11 @@ var todo_extract = createSwarmTool({
61918
62539
  const allEntries = [];
61919
62540
  for (const filePath of filesToScan) {
61920
62541
  try {
61921
- const fileStat = fs37.statSync(filePath);
62542
+ const fileStat = fs39.statSync(filePath);
61922
62543
  if (fileStat.size > MAX_FILE_SIZE_BYTES8) {
61923
62544
  continue;
61924
62545
  }
61925
- const content = fs37.readFileSync(filePath, "utf-8");
62546
+ const content = fs39.readFileSync(filePath, "utf-8");
61926
62547
  const entries = parseTodoComments(content, filePath, tagsSet);
61927
62548
  allEntries.push(...entries);
61928
62549
  } catch {}
@@ -61950,9 +62571,95 @@ var todo_extract = createSwarmTool({
61950
62571
  // src/tools/update-task-status.ts
61951
62572
  init_tool();
61952
62573
  init_schema();
62574
+ import * as fs41 from "fs";
62575
+ import * as path54 from "path";
62576
+
62577
+ // src/hooks/diff-scope.ts
62578
+ import * as fs40 from "fs";
62579
+ import * as path53 from "path";
62580
+ function getDeclaredScope(taskId, directory) {
62581
+ try {
62582
+ const planPath = path53.join(directory, ".swarm", "plan.json");
62583
+ if (!fs40.existsSync(planPath))
62584
+ return null;
62585
+ const raw = fs40.readFileSync(planPath, "utf-8");
62586
+ const plan = JSON.parse(raw);
62587
+ for (const phase of plan.phases ?? []) {
62588
+ for (const task of phase.tasks ?? []) {
62589
+ if (task.id !== taskId)
62590
+ continue;
62591
+ const ft = task.files_touched;
62592
+ if (Array.isArray(ft) && ft.length > 0) {
62593
+ return ft;
62594
+ }
62595
+ if (typeof ft === "string" && ft.length > 0) {
62596
+ return [ft];
62597
+ }
62598
+ return null;
62599
+ }
62600
+ }
62601
+ return null;
62602
+ } catch {
62603
+ return null;
62604
+ }
62605
+ }
62606
+ async function getChangedFiles(directory) {
62607
+ try {
62608
+ const proc = Bun.spawn(["git", "diff", "--name-only", "HEAD~1"], {
62609
+ cwd: directory,
62610
+ stdout: "pipe",
62611
+ stderr: "pipe"
62612
+ });
62613
+ const [exitCode, stdout] = await Promise.all([
62614
+ proc.exited,
62615
+ new Response(proc.stdout).text()
62616
+ ]);
62617
+ if (exitCode === 0) {
62618
+ return stdout.trim().split(`
62619
+ `).map((f) => f.trim()).filter((f) => f.length > 0);
62620
+ }
62621
+ const proc2 = Bun.spawn(["git", "diff", "--name-only", "HEAD"], {
62622
+ cwd: directory,
62623
+ stdout: "pipe",
62624
+ stderr: "pipe"
62625
+ });
62626
+ const [exitCode2, stdout2] = await Promise.all([
62627
+ proc2.exited,
62628
+ new Response(proc2.stdout).text()
62629
+ ]);
62630
+ if (exitCode2 === 0) {
62631
+ return stdout2.trim().split(`
62632
+ `).map((f) => f.trim()).filter((f) => f.length > 0);
62633
+ }
62634
+ return null;
62635
+ } catch {
62636
+ return null;
62637
+ }
62638
+ }
62639
+ async function validateDiffScope(taskId, directory) {
62640
+ try {
62641
+ const declaredScope = getDeclaredScope(taskId, directory);
62642
+ if (!declaredScope)
62643
+ return null;
62644
+ const changedFiles = await getChangedFiles(directory);
62645
+ if (!changedFiles)
62646
+ return null;
62647
+ const normalise = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
62648
+ const normScope = new Set(declaredScope.map(normalise));
62649
+ const undeclared = changedFiles.map(normalise).filter((f) => !normScope.has(f));
62650
+ if (undeclared.length === 0)
62651
+ return null;
62652
+ const scopeStr = declaredScope.join(", ");
62653
+ const undeclaredStr = undeclared.slice(0, 5).join(", ");
62654
+ const extra = undeclared.length > 5 ? ` (+${undeclared.length - 5} more)` : "";
62655
+ return `SCOPE WARNING: Task ${taskId} declared scope [${scopeStr}] but also modified [${undeclaredStr}${extra}]. Reviewer should verify these changes are intentional.`;
62656
+ } catch {
62657
+ return null;
62658
+ }
62659
+ }
62660
+
62661
+ // src/tools/update-task-status.ts
61953
62662
  init_manager2();
61954
- import * as fs38 from "fs";
61955
- import * as path51 from "path";
61956
62663
  init_create_tool();
61957
62664
  var VALID_STATUSES2 = [
61958
62665
  "pending",
@@ -61987,7 +62694,7 @@ var TIER_3_PATTERNS = [
61987
62694
  ];
61988
62695
  function matchesTier3Pattern(files) {
61989
62696
  for (const file3 of files) {
61990
- const fileName = path51.basename(file3);
62697
+ const fileName = path54.basename(file3);
61991
62698
  for (const pattern of TIER_3_PATTERNS) {
61992
62699
  if (pattern.test(fileName)) {
61993
62700
  return true;
@@ -62009,8 +62716,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62009
62716
  if (hasActiveTurboMode2()) {
62010
62717
  const resolvedDir2 = workingDirectory ?? process.cwd();
62011
62718
  try {
62012
- const planPath = path51.join(resolvedDir2, ".swarm", "plan.json");
62013
- const planRaw = fs38.readFileSync(planPath, "utf-8");
62719
+ const planPath = path54.join(resolvedDir2, ".swarm", "plan.json");
62720
+ const planRaw = fs41.readFileSync(planPath, "utf-8");
62014
62721
  const plan = JSON.parse(planRaw);
62015
62722
  for (const planPhase of plan.phases ?? []) {
62016
62723
  for (const task of planPhase.tasks ?? []) {
@@ -62029,8 +62736,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62029
62736
  }
62030
62737
  const resolvedDir = workingDirectory ?? process.cwd();
62031
62738
  try {
62032
- const evidencePath = path51.join(resolvedDir, ".swarm", "evidence", `${taskId}.json`);
62033
- const raw = fs38.readFileSync(evidencePath, "utf-8");
62739
+ const evidencePath = path54.join(resolvedDir, ".swarm", "evidence", `${taskId}.json`);
62740
+ const raw = fs41.readFileSync(evidencePath, "utf-8");
62034
62741
  const evidence = JSON.parse(raw);
62035
62742
  if (evidence?.required_gates && Array.isArray(evidence.required_gates) && evidence?.gates) {
62036
62743
  const allGatesMet = evidence.required_gates.every((gate) => evidence.gates[gate] != null);
@@ -62053,8 +62760,8 @@ function checkReviewerGate(taskId, workingDirectory) {
62053
62760
  continue;
62054
62761
  }
62055
62762
  validSessionCount++;
62056
- const state = getTaskState(session, taskId);
62057
- if (state === "tests_run" || state === "complete") {
62763
+ const state2 = getTaskState(session, taskId);
62764
+ if (state2 === "tests_run" || state2 === "complete") {
62058
62765
  return { blocked: false, reason: "" };
62059
62766
  }
62060
62767
  }
@@ -62065,13 +62772,13 @@ function checkReviewerGate(taskId, workingDirectory) {
62065
62772
  for (const [sessionId, session] of swarmState.agentSessions) {
62066
62773
  if (!(session.taskWorkflowStates instanceof Map))
62067
62774
  continue;
62068
- const state = getTaskState(session, taskId);
62069
- stateEntries.push(`${sessionId}: ${state}`);
62775
+ const state2 = getTaskState(session, taskId);
62776
+ stateEntries.push(`${sessionId}: ${state2}`);
62070
62777
  }
62071
62778
  try {
62072
62779
  const resolvedDir2 = workingDirectory ?? process.cwd();
62073
- const planPath = path51.join(resolvedDir2, ".swarm", "plan.json");
62074
- const planRaw = fs38.readFileSync(planPath, "utf-8");
62780
+ const planPath = path54.join(resolvedDir2, ".swarm", "plan.json");
62781
+ const planRaw = fs41.readFileSync(planPath, "utf-8");
62075
62782
  const plan = JSON.parse(planRaw);
62076
62783
  for (const planPhase of plan.phases ?? []) {
62077
62784
  for (const task of planPhase.tasks ?? []) {
@@ -62129,6 +62836,17 @@ function checkReviewerGate(taskId, workingDirectory) {
62129
62836
  return { blocked: false, reason: "" };
62130
62837
  }
62131
62838
  }
62839
+ async function checkReviewerGateWithScope(taskId, workingDirectory) {
62840
+ const result = checkReviewerGate(taskId, workingDirectory);
62841
+ const scopeWarning = await validateDiffScope(taskId, workingDirectory ?? process.cwd()).catch(() => null);
62842
+ if (!scopeWarning)
62843
+ return result;
62844
+ return {
62845
+ ...result,
62846
+ reason: result.reason ? `${result.reason}
62847
+ ${scopeWarning}` : scopeWarning
62848
+ };
62849
+ }
62132
62850
  function recoverTaskStateFromDelegations(taskId) {
62133
62851
  let hasReviewer = false;
62134
62852
  let hasTestEngineer = false;
@@ -62241,8 +62959,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62241
62959
  };
62242
62960
  }
62243
62961
  }
62244
- normalizedDir = path51.normalize(args2.working_directory);
62245
- const pathParts = normalizedDir.split(path51.sep);
62962
+ normalizedDir = path54.normalize(args2.working_directory);
62963
+ const pathParts = normalizedDir.split(path54.sep);
62246
62964
  if (pathParts.includes("..")) {
62247
62965
  return {
62248
62966
  success: false,
@@ -62252,11 +62970,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62252
62970
  ]
62253
62971
  };
62254
62972
  }
62255
- const resolvedDir = path51.resolve(normalizedDir);
62973
+ const resolvedDir = path54.resolve(normalizedDir);
62256
62974
  try {
62257
- const realPath = fs38.realpathSync(resolvedDir);
62258
- const planPath = path51.join(realPath, ".swarm", "plan.json");
62259
- if (!fs38.existsSync(planPath)) {
62975
+ const realPath = fs41.realpathSync(resolvedDir);
62976
+ const planPath = path54.join(realPath, ".swarm", "plan.json");
62977
+ if (!fs41.existsSync(planPath)) {
62260
62978
  return {
62261
62979
  success: false,
62262
62980
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -62280,7 +62998,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
62280
62998
  }
62281
62999
  if (args2.status === "completed") {
62282
63000
  recoverTaskStateFromDelegations(args2.task_id);
62283
- const reviewerCheck = checkReviewerGate(args2.task_id, directory);
63001
+ const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
62284
63002
  if (reviewerCheck.blocked) {
62285
63003
  return {
62286
63004
  success: false,
@@ -62403,6 +63121,43 @@ var OpenCodeSwarm = async (ctx) => {
62403
63121
  const steeringConsumedHook = createSteeringConsumedHook(ctx.directory);
62404
63122
  const coChangeSuggesterHook = createCoChangeSuggesterHook(ctx.directory);
62405
63123
  const darkMatterDetectorHook = createDarkMatterDetectorHook(ctx.directory);
63124
+ const slopDetectorHook = config3.slop_detector?.enabled !== false ? createSlopDetectorHook(config3.slop_detector ?? {
63125
+ enabled: true,
63126
+ classThreshold: 3,
63127
+ commentStripThreshold: 5,
63128
+ diffLineThreshold: 200
63129
+ }, ctx.directory, (sessionId, message) => {
63130
+ const s = swarmState.agentSessions.get(sessionId);
63131
+ if (s) {
63132
+ s.pendingAdvisoryMessages ??= [];
63133
+ s.pendingAdvisoryMessages.push(message);
63134
+ }
63135
+ }) : null;
63136
+ const incrementalVerifyHook = config3.incremental_verify?.enabled !== false ? createIncrementalVerifyHook(config3.incremental_verify ?? {
63137
+ enabled: true,
63138
+ command: null,
63139
+ timeoutMs: 30000,
63140
+ triggerAgents: ["coder"]
63141
+ }, ctx.directory, (sessionId, message) => {
63142
+ const s = swarmState.agentSessions.get(sessionId);
63143
+ if (s) {
63144
+ s.pendingAdvisoryMessages ??= [];
63145
+ s.pendingAdvisoryMessages.push(message);
63146
+ }
63147
+ }) : null;
63148
+ const compactionServiceHook = config3.compaction_service?.enabled !== false ? createCompactionService(config3.compaction_service ?? {
63149
+ enabled: true,
63150
+ observationThreshold: 40,
63151
+ reflectionThreshold: 60,
63152
+ emergencyThreshold: 80,
63153
+ preserveLastNTurns: 5
63154
+ }, ctx.directory, (sessionId, message) => {
63155
+ const s = swarmState.agentSessions.get(sessionId);
63156
+ if (s) {
63157
+ s.pendingAdvisoryMessages ??= [];
63158
+ s.pendingAdvisoryMessages.push(message);
63159
+ }
63160
+ }) : null;
62406
63161
  const snapshotWriterHook = createSnapshotWriterHook(ctx.directory);
62407
63162
  const automationConfig = AutomationConfigSchema.parse(config3.automation ?? {});
62408
63163
  let automationManager;
@@ -62413,7 +63168,7 @@ var OpenCodeSwarm = async (ctx) => {
62413
63168
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
62414
63169
  preflightTriggerManager = new PTM(automationConfig);
62415
63170
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
62416
- const swarmDir = path52.resolve(ctx.directory, ".swarm");
63171
+ const swarmDir = path55.resolve(ctx.directory, ".swarm");
62417
63172
  statusArtifact = new ASA(swarmDir);
62418
63173
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
62419
63174
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -62675,6 +63430,14 @@ var OpenCodeSwarm = async (ctx) => {
62675
63430
  }
62676
63431
  }
62677
63432
  await guardrailsHooks.toolBefore(input, output);
63433
+ if (swarmState.lastBudgetPct >= 50) {
63434
+ const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
63435
+ if (!pressureSession.contextPressureWarningSent) {
63436
+ pressureSession.contextPressureWarningSent = true;
63437
+ pressureSession.pendingAdvisoryMessages ??= [];
63438
+ pressureSession.pendingAdvisoryMessages.push(`CONTEXT PRESSURE: ${swarmState.lastBudgetPct.toFixed(1)}% of context window used. Prioritize completing the current task before starting new work.`);
63439
+ }
63440
+ }
62678
63441
  await safeHook(activityHooks.toolBefore)(input, output);
62679
63442
  },
62680
63443
  "tool.execute.after": async (input, output) => {
@@ -62690,6 +63453,12 @@ var OpenCodeSwarm = async (ctx) => {
62690
63453
  await safeHook(darkMatterDetectorHook)(input, output);
62691
63454
  await snapshotWriterHook(input, output);
62692
63455
  await toolSummarizerHook?.(input, output);
63456
+ if (slopDetectorHook)
63457
+ await slopDetectorHook.toolAfter(input, output);
63458
+ if (incrementalVerifyHook)
63459
+ await incrementalVerifyHook.toolAfter(input, output);
63460
+ if (compactionServiceHook)
63461
+ await compactionServiceHook.toolAfter(input, output);
62693
63462
  const toolOutputConfig = config3.tool_output;
62694
63463
  if (toolOutputConfig && toolOutputConfig.truncation_enabled !== false && typeof output.output === "string") {
62695
63464
  const skipTools = [