opencode-swarm 6.11.0 → 6.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14150,6 +14150,7 @@ var init_plan_schema = __esm(() => {
14150
14150
  "pending",
14151
14151
  "in_progress",
14152
14152
  "complete",
14153
+ "completed",
14153
14154
  "blocked"
14154
14155
  ]);
14155
14156
  MigrationStatusSchema = exports_external.enum([
@@ -14179,7 +14180,7 @@ var init_plan_schema = __esm(() => {
14179
14180
  schema_version: exports_external.literal("1.0.0"),
14180
14181
  title: exports_external.string().min(1),
14181
14182
  swarm: exports_external.string().min(1),
14182
- current_phase: exports_external.number().int().min(1),
14183
+ current_phase: exports_external.number().int().min(1).optional(),
14183
14184
  phases: exports_external.array(PhaseSchema).min(1),
14184
14185
  migration_status: MigrationStatusSchema.optional()
14185
14186
  });
@@ -14576,6 +14577,10 @@ import * as path4 from "path";
14576
14577
  async function loadPlanJsonOnly(directory) {
14577
14578
  const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
14578
14579
  if (planJsonContent !== null) {
14580
+ if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
14581
+ warn("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
14582
+ return null;
14583
+ }
14579
14584
  try {
14580
14585
  const parsed = JSON.parse(planJsonContent);
14581
14586
  const validated = PlanSchema.parse(parsed);
@@ -14660,25 +14665,29 @@ ${markdown}`;
14660
14665
  async function loadPlan(directory) {
14661
14666
  const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
14662
14667
  if (planJsonContent !== null) {
14663
- try {
14664
- const parsed = JSON.parse(planJsonContent);
14665
- const validated = PlanSchema.parse(parsed);
14666
- const inSync = await isPlanMdInSync(directory, validated);
14667
- if (!inSync) {
14668
- try {
14669
- await regeneratePlanMarkdown(directory, validated);
14670
- } catch (regenError) {
14671
- warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
14668
+ if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
14669
+ warn("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
14670
+ } else {
14671
+ try {
14672
+ const parsed = JSON.parse(planJsonContent);
14673
+ const validated = PlanSchema.parse(parsed);
14674
+ const inSync = await isPlanMdInSync(directory, validated);
14675
+ if (!inSync) {
14676
+ try {
14677
+ await regeneratePlanMarkdown(directory, validated);
14678
+ } catch (regenError) {
14679
+ warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
14680
+ }
14681
+ }
14682
+ return validated;
14683
+ } catch (error49) {
14684
+ warn(`Plan validation failed for .swarm/plan.json: ${error49 instanceof Error ? error49.message : String(error49)}`);
14685
+ const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
14686
+ if (planMdContent2 !== null) {
14687
+ const migrated = migrateLegacyPlan(planMdContent2);
14688
+ await savePlan(directory, migrated);
14689
+ return migrated;
14672
14690
  }
14673
- }
14674
- return validated;
14675
- } catch (error49) {
14676
- warn(`Plan validation failed for .swarm/plan.json: ${error49 instanceof Error ? error49.message : String(error49)}`);
14677
- const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
14678
- if (planMdContent2 !== null) {
14679
- const migrated = migrateLegacyPlan(planMdContent2);
14680
- await savePlan(directory, migrated);
14681
- return migrated;
14682
14691
  }
14683
14692
  }
14684
14693
  }
@@ -14712,10 +14721,11 @@ function derivePlanMarkdown(plan) {
14712
14721
  blocked: "BLOCKED"
14713
14722
  };
14714
14723
  const now = new Date().toISOString();
14715
- const phaseStatus = statusMap[plan.phases[plan.current_phase - 1]?.status] || "PENDING";
14724
+ const currentPhase = plan.current_phase ?? 1;
14725
+ const phaseStatus = statusMap[plan.phases[currentPhase - 1]?.status] || "PENDING";
14716
14726
  let markdown = `# ${plan.title}
14717
14727
  Swarm: ${plan.swarm}
14718
- Phase: ${plan.current_phase} [${phaseStatus}] | Updated: ${now}
14728
+ Phase: ${currentPhase} [${phaseStatus}] | Updated: ${now}
14719
14729
  `;
14720
14730
  const sortedPhases = [...plan.phases].sort((a, b) => a.id - b.id);
14721
14731
  for (const phase of sortedPhases) {
@@ -15014,7 +15024,7 @@ async function buildPhaseSummary(phase) {
15014
15024
  const taskIds = await listEvidenceTaskIds(".");
15015
15025
  const phaseTaskIds = new Set(phase.tasks.map((t) => t.id));
15016
15026
  const taskSummaries = [];
15017
- const taskMap = new Map(phase.tasks.map((t) => [t.id, t]));
15027
+ const _taskMap = new Map(phase.tasks.map((t) => [t.id, t]));
15018
15028
  for (const task of phase.tasks) {
15019
15029
  const summary = await buildTaskSummary(task, task.id);
15020
15030
  taskSummaries.push(summary);
@@ -15138,7 +15148,7 @@ async function buildEvidenceSummary(directory, currentPhase) {
15138
15148
  schema_version: EVIDENCE_SUMMARY_VERSION,
15139
15149
  generated_at: new Date().toISOString(),
15140
15150
  planTitle: plan.title,
15141
- currentPhase: currentPhase ?? plan.current_phase,
15151
+ currentPhase: currentPhase ?? plan.current_phase ?? 1,
15142
15152
  phaseSummaries,
15143
15153
  overallCompletionRatio,
15144
15154
  overallBlockers,
@@ -15670,6 +15680,9 @@ class PhaseBoundaryTrigger {
15670
15680
  getCurrentPhase() {
15671
15681
  return this.lastKnownPhase;
15672
15682
  }
15683
+ get lastTriggeredPhaseValue() {
15684
+ return this.lastTriggeredPhase;
15685
+ }
15673
15686
  detectBoundary(newPhase, completedTasks, totalTasks) {
15674
15687
  if (newPhase === this.lastKnownPhase) {
15675
15688
  return {
@@ -15847,7 +15860,7 @@ class PreflightTriggerManager {
15847
15860
  enabled: this.isEnabled(),
15848
15861
  mode,
15849
15862
  currentPhase: this.trigger.getCurrentPhase(),
15850
- lastTriggeredPhase: this.trigger["lastTriggeredPhase"],
15863
+ lastTriggeredPhase: this.trigger.lastTriggeredPhaseValue,
15851
15864
  pendingRequests: this.getQueueSize()
15852
15865
  };
15853
15866
  }
@@ -16053,7 +16066,7 @@ function readConfigFromFile(directory) {
16053
16066
  return null;
16054
16067
  }
16055
16068
  }
16056
- function validateConfigKey(path10, value, config2) {
16069
+ function validateConfigKey(path10, value, _config) {
16057
16070
  const findings = [];
16058
16071
  switch (path10) {
16059
16072
  case "agents": {
@@ -29285,7 +29298,7 @@ async function runLint(linter, mode) {
29285
29298
  ` : "") + stderr;
29286
29299
  }
29287
29300
  if (output.length > MAX_OUTPUT_BYTES) {
29288
- output = output.slice(0, MAX_OUTPUT_BYTES) + `
29301
+ output = `${output.slice(0, MAX_OUTPUT_BYTES)}
29289
29302
  ... (output truncated)`;
29290
29303
  }
29291
29304
  const result = {
@@ -29425,15 +29438,14 @@ function isBinaryFile(filePath, buffer) {
29425
29438
  }
29426
29439
  return nullCount > checkLen * BINARY_NULL_THRESHOLD;
29427
29440
  }
29428
- function scanLineForSecrets(line, lineNum) {
29441
+ function scanLineForSecrets(line, _lineNum) {
29429
29442
  const results = [];
29430
29443
  if (line.length > MAX_LINE_LENGTH) {
29431
29444
  return results;
29432
29445
  }
29433
29446
  for (const pattern of SECRET_PATTERNS) {
29434
29447
  pattern.regex.lastIndex = 0;
29435
- let match;
29436
- while ((match = pattern.regex.exec(line)) !== null) {
29448
+ for (let match = pattern.regex.exec(line);match !== null; match = pattern.regex.exec(line)) {
29437
29449
  const fullMatch = match[0];
29438
29450
  const redacted = pattern.redactTemplate(fullMatch);
29439
29451
  results.push({
@@ -29544,7 +29556,7 @@ function isSymlinkLoop(realPath, visited) {
29544
29556
  function isPathWithinScope(realPath, scanDir) {
29545
29557
  const resolvedScanDir = path11.resolve(scanDir);
29546
29558
  const resolvedRealPath = path11.resolve(realPath);
29547
- return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(resolvedScanDir + "/") || resolvedRealPath.startsWith(resolvedScanDir + "\\");
29559
+ return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
29548
29560
  }
29549
29561
  function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
29550
29562
  skippedDirs: 0,
@@ -29971,7 +29983,7 @@ var init_secretscan = __esm(() => {
29971
29983
  parts2.push(`${skippedFiles + stats.fileErrors + stats.symlinkSkipped} files skipped (binary/oversized/symlinks/errors)`);
29972
29984
  }
29973
29985
  if (parts2.length > 0) {
29974
- result.message = parts2.join("; ") + ".";
29986
+ result.message = `${parts2.join("; ")}.`;
29975
29987
  }
29976
29988
  let jsonOutput = JSON.stringify(result, null, 2);
29977
29989
  if (jsonOutput.length > MAX_OUTPUT_BYTES2) {
@@ -30092,13 +30104,14 @@ function hasDevDependency(devDeps, ...patterns) {
30092
30104
  return false;
30093
30105
  return hasPackageJsonDependency(devDeps, ...patterns);
30094
30106
  }
30095
- async function detectTestFramework() {
30107
+ async function detectTestFramework(cwd) {
30108
+ const baseDir = cwd || process.cwd();
30096
30109
  try {
30097
- const packageJsonPath = path12.join(process.cwd(), "package.json");
30110
+ const packageJsonPath = path12.join(baseDir, "package.json");
30098
30111
  if (fs7.existsSync(packageJsonPath)) {
30099
30112
  const content = fs7.readFileSync(packageJsonPath, "utf-8");
30100
30113
  const pkg = JSON.parse(content);
30101
- const deps = pkg.dependencies || {};
30114
+ const _deps = pkg.dependencies || {};
30102
30115
  const devDeps = pkg.devDependencies || {};
30103
30116
  const scripts = pkg.scripts || {};
30104
30117
  if (scripts.test?.includes("vitest"))
@@ -30115,16 +30128,16 @@ async function detectTestFramework() {
30115
30128
  return "jest";
30116
30129
  if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
30117
30130
  return "mocha";
30118
- if (fs7.existsSync(path12.join(process.cwd(), "bun.lockb")) || fs7.existsSync(path12.join(process.cwd(), "bun.lock"))) {
30131
+ if (fs7.existsSync(path12.join(baseDir, "bun.lockb")) || fs7.existsSync(path12.join(baseDir, "bun.lock"))) {
30119
30132
  if (scripts.test?.includes("bun"))
30120
30133
  return "bun";
30121
30134
  }
30122
30135
  }
30123
30136
  } catch {}
30124
30137
  try {
30125
- const pyprojectTomlPath = path12.join(process.cwd(), "pyproject.toml");
30126
- const setupCfgPath = path12.join(process.cwd(), "setup.cfg");
30127
- const requirementsTxtPath = path12.join(process.cwd(), "requirements.txt");
30138
+ const pyprojectTomlPath = path12.join(baseDir, "pyproject.toml");
30139
+ const setupCfgPath = path12.join(baseDir, "setup.cfg");
30140
+ const requirementsTxtPath = path12.join(baseDir, "requirements.txt");
30128
30141
  if (fs7.existsSync(pyprojectTomlPath)) {
30129
30142
  const content = fs7.readFileSync(pyprojectTomlPath, "utf-8");
30130
30143
  if (content.includes("[tool.pytest"))
@@ -30144,7 +30157,7 @@ async function detectTestFramework() {
30144
30157
  }
30145
30158
  } catch {}
30146
30159
  try {
30147
- const cargoTomlPath = path12.join(process.cwd(), "Cargo.toml");
30160
+ const cargoTomlPath = path12.join(baseDir, "Cargo.toml");
30148
30161
  if (fs7.existsSync(cargoTomlPath)) {
30149
30162
  const content = fs7.readFileSync(cargoTomlPath, "utf-8");
30150
30163
  if (content.includes("[dev-dependencies]")) {
@@ -30155,9 +30168,9 @@ async function detectTestFramework() {
30155
30168
  }
30156
30169
  } catch {}
30157
30170
  try {
30158
- const pesterConfigPath = path12.join(process.cwd(), "pester.config.ps1");
30159
- const pesterConfigJsonPath = path12.join(process.cwd(), "pester.config.ps1.json");
30160
- const pesterPs1Path = path12.join(process.cwd(), "tests.ps1");
30171
+ const pesterConfigPath = path12.join(baseDir, "pester.config.ps1");
30172
+ const pesterConfigJsonPath = path12.join(baseDir, "pester.config.ps1.json");
30173
+ const pesterPs1Path = path12.join(baseDir, "tests.ps1");
30161
30174
  if (fs7.existsSync(pesterConfigPath) || fs7.existsSync(pesterConfigJsonPath) || fs7.existsSync(pesterPs1Path)) {
30162
30175
  return "pester";
30163
30176
  }
@@ -30179,7 +30192,7 @@ function getTestFilesFromConvention(sourceFiles) {
30179
30192
  }
30180
30193
  continue;
30181
30194
  }
30182
- for (const pattern of TEST_PATTERNS) {
30195
+ for (const _pattern of TEST_PATTERNS) {
30183
30196
  const nameWithoutExt = basename2.replace(/\.[^.]+$/, "");
30184
30197
  const ext = path12.extname(basename2);
30185
30198
  const possibleTestFiles = [
@@ -30210,7 +30223,8 @@ async function getTestFilesFromGraph(sourceFiles) {
30210
30223
  const testDir = path12.dirname(testFile);
30211
30224
  const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
30212
30225
  let match;
30213
- while ((match = importRegex.exec(content)) !== null) {
30226
+ match = importRegex.exec(content);
30227
+ while (match !== null) {
30214
30228
  const importPath = match[1];
30215
30229
  let resolvedImport;
30216
30230
  if (importPath.startsWith(".")) {
@@ -30248,9 +30262,11 @@ async function getTestFilesFromGraph(sourceFiles) {
30248
30262
  break;
30249
30263
  }
30250
30264
  }
30265
+ match = importRegex.exec(content);
30251
30266
  }
30252
30267
  const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
30253
- while ((match = requireRegex.exec(content)) !== null) {
30268
+ match = requireRegex.exec(content);
30269
+ while (match !== null) {
30254
30270
  const importPath = match[1];
30255
30271
  if (importPath.startsWith(".")) {
30256
30272
  let resolvedImport = path12.resolve(testDir, importPath);
@@ -30285,6 +30301,7 @@ async function getTestFilesFromGraph(sourceFiles) {
30285
30301
  }
30286
30302
  }
30287
30303
  }
30304
+ match = requireRegex.exec(content);
30288
30305
  }
30289
30306
  } catch {}
30290
30307
  }
@@ -30463,7 +30480,7 @@ function parseTestOutput(framework, output) {
30463
30480
  }
30464
30481
  return { totals, coveragePercent };
30465
30482
  }
30466
- async function runTests(framework, scope, files, coverage, timeout_ms) {
30483
+ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
30467
30484
  const command = buildTestCommand(framework, scope, files, coverage);
30468
30485
  if (!command) {
30469
30486
  return {
@@ -30488,7 +30505,8 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30488
30505
  try {
30489
30506
  const proc = Bun.spawn(command, {
30490
30507
  stdout: "pipe",
30491
- stderr: "pipe"
30508
+ stderr: "pipe",
30509
+ cwd: cwd || process.cwd()
30492
30510
  });
30493
30511
  const exitPromise = proc.exited;
30494
30512
  const timeoutPromise = new Promise((resolve7) => setTimeout(() => {
@@ -30516,7 +30534,7 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30516
30534
  }
30517
30535
  truncIndex--;
30518
30536
  }
30519
- output = output.slice(0, truncIndex) + `
30537
+ output = `${output.slice(0, truncIndex)}
30520
30538
  ... (output truncated)`;
30521
30539
  }
30522
30540
  const { totals, coveragePercent } = parseTestOutput(framework, output);
@@ -30660,7 +30678,46 @@ var init_test_runner = __esm(() => {
30660
30678
  coverage: tool.schema.boolean().optional().describe("Enable coverage reporting if supported"),
30661
30679
  timeout_ms: tool.schema.number().optional().describe("Timeout in milliseconds (default 60000, max 300000)")
30662
30680
  },
30663
- async execute(args2, _context) {
30681
+ async execute(args2, context) {
30682
+ const ctx = context;
30683
+ const rawDir = ctx?.directory || ctx?.worktree || process.cwd();
30684
+ const workingDir = rawDir.trim() || process.cwd();
30685
+ if (workingDir.length > 4096) {
30686
+ const errorResult = {
30687
+ success: false,
30688
+ framework: "none",
30689
+ scope: "all",
30690
+ error: "Invalid working directory"
30691
+ };
30692
+ return JSON.stringify(errorResult, null, 2);
30693
+ }
30694
+ if (/^[/\\]{2}/.test(workingDir)) {
30695
+ const errorResult = {
30696
+ success: false,
30697
+ framework: "none",
30698
+ scope: "all",
30699
+ error: "Invalid working directory"
30700
+ };
30701
+ return JSON.stringify(errorResult, null, 2);
30702
+ }
30703
+ if (containsControlChars2(workingDir)) {
30704
+ const errorResult = {
30705
+ success: false,
30706
+ framework: "none",
30707
+ scope: "all",
30708
+ error: "Invalid working directory"
30709
+ };
30710
+ return JSON.stringify(errorResult, null, 2);
30711
+ }
30712
+ if (containsPathTraversal2(workingDir)) {
30713
+ const errorResult = {
30714
+ success: false,
30715
+ framework: "none",
30716
+ scope: "all",
30717
+ error: "Invalid working directory"
30718
+ };
30719
+ return JSON.stringify(errorResult, null, 2);
30720
+ }
30664
30721
  if (!validateArgs2(args2)) {
30665
30722
  const errorResult = {
30666
30723
  success: false,
@@ -30672,10 +30729,10 @@ var init_test_runner = __esm(() => {
30672
30729
  return JSON.stringify(errorResult, null, 2);
30673
30730
  }
30674
30731
  const scope = args2.scope || "all";
30675
- const files = args2.files || [];
30732
+ const _files = args2.files || [];
30676
30733
  const coverage = args2.coverage || false;
30677
30734
  const timeout_ms = Math.min(args2.timeout_ms || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
30678
- const framework = await detectTestFramework();
30735
+ const framework = await detectTestFramework(workingDir);
30679
30736
  if (framework === "none") {
30680
30737
  const result2 = {
30681
30738
  success: false,
@@ -30701,13 +30758,13 @@ var init_test_runner = __esm(() => {
30701
30758
  const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
30702
30759
  const ext = path12.extname(f).toLowerCase();
30703
30760
  return SOURCE_EXTENSIONS.has(ext);
30704
- }) : findSourceFiles(process.cwd());
30761
+ }) : findSourceFiles(workingDir);
30705
30762
  testFiles = getTestFilesFromConvention(sourceFiles);
30706
30763
  } else if (scope === "graph") {
30707
30764
  const sourceFiles = args2.files && args2.files.length > 0 ? args2.files.filter((f) => {
30708
30765
  const ext = path12.extname(f).toLowerCase();
30709
30766
  return SOURCE_EXTENSIONS.has(ext);
30710
- }) : findSourceFiles(process.cwd());
30767
+ }) : findSourceFiles(workingDir);
30711
30768
  const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
30712
30769
  if (graphTestFiles.length > 0) {
30713
30770
  testFiles = graphTestFiles;
@@ -30717,7 +30774,7 @@ var init_test_runner = __esm(() => {
30717
30774
  testFiles = getTestFilesFromConvention(sourceFiles);
30718
30775
  }
30719
30776
  }
30720
- const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms);
30777
+ const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms, workingDir);
30721
30778
  if (graphFallbackReason && result.message) {
30722
30779
  result.message = `${result.message} (${graphFallbackReason})`;
30723
30780
  }
@@ -30798,7 +30855,7 @@ function getVersionFileVersion(dir) {
30798
30855
  }
30799
30856
  return null;
30800
30857
  }
30801
- async function runVersionCheck(dir, timeoutMs) {
30858
+ async function runVersionCheck(dir, _timeoutMs) {
30802
30859
  const startTime = Date.now();
30803
30860
  try {
30804
30861
  const packageVersion = getPackageVersion(dir);
@@ -30854,7 +30911,7 @@ async function runVersionCheck(dir, timeoutMs) {
30854
30911
  };
30855
30912
  }
30856
30913
  }
30857
- async function runLintCheck(dir, linter, timeoutMs) {
30914
+ async function runLintCheck(_dir, linter, timeoutMs) {
30858
30915
  const startTime = Date.now();
30859
30916
  try {
30860
30917
  const lintPromise = runLint(linter, "check");
@@ -30921,7 +30978,7 @@ async function runLintCheck(dir, linter, timeoutMs) {
30921
30978
  };
30922
30979
  }
30923
30980
  }
30924
- async function runTestsCheck(dir, scope, timeoutMs) {
30981
+ async function runTestsCheck(_dir, scope, timeoutMs) {
30925
30982
  const startTime = Date.now();
30926
30983
  try {
30927
30984
  const result = await runTests("none", scope, [], false, timeoutMs);
@@ -31366,7 +31423,35 @@ var init_preflight_integration = __esm(() => {
31366
31423
  });
31367
31424
 
31368
31425
  // src/index.ts
31369
- import * as path31 from "path";
31426
+ import * as path32 from "path";
31427
+
31428
+ // src/tools/tool-names.ts
31429
+ var TOOL_NAMES = [
31430
+ "diff",
31431
+ "syntax_check",
31432
+ "placeholder_scan",
31433
+ "imports",
31434
+ "lint",
31435
+ "secretscan",
31436
+ "sast_scan",
31437
+ "build_check",
31438
+ "pre_check_batch",
31439
+ "quality_budget",
31440
+ "symbols",
31441
+ "complexity_hotspots",
31442
+ "schema_drift",
31443
+ "todo_extract",
31444
+ "evidence_check",
31445
+ "sbom_generate",
31446
+ "checkpoint",
31447
+ "pkg_audit",
31448
+ "test_runner",
31449
+ "detect_domains",
31450
+ "gitingest",
31451
+ "retrieve_summary",
31452
+ "extract_code_blocks"
31453
+ ];
31454
+ var TOOL_NAME_SET = new Set(TOOL_NAMES);
31370
31455
 
31371
31456
  // src/config/constants.ts
31372
31457
  var QA_AGENTS = ["reviewer", "critic"];
@@ -31383,6 +31468,102 @@ var ALL_AGENT_NAMES = [
31383
31468
  ORCHESTRATOR_NAME,
31384
31469
  ...ALL_SUBAGENT_NAMES
31385
31470
  ];
31471
+ var AGENT_TOOL_MAP = {
31472
+ architect: [
31473
+ "checkpoint",
31474
+ "complexity_hotspots",
31475
+ "detect_domains",
31476
+ "evidence_check",
31477
+ "extract_code_blocks",
31478
+ "gitingest",
31479
+ "imports",
31480
+ "lint",
31481
+ "diff",
31482
+ "pkg_audit",
31483
+ "pre_check_batch",
31484
+ "retrieve_summary",
31485
+ "schema_drift",
31486
+ "secretscan",
31487
+ "symbols",
31488
+ "test_runner",
31489
+ "todo_extract"
31490
+ ],
31491
+ explorer: [
31492
+ "complexity_hotspots",
31493
+ "detect_domains",
31494
+ "extract_code_blocks",
31495
+ "gitingest",
31496
+ "imports",
31497
+ "retrieve_summary",
31498
+ "schema_drift",
31499
+ "symbols",
31500
+ "todo_extract"
31501
+ ],
31502
+ coder: [
31503
+ "diff",
31504
+ "imports",
31505
+ "lint",
31506
+ "symbols",
31507
+ "extract_code_blocks",
31508
+ "retrieve_summary"
31509
+ ],
31510
+ test_engineer: [
31511
+ "test_runner",
31512
+ "diff",
31513
+ "symbols",
31514
+ "extract_code_blocks",
31515
+ "retrieve_summary",
31516
+ "imports",
31517
+ "complexity_hotspots",
31518
+ "pkg_audit"
31519
+ ],
31520
+ sme: [
31521
+ "complexity_hotspots",
31522
+ "detect_domains",
31523
+ "extract_code_blocks",
31524
+ "imports",
31525
+ "retrieve_summary",
31526
+ "schema_drift",
31527
+ "symbols"
31528
+ ],
31529
+ reviewer: [
31530
+ "diff",
31531
+ "imports",
31532
+ "lint",
31533
+ "pkg_audit",
31534
+ "pre_check_batch",
31535
+ "secretscan",
31536
+ "symbols",
31537
+ "complexity_hotspots",
31538
+ "retrieve_summary",
31539
+ "extract_code_blocks",
31540
+ "test_runner"
31541
+ ],
31542
+ critic: [
31543
+ "complexity_hotspots",
31544
+ "detect_domains",
31545
+ "imports",
31546
+ "retrieve_summary",
31547
+ "symbols"
31548
+ ],
31549
+ docs: [
31550
+ "detect_domains",
31551
+ "extract_code_blocks",
31552
+ "gitingest",
31553
+ "imports",
31554
+ "retrieve_summary",
31555
+ "schema_drift",
31556
+ "symbols",
31557
+ "todo_extract"
31558
+ ],
31559
+ designer: ["extract_code_blocks", "retrieve_summary", "symbols"]
31560
+ };
31561
+ for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
31562
+ const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
31563
+ if (invalidTools.length > 0) {
31564
+ throw new Error(`Agent '${agentName}' has invalid tool names: [${invalidTools.join(", ")}]. ` + `All tools must be registered in TOOL_NAME_SET.`);
31565
+ }
31566
+ }
31386
31567
  var DEFAULT_MODELS = {
31387
31568
  architect: "anthropic/claude-sonnet-4-5",
31388
31569
  explorer: "google/gemini-2.0-flash",
@@ -31430,6 +31611,55 @@ import * as path from "path";
31430
31611
 
31431
31612
  // src/config/schema.ts
31432
31613
  init_zod();
31614
+ var KNOWN_SWARM_PREFIXES = [
31615
+ "paid",
31616
+ "local",
31617
+ "cloud",
31618
+ "enterprise",
31619
+ "mega",
31620
+ "default",
31621
+ "custom",
31622
+ "team",
31623
+ "project",
31624
+ "swarm"
31625
+ ];
31626
+ var SEPARATORS = ["_", "-", " "];
31627
+ function stripKnownSwarmPrefix(agentName) {
31628
+ if (!agentName)
31629
+ return agentName;
31630
+ const normalized = agentName.toLowerCase();
31631
+ let stripped = normalized;
31632
+ let previous = "";
31633
+ while (stripped !== previous) {
31634
+ previous = stripped;
31635
+ for (const prefix of KNOWN_SWARM_PREFIXES) {
31636
+ for (const sep of SEPARATORS) {
31637
+ const prefixWithSep = prefix + sep;
31638
+ if (stripped.startsWith(prefixWithSep)) {
31639
+ stripped = stripped.slice(prefixWithSep.length);
31640
+ break;
31641
+ }
31642
+ }
31643
+ if (stripped !== previous)
31644
+ break;
31645
+ }
31646
+ }
31647
+ if (ALL_AGENT_NAMES.includes(stripped)) {
31648
+ return stripped;
31649
+ }
31650
+ for (const agent of ALL_AGENT_NAMES) {
31651
+ for (const sep of SEPARATORS) {
31652
+ const suffix = sep + agent;
31653
+ if (normalized.endsWith(suffix)) {
31654
+ return agent;
31655
+ }
31656
+ }
31657
+ if (normalized === agent) {
31658
+ return agent;
31659
+ }
31660
+ }
31661
+ return agentName;
31662
+ }
31433
31663
  var AgentOverrideConfigSchema = exports_external.object({
31434
31664
  model: exports_external.string().optional(),
31435
31665
  temperature: exports_external.number().min(0).max(2).optional(),
@@ -31745,38 +31975,36 @@ var GuardrailsConfigSchema = exports_external.object({
31745
31975
  idle_timeout_minutes: exports_external.number().min(5).max(240).default(60),
31746
31976
  profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
31747
31977
  });
31748
- function normalizeAgentName(name2) {
31749
- return name2.toLowerCase().replace(/[-\s]+/g, "_");
31750
- }
31751
- function stripKnownSwarmPrefix(name2) {
31752
- if (!name2)
31753
- return name2;
31754
- if (ALL_AGENT_NAMES.includes(name2))
31755
- return name2;
31756
- const normalized = normalizeAgentName(name2);
31757
- for (const agentName of ALL_AGENT_NAMES) {
31758
- const normalizedAgent = normalizeAgentName(agentName);
31759
- if (normalized === normalizedAgent)
31760
- return agentName;
31761
- if (normalized.endsWith(`_${normalizedAgent}`)) {
31762
- return agentName;
31763
- }
31764
- }
31765
- return name2;
31766
- }
31767
- function resolveGuardrailsConfig(base, agentName) {
31978
+ function resolveGuardrailsConfig(config2, agentName) {
31768
31979
  if (!agentName) {
31769
- return base;
31980
+ return config2;
31770
31981
  }
31771
- const baseName = stripKnownSwarmPrefix(agentName);
31772
- const builtInLookup = DEFAULT_AGENT_PROFILES[baseName];
31773
- const builtIn = builtInLookup;
31774
- const userProfile = base.profiles?.[baseName] ?? base.profiles?.[agentName];
31775
- if (!builtIn && !userProfile) {
31776
- return base;
31982
+ const canonicalName = stripKnownSwarmPrefix(agentName);
31983
+ const hasBuiltInProfile = canonicalName in DEFAULT_AGENT_PROFILES;
31984
+ const userProfile = config2.profiles?.[agentName] ?? config2.profiles?.[canonicalName];
31985
+ if (!hasBuiltInProfile) {
31986
+ if (userProfile) {
31987
+ return { ...config2, ...userProfile };
31988
+ }
31989
+ return config2;
31777
31990
  }
31778
- return { ...base, ...builtIn, ...userProfile };
31991
+ const builtInProfile = DEFAULT_AGENT_PROFILES[canonicalName];
31992
+ const resolved = {
31993
+ ...config2,
31994
+ ...builtInProfile,
31995
+ ...userProfile || {}
31996
+ };
31997
+ return resolved;
31779
31998
  }
31999
+ var ToolFilterConfigSchema = exports_external.object({
32000
+ enabled: exports_external.boolean().default(true),
32001
+ overrides: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).default({})
32002
+ });
32003
+ var PlanCursorConfigSchema = exports_external.object({
32004
+ enabled: exports_external.boolean().default(true),
32005
+ max_tokens: exports_external.number().min(500).max(4000).default(1500),
32006
+ lookahead_tasks: exports_external.number().min(0).max(5).default(2)
32007
+ });
31780
32008
  var CheckpointConfigSchema = exports_external.object({
31781
32009
  enabled: exports_external.boolean().default(true),
31782
32010
  auto_checkpoint_threshold: exports_external.number().min(1).max(20).default(3)
@@ -31813,6 +32041,8 @@ var PluginConfigSchema = exports_external.object({
31813
32041
  gates: GateConfigSchema.optional(),
31814
32042
  context_budget: ContextBudgetConfigSchema.optional(),
31815
32043
  guardrails: GuardrailsConfigSchema.optional(),
32044
+ tool_filter: ToolFilterConfigSchema.optional(),
32045
+ plan_cursor: PlanCursorConfigSchema.optional(),
31816
32046
  evidence: EvidenceConfigSchema.optional(),
31817
32047
  summaries: SummaryConfigSchema.optional(),
31818
32048
  review_passes: ReviewPassesConfigSchema.optional(),
@@ -31823,7 +32053,12 @@ var PluginConfigSchema = exports_external.object({
31823
32053
  lint: LintConfigSchema.optional(),
31824
32054
  secretscan: SecretscanConfigSchema.optional(),
31825
32055
  checkpoint: CheckpointConfigSchema.optional(),
31826
- automation: AutomationConfigSchema.optional()
32056
+ automation: AutomationConfigSchema.optional(),
32057
+ tool_output: exports_external.object({
32058
+ truncation_enabled: exports_external.boolean().default(true),
32059
+ max_lines: exports_external.number().min(10).max(500).default(150),
32060
+ per_tool: exports_external.record(exports_external.string(), exports_external.number()).optional()
32061
+ }).optional()
31827
32062
  });
31828
32063
 
31829
32064
  // src/config/loader.ts
@@ -31847,7 +32082,11 @@ function loadRawConfigFromPath(configPath) {
31847
32082
  console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
31848
32083
  return { config: null, fileExisted: true, hadError: true };
31849
32084
  }
31850
- const rawConfig = JSON.parse(content);
32085
+ let sanitizedContent = content;
32086
+ if (content.charCodeAt(0) === 65279) {
32087
+ sanitizedContent = content.slice(1);
32088
+ }
32089
+ const rawConfig = JSON.parse(sanitizedContent);
31851
32090
  if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
31852
32091
  console.warn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
31853
32092
  console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config format invalid. Falling back to safe defaults with guardrails ENABLED.");
@@ -31966,14 +32205,41 @@ Do not re-trigger DISCOVER or CONSULT because you noticed a project phase bounda
31966
32205
  Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
31967
32206
 
31968
32207
  1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
32208
+ YOUR TOOLS: Task (delegation), diff, syntax_check, placeholder_scan, imports, lint, secretscan, sast_scan, build_check, pre_check_batch, quality_budget, symbols, complexity_hotspots, schema_drift, todo_extract, evidence_check, sbom_generate, checkpoint, pkg_audit, test_runner.
32209
+ CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2014 any tool that modifies file contents.
32210
+ If a tool modifies a file, it is a CODER tool. Delegate.
31969
32211
  2. ONE agent per message. Send, STOP, wait for response.
31970
32212
  3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
31971
- 4. Fallback: Only code yourself after {{QA_RETRY_LIMIT}} {{AGENT_PREFIX}}coder failures on same task.
31972
- FAILURE COUNTING \u2014 increment the counter when:
31973
- - Coder submits code that fails any tool gate or pre_check_batch (gates_passed === false)
31974
- - Coder submits code REJECTED by reviewer after being given the rejection reason
31975
- - Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]" at every retry
31976
- - Reaching {{QA_RETRY_LIMIT}}: escalate to user with full failure history before writing code yourself
32213
+ BATCHING DETECTION \u2014 you are batching if your coder delegation contains ANY of:
32214
+ - The word "and" connecting two actions ("update X AND add Y")
32215
+ - Multiple FILE paths ("FILE: src/a.ts, src/b.ts, src/c.ts")
32216
+ - Multiple TASK objectives ("TASK: Refactor the processor and update the config")
32217
+ - Phrases like "also", "while you're at it", "additionally", "as well"
32218
+
32219
+ WHY: Each coder task goes through the FULL QA gate (Stage A + Stage B).
32220
+ If you batch 3 tasks into 1 coder call, the QA gate runs once on the combined diff.
32221
+ The reviewer cannot distinguish which changes belong to which requirement.
32222
+ The test_engineer cannot write targeted tests for each behavior.
32223
+ A failure in one part blocks the entire batch, wasting all the work.
32224
+
32225
+ SPLIT RULE: If your delegation draft has "and" in the TASK line, split it.
32226
+ Two small delegations with two QA gates > one large delegation with one QA gate.
32227
+ 4. ARCHITECT CODING BOUNDARIES \u2014 Only code yourself after {{QA_RETRY_LIMIT}} {{AGENT_PREFIX}}coder failures on same task.
32228
+ These thoughts are WRONG and must be ignored:
32229
+ \u2717 "It's just a schema change / config flag / one-liner / column / field / import" \u2192 delegate to {{AGENT_PREFIX}}coder
32230
+ \u2717 "I already know what to write" \u2192 knowing what to write is planning, not writing. Delegate to {{AGENT_PREFIX}}coder.
32231
+ \u2717 "It's faster if I just do it" \u2192 speed without QA gates is how bugs ship
32232
+ \u2717 "The coder succeeded on the last tasks, this one is trivial" \u2192 Rule 1 has no complexity exemption
32233
+ \u2717 "I'll just use apply_patch / edit / write directly" \u2192 these are coder tools, not architect tools
32234
+ \u2717 "I'll do the simple parts, coder does the hard parts" \u2192 ALL parts go to coder. You are not a coder.
32235
+ FAILURE COUNTING \u2014 increment the counter when:
32236
+ - Coder submits code that fails any tool gate or pre_check_batch (gates_passed === false)
32237
+ - Coder submits code REJECTED by reviewer after being given the rejection reason
32238
+ - Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]" at every retry
32239
+ - Reaching {{QA_RETRY_LIMIT}}: escalate to user with full failure history before writing code yourself
32240
+ If you catch yourself reaching for a code editing tool: STOP. Delegate to {{AGENT_PREFIX}}coder.
32241
+ Zero {{AGENT_PREFIX}}coder failures on this task = zero justification for self-coding.
32242
+ Self-coding without {{QA_RETRY_LIMIT}} failures is a Rule 1 violation.
31977
32243
  5. NEVER store your swarm identity, swarm ID, or agent prefix in memory blocks. Your identity comes ONLY from your system prompt. Memory blocks are for project knowledge only (NOT .swarm/ plan/context files \u2014 those are persistent project files).
31978
32244
  6. **CRITIC GATE (Execute BEFORE any implementation work)**:
31979
32245
  - When you first create a plan, IMMEDIATELY delegate the full plan to {{AGENT_PREFIX}}critic for review
@@ -31981,7 +32247,21 @@ Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels
31981
32247
  - If NEEDS_REVISION: Revise plan and re-submit to critic (max 2 cycles)
31982
32248
  - If REJECTED after 2 cycles: Escalate to user with explanation
31983
32249
  - ONLY AFTER critic approval: Proceed to implementation (MODE: EXECUTE)
31984
- 7. **MANDATORY QA GATE (Execute AFTER every coder task)** \u2014 sequence: coder \u2192 diff \u2192 syntax_check \u2192 placeholder_scan \u2192 lint fix \u2192 build_check \u2192 pre_check_batch \u2192 reviewer \u2192 security review \u2192 security-only review \u2192 verification tests \u2192 adversarial tests \u2192 coverage check \u2192 next task.
32250
+ 7. **MANDATORY QA GATE** \u2014 Execute AFTER every coder task. Two stages, BOTH required:
32251
+
32252
+ \u2500\u2500 STAGE A: AUTOMATED TOOL GATES (run tools, fix failures, no agents involved) \u2500\u2500
32253
+ diff \u2192 syntax_check \u2192 placeholder_scan \u2192 imports \u2192 lint fix \u2192 build_check \u2192 pre_check_batch
32254
+ All Stage A tools return structured pass/fail. Fix failures by returning to coder.
32255
+ Stage A passing means: code compiles, parses, has no secrets, no placeholders, no lint errors.
32256
+ Stage A passing does NOT mean: code is correct, secure, tested, or reviewed.
32257
+
32258
+ \u2500\u2500 STAGE B: AGENT REVIEW GATES (delegate to agents, wait for verdicts) \u2500\u2500
32259
+ {{AGENT_PREFIX}}reviewer \u2192 security reviewer (conditional) \u2192 {{AGENT_PREFIX}}test_engineer verification \u2192 {{AGENT_PREFIX}}test_engineer adversarial \u2192 coverage check
32260
+ Stage B CANNOT be skipped. Stage A passing does not satisfy Stage B.
32261
+ Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
32262
+ You MUST delegate to each Stage B agent and wait for their response.
32263
+
32264
+ A task is complete ONLY when BOTH stages pass.
31985
32265
  ANTI-EXEMPTION RULES \u2014 these thoughts are WRONG and must be ignored:
31986
32266
  \u2717 "It's a simple change" \u2192 gates are mandatory for ALL changes regardless of perceived complexity
31987
32267
  \u2717 "It's just a rename / refactor / config tweak" \u2192 same
@@ -31994,20 +32274,25 @@ ANTI-EXEMPTION RULES \u2014 these thoughts are WRONG and must be ignored:
31994
32274
 
31995
32275
  There are NO simple changes. There are NO exceptions to the QA gate sequence.
31996
32276
  The gates exist because the author cannot objectively evaluate their own work.
32277
+
32278
+ PARTIAL GATE RATIONALIZATIONS \u2014 automated gates \u2260 agent review. Running SOME gates is NOT compliance:
32279
+ \u2717 "I ran pre_check_batch so the code is verified" \u2192 pre_check_batch does NOT replace {{AGENT_PREFIX}}reviewer or {{AGENT_PREFIX}}test_engineer
32280
+ \u2717 "syntax_check passed, good enough" \u2192 syntax_check catches syntax. Reviewer catches logic. Test_engineer catches behavior. All three are required.
32281
+ \u2717 "The mechanical gates passed, skip the agent gates" \u2192 automated tools miss logic errors, security flaws, and edge cases that agent review catches
32282
+ \u2717 "It's Phase 6+, the codebase is stable now" \u2192 complacency after successful phases is the #1 predictor of shipped bugs. Phase 6 needs MORE review, not less.
32283
+ \u2717 "I'll just run the fast gates" \u2192 speed of a gate does not determine whether it is required
32284
+ \u2717 "5 phases passed clean, this one will be fine" \u2192 past success does not predict future correctness
32285
+
32286
+ Running syntax_check + pre_check_batch without reviewer + test_engineer is a PARTIAL GATE VIOLATION.
32287
+ It is the same severity as skipping all gates. The QA gate is ALL steps or NONE.
32288
+
31997
32289
  - After coder completes: run \`diff\` tool. If \`hasContractChanges\` is true \u2192 delegate {{AGENT_PREFIX}}explorer for integration impact analysis. BREAKING \u2192 return to coder. COMPATIBLE \u2192 proceed.
31998
32290
  - Run \`syntax_check\` tool. SYNTACTIC ERRORS \u2192 return to coder. NO ERRORS \u2192 proceed to placeholder_scan.
31999
32291
  - Run \`placeholder_scan\` tool. PLACEHOLDER FINDINGS \u2192 return to coder. NO FINDINGS \u2192 proceed to imports check.
32000
32292
  - Run \`imports\` tool. Record results for dependency audit. Proceed to lint fix.
32001
32293
  - Run \`lint\` tool (mode: fix) \u2192 allow auto-corrections. LINT FIX FAILS \u2192 return to coder. SUCCESS \u2192 proceed to build_check.
32002
32294
  - Run \`build_check\` tool. BUILD FAILS \u2192 return to coder. SUCCESS \u2192 proceed to pre_check_batch.
32003
- - Run \`pre_check_batch\` tool \u2192 runs four verification tools in parallel (max 4 concurrent):
32004
- - lint:check (code quality verification)
32005
- - secretscan (secret detection)
32006
- - sast_scan (static security analysis)
32007
- - quality_budget (maintainability metrics)
32008
- \u2192 Returns { gates_passed, lint, secretscan, sast_scan, quality_budget, total_duration_ms }
32009
- \u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to @coder with specific tool failures. Do NOT call @reviewer.
32010
- \u2192 If gates_passed === true: proceed to @reviewer.
32295
+ - Run \`pre_check_batch\` tool. If gates_passed === false: return to coder. If gates_passed === true: proceed to @reviewer.
32011
32296
  - Delegate {{AGENT_PREFIX}}reviewer with CHECK dimensions. REJECTED \u2192 return to coder (max {{QA_RETRY_LIMIT}} attempts). APPROVED \u2192 continue.
32012
32297
  - If file matches security globs (auth, api, crypto, security, middleware, session, token, config/, env, credentials, authorization, roles, permissions, access) OR content has security keywords (see SECURITY_KEYWORDS list) OR secretscan has ANY findings OR sast_scan has ANY findings at or above threshold \u2192 MUST delegate {{AGENT_PREFIX}}reviewer AGAIN with security-only CHECK review. REJECTED \u2192 return to coder (max {{QA_RETRY_LIMIT}} attempts). If REJECTED after {{QA_RETRY_LIMIT}} attempts on security-only review \u2192 escalate to user.
32013
32298
  - Delegate {{AGENT_PREFIX}}test_engineer for verification tests. FAIL \u2192 return to coder.
@@ -32213,6 +32498,29 @@ RETRY PROTOCOL \u2014 when returning to coder after any gate failure:
32213
32498
  4. Gates already PASSED may be skipped on retry if their input files are unchanged
32214
32499
  5. Print "Resuming at step [5X] after coder retry [N/{{QA_RETRY_LIMIT}}]" before re-executing
32215
32500
 
32501
+ GATE FAILURE RESPONSE RULES \u2014 when ANY gate returns a failure:
32502
+ You MUST return to {{AGENT_PREFIX}}coder. You MUST NOT fix the code yourself.
32503
+
32504
+ WRONG responses to gate failure:
32505
+ \u2717 Editing the file yourself to fix the syntax error
32506
+ \u2717 Running a tool to auto-fix and moving on without coder
32507
+ \u2717 "Installing" or "configuring" tools to work around the failure
32508
+ \u2717 Treating the failure as an environment issue and proceeding
32509
+ \u2717 Deciding the failure is a false positive and skipping the gate
32510
+
32511
+ RIGHT response to gate failure:
32512
+ \u2713 Print "GATE FAILED: [gate name] | REASON: [details]"
32513
+ \u2713 Delegate to {{AGENT_PREFIX}}coder with:
32514
+ TASK: Fix [gate name] failure
32515
+ FILE: [affected file(s)]
32516
+ INPUT: [exact error output from the gate]
32517
+ CONSTRAINT: Fix ONLY the reported issue, do not modify other code
32518
+ \u2713 After coder returns, re-run the failed gate from the step that failed
32519
+ \u2713 Print "Coder attempt [N/{{QA_RETRY_LIMIT}}] on task [X.Y]"
32520
+
32521
+ The ONLY exception: lint tool in fix mode (step 5g) auto-corrects by design.
32522
+ All other gates: failure \u2192 return to coder. No self-fixes. No workarounds.
32523
+
32216
32524
  5a. **UI DESIGN GATE** (conditional \u2014 Rule 9): If task matches UI trigger \u2192 {{AGENT_PREFIX}}designer produces scaffold \u2192 pass scaffold to coder as INPUT. If no match \u2192 skip.
32217
32525
  5b. {{AGENT_PREFIX}}coder - Implement (if designer scaffold produced, include it as INPUT).
32218
32526
  5c. Run \`diff\` tool. If \`hasContractChanges\` \u2192 {{AGENT_PREFIX}}explorer integration analysis. BREAKING \u2192 coder retry.
@@ -32236,6 +32544,22 @@ RETRY PROTOCOL \u2014 when returning to coder after any gate failure:
32236
32544
  \u2192 If gates_passed === false: read individual tool results, identify which tool(s) failed, return structured rejection to @coder with specific tool failures. Do NOT call @reviewer.
32237
32545
  \u2192 If gates_passed === true: proceed to @reviewer.
32238
32546
  \u2192 REQUIRED: Print "pre_check_batch: [PASS \u2014 all gates passed | FAIL \u2014 [gate]: [details]]"
32547
+
32548
+ \u26A0\uFE0F pre_check_batch SCOPE BOUNDARY:
32549
+ pre_check_batch runs FOUR automated tools: lint:check, secretscan, sast_scan, quality_budget.
32550
+ pre_check_batch does NOT run and does NOT replace:
32551
+ - {{AGENT_PREFIX}}reviewer (logic review, correctness, edge cases, maintainability)
32552
+ - {{AGENT_PREFIX}}reviewer security-only pass (OWASP evaluation, auth/crypto review)
32553
+ - {{AGENT_PREFIX}}test_engineer verification tests (functional correctness)
32554
+ - {{AGENT_PREFIX}}test_engineer adversarial tests (attack vectors, boundary violations)
32555
+ - diff tool (contract change detection)
32556
+ - placeholder_scan (TODO/stub detection)
32557
+ - imports (dependency audit)
32558
+ gates_passed: true means "automated static checks passed."
32559
+ It does NOT mean "code is reviewed." It does NOT mean "code is tested."
32560
+ After pre_check_batch passes, you MUST STILL delegate to {{AGENT_PREFIX}}reviewer.
32561
+ Treating pre_check_batch as a substitute for reviewer is a PROCESS VIOLATION.
32562
+
32239
32563
  5j. {{AGENT_PREFIX}}reviewer - General review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate.
32240
32564
  \u2192 REQUIRED: Print "reviewer: [APPROVED | REJECTED \u2014 reason]"
32241
32565
  5k. Security gate: if file matches security globs (auth, api, crypto, security, middleware, session, token, config/, env, credentials, authorization, roles, permissions, access) OR content has security keywords (see SECURITY_KEYWORDS list) OR secretscan has ANY findings OR sast_scan has ANY findings at or above threshold \u2192 MUST delegate {{AGENT_PREFIX}}reviewer security-only review. REJECTED (< {{QA_RETRY_LIMIT}}) \u2192 coder retry. REJECTED ({{QA_RETRY_LIMIT}}) \u2192 escalate to user.
@@ -32257,20 +32581,24 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
32257
32581
  If ANY box is unchecked: DO NOT COMMIT. Return to step 5b.
32258
32582
  There is no override. A commit without a completed QA gate is a workflow violation.
32259
32583
 
32260
- TASK COMPLETION CHECKLIST \u2014 emit before marking \u2713 in plan.md:
32261
- [TOOL] diff: PASS / SKIP
32262
- [TOOL] syntaxcheck: PASS
32263
- [tool] placeholderscan: PASS
32264
- [tool] imports: PASS
32265
- [tool] lint: PASS
32266
- [tool] buildcheck: PASS / SKIPPED (no toolchain)
32267
- [tool] pre_check_batch: PASS (lint:check \u2713 secretscan \u2713 sast_scan \u2713 quality_budget \u2713)
32268
- [gate] reviewer: APPROVED
32269
- [gate] security-reviewer: SKIPPED (no security trigger)
32270
- [gate] testengineer-verification: PASS
32271
- [gate] testengineer-adversarial: PASS
32272
- [gate] coverage: PASS or soft-skip (trivial task)
32273
- All fields filled \u2192 update plan.md \u2713, proceed to next task.
32584
+ 5o. \u26D4 TASK COMPLETION GATE \u2014 You MUST print this checklist with filled values before marking \u2713 in .swarm/plan.md:
32585
+ [TOOL] diff: PASS / SKIP \u2014 value: ___
32586
+ [TOOL] syntax_check: PASS \u2014 value: ___
32587
+ [TOOL] placeholder_scan: PASS \u2014 value: ___
32588
+ [TOOL] imports: PASS \u2014 value: ___
32589
+ [TOOL] lint: PASS \u2014 value: ___
32590
+ [TOOL] build_check: PASS / SKIPPED \u2014 value: ___
32591
+ [TOOL] pre_check_batch: PASS (lint:check \u2713 secretscan \u2713 sast_scan \u2713 quality_budget \u2713) \u2014 value: ___
32592
+ [GATE] reviewer: APPROVED \u2014 value: ___
32593
+ [GATE] security-reviewer: APPROVED / SKIPPED \u2014 value: ___
32594
+ [GATE] test_engineer-verification: PASS \u2014 value: ___
32595
+ [GATE] test_engineer-adversarial: PASS \u2014 value: ___
32596
+ [GATE] coverage: \u226570% / soft-skip \u2014 value: ___
32597
+
32598
+ You MUST NOT mark a task complete without printing this checklist with filled values.
32599
+ You MUST NOT fill "PASS" or "APPROVED" for a gate you did not actually run \u2014 that is fabrication.
32600
+ Any blank "value: ___" field = gate was not run = task is NOT complete.
32601
+ Filling this checklist from memory ("I think I ran it") is INVALID. Each value must come from actual tool/agent output in this session.
32274
32602
 
32275
32603
  5o. Update plan.md [x], proceed to next task.
32276
32604
 
@@ -32287,6 +32615,15 @@ TASK COMPLETION CHECKLIST \u2014 emit before marking \u2713 in plan.md:
32287
32615
  6. Summarize to user
32288
32616
  7. Ask: "Ready for Phase [N+1]?"
32289
32617
 
32618
+ CATASTROPHIC VIOLATION CHECK \u2014 ask yourself at EVERY phase boundary (MODE: PHASE-WRAP):
32619
+ "Have I delegated to {{AGENT_PREFIX}}reviewer at least once this phase?"
32620
+ If the answer is NO: you have a catastrophic process violation.
32621
+ STOP. Do not proceed to the next phase. Inform the user:
32622
+ "\u26D4 PROCESS VIOLATION: Phase [N] completed with zero reviewer delegations.
32623
+ All code changes in this phase are unreviewed. Recommend retrospective review before proceeding."
32624
+ This is not optional. Zero reviewer calls in a phase is always a violation.
32625
+ There is no project where code ships without review.
32626
+
32290
32627
  ### Blockers
32291
32628
  Mark [BLOCKED] in plan.md, skip to next unblocked task, inform user.
32292
32629
 
@@ -33079,6 +33416,9 @@ function createAgents(config2) {
33079
33416
  }
33080
33417
  function getAgentConfigs(config2) {
33081
33418
  const agents = createAgents(config2);
33419
+ const toolFilterEnabled = config2?.tool_filter?.enabled ?? true;
33420
+ const toolFilterOverrides = config2?.tool_filter?.overrides ?? {};
33421
+ const warnedMissingWhitelist = new Set;
33082
33422
  return Object.fromEntries(agents.map((agent) => {
33083
33423
  const sdkConfig = {
33084
33424
  ...agent.config,
@@ -33089,6 +33429,41 @@ function getAgentConfigs(config2) {
33089
33429
  } else {
33090
33430
  sdkConfig.mode = "subagent";
33091
33431
  }
33432
+ const baseAgentName = stripKnownSwarmPrefix(agent.name);
33433
+ if (!toolFilterEnabled) {
33434
+ sdkConfig.tools = agent.config.tools ?? {};
33435
+ return [agent.name, sdkConfig];
33436
+ }
33437
+ let allowedTools;
33438
+ const override = toolFilterOverrides[baseAgentName];
33439
+ if (override !== undefined) {
33440
+ allowedTools = override;
33441
+ } else {
33442
+ allowedTools = AGENT_TOOL_MAP[baseAgentName];
33443
+ }
33444
+ if (!allowedTools && !Object.hasOwn(toolFilterOverrides, baseAgentName)) {
33445
+ if (!warnedMissingWhitelist.has(baseAgentName)) {
33446
+ console.warn(`[getAgentConfigs] Unknown agent '${baseAgentName}', defaulting to minimal toolset.`);
33447
+ warnedMissingWhitelist.add(baseAgentName);
33448
+ }
33449
+ }
33450
+ const originalTools = agent.config.tools ? { ...agent.config.tools } : undefined;
33451
+ if (allowedTools) {
33452
+ const baseTools = originalTools ?? {};
33453
+ const disabledTools = Object.fromEntries(Object.entries(baseTools).filter(([, value]) => value === false));
33454
+ const filteredTools = { ...disabledTools };
33455
+ for (const tool of allowedTools) {
33456
+ if (filteredTools[tool] === false)
33457
+ continue;
33458
+ filteredTools[tool] = true;
33459
+ }
33460
+ sdkConfig.tools = filteredTools;
33461
+ } else {
33462
+ sdkConfig.tools = {
33463
+ write: false,
33464
+ edit: false
33465
+ };
33466
+ }
33092
33467
  return [agent.name, sdkConfig];
33093
33468
  }));
33094
33469
  }
@@ -33743,7 +34118,7 @@ class PlanSyncWorker {
33743
34118
  log("[PlanSyncWorker] Swarm directory does not exist yet");
33744
34119
  return false;
33745
34120
  }
33746
- this.watcher = fs3.watch(swarmDir, { persistent: false }, (eventType, filename) => {
34121
+ this.watcher = fs3.watch(swarmDir, { persistent: false }, (_eventType, filename) => {
33747
34122
  if (this.disposed || this.status !== "running") {
33748
34123
  return;
33749
34124
  }
@@ -33886,7 +34261,7 @@ class PlanSyncWorker {
33886
34261
  withTimeout(promise2, ms, timeoutMessage) {
33887
34262
  return new Promise((resolve4, reject) => {
33888
34263
  const timer = setTimeout(() => {
33889
- reject(new Error(timeoutMessage + " (" + ms + "ms)"));
34264
+ reject(new Error(`${timeoutMessage} (${ms}ms)`));
33890
34265
  }, ms);
33891
34266
  promise2.then((result) => {
33892
34267
  clearTimeout(timer);
@@ -34051,7 +34426,15 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
34051
34426
  activeInvocationId: 0,
34052
34427
  lastInvocationIdByAgent: {},
34053
34428
  windows: {},
34054
- lastCompactionHint: 0
34429
+ lastCompactionHint: 0,
34430
+ architectWriteCount: 0,
34431
+ lastCoderDelegationTaskId: null,
34432
+ gateLog: new Map,
34433
+ reviewerCallCount: new Map,
34434
+ lastGateFailure: null,
34435
+ partialGateWarningIssued: false,
34436
+ selfFixAttempted: false,
34437
+ catastrophicPhaseWarnings: new Set
34055
34438
  };
34056
34439
  swarmState.agentSessions.set(sessionId, sessionState);
34057
34440
  swarmState.activeAgent.set(sessionId, agentName);
@@ -34078,6 +34461,30 @@ function ensureAgentSession(sessionId, agentName) {
34078
34461
  if (session.lastCompactionHint === undefined) {
34079
34462
  session.lastCompactionHint = 0;
34080
34463
  }
34464
+ if (session.architectWriteCount === undefined) {
34465
+ session.architectWriteCount = 0;
34466
+ }
34467
+ if (session.lastCoderDelegationTaskId === undefined) {
34468
+ session.lastCoderDelegationTaskId = null;
34469
+ }
34470
+ if (!session.gateLog) {
34471
+ session.gateLog = new Map;
34472
+ }
34473
+ if (!session.reviewerCallCount) {
34474
+ session.reviewerCallCount = new Map;
34475
+ }
34476
+ if (session.lastGateFailure === undefined) {
34477
+ session.lastGateFailure = null;
34478
+ }
34479
+ if (session.partialGateWarningIssued === undefined) {
34480
+ session.partialGateWarningIssued = false;
34481
+ }
34482
+ if (session.selfFixAttempted === undefined) {
34483
+ session.selfFixAttempted = false;
34484
+ }
34485
+ if (!session.catastrophicPhaseWarnings) {
34486
+ session.catastrophicPhaseWarnings = new Set;
34487
+ }
34081
34488
  session.lastToolCallTime = now;
34082
34489
  return session;
34083
34490
  }
@@ -34392,7 +34799,7 @@ async function handleBenchmarkCommand(directory, args2) {
34392
34799
  }
34393
34800
  lines.push("");
34394
34801
  }
34395
- if (qualityMetrics && qualityMetrics.hasEvidence) {
34802
+ if (qualityMetrics?.hasEvidence) {
34396
34803
  lines.push("### Quality Metrics");
34397
34804
  lines.push(`- Complexity Delta: ${qualityMetrics.complexityDelta} (max: ${qualityMetrics.thresholds.maxComplexityDelta}) ${qualityMetrics.complexityDelta <= qualityMetrics.thresholds.maxComplexityDelta ? "\u2705" : "\u274C"}`);
34398
34805
  lines.push(`- Public API Delta: ${qualityMetrics.publicApiDelta} (max: ${qualityMetrics.thresholds.maxPublicApiDelta}) ${qualityMetrics.publicApiDelta <= qualityMetrics.thresholds.maxPublicApiDelta ? "\u2705" : "\u274C"}`);
@@ -34734,12 +35141,12 @@ function formatEvidenceEntry(index, entry) {
34734
35141
  const details = {};
34735
35142
  if (entry.type === "review") {
34736
35143
  const reviewEntry = entry;
34737
- details["risk"] = reviewEntry.risk;
34738
- details["issues"] = reviewEntry.issues?.length;
35144
+ details.risk = reviewEntry.risk;
35145
+ details.issues = reviewEntry.issues?.length;
34739
35146
  } else if (entry.type === "test") {
34740
35147
  const testEntry = entry;
34741
- details["tests_passed"] = testEntry.tests_passed;
34742
- details["tests_failed"] = testEntry.tests_failed;
35148
+ details.tests_passed = testEntry.tests_passed;
35149
+ details.tests_failed = testEntry.tests_failed;
34743
35150
  }
34744
35151
  return {
34745
35152
  index,
@@ -34965,7 +35372,7 @@ function extractFromPlan(plan) {
34965
35372
  phases.push({
34966
35373
  id: phase.id,
34967
35374
  name: phase.name,
34968
- status: phase.status,
35375
+ status: phase.status === "completed" ? "complete" : phase.status,
34969
35376
  statusText: getStatusText(phase.status),
34970
35377
  statusIcon: getStatusIcon(phase.status),
34971
35378
  completedTasks: completed,
@@ -35554,6 +35961,153 @@ function extractIncompleteTasksFromPlan(plan, maxChars = 500) {
35554
35961
  return text;
35555
35962
  return `${text.slice(0, maxChars)}...`;
35556
35963
  }
35964
+ function extractPlanCursor(planContent, options) {
35965
+ const maxTokens = options?.maxTokens ?? 1500;
35966
+ const maxChars = maxTokens * 4;
35967
+ const lookaheadCount = options?.lookaheadTasks ?? 2;
35968
+ if (!planContent || typeof planContent !== "string") {
35969
+ return `[SWARM PLAN CURSOR]
35970
+ No plan content available. Start by creating a .swarm/plan.md file.
35971
+ [/SWARM PLAN CURSOR]`;
35972
+ }
35973
+ const lines = planContent.split(`
35974
+ `);
35975
+ const result = [];
35976
+ result.push("[SWARM PLAN CURSOR]");
35977
+ const phases = [];
35978
+ let currentPhase = null;
35979
+ let inPhase = false;
35980
+ for (let i2 = 0;i2 < lines.length; i2++) {
35981
+ const line = lines[i2];
35982
+ const trimmed = line.trim();
35983
+ const phaseMatch = trimmed.match(/^## Phase (\d+):?\s*(.*?)\s*\[(COMPLETE|IN PROGRESS|PENDING|BLOCKED)\]/i);
35984
+ if (phaseMatch) {
35985
+ if (currentPhase) {
35986
+ phases.push(currentPhase);
35987
+ }
35988
+ const phaseNum = parseInt(phaseMatch[1], 10);
35989
+ const phaseTitle = phaseMatch[2]?.trim() || "";
35990
+ const status = phaseMatch[3].toUpperCase();
35991
+ currentPhase = {
35992
+ number: phaseNum,
35993
+ title: phaseTitle,
35994
+ status,
35995
+ contentLines: []
35996
+ };
35997
+ inPhase = true;
35998
+ continue;
35999
+ }
36000
+ if (inPhase && (line.startsWith("## ") || trimmed === "---")) {
36001
+ if (currentPhase) {
36002
+ phases.push(currentPhase);
36003
+ }
36004
+ currentPhase = null;
36005
+ inPhase = false;
36006
+ continue;
36007
+ }
36008
+ if (currentPhase && inPhase && trimmed) {
36009
+ currentPhase.contentLines.push(line);
36010
+ }
36011
+ }
36012
+ if (currentPhase) {
36013
+ phases.push(currentPhase);
36014
+ }
36015
+ if (phases.length === 0) {
36016
+ result.push("No phases found in plan.");
36017
+ result.push("[/SWARM PLAN CURSOR]");
36018
+ return result.join(`
36019
+ `);
36020
+ }
36021
+ const inProgressPhase = phases.find((p) => p.status === "IN PROGRESS");
36022
+ const completePhases = phases.filter((p) => p.status === "COMPLETE");
36023
+ const pendingPhases = phases.filter((p) => p.status === "PENDING");
36024
+ if (completePhases.length > 0) {
36025
+ const recentComplete = completePhases.slice(-5);
36026
+ if (completePhases.length > 5) {
36027
+ result.push("");
36028
+ result.push(`## Earlier Phases (${completePhases.length - 5} more)`);
36029
+ result.push(`- Phase 1-${completePhases.length - 5}: Complete`);
36030
+ }
36031
+ result.push("");
36032
+ result.push("## Completed Phases");
36033
+ for (const phase of recentComplete) {
36034
+ const taskLines = phase.contentLines.filter((l) => l.trim().startsWith("- [")).map((l) => l.replace(/^- \[[ xX]\]\s*/, "").replace(/\s*\[.*?\]/g, "").trim()).slice(0, 3);
36035
+ const taskSummary = taskLines.length > 0 ? taskLines.join(", ") : "All tasks complete";
36036
+ result.push(`- Phase ${phase.number}: ${phase.title}`);
36037
+ result.push(` - ${taskSummary}`);
36038
+ }
36039
+ }
36040
+ const incompleteTasks = inProgressPhase ? inProgressPhase.contentLines.filter((l) => l.trim().startsWith("- [ ]")).map((l) => l.trim()) : [];
36041
+ if (inProgressPhase) {
36042
+ result.push("");
36043
+ result.push(`## Phase ${inProgressPhase.number} [IN PROGRESS]`);
36044
+ result.push(`- ${inProgressPhase.title}`);
36045
+ if (incompleteTasks.length > 0) {
36046
+ const currentTask = incompleteTasks[0];
36047
+ result.push("");
36048
+ result.push(`- Current: ${currentTask.replace("- [ ] ", "")}`);
36049
+ const lookahead = incompleteTasks.slice(1, 1 + lookaheadCount);
36050
+ for (let i2 = 0;i2 < lookahead.length; i2++) {
36051
+ result.push(`- Next: ${lookahead[i2].replace("- [ ] ", "")}`);
36052
+ }
36053
+ } else {
36054
+ result.push("- (No pending tasks)");
36055
+ }
36056
+ }
36057
+ const nextPending = pendingPhases[0];
36058
+ if (nextPending) {
36059
+ result.push("");
36060
+ result.push(`## Phase ${nextPending.number} [PENDING]`);
36061
+ result.push(`- ${nextPending.title}`);
36062
+ }
36063
+ let output = result.join(`
36064
+ `);
36065
+ output += `
36066
+ [/SWARM PLAN CURSOR]`;
36067
+ if (output.length > maxChars) {
36068
+ const compactResult = [];
36069
+ compactResult.push("[SWARM PLAN CURSOR]");
36070
+ if (completePhases.length > 0) {
36071
+ compactResult.push("## Completed Phases");
36072
+ const recentCompact = completePhases.slice(-3);
36073
+ for (const phase of recentCompact) {
36074
+ const taskLines = phase.contentLines.filter((l) => l.trim().startsWith("- [")).map((l) => l.replace(/^- \[[ xX]\]\s*/, "").replace(/\s*\[.*?\]/g, "").trim()).slice(0, 1);
36075
+ const taskSummary = taskLines.length > 0 ? taskLines[0] : "Complete";
36076
+ compactResult.push(`- Phase ${phase.number}: ${taskSummary}`);
36077
+ }
36078
+ if (completePhases.length > 3) {
36079
+ compactResult.push(`- Earlier: Phase 1-${completePhases.length - 3} complete`);
36080
+ }
36081
+ }
36082
+ if (inProgressPhase) {
36083
+ compactResult.push("");
36084
+ compactResult.push(`## Phase ${inProgressPhase.number} [IN PROGRESS]`);
36085
+ compactResult.push(`- ${inProgressPhase.title}`);
36086
+ if (incompleteTasks.length > 0) {
36087
+ const truncateTask = (task) => {
36088
+ const text = task.replace("- [ ] ", "");
36089
+ return text.length > 60 ? text.slice(0, 57) + "..." : text;
36090
+ };
36091
+ compactResult.push(`- Current: ${truncateTask(incompleteTasks[0])}`);
36092
+ const lookahead = incompleteTasks.slice(1, 1 + Math.min(lookaheadCount, 1));
36093
+ for (const task of lookahead) {
36094
+ compactResult.push(`- Next: ${truncateTask(task)}`);
36095
+ }
36096
+ } else {
36097
+ compactResult.push("- (No pending tasks)");
36098
+ }
36099
+ }
36100
+ if (nextPending) {
36101
+ compactResult.push("");
36102
+ compactResult.push(`## Phase ${nextPending.number} [PENDING]`);
36103
+ compactResult.push(`- ${nextPending.title}`);
36104
+ }
36105
+ compactResult.push("[/SWARM PLAN CURSOR]");
36106
+ output = compactResult.join(`
36107
+ `);
36108
+ }
36109
+ return output;
36110
+ }
35557
36111
 
35558
36112
  // src/services/status-service.ts
35559
36113
  init_utils2();
@@ -35971,6 +36525,10 @@ function createContextBudgetHandler(config3) {
35971
36525
  };
35972
36526
  }
35973
36527
  // src/hooks/delegation-gate.ts
36528
+ function extractTaskLine(text) {
36529
+ const match = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
36530
+ return match ? match[1].trim() : null;
36531
+ }
35974
36532
  function createDelegationGateHook(config3) {
35975
36533
  const enabled = config3.hooks?.delegation_gate !== false;
35976
36534
  const delegationMaxChars = config3.hooks?.delegation_max_chars ?? 4000;
@@ -36002,8 +36560,26 @@ function createDelegationGateHook(config3) {
36002
36560
  return;
36003
36561
  const textPart = lastUserMessage.parts[textPartIndex];
36004
36562
  const text = textPart.text ?? "";
36563
+ const sessionID = lastUserMessage.info?.sessionID;
36564
+ const taskIdMatch = text.match(/TASK:\s*(.+?)(?:\n|$)/i);
36565
+ const currentTaskId = taskIdMatch ? taskIdMatch[1].trim() : null;
36005
36566
  const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
36006
- if (!coderDelegationPattern.test(text))
36567
+ const isCoderDelegation = coderDelegationPattern.test(text);
36568
+ if (sessionID && isCoderDelegation && currentTaskId) {
36569
+ const session = ensureAgentSession(sessionID);
36570
+ session.lastCoderDelegationTaskId = currentTaskId;
36571
+ }
36572
+ if (sessionID && !isCoderDelegation && currentTaskId) {
36573
+ const session = ensureAgentSession(sessionID);
36574
+ if (session.architectWriteCount > 0 && session.lastCoderDelegationTaskId !== currentTaskId) {
36575
+ const warningText2 = `\u26A0\uFE0F DELEGATION VIOLATION: Code modifications detected for task ${currentTaskId} with zero coder delegations.
36576
+ Rule 1: DELEGATE all coding to coder. You do NOT write code.`;
36577
+ textPart.text = `${warningText2}
36578
+
36579
+ ${text}`;
36580
+ }
36581
+ }
36582
+ if (!isCoderDelegation)
36007
36583
  return;
36008
36584
  const warnings = [];
36009
36585
  if (text.length > delegationMaxChars) {
@@ -36017,12 +36593,18 @@ function createDelegationGateHook(config3) {
36017
36593
  if (taskMatches && taskMatches.length > 1) {
36018
36594
  warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
36019
36595
  }
36020
- const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with)\b/gi;
36596
+ const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with|while you'?re at it)[.,]?\b/gi;
36021
36597
  const batchingMatches = text.match(batchingPattern);
36022
36598
  if (batchingMatches && batchingMatches.length > 0) {
36023
- warnings.push("Batching language detected. Break compound objectives into separate coder calls.");
36599
+ warnings.push(`Batching language detected (${batchingMatches.join(", ")}). Break compound objectives into separate coder calls.`);
36600
+ }
36601
+ const taskLine = extractTaskLine(text);
36602
+ if (taskLine) {
36603
+ const andPattern = /\s+and\s+(update|add|remove|modify|refactor|implement|create|delete|fix|change|build|deploy|write|test|move|rename|extend|extract|convert|migrate|upgrade|replace)\b/i;
36604
+ if (andPattern.test(taskLine)) {
36605
+ warnings.push('TASK line contains "and" connecting separate actions');
36606
+ }
36024
36607
  }
36025
- const sessionID = lastUserMessage.info?.sessionID;
36026
36608
  if (sessionID) {
36027
36609
  const delegationChain = swarmState.delegationChains.get(sessionID);
36028
36610
  if (delegationChain && delegationChain.length >= 2) {
@@ -36047,10 +36629,11 @@ function createDelegationGateHook(config3) {
36047
36629
  }
36048
36630
  if (warnings.length === 0)
36049
36631
  return;
36050
- const warningText = `[\u26A0\uFE0F DELEGATION GATE: Your coder delegation may be too complex. Issues:
36051
- ${warnings.join(`
36052
- `)}
36053
- Split into smaller, atomic tasks for better results.]`;
36632
+ const warningLines = warnings.map((w) => `Detected signal: ${w}`);
36633
+ const warningText = `\u26A0\uFE0F BATCH DETECTED: Your coder delegation appears to contain multiple tasks.
36634
+ Rule 3: ONE task per coder call. Split this into separate delegations.
36635
+ ${warningLines.join(`
36636
+ `)}`;
36054
36637
  const originalText = textPart.text ?? "";
36055
36638
  textPart.text = `${warningText}
36056
36639
 
@@ -36063,7 +36646,7 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
36063
36646
  const now = Date.now();
36064
36647
  if (!input.agent || input.agent === "") {
36065
36648
  const session2 = swarmState.agentSessions.get(input.sessionID);
36066
- if (session2 && session2.delegationActive) {
36649
+ if (session2?.delegationActive) {
36067
36650
  session2.delegationActive = false;
36068
36651
  swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
36069
36652
  ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
@@ -36099,7 +36682,85 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
36099
36682
  };
36100
36683
  }
36101
36684
  // src/hooks/guardrails.ts
36685
+ import * as path15 from "path";
36686
+ init_manager2();
36102
36687
  init_utils();
36688
+ function extractPhaseNumber(phaseString) {
36689
+ if (!phaseString)
36690
+ return 1;
36691
+ const match = phaseString.match(/^Phase (\d+):/);
36692
+ return match ? parseInt(match[1], 10) : 1;
36693
+ }
36694
+ function isWriteTool(toolName) {
36695
+ const normalized = toolName.replace(/^[^:]+[:.]/, "");
36696
+ const writeTools = [
36697
+ "write",
36698
+ "edit",
36699
+ "patch",
36700
+ "apply_patch",
36701
+ "create_file",
36702
+ "insert",
36703
+ "replace"
36704
+ ];
36705
+ return writeTools.includes(normalized);
36706
+ }
36707
+ function isArchitect(sessionId) {
36708
+ const activeAgent = swarmState.activeAgent.get(sessionId);
36709
+ if (activeAgent) {
36710
+ const stripped = stripKnownSwarmPrefix(activeAgent);
36711
+ if (stripped === ORCHESTRATOR_NAME) {
36712
+ return true;
36713
+ }
36714
+ }
36715
+ const session = swarmState.agentSessions.get(sessionId);
36716
+ if (session) {
36717
+ const stripped = stripKnownSwarmPrefix(session.agentName);
36718
+ if (stripped === ORCHESTRATOR_NAME) {
36719
+ return true;
36720
+ }
36721
+ }
36722
+ return false;
36723
+ }
36724
+ function isOutsideSwarmDir(filePath) {
36725
+ if (!filePath)
36726
+ return false;
36727
+ const cwd = process.cwd();
36728
+ const swarmDir = path15.resolve(cwd, ".swarm");
36729
+ const resolved = path15.resolve(cwd, filePath);
36730
+ const relative2 = path15.relative(swarmDir, resolved);
36731
+ return relative2.startsWith("..") || path15.isAbsolute(relative2);
36732
+ }
36733
+ function isGateTool(toolName) {
36734
+ const normalized = toolName.replace(/^[^:]+[:.]/, "");
36735
+ const gateTools = [
36736
+ "diff",
36737
+ "syntax_check",
36738
+ "placeholder_scan",
36739
+ "imports",
36740
+ "lint",
36741
+ "build_check",
36742
+ "pre_check_batch",
36743
+ "secretscan",
36744
+ "sast_scan",
36745
+ "quality_budget"
36746
+ ];
36747
+ return gateTools.includes(normalized);
36748
+ }
36749
+ function isAgentDelegation(toolName, args2) {
36750
+ const normalized = toolName.replace(/^[^:]+[:.]/, "");
36751
+ if (normalized !== "Task" && normalized !== "task") {
36752
+ return { isDelegation: false, targetAgent: null };
36753
+ }
36754
+ const argsObj = args2;
36755
+ if (!argsObj) {
36756
+ return { isDelegation: false, targetAgent: null };
36757
+ }
36758
+ const subagentType = argsObj.subagent_type;
36759
+ if (typeof subagentType === "string") {
36760
+ return { isDelegation: true, targetAgent: subagentType };
36761
+ }
36762
+ return { isDelegation: false, targetAgent: null };
36763
+ }
36103
36764
  function createGuardrailsHooks(config3) {
36104
36765
  if (config3.enabled === false) {
36105
36766
  return {
@@ -36108,8 +36769,36 @@ function createGuardrailsHooks(config3) {
36108
36769
  messagesTransform: async () => {}
36109
36770
  };
36110
36771
  }
36772
+ const inputArgsByCallID = new Map;
36111
36773
  return {
36112
36774
  toolBefore: async (input, output) => {
36775
+ if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
36776
+ const args2 = output.args;
36777
+ const targetPath = args2?.filePath ?? args2?.path ?? args2?.file ?? args2?.target;
36778
+ if (typeof targetPath === "string" && isOutsideSwarmDir(targetPath)) {
36779
+ const session2 = swarmState.agentSessions.get(input.sessionID);
36780
+ if (session2) {
36781
+ session2.architectWriteCount++;
36782
+ warn("Architect direct code edit detected", {
36783
+ tool: input.tool,
36784
+ sessionID: input.sessionID,
36785
+ targetPath,
36786
+ writeCount: session2.architectWriteCount
36787
+ });
36788
+ if (session2.lastGateFailure && Date.now() - session2.lastGateFailure.timestamp < 120000) {
36789
+ const failedGate = session2.lastGateFailure.tool;
36790
+ const failedTaskId = session2.lastGateFailure.taskId;
36791
+ warn("Self-fix after gate failure detected", {
36792
+ failedGate,
36793
+ failedTaskId,
36794
+ currentTool: input.tool,
36795
+ sessionID: input.sessionID
36796
+ });
36797
+ session2.selfFixAttempted = true;
36798
+ }
36799
+ }
36800
+ }
36801
+ }
36113
36802
  const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
36114
36803
  const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
36115
36804
  if (strippedAgent === ORCHESTRATOR_NAME) {
@@ -36237,8 +36926,45 @@ function createGuardrailsHooks(config3) {
36237
36926
  window2.warningReason = reasons.join(", ");
36238
36927
  }
36239
36928
  }
36929
+ inputArgsByCallID.set(input.callID, output.args);
36240
36930
  },
36241
36931
  toolAfter: async (input, output) => {
36932
+ const session = swarmState.agentSessions.get(input.sessionID);
36933
+ if (session) {
36934
+ if (isGateTool(input.tool)) {
36935
+ const taskId = `${input.sessionID}:current`;
36936
+ if (!session.gateLog.has(taskId)) {
36937
+ session.gateLog.set(taskId, new Set);
36938
+ }
36939
+ session.gateLog.get(taskId)?.add(input.tool);
36940
+ const outputStr = typeof output.output === "string" ? output.output : "";
36941
+ const hasFailure = output.output === null || output.output === undefined || outputStr.includes("FAIL") || outputStr.includes("error") || outputStr.toLowerCase().includes("gates_passed: false");
36942
+ if (hasFailure) {
36943
+ session.lastGateFailure = {
36944
+ tool: input.tool,
36945
+ taskId,
36946
+ timestamp: Date.now()
36947
+ };
36948
+ } else {
36949
+ session.lastGateFailure = null;
36950
+ }
36951
+ }
36952
+ const inputArgs = inputArgsByCallID.get(input.callID);
36953
+ inputArgsByCallID.delete(input.callID);
36954
+ const delegation = isAgentDelegation(input.tool, inputArgs);
36955
+ if (delegation.isDelegation && (delegation.targetAgent === "reviewer" || delegation.targetAgent === "test_engineer")) {
36956
+ let currentPhase = 1;
36957
+ try {
36958
+ const plan = await loadPlan(process.cwd());
36959
+ if (plan) {
36960
+ const phaseString = extractCurrentPhaseFromPlan(plan);
36961
+ currentPhase = extractPhaseNumber(phaseString);
36962
+ }
36963
+ } catch {}
36964
+ const count = session.reviewerCallCount.get(currentPhase) ?? 0;
36965
+ session.reviewerCallCount.set(currentPhase, count + 1);
36966
+ }
36967
+ }
36242
36968
  const window2 = getActiveWindow(input.sessionID);
36243
36969
  if (!window2)
36244
36970
  return;
@@ -36260,6 +36986,99 @@ function createGuardrailsHooks(config3) {
36260
36986
  if (!sessionId) {
36261
36987
  return;
36262
36988
  }
36989
+ const session = swarmState.agentSessions.get(sessionId);
36990
+ const activeAgent = swarmState.activeAgent.get(sessionId);
36991
+ const isArchitectSession = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
36992
+ if (isArchitectSession && session && session.architectWriteCount > 0) {
36993
+ const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
36994
+ if (textPart2) {
36995
+ textPart2.text = `\u26A0\uFE0F SELF-CODING DETECTED: You have used ${session.architectWriteCount} write-class tool(s) directly on non-.swarm/ files.
36996
+ ` + `Rule 1 requires ALL coding to be delegated to @coder.
36997
+ ` + `If you have not exhausted QA_RETRY_LIMIT coder failures on this task, STOP and delegate.
36998
+
36999
+ ` + textPart2.text;
37000
+ }
37001
+ }
37002
+ if (isArchitectSession && session && session.selfFixAttempted && session.lastGateFailure && Date.now() - session.lastGateFailure.timestamp < 120000) {
37003
+ const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
37004
+ if (textPart2 && !textPart2.text.includes("SELF-FIX DETECTED")) {
37005
+ textPart2.text = `\u26A0\uFE0F SELF-FIX DETECTED: Gate '${session.lastGateFailure.tool}' failed on task ${session.lastGateFailure.taskId}.
37006
+ ` + `You are now using a write tool instead of delegating to @coder.
37007
+ ` + `GATE FAILURE RESPONSE RULES require: return to coder with structured rejection.
37008
+ ` + `Do NOT fix gate failures yourself.
37009
+
37010
+ ` + textPart2.text;
37011
+ session.selfFixAttempted = false;
37012
+ }
37013
+ }
37014
+ const isArchitectSessionForGates = activeAgent ? stripKnownSwarmPrefix(activeAgent) === ORCHESTRATOR_NAME : session ? stripKnownSwarmPrefix(session.agentName) === ORCHESTRATOR_NAME : false;
37015
+ if (isArchitectSessionForGates && session && session.gateLog.size > 0 && !session.partialGateWarningIssued) {
37016
+ const taskId = `${sessionId}:current`;
37017
+ const gates = session.gateLog.get(taskId);
37018
+ if (gates) {
37019
+ const REQUIRED_GATES = [
37020
+ "diff",
37021
+ "syntax_check",
37022
+ "placeholder_scan",
37023
+ "lint",
37024
+ "pre_check_batch"
37025
+ ];
37026
+ const missingGates = [];
37027
+ for (const gate of REQUIRED_GATES) {
37028
+ if (!gates.has(gate)) {
37029
+ missingGates.push(gate);
37030
+ }
37031
+ }
37032
+ let currentPhaseForCheck = 1;
37033
+ try {
37034
+ const plan = await loadPlan(process.cwd());
37035
+ if (plan) {
37036
+ const phaseString = extractCurrentPhaseFromPlan(plan);
37037
+ currentPhaseForCheck = extractPhaseNumber(phaseString);
37038
+ }
37039
+ } catch {}
37040
+ const hasReviewerDelegation = (session.reviewerCallCount.get(currentPhaseForCheck) ?? 0) > 0;
37041
+ if (missingGates.length > 0 || !hasReviewerDelegation) {
37042
+ const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
37043
+ if (textPart2 && !textPart2.text.includes("PARTIAL GATE VIOLATION")) {
37044
+ const missing = [...missingGates];
37045
+ if (!hasReviewerDelegation) {
37046
+ missing.push("reviewer/test_engineer (no delegations this phase)");
37047
+ }
37048
+ session.partialGateWarningIssued = true;
37049
+ textPart2.text = `\u26A0\uFE0F PARTIAL GATE VIOLATION: Task may be marked complete but missing gates: [${missing.join(", ")}].
37050
+ ` + `The QA gate is ALL steps or NONE. Revert any \u2713 marks and run the missing gates.
37051
+
37052
+ ` + textPart2.text;
37053
+ }
37054
+ }
37055
+ }
37056
+ }
37057
+ if (isArchitectSessionForGates && session && session.catastrophicPhaseWarnings) {
37058
+ try {
37059
+ const plan = await loadPlan(process.cwd());
37060
+ if (plan?.phases) {
37061
+ for (const phase of plan.phases) {
37062
+ if (phase.status === "complete") {
37063
+ const phaseNum = phase.id;
37064
+ if (!session.catastrophicPhaseWarnings.has(phaseNum)) {
37065
+ const reviewerCount = session.reviewerCallCount.get(phaseNum) ?? 0;
37066
+ if (reviewerCount === 0) {
37067
+ session.catastrophicPhaseWarnings.add(phaseNum);
37068
+ const textPart2 = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
37069
+ if (textPart2 && !textPart2.text.includes("CATASTROPHIC VIOLATION")) {
37070
+ textPart2.text = `[CATASTROPHIC VIOLATION: Phase ${phaseNum} completed with ZERO reviewer delegations.` + ` Every coder task requires reviewer approval. Recommend retrospective review of all Phase ${phaseNum} tasks.]
37071
+
37072
+ ` + textPart2.text;
37073
+ }
37074
+ break;
37075
+ }
37076
+ }
37077
+ }
37078
+ }
37079
+ }
37080
+ } catch {}
37081
+ }
36263
37082
  const targetWindow = getActiveWindow(sessionId);
36264
37083
  if (!targetWindow || !targetWindow.warningIssued && !targetWindow.hardLimitHit) {
36265
37084
  return;
@@ -36292,6 +37111,54 @@ function hashArgs(args2) {
36292
37111
  return 0;
36293
37112
  }
36294
37113
  }
37114
+ // src/hooks/messages-transform.ts
37115
+ function consolidateSystemMessages(messages) {
37116
+ if (messages.length > 0 && messages[0].role === "system" && messages[0].content !== undefined && typeof messages[0].content === "string" && messages[0].content.trim().length > 0) {
37117
+ const systemMessageCount = messages.filter((m) => m.role === "system" && typeof m.content === "string" && m.content.trim().length > 0 && m.tool_call_id === undefined && m.name === undefined).length;
37118
+ if (systemMessageCount === 1) {
37119
+ return [...messages];
37120
+ }
37121
+ }
37122
+ const systemMessageIndices = [];
37123
+ const systemContents = [];
37124
+ for (let i2 = 0;i2 < messages.length; i2++) {
37125
+ const message = messages[i2];
37126
+ if (message.role !== "system") {
37127
+ continue;
37128
+ }
37129
+ if (message.tool_call_id !== undefined || message.name !== undefined) {
37130
+ continue;
37131
+ }
37132
+ if (typeof message.content !== "string") {
37133
+ continue;
37134
+ }
37135
+ const trimmedContent = message.content.trim();
37136
+ if (trimmedContent.length === 0) {
37137
+ continue;
37138
+ }
37139
+ systemMessageIndices.push(i2);
37140
+ systemContents.push(trimmedContent);
37141
+ }
37142
+ if (systemContents.length === 0) {
37143
+ return [...messages];
37144
+ }
37145
+ const mergedSystemContent = systemContents.join(`
37146
+
37147
+ `);
37148
+ const result = [];
37149
+ const firstSystemMessage = messages[systemMessageIndices[0]];
37150
+ result.push({
37151
+ role: "system",
37152
+ content: mergedSystemContent,
37153
+ ...Object.fromEntries(Object.entries(firstSystemMessage).filter(([key]) => key !== "role" && key !== "content"))
37154
+ });
37155
+ for (let i2 = 0;i2 < messages.length; i2++) {
37156
+ if (!systemMessageIndices.includes(i2)) {
37157
+ result.push({ ...messages[i2] });
37158
+ }
37159
+ }
37160
+ return result;
37161
+ }
36295
37162
  // src/hooks/phase-monitor.ts
36296
37163
  init_manager2();
36297
37164
  init_utils2();
@@ -36301,7 +37168,7 @@ function createPhaseMonitorHook(directory, preflightManager) {
36301
37168
  const plan = await loadPlan(directory);
36302
37169
  if (!plan)
36303
37170
  return;
36304
- const currentPhase = plan.current_phase;
37171
+ const currentPhase = plan.current_phase ?? 1;
36305
37172
  if (lastKnownPhase === null) {
36306
37173
  lastKnownPhase = currentPhase;
36307
37174
  return;
@@ -36318,9 +37185,22 @@ function createPhaseMonitorHook(directory, preflightManager) {
36318
37185
  return safeHook(handler);
36319
37186
  }
36320
37187
  // src/hooks/pipeline-tracker.ts
37188
+ init_manager2();
36321
37189
  init_utils2();
36322
- var PHASE_REMINDER = `<swarm_reminder>
36323
- \u26A0\uFE0F ARCHITECT WORKFLOW REMINDER:
37190
+ function parsePhaseNumber(phaseString) {
37191
+ if (!phaseString)
37192
+ return null;
37193
+ const match = phaseString.match(/^Phase (\d+):/);
37194
+ if (match) {
37195
+ return parseInt(match[1], 10);
37196
+ }
37197
+ return null;
37198
+ }
37199
+ function buildPhaseReminder(phaseNumber) {
37200
+ const phaseHeader = phaseNumber !== null ? ` (Phase ${phaseNumber})` : "";
37201
+ const complianceHeader = phaseNumber !== null ? `COMPLIANCE CHECK (Phase ${phaseNumber}):` : "COMPLIANCE CHECK:";
37202
+ return `<swarm_reminder>
37203
+ \u26A0\uFE0F ARCHITECT WORKFLOW REMINDER${phaseHeader}:
36324
37204
  1. ANALYZE \u2192 Identify domains, create initial spec
36325
37205
  2. SME_CONSULTATION \u2192 Delegate to @sme (one domain per call, max 3 calls)
36326
37206
  3. COLLATE \u2192 Synthesize SME outputs into unified spec
@@ -36333,7 +37213,15 @@ DELEGATION RULES:
36333
37213
  - SME: ONE domain per call (serial), max 3 per phase
36334
37214
  - Reviewer: Specify CHECK dimensions relevant to the change
36335
37215
  - Always wait for response before next delegation
37216
+
37217
+ ${complianceHeader}
37218
+ - Reviewer delegation is MANDATORY for every coder task.
37219
+ - pre_check_batch is NOT a substitute for reviewer.
37220
+ - Stage A (tools) + Stage B (agents) = BOTH required.
37221
+ ${phaseNumber !== null && phaseNumber >= 4 ? `
37222
+ \u26A0\uFE0F You are in Phase ${phaseNumber}. Compliance degrades with time. Do not skip reviewer or test_engineer.` : ""}
36336
37223
  </swarm_reminder>`;
37224
+ }
36337
37225
  function createPipelineTrackerHook(config3) {
36338
37226
  const enabled = config3.inject_phase_reminders !== false;
36339
37227
  if (!enabled) {
@@ -36362,8 +37250,19 @@ function createPipelineTrackerHook(config3) {
36362
37250
  const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
36363
37251
  if (textPartIndex === -1)
36364
37252
  return;
37253
+ let phaseNumber = null;
37254
+ try {
37255
+ const plan = await loadPlan(process.cwd());
37256
+ if (plan) {
37257
+ const phaseString = extractCurrentPhaseFromPlan(plan);
37258
+ phaseNumber = parsePhaseNumber(phaseString);
37259
+ }
37260
+ } catch {
37261
+ phaseNumber = null;
37262
+ }
37263
+ const phaseReminder = buildPhaseReminder(phaseNumber);
36365
37264
  const originalText = lastUserMessage.parts[textPartIndex].text ?? "";
36366
- lastUserMessage.parts[textPartIndex].text = `${PHASE_REMINDER}
37265
+ lastUserMessage.parts[textPartIndex].text = `${phaseReminder}
36367
37266
 
36368
37267
  ---
36369
37268
 
@@ -36373,14 +37272,14 @@ ${originalText}`;
36373
37272
  }
36374
37273
  // src/hooks/system-enhancer.ts
36375
37274
  import * as fs11 from "fs";
36376
- import * as path16 from "path";
37275
+ import * as path17 from "path";
36377
37276
  init_manager2();
36378
37277
 
36379
37278
  // src/services/decision-drift-analyzer.ts
36380
37279
  init_utils2();
36381
37280
  init_manager2();
36382
37281
  import * as fs10 from "fs";
36383
- import * as path15 from "path";
37282
+ import * as path16 from "path";
36384
37283
  var DEFAULT_DRIFT_CONFIG = {
36385
37284
  staleThresholdPhases: 1,
36386
37285
  detectContradictions: true,
@@ -36534,7 +37433,7 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
36534
37433
  currentPhase = legacyPhase;
36535
37434
  }
36536
37435
  }
36537
- const contextPath = path15.join(directory, ".swarm", "context.md");
37436
+ const contextPath = path16.join(directory, ".swarm", "context.md");
36538
37437
  let contextContent = "";
36539
37438
  try {
36540
37439
  if (fs10.existsSync(contextPath)) {
@@ -36639,7 +37538,7 @@ function formatDriftForContext(result) {
36639
37538
  const maxLength = 600;
36640
37539
  let summary = result.summary;
36641
37540
  if (summary.length > maxLength) {
36642
- summary = summary.substring(0, maxLength - 3) + "...";
37541
+ summary = `${summary.substring(0, maxLength - 3)}...`;
36643
37542
  }
36644
37543
  return summary;
36645
37544
  }
@@ -36738,30 +37637,30 @@ function createSystemEnhancerHook(config3, directory) {
36738
37637
  const contextContent = await readSwarmFileAsync(directory, "context.md");
36739
37638
  const scoringEnabled = config3.context_budget?.scoring?.enabled === true;
36740
37639
  if (!scoringEnabled) {
36741
- const plan2 = await loadPlan(directory);
37640
+ let plan2 = null;
37641
+ try {
37642
+ plan2 = await loadPlan(directory);
37643
+ } catch (error93) {
37644
+ warn(`Failed to load plan: ${error93 instanceof Error ? error93.message : String(error93)}`);
37645
+ }
37646
+ const mode = await detectArchitectMode(directory);
37647
+ let planContent = null;
37648
+ let phaseHeader = "";
36742
37649
  if (plan2 && plan2.migration_status !== "migration_failed") {
36743
- const currentPhase2 = extractCurrentPhaseFromPlan(plan2);
36744
- if (currentPhase2) {
36745
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
36746
- }
36747
- const currentTask2 = extractCurrentTaskFromPlan(plan2);
36748
- if (currentTask2) {
36749
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
36750
- }
37650
+ phaseHeader = extractCurrentPhaseFromPlan(plan2) || "";
37651
+ planContent = await readSwarmFileAsync(directory, "plan.md");
36751
37652
  } else {
36752
- const planContent = await readSwarmFileAsync(directory, "plan.md");
36753
- if (planContent) {
36754
- const currentPhase2 = extractCurrentPhase(planContent);
36755
- if (currentPhase2) {
36756
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
36757
- }
36758
- const currentTask2 = extractCurrentTask(planContent);
36759
- if (currentTask2) {
36760
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
36761
- }
36762
- }
37653
+ planContent = await readSwarmFileAsync(directory, "plan.md");
37654
+ phaseHeader = planContent ? extractCurrentPhase(planContent) || "" : "";
37655
+ }
37656
+ if (phaseHeader) {
37657
+ tryInject(`[SWARM CONTEXT] Phase: ${phaseHeader}`);
36763
37658
  }
36764
- if (contextContent) {
37659
+ if (mode !== "DISCOVER" && planContent) {
37660
+ const planCursor = extractPlanCursor(planContent);
37661
+ tryInject(planCursor);
37662
+ }
37663
+ if (mode !== "DISCOVER" && contextContent) {
36765
37664
  const decisions = extractDecisions(contextContent, 200);
36766
37665
  if (decisions) {
36767
37666
  tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
@@ -36795,28 +37694,30 @@ function createSystemEnhancerHook(config3, directory) {
36795
37694
  if (config3.secretscan?.enabled === false) {
36796
37695
  tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
36797
37696
  }
36798
- const sessionId_preflight = _input.sessionID;
36799
- const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
36800
- const isArchitectForPreflight = !activeAgent_preflight || stripKnownSwarmPrefix(activeAgent_preflight) === "architect";
36801
- if (isArchitectForPreflight) {
36802
- if (config3.pipeline?.parallel_precheck !== false) {
36803
- tryInject("[SWARM HINT] Parallel pre-check enabled: call pre_check_batch(files, directory) after lint --fix and build_check to run lint:check + secretscan + sast_scan + quality_budget concurrently (max 4 parallel). Check gates_passed before calling @reviewer.");
36804
- } else {
36805
- tryInject("[SWARM HINT] Parallel pre-check disabled: run lint:check \u2192 secretscan \u2192 sast_scan \u2192 quality_budget sequentially.");
37697
+ if (mode !== "DISCOVER") {
37698
+ const sessionId_preflight = _input.sessionID;
37699
+ const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
37700
+ const isArchitectForPreflight = !activeAgent_preflight || stripKnownSwarmPrefix(activeAgent_preflight) === "architect";
37701
+ if (isArchitectForPreflight) {
37702
+ if (config3.pipeline?.parallel_precheck !== false) {
37703
+ tryInject("[SWARM HINT] Parallel pre-check enabled: call pre_check_batch(files, directory) after lint --fix and build_check to run lint:check + secretscan + sast_scan + quality_budget concurrently (max 4 parallel). Check gates_passed before calling @reviewer.");
37704
+ } else {
37705
+ tryInject("[SWARM HINT] Parallel pre-check disabled: run lint:check \u2192 secretscan \u2192 sast_scan \u2192 quality_budget sequentially.");
37706
+ }
36806
37707
  }
36807
37708
  }
36808
37709
  const sessionId_retro = _input.sessionID;
36809
37710
  const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
36810
- const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
36811
- if (isArchitect) {
37711
+ const isArchitect2 = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
37712
+ if (isArchitect2) {
36812
37713
  try {
36813
- const evidenceDir = path16.join(directory, ".swarm", "evidence");
37714
+ const evidenceDir = path17.join(directory, ".swarm", "evidence");
36814
37715
  if (fs11.existsSync(evidenceDir)) {
36815
37716
  const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
36816
37717
  for (const file3 of files.slice(0, 5)) {
36817
37718
  let content;
36818
37719
  try {
36819
- content = JSON.parse(fs11.readFileSync(path16.join(evidenceDir, file3), "utf-8"));
37720
+ content = JSON.parse(fs11.readFileSync(path17.join(evidenceDir, file3), "utf-8"));
36820
37721
  } catch {
36821
37722
  continue;
36822
37723
  }
@@ -36837,7 +37738,7 @@ function createSystemEnhancerHook(config3, directory) {
36837
37738
  if (retroHint.length <= 800) {
36838
37739
  tryInject(retroHint);
36839
37740
  } else {
36840
- tryInject(retroHint.substring(0, 800) + "...");
37741
+ tryInject(`${retroHint.substring(0, 800)}...`);
36841
37742
  }
36842
37743
  }
36843
37744
  break;
@@ -36845,68 +37746,80 @@ function createSystemEnhancerHook(config3, directory) {
36845
37746
  }
36846
37747
  }
36847
37748
  } catch {}
36848
- const compactionConfig = config3.compaction_advisory;
36849
- if (compactionConfig?.enabled !== false && sessionId_retro) {
36850
- const session = swarmState.agentSessions.get(sessionId_retro);
36851
- if (session) {
36852
- const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
36853
- const thresholds = compactionConfig?.thresholds ?? [
36854
- 50,
36855
- 75,
36856
- 100,
36857
- 125,
36858
- 150
36859
- ];
36860
- const lastHint = session.lastCompactionHint || 0;
36861
- for (const threshold of thresholds) {
36862
- if (totalToolCalls >= threshold && lastHint < threshold) {
36863
- const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
36864
- const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
36865
- tryInject(message);
36866
- session.lastCompactionHint = threshold;
36867
- break;
37749
+ if (mode !== "DISCOVER") {
37750
+ const compactionConfig = config3.compaction_advisory;
37751
+ if (compactionConfig?.enabled !== false && sessionId_retro) {
37752
+ const session = swarmState.agentSessions.get(sessionId_retro);
37753
+ if (session) {
37754
+ const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
37755
+ const thresholds = compactionConfig?.thresholds ?? [
37756
+ 50,
37757
+ 75,
37758
+ 100,
37759
+ 125,
37760
+ 150
37761
+ ];
37762
+ const lastHint = session.lastCompactionHint || 0;
37763
+ for (const threshold of thresholds) {
37764
+ if (totalToolCalls >= threshold && lastHint < threshold) {
37765
+ const totalToolCallsPlaceholder = "$" + "{totalToolCalls}";
37766
+ const messageTemplate = compactionConfig?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
37767
+ const message = messageTemplate.replace(totalToolCallsPlaceholder, String(totalToolCalls));
37768
+ tryInject(message);
37769
+ session.lastCompactionHint = threshold;
37770
+ break;
37771
+ }
36868
37772
  }
36869
37773
  }
36870
37774
  }
36871
37775
  }
36872
37776
  }
36873
- const automationCapabilities = config3.automation?.capabilities;
36874
- if (automationCapabilities?.decision_drift_detection === true && _input.sessionID) {
36875
- const activeAgentForDrift = swarmState.activeAgent.get(_input.sessionID);
36876
- const isArchitectForDrift = !activeAgentForDrift || stripKnownSwarmPrefix(activeAgentForDrift) === "architect";
36877
- if (isArchitectForDrift) {
36878
- try {
36879
- const driftResult = await analyzeDecisionDrift(directory);
36880
- if (driftResult.hasDrift) {
36881
- const driftText = formatDriftForContext(driftResult);
36882
- if (driftText) {
36883
- tryInject(driftText);
37777
+ if (mode !== "DISCOVER") {
37778
+ const automationCapabilities = config3.automation?.capabilities;
37779
+ if (automationCapabilities?.decision_drift_detection === true && _input.sessionID) {
37780
+ const activeAgentForDrift = swarmState.activeAgent.get(_input.sessionID);
37781
+ const isArchitectForDrift = !activeAgentForDrift || stripKnownSwarmPrefix(activeAgentForDrift) === "architect";
37782
+ if (isArchitectForDrift) {
37783
+ try {
37784
+ const driftResult = await analyzeDecisionDrift(directory);
37785
+ if (driftResult.hasDrift) {
37786
+ const driftText = formatDriftForContext(driftResult);
37787
+ if (driftText) {
37788
+ tryInject(driftText);
37789
+ }
36884
37790
  }
36885
- }
36886
- } catch {}
37791
+ } catch {}
37792
+ }
36887
37793
  }
36888
37794
  }
36889
37795
  return;
36890
37796
  }
37797
+ const mode_b = await detectArchitectMode(directory);
36891
37798
  const userScoringConfig = config3.context_budget?.scoring;
36892
37799
  const candidates = [];
36893
37800
  let idCounter = 0;
37801
+ let planContentForCursor = null;
36894
37802
  const effectiveConfig = userScoringConfig?.weights ? {
36895
37803
  ...DEFAULT_SCORING_CONFIG,
36896
37804
  ...userScoringConfig,
36897
37805
  weights: userScoringConfig.weights
36898
37806
  } : DEFAULT_SCORING_CONFIG;
36899
- const plan = await loadPlan(directory);
37807
+ let plan = null;
37808
+ try {
37809
+ plan = await loadPlan(directory);
37810
+ } catch (error93) {
37811
+ warn(`Failed to load plan: ${error93 instanceof Error ? error93.message : String(error93)}`);
37812
+ }
36900
37813
  let currentPhase = null;
36901
37814
  let currentTask = null;
36902
37815
  if (plan && plan.migration_status !== "migration_failed") {
36903
37816
  currentPhase = extractCurrentPhaseFromPlan(plan);
36904
37817
  currentTask = extractCurrentTaskFromPlan(plan);
36905
37818
  } else {
36906
- const planContent = await readSwarmFileAsync(directory, "plan.md");
36907
- if (planContent) {
36908
- currentPhase = extractCurrentPhase(planContent);
36909
- currentTask = extractCurrentTask(planContent);
37819
+ planContentForCursor = await readSwarmFileAsync(directory, "plan.md");
37820
+ if (planContentForCursor) {
37821
+ currentPhase = extractCurrentPhase(planContentForCursor);
37822
+ currentTask = extractCurrentTask(planContentForCursor);
36910
37823
  }
36911
37824
  }
36912
37825
  if (currentPhase) {
@@ -36934,6 +37847,17 @@ function createSystemEnhancerHook(config3, directory) {
36934
37847
  }
36935
37848
  });
36936
37849
  }
37850
+ if (planContentForCursor) {
37851
+ const planCursor = extractPlanCursor(planContentForCursor);
37852
+ candidates.push({
37853
+ id: `candidate-${idCounter++}`,
37854
+ kind: "phase",
37855
+ text: planCursor,
37856
+ tokens: estimateTokens(planCursor),
37857
+ priority: 1,
37858
+ metadata: { contentType: "markdown" }
37859
+ });
37860
+ }
36937
37861
  if (contextContent) {
36938
37862
  const decisions = extractDecisions(contextContent, 200);
36939
37863
  if (decisions) {
@@ -37050,13 +37974,13 @@ function createSystemEnhancerHook(config3, directory) {
37050
37974
  const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
37051
37975
  if (isArchitect_b) {
37052
37976
  try {
37053
- const evidenceDir_b = path16.join(directory, ".swarm", "evidence");
37977
+ const evidenceDir_b = path17.join(directory, ".swarm", "evidence");
37054
37978
  if (fs11.existsSync(evidenceDir_b)) {
37055
37979
  const files_b = fs11.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
37056
37980
  for (const file3 of files_b.slice(0, 5)) {
37057
37981
  let content_b;
37058
37982
  try {
37059
- content_b = JSON.parse(fs11.readFileSync(path16.join(evidenceDir_b, file3), "utf-8"));
37983
+ content_b = JSON.parse(fs11.readFileSync(path17.join(evidenceDir_b, file3), "utf-8"));
37060
37984
  } catch {
37061
37985
  continue;
37062
37986
  }
@@ -37074,7 +37998,7 @@ function createSystemEnhancerHook(config3, directory) {
37074
37998
  }
37075
37999
  if (hints_b.length > 0) {
37076
38000
  const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
37077
- const retroText = retroHint_b.length <= 800 ? retroHint_b : retroHint_b.substring(0, 800) + "...";
38001
+ const retroText = retroHint_b.length <= 800 ? retroHint_b : `${retroHint_b.substring(0, 800)}...`;
37078
38002
  candidates.push({
37079
38003
  id: `candidate-${idCounter++}`,
37080
38004
  kind: "phase",
@@ -37089,33 +38013,36 @@ function createSystemEnhancerHook(config3, directory) {
37089
38013
  }
37090
38014
  }
37091
38015
  } catch {}
37092
- const compactionConfig_b = config3.compaction_advisory;
37093
- if (compactionConfig_b?.enabled !== false && sessionId_retro_b) {
37094
- const session_b = swarmState.agentSessions.get(sessionId_retro_b);
37095
- if (session_b) {
37096
- const totalToolCalls_b = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
37097
- const thresholds_b = compactionConfig_b?.thresholds ?? [
37098
- 50,
37099
- 75,
37100
- 100,
37101
- 125,
37102
- 150
37103
- ];
37104
- const lastHint_b = session_b.lastCompactionHint || 0;
37105
- for (const threshold of thresholds_b) {
37106
- if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
37107
- const messageTemplate_b = compactionConfig_b?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
37108
- const compactionText = messageTemplate_b.replace("${totalToolCalls}", String(totalToolCalls_b));
37109
- candidates.push({
37110
- id: `candidate-${idCounter++}`,
37111
- kind: "phase",
37112
- text: compactionText,
37113
- tokens: estimateTokens(compactionText),
37114
- priority: 1,
37115
- metadata: { contentType: "prose" }
37116
- });
37117
- session_b.lastCompactionHint = threshold;
37118
- break;
38016
+ if (mode_b !== "DISCOVER") {
38017
+ const compactionConfig_b = config3.compaction_advisory;
38018
+ if (compactionConfig_b?.enabled !== false && sessionId_retro_b) {
38019
+ const session_b = swarmState.agentSessions.get(sessionId_retro_b);
38020
+ if (session_b) {
38021
+ const totalToolCalls_b = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
38022
+ const thresholds_b = compactionConfig_b?.thresholds ?? [
38023
+ 50,
38024
+ 75,
38025
+ 100,
38026
+ 125,
38027
+ 150
38028
+ ];
38029
+ const lastHint_b = session_b.lastCompactionHint || 0;
38030
+ for (const threshold of thresholds_b) {
38031
+ if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
38032
+ const totalToolCallsPlaceholder_b = "$" + "{totalToolCalls}";
38033
+ const messageTemplate_b = compactionConfig_b?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder_b} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
38034
+ const compactionText = messageTemplate_b.replace(totalToolCallsPlaceholder_b, String(totalToolCalls_b));
38035
+ candidates.push({
38036
+ id: `candidate-${idCounter++}`,
38037
+ kind: "phase",
38038
+ text: compactionText,
38039
+ tokens: estimateTokens(compactionText),
38040
+ priority: 1,
38041
+ metadata: { contentType: "prose" }
38042
+ });
38043
+ session_b.lastCompactionHint = threshold;
38044
+ break;
38045
+ }
37119
38046
  }
37120
38047
  }
37121
38048
  }
@@ -37190,6 +38117,26 @@ ${activitySection}`;
37190
38117
  }
37191
38118
  return contextSummary;
37192
38119
  }
38120
+ async function detectArchitectMode(directory) {
38121
+ try {
38122
+ const plan = await loadPlan(directory);
38123
+ if (!plan) {
38124
+ return "DISCOVER";
38125
+ }
38126
+ const hasActiveTask = plan.phases?.some((phase) => phase.tasks?.some((task) => task.status === "in_progress")) ?? false;
38127
+ if (hasActiveTask) {
38128
+ return "EXECUTE";
38129
+ }
38130
+ const hasPendingTask = plan.phases?.some((phase) => phase.tasks?.some((task) => task.status === "pending")) ?? false;
38131
+ if (!hasPendingTask) {
38132
+ return "PHASE-WRAP";
38133
+ }
38134
+ return "PLAN";
38135
+ } catch (error93) {
38136
+ warn(`Failed to detect architect mode: ${error93 instanceof Error ? error93.message : String(error93)}`);
38137
+ return "UNKNOWN";
38138
+ }
38139
+ }
37193
38140
  // src/summaries/summarizer.ts
37194
38141
  var HYSTERESIS_FACTOR = 1.25;
37195
38142
  function detectContentType(output, toolName) {
@@ -37308,7 +38255,7 @@ function createSummary(output, toolName, summaryId, maxSummaryChars) {
37308
38255
  }
37309
38256
  }
37310
38257
  if (preview.length > maxPreviewChars) {
37311
- preview = preview.substring(0, maxPreviewChars - 3) + "...";
38258
+ preview = `${preview.substring(0, maxPreviewChars - 3)}...`;
37312
38259
  }
37313
38260
  return `${headerLine}
37314
38261
  ${preview}
@@ -37352,7 +38299,7 @@ init_dist();
37352
38299
  // src/build/discovery.ts
37353
38300
  init_dist();
37354
38301
  import * as fs12 from "fs";
37355
- import * as path17 from "path";
38302
+ import * as path18 from "path";
37356
38303
  var ECOSYSTEMS = [
37357
38304
  {
37358
38305
  ecosystem: "node",
@@ -37466,15 +38413,15 @@ function findBuildFiles(workingDir, patterns) {
37466
38413
  try {
37467
38414
  const files = fs12.readdirSync(dir);
37468
38415
  const matches = files.filter((f) => {
37469
- const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
38416
+ const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
37470
38417
  return regex.test(f);
37471
38418
  });
37472
38419
  if (matches.length > 0) {
37473
- return path17.join(dir, matches[0]);
38420
+ return path18.join(dir, matches[0]);
37474
38421
  }
37475
38422
  } catch {}
37476
38423
  } else {
37477
- const filePath = path17.join(workingDir, pattern);
38424
+ const filePath = path18.join(workingDir, pattern);
37478
38425
  if (fs12.existsSync(filePath)) {
37479
38426
  return filePath;
37480
38427
  }
@@ -37483,7 +38430,7 @@ function findBuildFiles(workingDir, patterns) {
37483
38430
  return null;
37484
38431
  }
37485
38432
  function getRepoDefinedScripts(workingDir, scripts) {
37486
- const packageJsonPath = path17.join(workingDir, "package.json");
38433
+ const packageJsonPath = path18.join(workingDir, "package.json");
37487
38434
  if (!fs12.existsSync(packageJsonPath)) {
37488
38435
  return [];
37489
38436
  }
@@ -37521,10 +38468,10 @@ function findAllBuildFiles(workingDir) {
37521
38468
  for (const ecosystem of ECOSYSTEMS) {
37522
38469
  for (const pattern of ecosystem.buildFiles) {
37523
38470
  if (pattern.includes("*")) {
37524
- const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
38471
+ const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
37525
38472
  findFilesRecursive(workingDir, regex, allBuildFiles);
37526
38473
  } else {
37527
- const filePath = path17.join(workingDir, pattern);
38474
+ const filePath = path18.join(workingDir, pattern);
37528
38475
  if (fs12.existsSync(filePath)) {
37529
38476
  allBuildFiles.add(filePath);
37530
38477
  }
@@ -37537,7 +38484,7 @@ function findFilesRecursive(dir, regex, results) {
37537
38484
  try {
37538
38485
  const entries = fs12.readdirSync(dir, { withFileTypes: true });
37539
38486
  for (const entry of entries) {
37540
- const fullPath = path17.join(dir, entry.name);
38487
+ const fullPath = path18.join(dir, entry.name);
37541
38488
  if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
37542
38489
  findFilesRecursive(fullPath, regex, results);
37543
38490
  } else if (entry.isFile() && regex.test(entry.name)) {
@@ -37549,7 +38496,7 @@ function findFilesRecursive(dir, regex, results) {
37549
38496
  async function discoverBuildCommands(workingDir, options) {
37550
38497
  const scope = options?.scope ?? "all";
37551
38498
  const changedFiles = options?.changedFiles ?? [];
37552
- const filesToCheck = filterByScope(workingDir, scope, changedFiles);
38499
+ const _filesToCheck = filterByScope(workingDir, scope, changedFiles);
37553
38500
  const commands = [];
37554
38501
  const skipped = [];
37555
38502
  for (const ecosystem of ECOSYSTEMS) {
@@ -37779,7 +38726,7 @@ var build_check = tool({
37779
38726
  init_tool();
37780
38727
  import { spawnSync } from "child_process";
37781
38728
  import * as fs13 from "fs";
37782
- import * as path18 from "path";
38729
+ import * as path19 from "path";
37783
38730
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
37784
38731
  var MAX_LABEL_LENGTH = 100;
37785
38732
  var GIT_TIMEOUT_MS = 30000;
@@ -37830,7 +38777,7 @@ function validateLabel(label) {
37830
38777
  return null;
37831
38778
  }
37832
38779
  function getCheckpointLogPath() {
37833
- return path18.join(process.cwd(), CHECKPOINT_LOG_PATH);
38780
+ return path19.join(process.cwd(), CHECKPOINT_LOG_PATH);
37834
38781
  }
37835
38782
  function readCheckpointLog() {
37836
38783
  const logPath = getCheckpointLogPath();
@@ -37848,7 +38795,7 @@ function readCheckpointLog() {
37848
38795
  }
37849
38796
  function writeCheckpointLog(log2) {
37850
38797
  const logPath = getCheckpointLogPath();
37851
- const dir = path18.dirname(logPath);
38798
+ const dir = path19.dirname(logPath);
37852
38799
  if (!fs13.existsSync(dir)) {
37853
38800
  fs13.mkdirSync(dir, { recursive: true });
37854
38801
  }
@@ -38055,7 +39002,7 @@ var checkpoint = tool({
38055
39002
  // src/tools/complexity-hotspots.ts
38056
39003
  init_dist();
38057
39004
  import * as fs14 from "fs";
38058
- import * as path19 from "path";
39005
+ import * as path20 from "path";
38059
39006
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
38060
39007
  var DEFAULT_DAYS = 90;
38061
39008
  var DEFAULT_TOP_N = 20;
@@ -38198,7 +39145,7 @@ async function analyzeHotspots(days, topN, extensions) {
38198
39145
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
38199
39146
  const filteredChurn = new Map;
38200
39147
  for (const [file3, count] of churnMap) {
38201
- const ext = path19.extname(file3).toLowerCase();
39148
+ const ext = path20.extname(file3).toLowerCase();
38202
39149
  if (extSet.has(ext)) {
38203
39150
  filteredChurn.set(file3, count);
38204
39151
  }
@@ -38209,7 +39156,7 @@ async function analyzeHotspots(days, topN, extensions) {
38209
39156
  for (const [file3, churnCount] of filteredChurn) {
38210
39157
  let fullPath = file3;
38211
39158
  if (!fs14.existsSync(fullPath)) {
38212
- fullPath = path19.join(cwd, file3);
39159
+ fullPath = path20.join(cwd, file3);
38213
39160
  }
38214
39161
  const complexity = getComplexityForFile(fullPath);
38215
39162
  if (complexity !== null) {
@@ -38367,14 +39314,14 @@ function validateBase(base) {
38367
39314
  function validatePaths(paths) {
38368
39315
  if (!paths)
38369
39316
  return null;
38370
- for (const path20 of paths) {
38371
- if (!path20 || path20.length === 0) {
39317
+ for (const path21 of paths) {
39318
+ if (!path21 || path21.length === 0) {
38372
39319
  return "empty path not allowed";
38373
39320
  }
38374
- if (path20.length > MAX_PATH_LENGTH) {
39321
+ if (path21.length > MAX_PATH_LENGTH) {
38375
39322
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
38376
39323
  }
38377
- if (SHELL_METACHARACTERS2.test(path20)) {
39324
+ if (SHELL_METACHARACTERS2.test(path21)) {
38378
39325
  return "path contains shell metacharacters";
38379
39326
  }
38380
39327
  }
@@ -38389,7 +39336,7 @@ var diff = tool({
38389
39336
  async execute(args2, _context) {
38390
39337
  try {
38391
39338
  const base = args2.base ?? "HEAD";
38392
- const pathSpec = args2.paths?.length ? "-- " + args2.paths.join(" ") : "";
39339
+ const pathSpec = args2.paths?.length ? `-- ${args2.paths.join(" ")}` : "";
38393
39340
  const baseValidationError = validateBase(base);
38394
39341
  if (baseValidationError) {
38395
39342
  const errorResult = {
@@ -38418,11 +39365,11 @@ var diff = tool({
38418
39365
  } else {
38419
39366
  gitCmd = `git --no-pager diff ${base}`;
38420
39367
  }
38421
- const numstatOutput = execSync(gitCmd + " --numstat " + pathSpec, {
39368
+ const numstatOutput = execSync(`${gitCmd} --numstat ${pathSpec}`, {
38422
39369
  encoding: "utf-8",
38423
39370
  timeout: DIFF_TIMEOUT_MS
38424
39371
  });
38425
- const fullDiffOutput = execSync(gitCmd + " -U3 " + pathSpec, {
39372
+ const fullDiffOutput = execSync(`${gitCmd} -U3 ${pathSpec}`, {
38426
39373
  encoding: "utf-8",
38427
39374
  timeout: DIFF_TIMEOUT_MS,
38428
39375
  maxBuffer: MAX_BUFFER_BYTES
@@ -38435,10 +39382,10 @@ var diff = tool({
38435
39382
  continue;
38436
39383
  const parts2 = line.split("\t");
38437
39384
  if (parts2.length >= 3) {
38438
- const additions = parseInt(parts2[0]) || 0;
38439
- const deletions = parseInt(parts2[1]) || 0;
38440
- const path20 = parts2[2];
38441
- files.push({ path: path20, additions, deletions });
39385
+ const additions = parseInt(parts2[0], 10) || 0;
39386
+ const deletions = parseInt(parts2[1], 10) || 0;
39387
+ const path21 = parts2[2];
39388
+ files.push({ path: path21, additions, deletions });
38442
39389
  }
38443
39390
  }
38444
39391
  const contractChanges = [];
@@ -38667,7 +39614,7 @@ Use these as DOMAIN values when delegating to @sme.`;
38667
39614
  // src/tools/evidence-check.ts
38668
39615
  init_dist();
38669
39616
  import * as fs15 from "fs";
38670
- import * as path20 from "path";
39617
+ import * as path21 from "path";
38671
39618
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
38672
39619
  var MAX_EVIDENCE_FILES = 1000;
38673
39620
  var EVIDENCE_DIR = ".swarm/evidence";
@@ -38690,16 +39637,15 @@ function validateRequiredTypes(input) {
38690
39637
  return null;
38691
39638
  }
38692
39639
  function isPathWithinSwarm(filePath, cwd) {
38693
- const normalizedCwd = path20.resolve(cwd);
38694
- const swarmPath = path20.join(normalizedCwd, ".swarm");
38695
- const normalizedPath = path20.resolve(filePath);
39640
+ const normalizedCwd = path21.resolve(cwd);
39641
+ const swarmPath = path21.join(normalizedCwd, ".swarm");
39642
+ const normalizedPath = path21.resolve(filePath);
38696
39643
  return normalizedPath.startsWith(swarmPath);
38697
39644
  }
38698
39645
  function parseCompletedTasks(planContent) {
38699
39646
  const tasks = [];
38700
39647
  const regex = /^-\s+\[x\]\s+(\d+\.\d+):\s+(.+)/gm;
38701
- let match;
38702
- while ((match = regex.exec(planContent)) !== null) {
39648
+ for (let match = regex.exec(planContent);match !== null; match = regex.exec(planContent)) {
38703
39649
  const taskId = match[1];
38704
39650
  let taskName = match[2].trim();
38705
39651
  taskName = taskName.replace(/\s*\[(SMALL|MEDIUM|LARGE)\]\s*$/i, "").trim();
@@ -38707,7 +39653,7 @@ function parseCompletedTasks(planContent) {
38707
39653
  }
38708
39654
  return tasks;
38709
39655
  }
38710
- function readEvidenceFiles(evidenceDir, cwd) {
39656
+ function readEvidenceFiles(evidenceDir, _cwd) {
38711
39657
  const evidence = [];
38712
39658
  if (!fs15.existsSync(evidenceDir) || !fs15.statSync(evidenceDir).isDirectory()) {
38713
39659
  return evidence;
@@ -38723,10 +39669,10 @@ function readEvidenceFiles(evidenceDir, cwd) {
38723
39669
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
38724
39670
  continue;
38725
39671
  }
38726
- const filePath = path20.join(evidenceDir, filename);
39672
+ const filePath = path21.join(evidenceDir, filename);
38727
39673
  try {
38728
- const resolvedPath = path20.resolve(filePath);
38729
- const evidenceDirResolved = path20.resolve(evidenceDir);
39674
+ const resolvedPath = path21.resolve(filePath);
39675
+ const evidenceDirResolved = path21.resolve(evidenceDir);
38730
39676
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
38731
39677
  continue;
38732
39678
  }
@@ -38780,7 +39726,7 @@ function analyzeGaps(completedTasks, evidence, requiredTypes) {
38780
39726
  for (const task of completedTasks) {
38781
39727
  const taskEvidence = evidenceByTask.get(task.taskId) || new Set;
38782
39728
  const requiredSet = new Set(requiredTypes.map((t) => t.toLowerCase()));
38783
- const presentSet = new Set([...taskEvidence].filter((t) => requiredSet.has(t.toLowerCase())));
39729
+ const _presentSet = new Set([...taskEvidence].filter((t) => requiredSet.has(t.toLowerCase())));
38784
39730
  const missing = [];
38785
39731
  const present = [];
38786
39732
  for (const reqType of requiredTypes) {
@@ -38833,7 +39779,7 @@ var evidence_check = tool({
38833
39779
  return JSON.stringify(errorResult, null, 2);
38834
39780
  }
38835
39781
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
38836
- const planPath = path20.join(cwd, PLAN_FILE);
39782
+ const planPath = path21.join(cwd, PLAN_FILE);
38837
39783
  if (!isPathWithinSwarm(planPath, cwd)) {
38838
39784
  const errorResult = {
38839
39785
  error: "plan file path validation failed",
@@ -38865,7 +39811,7 @@ var evidence_check = tool({
38865
39811
  };
38866
39812
  return JSON.stringify(result2, null, 2);
38867
39813
  }
38868
- const evidenceDir = path20.join(cwd, EVIDENCE_DIR);
39814
+ const evidenceDir = path21.join(cwd, EVIDENCE_DIR);
38869
39815
  const evidence = readEvidenceFiles(evidenceDir, cwd);
38870
39816
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
38871
39817
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -38882,7 +39828,7 @@ var evidence_check = tool({
38882
39828
  // src/tools/file-extractor.ts
38883
39829
  init_tool();
38884
39830
  import * as fs16 from "fs";
38885
- import * as path21 from "path";
39831
+ import * as path22 from "path";
38886
39832
  var EXT_MAP = {
38887
39833
  python: ".py",
38888
39834
  py: ".py",
@@ -38960,12 +39906,12 @@ var extract_code_blocks = tool({
38960
39906
  if (prefix) {
38961
39907
  filename = `${prefix}_${filename}`;
38962
39908
  }
38963
- let filepath = path21.join(targetDir, filename);
38964
- const base = path21.basename(filepath, path21.extname(filepath));
38965
- const ext = path21.extname(filepath);
39909
+ let filepath = path22.join(targetDir, filename);
39910
+ const base = path22.basename(filepath, path22.extname(filepath));
39911
+ const ext = path22.extname(filepath);
38966
39912
  let counter = 1;
38967
39913
  while (fs16.existsSync(filepath)) {
38968
- filepath = path21.join(targetDir, `${base}_${counter}${ext}`);
39914
+ filepath = path22.join(targetDir, `${base}_${counter}${ext}`);
38969
39915
  counter++;
38970
39916
  }
38971
39917
  try {
@@ -38998,7 +39944,7 @@ init_dist();
38998
39944
  var GITINGEST_TIMEOUT_MS = 1e4;
38999
39945
  var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
39000
39946
  var GITINGEST_MAX_RETRIES = 2;
39001
- var delay = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
39947
+ var delay = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
39002
39948
  async function fetchGitingest(args2) {
39003
39949
  for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
39004
39950
  try {
@@ -39078,7 +40024,7 @@ var gitingest = tool({
39078
40024
  // src/tools/imports.ts
39079
40025
  init_dist();
39080
40026
  import * as fs17 from "fs";
39081
- import * as path22 from "path";
40027
+ import * as path23 from "path";
39082
40028
  var MAX_FILE_PATH_LENGTH2 = 500;
39083
40029
  var MAX_SYMBOL_LENGTH = 256;
39084
40030
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -39132,7 +40078,7 @@ function validateSymbolInput(symbol3) {
39132
40078
  return null;
39133
40079
  }
39134
40080
  function isBinaryFile2(filePath, buffer) {
39135
- const ext = path22.extname(filePath).toLowerCase();
40081
+ const ext = path23.extname(filePath).toLowerCase();
39136
40082
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
39137
40083
  return false;
39138
40084
  }
@@ -39154,20 +40100,19 @@ function isBinaryFile2(filePath, buffer) {
39154
40100
  }
39155
40101
  function parseImports(content, targetFile, targetSymbol) {
39156
40102
  const imports = [];
39157
- let resolvedTarget;
40103
+ let _resolvedTarget;
39158
40104
  try {
39159
- resolvedTarget = path22.resolve(targetFile);
40105
+ _resolvedTarget = path23.resolve(targetFile);
39160
40106
  } catch {
39161
- resolvedTarget = targetFile;
40107
+ _resolvedTarget = targetFile;
39162
40108
  }
39163
- const targetBasename = path22.basename(targetFile, path22.extname(targetFile));
40109
+ const targetBasename = path23.basename(targetFile, path23.extname(targetFile));
39164
40110
  const targetWithExt = targetFile;
39165
40111
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
39166
- const normalizedTargetWithExt = path22.normalize(targetWithExt).replace(/\\/g, "/");
39167
- const normalizedTargetWithoutExt = path22.normalize(targetWithoutExt).replace(/\\/g, "/");
40112
+ const normalizedTargetWithExt = path23.normalize(targetWithExt).replace(/\\/g, "/");
40113
+ const normalizedTargetWithoutExt = path23.normalize(targetWithoutExt).replace(/\\/g, "/");
39168
40114
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
39169
- let match;
39170
- while ((match = importRegex.exec(content)) !== null) {
40115
+ for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
39171
40116
  const modulePath = match[1] || match[2] || match[3];
39172
40117
  if (!modulePath)
39173
40118
  continue;
@@ -39186,15 +40131,15 @@ function parseImports(content, targetFile, targetSymbol) {
39186
40131
  } else if (matchedString.includes("require(")) {
39187
40132
  importType = "require";
39188
40133
  }
39189
- const normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
40134
+ const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
39190
40135
  let isMatch = false;
39191
- const targetDir = path22.dirname(targetFile);
39192
- const targetExt = path22.extname(targetFile);
39193
- const targetBasenameNoExt = path22.basename(targetFile, targetExt);
40136
+ const _targetDir = path23.dirname(targetFile);
40137
+ const targetExt = path23.extname(targetFile);
40138
+ const targetBasenameNoExt = path23.basename(targetFile, targetExt);
39194
40139
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
39195
40140
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
39196
40141
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
39197
- if (modulePath === targetBasename || modulePath === targetBasenameNoExt || modulePath === "./" + targetBasename || modulePath === "./" + targetBasenameNoExt || modulePath === "../" + targetBasename || modulePath === "../" + targetBasenameNoExt || moduleNormalized === normalizedTargetWithExt || moduleNormalized === normalizedTargetWithoutExt || modulePath.endsWith("/" + targetBasename) || modulePath.endsWith("\\" + targetBasename) || modulePath.endsWith("/" + targetBasenameNoExt) || modulePath.endsWith("\\" + targetBasenameNoExt) || moduleNameNoExt === targetBasenameNoExt || "./" + moduleNameNoExt === targetBasenameNoExt || moduleName === targetBasename || moduleName === targetBasenameNoExt) {
40142
+ if (modulePath === targetBasename || modulePath === targetBasenameNoExt || modulePath === `./${targetBasename}` || modulePath === `./${targetBasenameNoExt}` || modulePath === `../${targetBasename}` || modulePath === `../${targetBasenameNoExt}` || moduleNormalized === normalizedTargetWithExt || moduleNormalized === normalizedTargetWithoutExt || modulePath.endsWith(`/${targetBasename}`) || modulePath.endsWith(`\\${targetBasename}`) || modulePath.endsWith(`/${targetBasenameNoExt}`) || modulePath.endsWith(`\\${targetBasenameNoExt}`) || moduleNameNoExt === targetBasenameNoExt || `./${moduleNameNoExt}` === targetBasenameNoExt || moduleName === targetBasename || moduleName === targetBasenameNoExt) {
39198
40143
  isMatch = true;
39199
40144
  }
39200
40145
  if (isMatch && targetSymbol) {
@@ -39258,10 +40203,10 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
39258
40203
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
39259
40204
  for (const entry of entries) {
39260
40205
  if (SKIP_DIRECTORIES2.has(entry)) {
39261
- stats.skippedDirs.push(path22.join(dir, entry));
40206
+ stats.skippedDirs.push(path23.join(dir, entry));
39262
40207
  continue;
39263
40208
  }
39264
- const fullPath = path22.join(dir, entry);
40209
+ const fullPath = path23.join(dir, entry);
39265
40210
  let stat;
39266
40211
  try {
39267
40212
  stat = fs17.statSync(fullPath);
@@ -39275,7 +40220,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
39275
40220
  if (stat.isDirectory()) {
39276
40221
  findSourceFiles2(fullPath, files, stats);
39277
40222
  } else if (stat.isFile()) {
39278
- const ext = path22.extname(fullPath).toLowerCase();
40223
+ const ext = path23.extname(fullPath).toLowerCase();
39279
40224
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
39280
40225
  files.push(fullPath);
39281
40226
  }
@@ -39331,7 +40276,7 @@ var imports = tool({
39331
40276
  return JSON.stringify(errorResult, null, 2);
39332
40277
  }
39333
40278
  try {
39334
- const targetFile = path22.resolve(file3);
40279
+ const targetFile = path23.resolve(file3);
39335
40280
  if (!fs17.existsSync(targetFile)) {
39336
40281
  const errorResult = {
39337
40282
  error: `target file not found: ${file3}`,
@@ -39353,7 +40298,7 @@ var imports = tool({
39353
40298
  };
39354
40299
  return JSON.stringify(errorResult, null, 2);
39355
40300
  }
39356
- const baseDir = path22.dirname(targetFile);
40301
+ const baseDir = path23.dirname(targetFile);
39357
40302
  const scanStats = {
39358
40303
  skippedDirs: [],
39359
40304
  skippedFiles: 0,
@@ -39395,7 +40340,7 @@ var imports = tool({
39395
40340
  raw: imp.raw
39396
40341
  });
39397
40342
  }
39398
- } catch (e) {
40343
+ } catch (_e) {
39399
40344
  skippedFileCount++;
39400
40345
  }
39401
40346
  }
@@ -39421,7 +40366,7 @@ var imports = tool({
39421
40366
  }
39422
40367
  }
39423
40368
  if (parts2.length > 0) {
39424
- result.message = parts2.join("; ") + ".";
40369
+ result.message = `${parts2.join("; ")}.`;
39425
40370
  }
39426
40371
  return JSON.stringify(result, null, 2);
39427
40372
  } catch (e) {
@@ -39443,7 +40388,7 @@ init_lint();
39443
40388
  // src/tools/pkg-audit.ts
39444
40389
  init_dist();
39445
40390
  import * as fs18 from "fs";
39446
- import * as path23 from "path";
40391
+ import * as path24 from "path";
39447
40392
  var MAX_OUTPUT_BYTES5 = 52428800;
39448
40393
  var AUDIT_TIMEOUT_MS = 120000;
39449
40394
  function isValidEcosystem(value) {
@@ -39461,13 +40406,13 @@ function validateArgs3(args2) {
39461
40406
  function detectEcosystems() {
39462
40407
  const ecosystems = [];
39463
40408
  const cwd = process.cwd();
39464
- if (fs18.existsSync(path23.join(cwd, "package.json"))) {
40409
+ if (fs18.existsSync(path24.join(cwd, "package.json"))) {
39465
40410
  ecosystems.push("npm");
39466
40411
  }
39467
- if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
40412
+ if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
39468
40413
  ecosystems.push("pip");
39469
40414
  }
39470
- if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
40415
+ if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
39471
40416
  ecosystems.push("cargo");
39472
40417
  }
39473
40418
  return ecosystems;
@@ -39480,7 +40425,7 @@ async function runNpmAudit() {
39480
40425
  stderr: "pipe",
39481
40426
  cwd: process.cwd()
39482
40427
  });
39483
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
40428
+ const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
39484
40429
  const result = await Promise.race([
39485
40430
  Promise.all([
39486
40431
  new Response(proc.stdout).text(),
@@ -39603,7 +40548,7 @@ async function runPipAudit() {
39603
40548
  stderr: "pipe",
39604
40549
  cwd: process.cwd()
39605
40550
  });
39606
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
40551
+ const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
39607
40552
  const result = await Promise.race([
39608
40553
  Promise.all([
39609
40554
  new Response(proc.stdout).text(),
@@ -39734,12 +40679,12 @@ async function runCargoAudit() {
39734
40679
  stderr: "pipe",
39735
40680
  cwd: process.cwd()
39736
40681
  });
39737
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
40682
+ const timeoutPromise = new Promise((resolve11) => setTimeout(() => resolve11("timeout"), AUDIT_TIMEOUT_MS));
39738
40683
  const result = await Promise.race([
39739
40684
  Promise.all([
39740
40685
  new Response(proc.stdout).text(),
39741
40686
  new Response(proc.stderr).text()
39742
- ]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
40687
+ ]).then(([stdout2, stderr]) => ({ stdout: stdout2, stderr })),
39743
40688
  timeoutPromise
39744
40689
  ]);
39745
40690
  if (result === "timeout") {
@@ -39755,7 +40700,7 @@ async function runCargoAudit() {
39755
40700
  note: `cargo audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
39756
40701
  };
39757
40702
  }
39758
- let { stdout, stderr } = result;
40703
+ let { stdout, stderr: _stderr } = result;
39759
40704
  if (stdout.length > MAX_OUTPUT_BYTES5) {
39760
40705
  stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
39761
40706
  }
@@ -39777,7 +40722,7 @@ async function runCargoAudit() {
39777
40722
  for (const line of lines) {
39778
40723
  try {
39779
40724
  const obj = JSON.parse(line);
39780
- if (obj.vulnerabilities && obj.vulnerabilities.list) {
40725
+ if (obj.vulnerabilities?.list) {
39781
40726
  for (const item of obj.vulnerabilities.list) {
39782
40727
  const cvss = item.advisory.cvss || 0;
39783
40728
  const severity = mapCargoSeverity(cvss);
@@ -41352,8 +42297,8 @@ var Module2 = (() => {
41352
42297
  var moduleRtn;
41353
42298
  var Module = moduleArg;
41354
42299
  var readyPromiseResolve, readyPromiseReject;
41355
- var readyPromise = new Promise((resolve10, reject) => {
41356
- readyPromiseResolve = resolve10;
42300
+ var readyPromise = new Promise((resolve11, reject) => {
42301
+ readyPromiseResolve = resolve11;
41357
42302
  readyPromiseReject = reject;
41358
42303
  });
41359
42304
  var ENVIRONMENT_IS_WEB = typeof window == "object";
@@ -41375,11 +42320,11 @@ var Module2 = (() => {
41375
42320
  throw toThrow;
41376
42321
  }, "quit_");
41377
42322
  var scriptDirectory = "";
41378
- function locateFile(path24) {
42323
+ function locateFile(path25) {
41379
42324
  if (Module["locateFile"]) {
41380
- return Module["locateFile"](path24, scriptDirectory);
42325
+ return Module["locateFile"](path25, scriptDirectory);
41381
42326
  }
41382
- return scriptDirectory + path24;
42327
+ return scriptDirectory + path25;
41383
42328
  }
41384
42329
  __name(locateFile, "locateFile");
41385
42330
  var readAsync, readBinary;
@@ -41433,13 +42378,13 @@ var Module2 = (() => {
41433
42378
  }
41434
42379
  readAsync = /* @__PURE__ */ __name(async (url3) => {
41435
42380
  if (isFileURI(url3)) {
41436
- return new Promise((resolve10, reject) => {
42381
+ return new Promise((resolve11, reject) => {
41437
42382
  var xhr = new XMLHttpRequest;
41438
42383
  xhr.open("GET", url3, true);
41439
42384
  xhr.responseType = "arraybuffer";
41440
42385
  xhr.onload = () => {
41441
42386
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
41442
- resolve10(xhr.response);
42387
+ resolve11(xhr.response);
41443
42388
  return;
41444
42389
  }
41445
42390
  reject(xhr.status);
@@ -41659,10 +42604,10 @@ var Module2 = (() => {
41659
42604
  __name(receiveInstantiationResult, "receiveInstantiationResult");
41660
42605
  var info2 = getWasmImports();
41661
42606
  if (Module["instantiateWasm"]) {
41662
- return new Promise((resolve10, reject) => {
42607
+ return new Promise((resolve11, reject) => {
41663
42608
  Module["instantiateWasm"](info2, (mod, inst) => {
41664
42609
  receiveInstance(mod, inst);
41665
- resolve10(mod.exports);
42610
+ resolve11(mod.exports);
41666
42611
  });
41667
42612
  });
41668
42613
  }
@@ -43193,7 +44138,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
43193
44138
  ]);
43194
44139
  // src/tools/pre-check-batch.ts
43195
44140
  init_dist();
43196
- import * as path26 from "path";
44141
+ import * as path27 from "path";
43197
44142
 
43198
44143
  // node_modules/yocto-queue/index.js
43199
44144
  class Node2 {
@@ -43284,26 +44229,26 @@ function pLimit(concurrency) {
43284
44229
  activeCount--;
43285
44230
  resumeNext();
43286
44231
  };
43287
- const run2 = async (function_, resolve10, arguments_2) => {
44232
+ const run2 = async (function_, resolve11, arguments_2) => {
43288
44233
  const result = (async () => function_(...arguments_2))();
43289
- resolve10(result);
44234
+ resolve11(result);
43290
44235
  try {
43291
44236
  await result;
43292
44237
  } catch {}
43293
44238
  next();
43294
44239
  };
43295
- const enqueue = (function_, resolve10, reject, arguments_2) => {
44240
+ const enqueue = (function_, resolve11, reject, arguments_2) => {
43296
44241
  const queueItem = { reject };
43297
44242
  new Promise((internalResolve) => {
43298
44243
  queueItem.run = internalResolve;
43299
44244
  queue.enqueue(queueItem);
43300
- }).then(run2.bind(undefined, function_, resolve10, arguments_2));
44245
+ }).then(run2.bind(undefined, function_, resolve11, arguments_2));
43301
44246
  if (activeCount < concurrency) {
43302
44247
  resumeNext();
43303
44248
  }
43304
44249
  };
43305
- const generator = (function_, ...arguments_2) => new Promise((resolve10, reject) => {
43306
- enqueue(function_, resolve10, reject, arguments_2);
44250
+ const generator = (function_, ...arguments_2) => new Promise((resolve11, reject) => {
44251
+ enqueue(function_, resolve11, reject, arguments_2);
43307
44252
  });
43308
44253
  Object.defineProperties(generator, {
43309
44254
  activeCount: {
@@ -43360,7 +44305,7 @@ init_manager();
43360
44305
 
43361
44306
  // src/quality/metrics.ts
43362
44307
  import * as fs19 from "fs";
43363
- import * as path24 from "path";
44308
+ import * as path25 from "path";
43364
44309
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
43365
44310
  var MIN_DUPLICATION_LINES = 10;
43366
44311
  function estimateCyclomaticComplexity(content) {
@@ -43412,7 +44357,7 @@ async function computeComplexityDelta(files, workingDir) {
43412
44357
  let totalComplexity = 0;
43413
44358
  const analyzedFiles = [];
43414
44359
  for (const file3 of files) {
43415
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44360
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
43416
44361
  if (!fs19.existsSync(fullPath)) {
43417
44362
  continue;
43418
44363
  }
@@ -43535,7 +44480,7 @@ function countGoExports(content) {
43535
44480
  function getExportCountForFile(filePath) {
43536
44481
  try {
43537
44482
  const content = fs19.readFileSync(filePath, "utf-8");
43538
- const ext = path24.extname(filePath).toLowerCase();
44483
+ const ext = path25.extname(filePath).toLowerCase();
43539
44484
  switch (ext) {
43540
44485
  case ".ts":
43541
44486
  case ".tsx":
@@ -43561,7 +44506,7 @@ async function computePublicApiDelta(files, workingDir) {
43561
44506
  let totalExports = 0;
43562
44507
  const analyzedFiles = [];
43563
44508
  for (const file3 of files) {
43564
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44509
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
43565
44510
  if (!fs19.existsSync(fullPath)) {
43566
44511
  continue;
43567
44512
  }
@@ -43595,7 +44540,7 @@ async function computeDuplicationRatio(files, workingDir) {
43595
44540
  let duplicateLines = 0;
43596
44541
  const analyzedFiles = [];
43597
44542
  for (const file3 of files) {
43598
- const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44543
+ const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
43599
44544
  if (!fs19.existsSync(fullPath)) {
43600
44545
  continue;
43601
44546
  }
@@ -43628,8 +44573,8 @@ function countCodeLines(content) {
43628
44573
  return lines.length;
43629
44574
  }
43630
44575
  function isTestFile(filePath) {
43631
- const basename5 = path24.basename(filePath);
43632
- const _ext = path24.extname(filePath).toLowerCase();
44576
+ const basename5 = path25.basename(filePath);
44577
+ const _ext = path25.extname(filePath).toLowerCase();
43633
44578
  const testPatterns = [
43634
44579
  ".test.",
43635
44580
  ".spec.",
@@ -43671,7 +44616,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
43671
44616
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
43672
44617
  let testLines = 0;
43673
44618
  let codeLines = 0;
43674
- const srcDir = path24.join(workingDir, "src");
44619
+ const srcDir = path25.join(workingDir, "src");
43675
44620
  if (fs19.existsSync(srcDir)) {
43676
44621
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
43677
44622
  codeLines += lines;
@@ -43679,14 +44624,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
43679
44624
  }
43680
44625
  const possibleSrcDirs = ["lib", "app", "source", "core"];
43681
44626
  for (const dir of possibleSrcDirs) {
43682
- const dirPath = path24.join(workingDir, dir);
44627
+ const dirPath = path25.join(workingDir, dir);
43683
44628
  if (fs19.existsSync(dirPath)) {
43684
44629
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
43685
44630
  codeLines += lines;
43686
44631
  });
43687
44632
  }
43688
44633
  }
43689
- const testsDir = path24.join(workingDir, "tests");
44634
+ const testsDir = path25.join(workingDir, "tests");
43690
44635
  if (fs19.existsSync(testsDir)) {
43691
44636
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
43692
44637
  testLines += lines;
@@ -43694,7 +44639,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
43694
44639
  }
43695
44640
  const possibleTestDirs = ["test", "__tests__", "specs"];
43696
44641
  for (const dir of possibleTestDirs) {
43697
- const dirPath = path24.join(workingDir, dir);
44642
+ const dirPath = path25.join(workingDir, dir);
43698
44643
  if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
43699
44644
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
43700
44645
  testLines += lines;
@@ -43709,7 +44654,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
43709
44654
  try {
43710
44655
  const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
43711
44656
  for (const entry of entries) {
43712
- const fullPath = path24.join(dirPath, entry.name);
44657
+ const fullPath = path25.join(dirPath, entry.name);
43713
44658
  if (entry.isDirectory()) {
43714
44659
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
43715
44660
  continue;
@@ -43717,7 +44662,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
43717
44662
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
43718
44663
  } else if (entry.isFile()) {
43719
44664
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
43720
- const ext = path24.extname(entry.name).toLowerCase();
44665
+ const ext = path25.extname(entry.name).toLowerCase();
43721
44666
  const validExts = [
43722
44667
  ".ts",
43723
44668
  ".tsx",
@@ -43972,7 +44917,7 @@ async function qualityBudget(input, directory) {
43972
44917
  // src/tools/sast-scan.ts
43973
44918
  init_manager();
43974
44919
  import * as fs20 from "fs";
43975
- import * as path25 from "path";
44920
+ import * as path26 from "path";
43976
44921
  import { extname as extname7 } from "path";
43977
44922
 
43978
44923
  // src/sast/rules/c.ts
@@ -44600,9 +45545,9 @@ function findPatternMatches(content, pattern) {
44600
45545
  `);
44601
45546
  for (let lineNum = 0;lineNum < lines.length; lineNum++) {
44602
45547
  const line = lines[lineNum];
44603
- const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
44604
- let match;
44605
- while ((match = workPattern.exec(line)) !== null) {
45548
+ const workPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
45549
+ let match = workPattern.exec(line);
45550
+ while (match !== null) {
44606
45551
  matches.push({
44607
45552
  text: match[0],
44608
45553
  line: lineNum + 1,
@@ -44611,6 +45556,7 @@ function findPatternMatches(content, pattern) {
44611
45556
  if (match[0].length === 0) {
44612
45557
  workPattern.lastIndex++;
44613
45558
  }
45559
+ match = workPattern.exec(line);
44614
45560
  }
44615
45561
  }
44616
45562
  return matches;
@@ -44657,7 +45603,7 @@ function executeRulesSync(filePath, content, language) {
44657
45603
  // src/sast/semgrep.ts
44658
45604
  import { execFile, execFileSync, spawn } from "child_process";
44659
45605
  import { promisify } from "util";
44660
- var execFileAsync = promisify(execFile);
45606
+ var _execFileAsync = promisify(execFile);
44661
45607
  var semgrepAvailableCache = null;
44662
45608
  var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
44663
45609
  var DEFAULT_TIMEOUT_MS3 = 30000;
@@ -44719,7 +45665,7 @@ function mapSemgrepSeverity(severity) {
44719
45665
  }
44720
45666
  }
44721
45667
  async function executeWithTimeout(command, args2, options) {
44722
- return new Promise((resolve10) => {
45668
+ return new Promise((resolve11) => {
44723
45669
  const child = spawn(command, args2, {
44724
45670
  shell: false,
44725
45671
  cwd: options.cwd
@@ -44728,7 +45674,7 @@ async function executeWithTimeout(command, args2, options) {
44728
45674
  let stderr = "";
44729
45675
  const timeout = setTimeout(() => {
44730
45676
  child.kill("SIGTERM");
44731
- resolve10({
45677
+ resolve11({
44732
45678
  stdout,
44733
45679
  stderr: "Process timed out",
44734
45680
  exitCode: 124
@@ -44742,7 +45688,7 @@ async function executeWithTimeout(command, args2, options) {
44742
45688
  });
44743
45689
  child.on("close", (code) => {
44744
45690
  clearTimeout(timeout);
44745
- resolve10({
45691
+ resolve11({
44746
45692
  stdout,
44747
45693
  stderr,
44748
45694
  exitCode: code ?? 0
@@ -44750,7 +45696,7 @@ async function executeWithTimeout(command, args2, options) {
44750
45696
  });
44751
45697
  child.on("error", (err2) => {
44752
45698
  clearTimeout(timeout);
44753
- resolve10({
45699
+ resolve11({
44754
45700
  stdout,
44755
45701
  stderr: err2.message,
44756
45702
  exitCode: 1
@@ -44778,7 +45724,7 @@ async function runSemgrep(options) {
44778
45724
  };
44779
45725
  }
44780
45726
  const args2 = [
44781
- "--config=./" + rulesDir,
45727
+ `--config=./${rulesDir}`,
44782
45728
  "--json",
44783
45729
  "--quiet",
44784
45730
  ...files
@@ -44921,25 +45867,25 @@ async function sastScan(input, directory, config3) {
44921
45867
  }
44922
45868
  const allFindings = [];
44923
45869
  let filesScanned = 0;
44924
- let filesSkipped = 0;
45870
+ let _filesSkipped = 0;
44925
45871
  const semgrepAvailable = isSemgrepAvailable();
44926
45872
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
44927
45873
  const filesByLanguage = new Map;
44928
45874
  for (const filePath of changed_files) {
44929
- const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
45875
+ const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
44930
45876
  if (!fs20.existsSync(resolvedPath)) {
44931
- filesSkipped++;
45877
+ _filesSkipped++;
44932
45878
  continue;
44933
45879
  }
44934
45880
  const skipResult = shouldSkipFile(resolvedPath);
44935
45881
  if (skipResult.skip) {
44936
- filesSkipped++;
45882
+ _filesSkipped++;
44937
45883
  continue;
44938
45884
  }
44939
45885
  const ext = extname7(resolvedPath).toLowerCase();
44940
45886
  const langDef = getLanguageForExtension(ext);
44941
45887
  if (!langDef) {
44942
- filesSkipped++;
45888
+ _filesSkipped++;
44943
45889
  continue;
44944
45890
  }
44945
45891
  const language = langDef.id;
@@ -45035,10 +45981,10 @@ function validatePath(inputPath, baseDir) {
45035
45981
  if (!inputPath || inputPath.length === 0) {
45036
45982
  return "path is required";
45037
45983
  }
45038
- const resolved = path26.resolve(baseDir, inputPath);
45039
- const baseResolved = path26.resolve(baseDir);
45040
- const relative2 = path26.relative(baseResolved, resolved);
45041
- if (relative2.startsWith("..") || path26.isAbsolute(relative2)) {
45984
+ const resolved = path27.resolve(baseDir, inputPath);
45985
+ const baseResolved = path27.resolve(baseDir);
45986
+ const relative3 = path27.relative(baseResolved, resolved);
45987
+ if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
45042
45988
  return "path traversal detected";
45043
45989
  }
45044
45990
  return null;
@@ -45160,7 +46106,7 @@ async function runPreCheckBatch(input) {
45160
46106
  warn(`pre_check_batch: Invalid file path: ${file3}`);
45161
46107
  continue;
45162
46108
  }
45163
- changedFiles.push(path26.resolve(directory, file3));
46109
+ changedFiles.push(path27.resolve(directory, file3));
45164
46110
  }
45165
46111
  } else {
45166
46112
  changedFiles = [];
@@ -45351,7 +46297,7 @@ var retrieve_summary = tool({
45351
46297
  init_dist();
45352
46298
  init_manager();
45353
46299
  import * as fs21 from "fs";
45354
- import * as path27 from "path";
46300
+ import * as path28 from "path";
45355
46301
 
45356
46302
  // src/sbom/detectors/dart.ts
45357
46303
  function parsePubspecLock(content) {
@@ -46198,7 +47144,7 @@ function findManifestFiles(rootDir) {
46198
47144
  try {
46199
47145
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
46200
47146
  for (const entry of entries) {
46201
- const fullPath = path27.join(dir, entry.name);
47147
+ const fullPath = path28.join(dir, entry.name);
46202
47148
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
46203
47149
  continue;
46204
47150
  }
@@ -46208,7 +47154,7 @@ function findManifestFiles(rootDir) {
46208
47154
  for (const pattern of patterns) {
46209
47155
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
46210
47156
  if (new RegExp(regex, "i").test(entry.name)) {
46211
- manifestFiles.push(path27.relative(cwd, fullPath));
47157
+ manifestFiles.push(path28.relative(cwd, fullPath));
46212
47158
  break;
46213
47159
  }
46214
47160
  }
@@ -46221,18 +47167,18 @@ function findManifestFiles(rootDir) {
46221
47167
  }
46222
47168
  function findManifestFilesInDirs(directories, workingDir) {
46223
47169
  const found = [];
46224
- const cwd = process.cwd();
47170
+ const _cwd = process.cwd();
46225
47171
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
46226
47172
  for (const dir of directories) {
46227
47173
  try {
46228
47174
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
46229
47175
  for (const entry of entries) {
46230
- const fullPath = path27.join(dir, entry.name);
47176
+ const fullPath = path28.join(dir, entry.name);
46231
47177
  if (entry.isFile()) {
46232
47178
  for (const pattern of patterns) {
46233
47179
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
46234
47180
  if (new RegExp(regex, "i").test(entry.name)) {
46235
- found.push(path27.relative(workingDir, fullPath));
47181
+ found.push(path28.relative(workingDir, fullPath));
46236
47182
  break;
46237
47183
  }
46238
47184
  }
@@ -46245,11 +47191,11 @@ function findManifestFilesInDirs(directories, workingDir) {
46245
47191
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
46246
47192
  const dirs = new Set;
46247
47193
  for (const file3 of changedFiles) {
46248
- let currentDir = path27.dirname(file3);
47194
+ let currentDir = path28.dirname(file3);
46249
47195
  while (true) {
46250
- if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
46251
- dirs.add(path27.join(workingDir, currentDir));
46252
- const parent = path27.dirname(currentDir);
47196
+ if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
47197
+ dirs.add(path28.join(workingDir, currentDir));
47198
+ const parent = path28.dirname(currentDir);
46253
47199
  if (parent === currentDir)
46254
47200
  break;
46255
47201
  currentDir = parent;
@@ -46356,7 +47302,7 @@ var sbom_generate = tool({
46356
47302
  const processedFiles = [];
46357
47303
  for (const manifestFile of manifestFiles) {
46358
47304
  try {
46359
- const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
47305
+ const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
46360
47306
  if (!fs21.existsSync(fullPath)) {
46361
47307
  continue;
46362
47308
  }
@@ -46373,7 +47319,7 @@ var sbom_generate = tool({
46373
47319
  const bom = generateCycloneDX(allComponents);
46374
47320
  const bomJson = serializeCycloneDX(bom);
46375
47321
  const filename = generateSbomFilename();
46376
- const outputPath = path27.join(outputDir, filename);
47322
+ const outputPath = path28.join(outputDir, filename);
46377
47323
  fs21.writeFileSync(outputPath, bomJson, "utf-8");
46378
47324
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
46379
47325
  try {
@@ -46416,7 +47362,7 @@ var sbom_generate = tool({
46416
47362
  // src/tools/schema-drift.ts
46417
47363
  init_dist();
46418
47364
  import * as fs22 from "fs";
46419
- import * as path28 from "path";
47365
+ import * as path29 from "path";
46420
47366
  var SPEC_CANDIDATES = [
46421
47367
  "openapi.json",
46422
47368
  "openapi.yaml",
@@ -46448,12 +47394,12 @@ function normalizePath(p) {
46448
47394
  }
46449
47395
  function discoverSpecFile(cwd, specFileArg) {
46450
47396
  if (specFileArg) {
46451
- const resolvedPath = path28.resolve(cwd, specFileArg);
46452
- const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
47397
+ const resolvedPath = path29.resolve(cwd, specFileArg);
47398
+ const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
46453
47399
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
46454
47400
  throw new Error("Invalid spec_file: path traversal detected");
46455
47401
  }
46456
- const ext = path28.extname(resolvedPath).toLowerCase();
47402
+ const ext = path29.extname(resolvedPath).toLowerCase();
46457
47403
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
46458
47404
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
46459
47405
  }
@@ -46467,7 +47413,7 @@ function discoverSpecFile(cwd, specFileArg) {
46467
47413
  return resolvedPath;
46468
47414
  }
46469
47415
  for (const candidate of SPEC_CANDIDATES) {
46470
- const candidatePath = path28.resolve(cwd, candidate);
47416
+ const candidatePath = path29.resolve(cwd, candidate);
46471
47417
  if (fs22.existsSync(candidatePath)) {
46472
47418
  const stats = fs22.statSync(candidatePath);
46473
47419
  if (stats.size <= MAX_SPEC_SIZE) {
@@ -46479,7 +47425,7 @@ function discoverSpecFile(cwd, specFileArg) {
46479
47425
  }
46480
47426
  function parseSpec(specFile) {
46481
47427
  const content = fs22.readFileSync(specFile, "utf-8");
46482
- const ext = path28.extname(specFile).toLowerCase();
47428
+ const ext = path29.extname(specFile).toLowerCase();
46483
47429
  if (ext === ".json") {
46484
47430
  return parseJsonSpec(content);
46485
47431
  }
@@ -46521,18 +47467,18 @@ function parseYamlSpec(content) {
46521
47467
  return paths;
46522
47468
  }
46523
47469
  const pathRegex = /^\s{2}(\/[^\s:]+):/gm;
46524
- let match;
46525
- while ((match = pathRegex.exec(content)) !== null) {
47470
+ for (let match = pathRegex.exec(content);match !== null; match = pathRegex.exec(content)) {
46526
47471
  const pathKey = match[1];
46527
47472
  const methodRegex = /^\s{4}(get|post|put|patch|delete|options|head):/gm;
46528
47473
  const methods = [];
46529
- let methodMatch;
46530
47474
  const pathStart = match.index;
46531
47475
  const nextPathMatch = content.substring(pathStart + 1).match(/^\s{2}\//m);
46532
47476
  const pathEnd = nextPathMatch && nextPathMatch.index !== undefined ? pathStart + 1 + nextPathMatch.index : content.length;
46533
47477
  const pathSection = content.substring(pathStart, pathEnd);
46534
- while ((methodMatch = methodRegex.exec(pathSection)) !== null) {
47478
+ let methodMatch = methodRegex.exec(pathSection);
47479
+ while (methodMatch !== null) {
46535
47480
  methods.push(methodMatch[1]);
47481
+ methodMatch = methodRegex.exec(pathSection);
46536
47482
  }
46537
47483
  if (methods.length > 0) {
46538
47484
  paths.push({ path: pathKey, methods });
@@ -46550,7 +47496,7 @@ function extractRoutes(cwd) {
46550
47496
  return;
46551
47497
  }
46552
47498
  for (const entry of entries) {
46553
- const fullPath = path28.join(dir, entry.name);
47499
+ const fullPath = path29.join(dir, entry.name);
46554
47500
  if (entry.isSymbolicLink()) {
46555
47501
  continue;
46556
47502
  }
@@ -46560,7 +47506,7 @@ function extractRoutes(cwd) {
46560
47506
  }
46561
47507
  walkDir(fullPath);
46562
47508
  } else if (entry.isFile()) {
46563
- const ext = path28.extname(entry.name).toLowerCase();
47509
+ const ext = path29.extname(entry.name).toLowerCase();
46564
47510
  const baseName = entry.name.toLowerCase();
46565
47511
  if (![".ts", ".js", ".mjs"].includes(ext)) {
46566
47512
  continue;
@@ -46584,10 +47530,10 @@ function extractRoutesFromFile(filePath) {
46584
47530
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
46585
47531
  for (let lineNum = 0;lineNum < lines.length; lineNum++) {
46586
47532
  const line = lines[lineNum];
46587
- let match;
47533
+ let match = expressRegex.exec(line);
46588
47534
  expressRegex.lastIndex = 0;
46589
47535
  flaskRegex.lastIndex = 0;
46590
- while ((match = expressRegex.exec(line)) !== null) {
47536
+ while (match !== null) {
46591
47537
  const method = match[1].toLowerCase();
46592
47538
  const routePath = match[2];
46593
47539
  routes.push({
@@ -46596,8 +47542,10 @@ function extractRoutesFromFile(filePath) {
46596
47542
  file: filePath,
46597
47543
  line: lineNum + 1
46598
47544
  });
47545
+ match = expressRegex.exec(line);
46599
47546
  }
46600
- while ((match = flaskRegex.exec(line)) !== null) {
47547
+ match = flaskRegex.exec(line);
47548
+ while (match !== null) {
46601
47549
  const routePath = match[1];
46602
47550
  routes.push({
46603
47551
  path: routePath,
@@ -46605,6 +47553,7 @@ function extractRoutesFromFile(filePath) {
46605
47553
  file: filePath,
46606
47554
  line: lineNum + 1
46607
47555
  });
47556
+ match = flaskRegex.exec(line);
46608
47557
  }
46609
47558
  }
46610
47559
  return routes;
@@ -46726,7 +47675,7 @@ init_secretscan();
46726
47675
  // src/tools/symbols.ts
46727
47676
  init_tool();
46728
47677
  import * as fs23 from "fs";
46729
- import * as path29 from "path";
47678
+ import * as path30 from "path";
46730
47679
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
46731
47680
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
46732
47681
  function containsControlCharacters(str) {
@@ -46755,11 +47704,11 @@ function containsWindowsAttacks(str) {
46755
47704
  }
46756
47705
  function isPathInWorkspace(filePath, workspace) {
46757
47706
  try {
46758
- const resolvedPath = path29.resolve(workspace, filePath);
47707
+ const resolvedPath = path30.resolve(workspace, filePath);
46759
47708
  const realWorkspace = fs23.realpathSync(workspace);
46760
47709
  const realResolvedPath = fs23.realpathSync(resolvedPath);
46761
- const relativePath = path29.relative(realWorkspace, realResolvedPath);
46762
- if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
47710
+ const relativePath = path30.relative(realWorkspace, realResolvedPath);
47711
+ if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
46763
47712
  return false;
46764
47713
  }
46765
47714
  return true;
@@ -46771,7 +47720,7 @@ function validatePathForRead(filePath, workspace) {
46771
47720
  return isPathInWorkspace(filePath, workspace);
46772
47721
  }
46773
47722
  function extractTSSymbols(filePath, cwd) {
46774
- const fullPath = path29.join(cwd, filePath);
47723
+ const fullPath = path30.join(cwd, filePath);
46775
47724
  if (!validatePathForRead(fullPath, cwd)) {
46776
47725
  return [];
46777
47726
  }
@@ -46802,7 +47751,7 @@ function extractTSSymbols(filePath, cwd) {
46802
47751
  jsdoc = jsdocLines.join(`
46803
47752
  `).trim();
46804
47753
  if (jsdoc.length > 300)
46805
- jsdoc = jsdoc.substring(0, 300) + "...";
47754
+ jsdoc = `${jsdoc.substring(0, 300)}...`;
46806
47755
  }
46807
47756
  const fnMatch = line.match(/^export\s+(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)(?:\s*:\s*(.+?))?(?:\s*\{|$)/);
46808
47757
  if (fnMatch) {
@@ -46923,7 +47872,7 @@ function extractTSSymbols(filePath, cwd) {
46923
47872
  });
46924
47873
  }
46925
47874
  function extractPythonSymbols(filePath, cwd) {
46926
- const fullPath = path29.join(cwd, filePath);
47875
+ const fullPath = path30.join(cwd, filePath);
46927
47876
  if (!validatePathForRead(fullPath, cwd)) {
46928
47877
  return [];
46929
47878
  }
@@ -47005,7 +47954,7 @@ var symbols = tool({
47005
47954
  }, null, 2);
47006
47955
  }
47007
47956
  const cwd = process.cwd();
47008
- const ext = path29.extname(file3);
47957
+ const ext = path30.extname(file3);
47009
47958
  if (containsControlCharacters(file3)) {
47010
47959
  return JSON.stringify({
47011
47960
  file: file3,
@@ -47074,7 +48023,7 @@ init_test_runner();
47074
48023
  // src/tools/todo-extract.ts
47075
48024
  init_dist();
47076
48025
  import * as fs24 from "fs";
47077
- import * as path30 from "path";
48026
+ import * as path31 from "path";
47078
48027
  var MAX_TEXT_LENGTH = 200;
47079
48028
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
47080
48029
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -47145,9 +48094,9 @@ function validatePathsInput(paths, cwd) {
47145
48094
  return { error: "paths contains path traversal", resolvedPath: null };
47146
48095
  }
47147
48096
  try {
47148
- const resolvedPath = path30.resolve(paths);
47149
- const normalizedCwd = path30.resolve(cwd);
47150
- const normalizedResolved = path30.resolve(resolvedPath);
48097
+ const resolvedPath = path31.resolve(paths);
48098
+ const normalizedCwd = path31.resolve(cwd);
48099
+ const normalizedResolved = path31.resolve(resolvedPath);
47151
48100
  if (!normalizedResolved.startsWith(normalizedCwd)) {
47152
48101
  return {
47153
48102
  error: "paths must be within the current working directory",
@@ -47163,7 +48112,7 @@ function validatePathsInput(paths, cwd) {
47163
48112
  }
47164
48113
  }
47165
48114
  function isSupportedExtension(filePath) {
47166
- const ext = path30.extname(filePath).toLowerCase();
48115
+ const ext = path31.extname(filePath).toLowerCase();
47167
48116
  return SUPPORTED_EXTENSIONS2.has(ext);
47168
48117
  }
47169
48118
  function findSourceFiles3(dir, files = []) {
@@ -47178,7 +48127,7 @@ function findSourceFiles3(dir, files = []) {
47178
48127
  if (SKIP_DIRECTORIES3.has(entry)) {
47179
48128
  continue;
47180
48129
  }
47181
- const fullPath = path30.join(dir, entry);
48130
+ const fullPath = path31.join(dir, entry);
47182
48131
  let stat;
47183
48132
  try {
47184
48133
  stat = fs24.statSync(fullPath);
@@ -47210,7 +48159,7 @@ function parseTodoComments(content, filePath, tagsSet) {
47210
48159
  let text = line.substring(match.index + match[0].length).trim();
47211
48160
  text = text.replace(/^[/*\-\s]+/, "");
47212
48161
  if (text.length > MAX_TEXT_LENGTH) {
47213
- text = text.substring(0, MAX_TEXT_LENGTH - 3) + "...";
48162
+ text = `${text.substring(0, MAX_TEXT_LENGTH - 3)}...`;
47214
48163
  }
47215
48164
  entries.push({
47216
48165
  file: filePath,
@@ -47290,7 +48239,7 @@ var todo_extract = tool({
47290
48239
  filesToScan.push(scanPath);
47291
48240
  } else {
47292
48241
  const errorResult = {
47293
- error: `unsupported file extension: ${path30.extname(scanPath)}`,
48242
+ error: `unsupported file extension: ${path31.extname(scanPath)}`,
47294
48243
  total: 0,
47295
48244
  byPriority: { high: 0, medium: 0, low: 0 },
47296
48245
  entries: []
@@ -47334,6 +48283,33 @@ var todo_extract = tool({
47334
48283
  });
47335
48284
  // src/index.ts
47336
48285
  init_utils();
48286
+
48287
+ // src/utils/tool-output.ts
48288
+ function truncateToolOutput(output, maxLines, toolName) {
48289
+ if (!output) {
48290
+ return output;
48291
+ }
48292
+ const lines = output.split(`
48293
+ `);
48294
+ if (lines.length <= maxLines) {
48295
+ return output;
48296
+ }
48297
+ const omittedCount = lines.length - maxLines;
48298
+ const truncated = lines.slice(0, maxLines);
48299
+ const footerLines = [];
48300
+ footerLines.push("");
48301
+ footerLines.push(`[... ${omittedCount} line${omittedCount === 1 ? "" : "s"} omitted ...]`);
48302
+ if (toolName) {
48303
+ footerLines.push(`Tool: ${toolName}`);
48304
+ }
48305
+ footerLines.push("Use /swarm retrieve <id> to get the full content");
48306
+ return truncated.join(`
48307
+ `) + `
48308
+ ` + footerLines.join(`
48309
+ `);
48310
+ }
48311
+
48312
+ // src/index.ts
47337
48313
  var OpenCodeSwarm = async (ctx) => {
47338
48314
  const { config: config3, loadedFromFile } = loadPluginConfigWithMeta(ctx.directory);
47339
48315
  const agents = getAgentConfigs(config3);
@@ -47378,7 +48354,7 @@ var OpenCodeSwarm = async (ctx) => {
47378
48354
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
47379
48355
  preflightTriggerManager = new PTM(automationConfig);
47380
48356
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
47381
- const swarmDir = path31.resolve(ctx.directory, ".swarm");
48357
+ const swarmDir = path32.resolve(ctx.directory, ".swarm");
47382
48358
  statusArtifact = new ASA(swarmDir);
47383
48359
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
47384
48360
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -47511,7 +48487,13 @@ var OpenCodeSwarm = async (ctx) => {
47511
48487
  pipelineHook["experimental.chat.messages.transform"],
47512
48488
  contextBudgetHandler,
47513
48489
  guardrailsHooks.messagesTransform,
47514
- delegationGateHandler
48490
+ delegationGateHandler,
48491
+ (_input, output) => {
48492
+ if (output.messages) {
48493
+ output.messages = consolidateSystemMessages(output.messages);
48494
+ }
48495
+ return Promise.resolve();
48496
+ }
47515
48497
  ].filter((fn) => Boolean(fn))),
47516
48498
  "experimental.chat.system.transform": composeHandlers(...[
47517
48499
  systemEnhancerHook["experimental.chat.system.transform"],
@@ -47542,6 +48524,21 @@ var OpenCodeSwarm = async (ctx) => {
47542
48524
  await activityHooks.toolAfter(input, output);
47543
48525
  await guardrailsHooks.toolAfter(input, output);
47544
48526
  await toolSummarizerHook?.(input, output);
48527
+ const toolOutputConfig = config3.tool_output;
48528
+ if (toolOutputConfig && toolOutputConfig.truncation_enabled !== false && typeof output.output === "string") {
48529
+ const skipTools = [
48530
+ "pre_check_batch",
48531
+ "pkg_audit",
48532
+ "schema_drift",
48533
+ "sbom_generate"
48534
+ ];
48535
+ if (!skipTools.includes(input.tool)) {
48536
+ const maxLines = toolOutputConfig.per_tool?.[input.tool] ?? toolOutputConfig.max_lines ?? 150;
48537
+ if (input.tool === "diff" || input.tool === "symbols") {
48538
+ output.output = truncateToolOutput(output.output, maxLines, input.tool);
48539
+ }
48540
+ }
48541
+ }
47545
48542
  if (input.tool === "task") {
47546
48543
  const sessionId = input.sessionID;
47547
48544
  swarmState.activeAgent.set(sessionId, ORCHESTRATOR_NAME);