opencode-swarm 6.39.0 → 6.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
21
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
22
  for (let key of __getOwnPropNames(mod))
11
23
  if (!__hasOwnProp.call(to, key))
12
24
  __defProp(to, key, {
13
- get: () => mod[key],
25
+ get: __accessProp.bind(mod, key),
14
26
  enumerable: true
15
27
  });
28
+ if (canCache)
29
+ cache.set(mod, to);
16
30
  return to;
17
31
  };
18
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name2, newValue) {
35
+ this[name2] = __returnValue.bind(null, newValue);
36
+ }
19
37
  var __export = (target, all) => {
20
38
  for (var name2 in all)
21
39
  __defProp(target, name2, {
22
40
  get: all[name2],
23
41
  enumerable: true,
24
42
  configurable: true,
25
- set: (newValue) => all[name2] = () => newValue
43
+ set: __exportSetter.bind(all, name2)
26
44
  });
27
45
  };
28
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -14892,8 +14910,8 @@ var init_schema = __esm(() => {
14892
14910
  });
14893
14911
  CheckpointConfigSchema = exports_external.object({
14894
14912
  enabled: exports_external.boolean().default(true),
14895
- auto_checkpoint_threshold: exports_external.number().min(1).max(20).default(3)
14896
- });
14913
+ auto_checkpoint_threshold: exports_external.number().int().min(1).max(20).default(3)
14914
+ }).strict();
14897
14915
  AutomationModeSchema = exports_external.enum(["manual", "hybrid", "auto"]);
14898
14916
  AutomationCapabilitiesSchema = exports_external.object({
14899
14917
  plan_sync: exports_external.boolean().default(true),
@@ -15013,7 +15031,8 @@ var init_schema = __esm(() => {
15013
15031
  block_on_threshold: exports_external.boolean().default(false).describe("If true, block phase completion when threshold exceeded. Default: advisory only.")
15014
15032
  }).optional(),
15015
15033
  incremental_verify: IncrementalVerifyConfigSchema.optional(),
15016
- compaction_service: CompactionConfigSchema.optional()
15034
+ compaction_service: CompactionConfigSchema.optional(),
15035
+ turbo_mode: exports_external.boolean().default(false).optional()
15017
15036
  });
15018
15037
  });
15019
15038
 
@@ -15192,7 +15211,8 @@ var init_plan_schema = __esm(() => {
15192
15211
  id: exports_external.number().int().min(1),
15193
15212
  name: exports_external.string().min(1),
15194
15213
  status: PhaseStatusSchema.default("pending"),
15195
- tasks: exports_external.array(TaskSchema).default([])
15214
+ tasks: exports_external.array(TaskSchema).default([]),
15215
+ required_agents: exports_external.array(exports_external.string()).optional()
15196
15216
  });
15197
15217
  PlanSchema = exports_external.object({
15198
15218
  schema_version: exports_external.literal("1.0.0"),
@@ -16100,7 +16120,7 @@ async function updateTaskStatus(directory, taskId, status) {
16100
16120
  throw new Error(`Task not found: ${taskId}`);
16101
16121
  }
16102
16122
  const updatedPlan = { ...plan, phases: updatedPhases };
16103
- await savePlan(directory, updatedPlan, { preserveCompletedStatuses: false });
16123
+ await savePlan(directory, updatedPlan, { preserveCompletedStatuses: true });
16104
16124
  return updatedPlan;
16105
16125
  }
16106
16126
  function derivePlanMarkdown(plan) {
@@ -16456,8 +16476,8 @@ function getTaskBlockers(task, summary, status) {
16456
16476
  }
16457
16477
  return blockers;
16458
16478
  }
16459
- async function buildTaskSummary(task, taskId) {
16460
- const result = await loadEvidence(".", taskId);
16479
+ async function buildTaskSummary(directory, task, taskId) {
16480
+ const result = await loadEvidence(directory, taskId);
16461
16481
  const bundle = result.status === "found" ? result.bundle : null;
16462
16482
  const phase = task?.phase ?? 0;
16463
16483
  const status = getTaskStatus(task, bundle);
@@ -16486,18 +16506,18 @@ async function buildTaskSummary(task, taskId) {
16486
16506
  lastEvidenceTimestamp: lastTimestamp
16487
16507
  };
16488
16508
  }
16489
- async function buildPhaseSummary(phase) {
16490
- const taskIds = await listEvidenceTaskIds(".");
16509
+ async function buildPhaseSummary(directory, phase) {
16510
+ const taskIds = await listEvidenceTaskIds(directory);
16491
16511
  const phaseTaskIds = new Set(phase.tasks.map((t) => t.id));
16492
16512
  const taskSummaries = [];
16493
16513
  const _taskMap = new Map(phase.tasks.map((t) => [t.id, t]));
16494
16514
  for (const task of phase.tasks) {
16495
- const summary = await buildTaskSummary(task, task.id);
16515
+ const summary = await buildTaskSummary(directory, task, task.id);
16496
16516
  taskSummaries.push(summary);
16497
16517
  }
16498
16518
  const extraTaskIds = taskIds.filter((id) => !phaseTaskIds.has(id));
16499
16519
  for (const taskId of extraTaskIds) {
16500
- const summary = await buildTaskSummary(undefined, taskId);
16520
+ const summary = await buildTaskSummary(directory, undefined, taskId);
16501
16521
  if (summary.phase === phase.id) {
16502
16522
  taskSummaries.push(summary);
16503
16523
  }
@@ -16598,7 +16618,7 @@ async function buildEvidenceSummary(directory, currentPhase) {
16598
16618
  let totalTasks = 0;
16599
16619
  let completedTasks = 0;
16600
16620
  for (const phase of phasesToProcess) {
16601
- const summary = await buildPhaseSummary(phase);
16621
+ const summary = await buildPhaseSummary(directory, phase);
16602
16622
  phaseSummaries.push(summary);
16603
16623
  totalTasks += summary.totalTasks;
16604
16624
  completedTasks += summary.completedTasks;
@@ -30176,6 +30196,11 @@ function isGitRepo() {
30176
30196
  }
30177
30197
  function handleSave(label, directory) {
30178
30198
  try {
30199
+ let maxCheckpoints = 20;
30200
+ try {
30201
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
30202
+ maxCheckpoints = config3.checkpoint?.auto_checkpoint_threshold ?? maxCheckpoints;
30203
+ } catch {}
30179
30204
  const log2 = readCheckpointLog(directory);
30180
30205
  const existingCheckpoint = log2.checkpoints.find((c) => c.label === label);
30181
30206
  if (existingCheckpoint) {
@@ -30280,6 +30305,7 @@ function handleDelete(label, directory) {
30280
30305
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json", MAX_LABEL_LENGTH = 100, GIT_TIMEOUT_MS = 30000, SHELL_METACHARACTERS, SAFE_LABEL_PATTERN, CONTROL_CHAR_PATTERN, NON_ASCII_PATTERN, checkpoint;
30281
30306
  var init_checkpoint = __esm(() => {
30282
30307
  init_tool();
30308
+ init_config();
30283
30309
  init_create_tool();
30284
30310
  SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
30285
30311
  SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
@@ -30303,7 +30329,7 @@ var init_checkpoint = __esm(() => {
30303
30329
  let label;
30304
30330
  try {
30305
30331
  action = String(args2.action);
30306
- label = args2.label !== undefined ? String(args2.label) : undefined;
30332
+ label = args2.label !== undefined && args2.label !== null ? String(args2.label) : undefined;
30307
30333
  } catch {
30308
30334
  return JSON.stringify({
30309
30335
  action: "unknown",
@@ -33518,6 +33544,59 @@ var init_profiles = __esm(() => {
33518
33544
  ]
33519
33545
  }
33520
33546
  });
33547
+ LANGUAGE_REGISTRY.register({
33548
+ id: "php",
33549
+ displayName: "PHP",
33550
+ tier: 3,
33551
+ extensions: [".php", ".phtml"],
33552
+ treeSitter: { grammarId: "php", wasmFile: "tree-sitter-php.wasm" },
33553
+ build: {
33554
+ detectFiles: ["composer.json"],
33555
+ commands: []
33556
+ },
33557
+ test: {
33558
+ detectFiles: ["phpunit.xml", "phpunit.xml.dist"],
33559
+ frameworks: [
33560
+ {
33561
+ name: "PHPUnit",
33562
+ detect: "phpunit.xml",
33563
+ cmd: "vendor/bin/phpunit",
33564
+ priority: 1
33565
+ }
33566
+ ]
33567
+ },
33568
+ lint: {
33569
+ detectFiles: [".php-cs-fixer.php", "phpcs.xml"],
33570
+ linters: [
33571
+ {
33572
+ name: "PHP-CS-Fixer",
33573
+ detect: ".php-cs-fixer.php",
33574
+ cmd: "vendor/bin/php-cs-fixer fix --dry-run --diff",
33575
+ priority: 1
33576
+ }
33577
+ ]
33578
+ },
33579
+ audit: {
33580
+ detectFiles: ["composer.lock"],
33581
+ command: "composer audit --format=json",
33582
+ outputFormat: "json"
33583
+ },
33584
+ sast: { nativeRuleSet: "php", semgrepSupport: "ga" },
33585
+ prompts: {
33586
+ coderConstraints: [
33587
+ "Follow PSR-12 coding standards",
33588
+ "Use strict types declaration: declare(strict_types=1)",
33589
+ "Prefer type hints and return type declarations on all functions",
33590
+ "Use dependency injection over static methods and singletons"
33591
+ ],
33592
+ reviewerChecklist: [
33593
+ "Verify no user input reaches SQL queries without parameterised binding",
33594
+ "Check for XSS \u2014 all output must be escaped with htmlspecialchars()",
33595
+ "Confirm no eval(), exec(), or shell_exec() with user-controlled input",
33596
+ "Validate proper error handling \u2014 no bare catch blocks that swallow errors"
33597
+ ]
33598
+ }
33599
+ });
33521
33600
  });
33522
33601
 
33523
33602
  // src/lang/detector.ts
@@ -34135,7 +34214,7 @@ async function detectAvailableLinter(directory) {
34135
34214
  async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
34136
34215
  const DETECT_TIMEOUT = 2000;
34137
34216
  try {
34138
- const biomeProc = Bun.spawn(["npx", "biome", "--version"], {
34217
+ const biomeProc = Bun.spawn([biomeBin, "--version"], {
34139
34218
  stdout: "pipe",
34140
34219
  stderr: "pipe"
34141
34220
  });
@@ -34149,7 +34228,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
34149
34228
  }
34150
34229
  } catch {}
34151
34230
  try {
34152
- const eslintProc = Bun.spawn(["npx", "eslint", "--version"], {
34231
+ const eslintProc = Bun.spawn([eslintBin, "--version"], {
34153
34232
  stdout: "pipe",
34154
34233
  stderr: "pipe"
34155
34234
  });
@@ -36161,12 +36240,13 @@ async function runLintCheck(dir, linter, timeoutMs) {
36161
36240
  const startTime = Date.now();
36162
36241
  try {
36163
36242
  const lintPromise = runLint(linter, "check", dir);
36243
+ let timeoutId;
36164
36244
  const timeoutPromise = new Promise((_, reject) => {
36165
- setTimeout(() => {
36245
+ timeoutId = setTimeout(() => {
36166
36246
  reject(new Error(`Lint check timed out after ${timeoutMs}ms`));
36167
36247
  }, timeoutMs);
36168
36248
  });
36169
- const result = await Promise.race([lintPromise, timeoutPromise]);
36249
+ const result = await Promise.race([lintPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
36170
36250
  if (!result.success) {
36171
36251
  return {
36172
36252
  type: "lint",
@@ -36658,7 +36738,7 @@ function deriveRequiredGates(agentType) {
36658
36738
  }
36659
36739
  function expandRequiredGates(existingGates, newAgentType) {
36660
36740
  const newGates = deriveRequiredGates(newAgentType);
36661
- const combined = [...new Set([...existingGates, ...newGates])];
36741
+ const combined = [...new Set([...existingGates ?? [], ...newGates])];
36662
36742
  return combined.sort();
36663
36743
  }
36664
36744
  function getEvidenceDir(directory) {
@@ -36793,10 +36873,10 @@ function createPreflightIntegration(config3) {
36793
36873
  });
36794
36874
  const report = await runPreflight(directory, request.currentPhase, preflightConfig);
36795
36875
  if (statusArtifact) {
36796
- const state2 = report.overall === "pass" ? "success" : "failure";
36797
- statusArtifact.recordOutcome(state2, request.currentPhase, report.message);
36876
+ const state = report.overall === "pass" ? "success" : "failure";
36877
+ statusArtifact.recordOutcome(state, request.currentPhase, report.message);
36798
36878
  console.log("[PreflightIntegration] Status artifact updated", {
36799
- state: state2,
36879
+ state,
36800
36880
  phase: request.currentPhase,
36801
36881
  message: report.message
36802
36882
  });
@@ -37238,7 +37318,7 @@ async function readPriorDriftReports(directory) {
37238
37318
  continue;
37239
37319
  try {
37240
37320
  const report = JSON.parse(content);
37241
- if (typeof report.phase !== "number" || typeof report.alignment !== "string") {
37321
+ if (typeof report.phase !== "number" || typeof report.alignment !== "string" || typeof report.timestamp !== "string" || typeof report.drift_score !== "number" || typeof report.schema_version !== "number" || !Array.isArray(report.compounding_effects)) {
37242
37322
  console.warn(`[curator-drift] Skipping corrupt drift report: ${filename}`);
37243
37323
  continue;
37244
37324
  }
@@ -40635,8 +40715,8 @@ async function loadGrammar(languageId) {
40635
40715
  const wasmFileName = getWasmFileName(normalizedId);
40636
40716
  const grammarsPath = getGrammarsPath();
40637
40717
  const wasmPath = fileURLToPath(new URL(`${grammarsPath}${wasmFileName}`, import.meta.url));
40638
- const { existsSync: existsSync27 } = await import("fs");
40639
- if (!existsSync27(wasmPath)) {
40718
+ const { existsSync: existsSync28 } = await import("fs");
40719
+ if (!existsSync28(wasmPath)) {
40640
40720
  throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
40641
40721
  Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
40642
40722
  }
@@ -40666,8 +40746,6 @@ var init_runtime = __esm(() => {
40666
40746
  c: "tree-sitter-cpp.wasm",
40667
40747
  csharp: "tree-sitter-c-sharp.wasm",
40668
40748
  css: "tree-sitter-css.wasm",
40669
- html: "tree-sitter-html.wasm",
40670
- json: "tree-sitter-json.wasm",
40671
40749
  bash: "tree-sitter-bash.wasm",
40672
40750
  ruby: "tree-sitter-ruby.wasm",
40673
40751
  php: "tree-sitter-php.wasm",
@@ -40760,7 +40838,9 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
40760
40838
  applyRehydrationCache(sessionState);
40761
40839
  if (directory) {
40762
40840
  let rehydrationPromise;
40763
- rehydrationPromise = rehydrateSessionFromDisk(directory, sessionState).catch(() => {}).finally(() => {
40841
+ rehydrationPromise = rehydrateSessionFromDisk(directory, sessionState).catch((err2) => {
40842
+ console.warn("[state] Rehydration failed:", err2 instanceof Error ? err2.message : String(err2));
40843
+ }).finally(() => {
40764
40844
  swarmState.pendingRehydrations.delete(rehydrationPromise);
40765
40845
  });
40766
40846
  swarmState.pendingRehydrations.add(rehydrationPromise);
@@ -45154,7 +45234,24 @@ async function handleBenchmarkCommand(directory, args2) {
45154
45234
  }
45155
45235
 
45156
45236
  // src/commands/checkpoint.ts
45237
+ init_zod();
45157
45238
  init_checkpoint();
45239
+ var CheckpointResultSchema = exports_external.object({
45240
+ action: exports_external.string().optional(),
45241
+ success: exports_external.boolean(),
45242
+ error: exports_external.string().optional(),
45243
+ checkpoints: exports_external.array(exports_external.unknown()).optional()
45244
+ }).passthrough();
45245
+ function safeParseResult(result) {
45246
+ const parsed = CheckpointResultSchema.safeParse(JSON.parse(result));
45247
+ if (!parsed.success) {
45248
+ return {
45249
+ success: false,
45250
+ error: `Invalid response: ${parsed.error.message}`
45251
+ };
45252
+ }
45253
+ return parsed.data;
45254
+ }
45158
45255
  async function handleCheckpointCommand(directory, args2) {
45159
45256
  const subcommand = args2[0] || "list";
45160
45257
  const label = args2[1];
@@ -45177,7 +45274,7 @@ async function handleSave2(directory, label) {
45177
45274
  const result = await checkpoint.execute({ action: "save", label }, {
45178
45275
  directory
45179
45276
  });
45180
- const parsed = JSON.parse(result);
45277
+ const parsed = safeParseResult(result);
45181
45278
  if (parsed.success) {
45182
45279
  return `\u2713 Checkpoint saved: "${label}"`;
45183
45280
  } else {
@@ -45196,7 +45293,7 @@ async function handleRestore2(directory, label) {
45196
45293
  const result = await checkpoint.execute({ action: "restore", label }, {
45197
45294
  directory
45198
45295
  });
45199
- const parsed = JSON.parse(result);
45296
+ const parsed = safeParseResult(result);
45200
45297
  if (parsed.success) {
45201
45298
  return `\u2713 Restored to checkpoint: "${label}"`;
45202
45299
  } else {
@@ -45215,7 +45312,7 @@ async function handleDelete2(directory, label) {
45215
45312
  const result = await checkpoint.execute({ action: "delete", label }, {
45216
45313
  directory
45217
45314
  });
45218
- const parsed = JSON.parse(result);
45315
+ const parsed = safeParseResult(result);
45219
45316
  if (parsed.success) {
45220
45317
  return `\u2713 Checkpoint deleted: "${label}"`;
45221
45318
  } else {
@@ -45231,7 +45328,7 @@ async function handleList2(directory) {
45231
45328
  const result = await checkpoint.execute({ action: "list" }, {
45232
45329
  directory
45233
45330
  });
45234
- const parsed = JSON.parse(result);
45331
+ const parsed = safeParseResult(result);
45235
45332
  if (!parsed.success) {
45236
45333
  return `Error: ${parsed.error || "Failed to list checkpoints"}`;
45237
45334
  }
@@ -48287,7 +48384,8 @@ async function extractFromLegacy(directory) {
48287
48384
  mappedStatus = "complete";
48288
48385
  else if (status === "IN PROGRESS")
48289
48386
  mappedStatus = "in_progress";
48290
- const headerLineIndex = lines.indexOf(match[0]);
48387
+ const headerLineIndex = planContent.substring(0, match.index).split(`
48388
+ `).length - 1;
48291
48389
  let completed = 0;
48292
48390
  let total = 0;
48293
48391
  if (headerLineIndex !== -1) {
@@ -48687,9 +48785,9 @@ async function getPlanData(directory, phaseArg) {
48687
48785
  return {
48688
48786
  hasPlan: true,
48689
48787
  fullMarkdown,
48690
- requestedPhase: NaN,
48788
+ requestedPhase: null,
48691
48789
  phaseMarkdown: null,
48692
- errorMessage: null,
48790
+ errorMessage: `Invalid phase number: "${phaseArg}"`,
48693
48791
  isLegacy: false
48694
48792
  };
48695
48793
  }
@@ -48740,9 +48838,9 @@ async function getPlanData(directory, phaseArg) {
48740
48838
  return {
48741
48839
  hasPlan: true,
48742
48840
  fullMarkdown: planContent,
48743
- requestedPhase: NaN,
48841
+ requestedPhase: null,
48744
48842
  phaseMarkdown: null,
48745
- errorMessage: null,
48843
+ errorMessage: `Invalid phase number: "${phaseArg}"`,
48746
48844
  isLegacy: true
48747
48845
  };
48748
48846
  }
@@ -48982,9 +49080,16 @@ function sanitizeSummaryId(id) {
48982
49080
  }
48983
49081
  async function storeSummary(directory, id, fullOutput, summaryText, maxStoredBytes) {
48984
49082
  const sanitizedId = sanitizeSummaryId(id);
48985
- const outputBytes = Buffer.byteLength(fullOutput, "utf8");
48986
- if (outputBytes > maxStoredBytes) {
48987
- throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
49083
+ const preCheckEntry = {
49084
+ id: sanitizedId,
49085
+ summaryText,
49086
+ fullOutput,
49087
+ timestamp: Date.now(),
49088
+ originalBytes: Buffer.byteLength(fullOutput, "utf8")
49089
+ };
49090
+ const serializedSize = Buffer.byteLength(JSON.stringify(preCheckEntry), "utf8");
49091
+ if (serializedSize > maxStoredBytes) {
49092
+ throw new Error(`Summary entry size (${serializedSize} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
48988
49093
  }
48989
49094
  const relativePath = path27.join("summaries", `${sanitizedId}.json`);
48990
49095
  const summaryPath = validateSwarmPath(directory, relativePath);
@@ -48994,7 +49099,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
48994
49099
  summaryText,
48995
49100
  fullOutput,
48996
49101
  timestamp: Date.now(),
48997
- originalBytes: outputBytes
49102
+ originalBytes: Buffer.byteLength(fullOutput, "utf8")
48998
49103
  };
48999
49104
  const entryJson = JSON.stringify(entry);
49000
49105
  mkdirSync9(summaryDir, { recursive: true });
@@ -49560,7 +49665,15 @@ function makeInitialState() {
49560
49665
  lastSnapshotAt: null
49561
49666
  };
49562
49667
  }
49563
- var state = makeInitialState();
49668
+ var sessionStates = new Map;
49669
+ function getSessionState(sessionId) {
49670
+ let state = sessionStates.get(sessionId);
49671
+ if (!state) {
49672
+ state = makeInitialState();
49673
+ sessionStates.set(sessionId, state);
49674
+ }
49675
+ return state;
49676
+ }
49564
49677
  function appendSnapshot(directory, tier, budgetPct, message) {
49565
49678
  try {
49566
49679
  const snapshotPath = path29.join(directory, ".swarm", "context-snapshot.md");
@@ -49599,6 +49712,7 @@ function createCompactionService(config3, directory, injectMessage) {
49599
49712
  if (budgetPct <= 0)
49600
49713
  return;
49601
49714
  const sessionId = _input.sessionID;
49715
+ const state = getSessionState(sessionId);
49602
49716
  try {
49603
49717
  if (budgetPct >= config3.emergencyThreshold && budgetPct > state.lastEmergencyAt + 5) {
49604
49718
  state.lastEmergencyAt = budgetPct;
@@ -49630,11 +49744,23 @@ function createCompactionService(config3, directory, injectMessage) {
49630
49744
  }
49631
49745
  };
49632
49746
  }
49633
- function getCompactionMetrics() {
49634
- return {
49635
- compactionCount: state.observationCount + state.reflectionCount + state.emergencyCount,
49636
- lastSnapshotAt: state.lastSnapshotAt
49637
- };
49747
+ function getCompactionMetrics(sessionId) {
49748
+ if (sessionId) {
49749
+ const state = getSessionState(sessionId);
49750
+ return {
49751
+ compactionCount: state.observationCount + state.reflectionCount + state.emergencyCount,
49752
+ lastSnapshotAt: state.lastSnapshotAt
49753
+ };
49754
+ }
49755
+ let total = 0;
49756
+ let lastSnapshot = null;
49757
+ for (const state of sessionStates.values()) {
49758
+ total += state.observationCount + state.reflectionCount + state.emergencyCount;
49759
+ if (state.lastSnapshotAt && (!lastSnapshot || state.lastSnapshotAt > lastSnapshot)) {
49760
+ lastSnapshot = state.lastSnapshotAt;
49761
+ }
49762
+ }
49763
+ return { compactionCount: total, lastSnapshotAt: lastSnapshot };
49638
49764
  }
49639
49765
 
49640
49766
  // src/services/context-budget-service.ts
@@ -49665,10 +49791,14 @@ async function readBudgetState(directory) {
49665
49791
  return null;
49666
49792
  }
49667
49793
  }
49668
- async function writeBudgetState(directory, state2) {
49669
- const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
49670
- const content = JSON.stringify(state2, null, 2);
49671
- await Bun.write(resolvedPath, content);
49794
+ async function writeBudgetState(directory, state) {
49795
+ try {
49796
+ const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
49797
+ const content = JSON.stringify(state, null, 2);
49798
+ await Bun.write(resolvedPath, content);
49799
+ } catch (error93) {
49800
+ console.warn("[context-budget] Failed to write budget state:", error93 instanceof Error ? error93.message : String(error93));
49801
+ }
49672
49802
  }
49673
49803
  async function countEvents(directory) {
49674
49804
  const content = await readSwarmFileAsync(directory, "events.jsonl");
@@ -49756,25 +49886,25 @@ async function formatBudgetWarning(report, directory, config3) {
49756
49886
  return formatWarningMessage(report);
49757
49887
  }
49758
49888
  const budgetState = await readBudgetState(directory);
49759
- const state2 = budgetState || {
49889
+ const state = budgetState || {
49760
49890
  warningFiredAtTurn: null,
49761
49891
  criticalFiredAtTurn: null,
49762
49892
  lastInjectedAtTurn: null
49763
49893
  };
49764
49894
  const currentTurn = report.estimatedTurnCount;
49765
49895
  if (report.status === "warning") {
49766
- if (config3.warningMode === "once" && state2.warningFiredAtTurn !== null) {
49896
+ if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
49767
49897
  return null;
49768
49898
  }
49769
- if (config3.warningMode === "interval" && state2.warningFiredAtTurn !== null && currentTurn - state2.warningFiredAtTurn < config3.warningIntervalTurns) {
49899
+ if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
49770
49900
  return null;
49771
49901
  }
49772
- state2.warningFiredAtTurn = currentTurn;
49773
- state2.lastInjectedAtTurn = currentTurn;
49774
- await writeBudgetState(directory, state2);
49902
+ state.warningFiredAtTurn = currentTurn;
49903
+ state.lastInjectedAtTurn = currentTurn;
49904
+ await writeBudgetState(directory, state);
49775
49905
  } else if (report.status === "critical") {
49776
- state2.criticalFiredAtTurn = currentTurn;
49777
- state2.lastInjectedAtTurn = currentTurn;
49906
+ state.criticalFiredAtTurn = currentTurn;
49907
+ state.lastInjectedAtTurn = currentTurn;
49778
49908
  }
49779
49909
  return formatWarningMessage(report);
49780
49910
  }
@@ -49938,6 +50068,7 @@ async function handleTurboCommand(_directory, args2, sessionID) {
49938
50068
 
49939
50069
  // src/tools/write-retro.ts
49940
50070
  init_tool();
50071
+ init_evidence_schema();
49941
50072
  init_manager();
49942
50073
  init_create_tool();
49943
50074
  async function executeWriteRetro(args2, directory) {
@@ -50172,8 +50303,9 @@ async function executeWriteRetro(args2, directory) {
50172
50303
  };
50173
50304
  const taxonomy = [];
50174
50305
  try {
50175
- for (const taskSuffix of ["1", "2", "3", "4", "5"]) {
50176
- const phaseTaskId = `${phase}.${taskSuffix}`;
50306
+ const allTaskIds = await listEvidenceTaskIds(directory);
50307
+ const phaseTaskIds = allTaskIds.filter((id) => id.startsWith(`${phase}.`));
50308
+ for (const phaseTaskId of phaseTaskIds) {
50177
50309
  const result = await loadEvidence(directory, phaseTaskId);
50178
50310
  if (result.status !== "found")
50179
50311
  continue;
@@ -50207,6 +50339,13 @@ async function executeWriteRetro(args2, directory) {
50207
50339
  }
50208
50340
  } catch {}
50209
50341
  retroEntry.error_taxonomy = [...new Set(taxonomy)];
50342
+ const validationResult = RetrospectiveEvidenceSchema.safeParse(retroEntry);
50343
+ if (!validationResult.success) {
50344
+ return JSON.stringify({
50345
+ success: false,
50346
+ error: `Retrospective entry failed validation: ${validationResult.error.message}`
50347
+ }, null, 2);
50348
+ }
50210
50349
  try {
50211
50350
  await saveEvidence(directory, taskId, retroEntry);
50212
50351
  return JSON.stringify({
@@ -50271,7 +50410,7 @@ var write_retro = createSwarmTool({
50271
50410
  }
50272
50411
  });
50273
50412
 
50274
- // src/commands/write_retro.ts
50413
+ // src/commands/write-retro.ts
50275
50414
  async function handleWriteRetroCommand(directory, args2) {
50276
50415
  if (args2.length === 0 || !args2[0] || args2[0].trim() === "") {
50277
50416
  return `## Usage: /swarm write-retro <json>
@@ -50537,6 +50676,7 @@ init_schema();
50537
50676
 
50538
50677
  // src/hooks/agent-activity.ts
50539
50678
  import { renameSync as renameSync8, unlinkSync as unlinkSync4 } from "fs";
50679
+ import * as nodePath2 from "path";
50540
50680
  init_utils();
50541
50681
  init_utils2();
50542
50682
  function createAgentActivityHooks(config3, directory) {
@@ -50561,7 +50701,7 @@ function createAgentActivityHooks(config3, directory) {
50561
50701
  return;
50562
50702
  swarmState.activeToolCalls.delete(input.callID);
50563
50703
  const duration5 = Date.now() - entry.startTime;
50564
- const success3 = output.output != null;
50704
+ const success3 = output.success === true;
50565
50705
  const key = entry.tool;
50566
50706
  const existing = swarmState.toolAggregates.get(key) ?? {
50567
50707
  tool: key,
@@ -50606,7 +50746,7 @@ async function doFlush(directory) {
50606
50746
  const activitySection = renderActivitySection();
50607
50747
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
50608
50748
  const flushedCount = swarmState.pendingEvents;
50609
- const path30 = `${directory}/.swarm/context.md`;
50749
+ const path30 = nodePath2.join(directory, ".swarm", "context.md");
50610
50750
  const tempPath = `${path30}.tmp`;
50611
50751
  try {
50612
50752
  await Bun.write(tempPath, updated);
@@ -50660,7 +50800,7 @@ ${content.substring(endIndex + 1)}`;
50660
50800
  // src/hooks/compaction-customizer.ts
50661
50801
  init_manager2();
50662
50802
  import * as fs20 from "fs";
50663
- import { join as join26 } from "path";
50803
+ import { join as join27 } from "path";
50664
50804
  init_utils2();
50665
50805
  function createCompactionCustomizerHook(config3, directory) {
50666
50806
  const enabled = config3.hooks?.compaction !== false;
@@ -50706,7 +50846,7 @@ function createCompactionCustomizerHook(config3, directory) {
50706
50846
  }
50707
50847
  }
50708
50848
  try {
50709
- const summariesDir = join26(directory, ".swarm", "summaries");
50849
+ const summariesDir = join27(directory, ".swarm", "summaries");
50710
50850
  const files = await fs20.promises.readdir(summariesDir);
50711
50851
  if (files.length > 0) {
50712
50852
  const count = files.length;
@@ -51262,6 +51402,67 @@ init_telemetry();
51262
51402
  import * as path30 from "path";
51263
51403
  init_constants();
51264
51404
  init_schema();
51405
+
51406
+ // src/context/zone-classifier.ts
51407
+ function classifyFile(filePath) {
51408
+ const normalized = filePath.toLowerCase().replace(/\\/g, "/");
51409
+ if (normalized.endsWith(".wasm") || normalized.includes("/dist/") || normalized.includes("/build/") || normalized.includes(".swarm/checkpoints/")) {
51410
+ return {
51411
+ filePath,
51412
+ zone: "generated",
51413
+ confidence: "high",
51414
+ reason: "path matches generated file patterns"
51415
+ };
51416
+ }
51417
+ if (normalized.includes("/test/") || normalized.includes("/tests/") || normalized.includes("/__tests__/") || normalized.includes(".test.") || normalized.includes(".spec.") || normalized.includes("/mocks/")) {
51418
+ return {
51419
+ filePath,
51420
+ zone: "test",
51421
+ confidence: "high",
51422
+ reason: "path matches test/**, tests/**, __tests__/**, *.test.*, *.spec.*, or **/mocks/**"
51423
+ };
51424
+ }
51425
+ if (normalized.includes("/scripts/") || normalized.includes("makefile") || normalized.includes("dockerfile") || normalized.includes(".github/")) {
51426
+ return {
51427
+ filePath,
51428
+ zone: "build",
51429
+ confidence: "high",
51430
+ reason: "path matches build scripts or CI configuration"
51431
+ };
51432
+ }
51433
+ if (normalized.endsWith(".json") && !normalized.includes(".github/") || normalized.endsWith(".yaml") && !normalized.includes(".github/") || normalized.endsWith(".yml") && !normalized.includes(".github/") || normalized.endsWith(".toml") || normalized.includes(".env") || normalized.endsWith("biome.json") || normalized.endsWith("tsconfig.json")) {
51434
+ return {
51435
+ filePath,
51436
+ zone: "config",
51437
+ confidence: "high",
51438
+ reason: "extension is config file type"
51439
+ };
51440
+ }
51441
+ if (normalized.includes("/docs/") || normalized.endsWith(".md") && !normalized.includes("/") || normalized.includes("readme") || normalized.includes("changelog") || normalized.includes("license")) {
51442
+ return {
51443
+ filePath,
51444
+ zone: "docs",
51445
+ confidence: "high",
51446
+ reason: "path matches docs/** or documentation files"
51447
+ };
51448
+ }
51449
+ if (normalized.includes("/src/") || normalized.includes("/lib/")) {
51450
+ return {
51451
+ filePath,
51452
+ zone: "production",
51453
+ confidence: "high",
51454
+ reason: "path matches src/** or lib/**"
51455
+ };
51456
+ }
51457
+ return {
51458
+ filePath,
51459
+ zone: "production",
51460
+ confidence: "medium",
51461
+ reason: "default classification"
51462
+ };
51463
+ }
51464
+
51465
+ // src/hooks/guardrails.ts
51265
51466
  init_manager2();
51266
51467
  init_telemetry();
51267
51468
  init_utils();
@@ -51551,6 +51752,12 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51551
51752
  const delegArgs = output.args;
51552
51753
  const delegTargetPath = delegArgs?.filePath ?? delegArgs?.path ?? delegArgs?.file ?? delegArgs?.target;
51553
51754
  if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
51755
+ const agentName2 = swarmState.activeAgent.get(input.sessionID) ?? "unknown";
51756
+ const cwd = directory ?? process.cwd();
51757
+ const authorityCheck = checkFileAuthority(agentName2, delegTargetPath, cwd);
51758
+ if (!authorityCheck.allowed) {
51759
+ throw new Error(`WRITE BLOCKED: Agent "${agentName2}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
51760
+ }
51554
51761
  if (!currentSession.modifiedFilesThisCoderTask.includes(delegTargetPath)) {
51555
51762
  currentSession.modifiedFilesThisCoderTask.push(delegTargetPath);
51556
51763
  }
@@ -51859,7 +52066,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51859
52066
  session.partialGateWarningsIssuedForTask?.delete(session.currentTaskId);
51860
52067
  if (session.declaredCoderScope !== null) {
51861
52068
  const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope));
51862
- if (undeclaredFiles.length > 2) {
52069
+ if (undeclaredFiles.length >= 1) {
51863
52070
  const safeTaskId = String(session.currentTaskId ?? "").replace(/[\r\n\t]/g, "_");
51864
52071
  session.lastScopeViolation = `Scope violation for task ${safeTaskId}: ` + `${undeclaredFiles.length} undeclared files modified: ` + undeclaredFiles.join(", ");
51865
52072
  session.scopeViolationDetected = true;
@@ -52245,6 +52452,98 @@ function hashArgs(args2) {
52245
52452
  return 0;
52246
52453
  }
52247
52454
  }
52455
+ var AGENT_AUTHORITY_RULES = {
52456
+ architect: {
52457
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52458
+ blockedZones: ["generated"]
52459
+ },
52460
+ coder: {
52461
+ blockedPrefix: [".swarm/"],
52462
+ allowedPrefix: ["src/", "tests/", "docs/", "scripts/"],
52463
+ blockedZones: ["generated", "config"]
52464
+ },
52465
+ reviewer: {
52466
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52467
+ blockedPrefix: ["src/"],
52468
+ allowedPrefix: [".swarm/evidence/", ".swarm/outputs/"],
52469
+ blockedZones: ["generated"]
52470
+ },
52471
+ explorer: {
52472
+ readOnly: true
52473
+ },
52474
+ sme: {
52475
+ readOnly: true
52476
+ },
52477
+ test_engineer: {
52478
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52479
+ blockedPrefix: ["src/"],
52480
+ allowedPrefix: ["tests/", ".swarm/evidence/"],
52481
+ blockedZones: ["generated"]
52482
+ },
52483
+ docs: {
52484
+ allowedPrefix: ["docs/", ".swarm/outputs/"],
52485
+ blockedZones: ["generated"]
52486
+ },
52487
+ designer: {
52488
+ allowedPrefix: ["docs/", ".swarm/outputs/"],
52489
+ blockedZones: ["generated"]
52490
+ },
52491
+ critic: {
52492
+ allowedPrefix: [".swarm/evidence/"],
52493
+ blockedZones: ["generated"]
52494
+ }
52495
+ };
52496
+ function checkFileAuthority(agentName, filePath, _cwd) {
52497
+ const normalizedAgent = agentName.toLowerCase();
52498
+ const normalizedPath = filePath.replace(/\\/g, "/");
52499
+ const rules = AGENT_AUTHORITY_RULES[normalizedAgent];
52500
+ if (!rules) {
52501
+ return { allowed: false, reason: `Unknown agent: ${agentName}` };
52502
+ }
52503
+ if (rules.readOnly) {
52504
+ return {
52505
+ allowed: false,
52506
+ reason: `Path blocked: ${normalizedPath} (agent ${normalizedAgent} is read-only)`
52507
+ };
52508
+ }
52509
+ if (rules.blockedExact) {
52510
+ for (const blocked of rules.blockedExact) {
52511
+ if (normalizedPath === blocked) {
52512
+ return { allowed: false, reason: `Path blocked: ${normalizedPath}` };
52513
+ }
52514
+ }
52515
+ }
52516
+ if (rules.blockedPrefix) {
52517
+ for (const prefix of rules.blockedPrefix) {
52518
+ if (normalizedPath.startsWith(prefix)) {
52519
+ return {
52520
+ allowed: false,
52521
+ reason: `Path blocked: ${normalizedPath} is under ${prefix}`
52522
+ };
52523
+ }
52524
+ }
52525
+ }
52526
+ if (rules.allowedPrefix && rules.allowedPrefix.length > 0) {
52527
+ const isAllowed = rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix));
52528
+ if (!isAllowed) {
52529
+ return {
52530
+ allowed: false,
52531
+ reason: `Path ${normalizedPath} not in allowed list for ${normalizedAgent}`
52532
+ };
52533
+ }
52534
+ }
52535
+ if (rules.blockedZones && rules.blockedZones.length > 0) {
52536
+ const { zone } = classifyFile(normalizedPath);
52537
+ if (rules.blockedZones.includes(zone)) {
52538
+ return {
52539
+ allowed: false,
52540
+ reason: `Path blocked: ${normalizedPath} is in ${zone} zone`,
52541
+ zone
52542
+ };
52543
+ }
52544
+ }
52545
+ return { allowed: true };
52546
+ }
52248
52547
 
52249
52548
  // src/hooks/delegation-gate.ts
52250
52549
  init_utils2();
@@ -52349,8 +52648,8 @@ function createDelegationGateHook(config3, directory) {
52349
52648
  const session = swarmState.agentSessions.get(input.sessionID);
52350
52649
  if (!session || !session.taskWorkflowStates)
52351
52650
  return;
52352
- for (const [taskId, state2] of session.taskWorkflowStates) {
52353
- if (state2 !== "coder_delegated")
52651
+ for (const [taskId, state] of session.taskWorkflowStates) {
52652
+ if (state !== "coder_delegated")
52354
52653
  continue;
52355
52654
  const turbo = hasActiveTurboMode(input.sessionID);
52356
52655
  if (turbo) {
@@ -52381,23 +52680,23 @@ function createDelegationGateHook(config3, directory) {
52381
52680
  if (targetAgent === "test_engineer")
52382
52681
  hasTestEngineer = true;
52383
52682
  if (targetAgent === "reviewer" && session.taskWorkflowStates) {
52384
- for (const [taskId, state2] of session.taskWorkflowStates) {
52385
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52683
+ for (const [taskId, state] of session.taskWorkflowStates) {
52684
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52386
52685
  try {
52387
52686
  advanceTaskState(session, taskId, "reviewer_run");
52388
52687
  } catch (err2) {
52389
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52688
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52390
52689
  }
52391
52690
  }
52392
52691
  }
52393
52692
  }
52394
52693
  if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
52395
- for (const [taskId, state2] of session.taskWorkflowStates) {
52396
- if (state2 === "reviewer_run") {
52694
+ for (const [taskId, state] of session.taskWorkflowStates) {
52695
+ if (state === "reviewer_run") {
52397
52696
  try {
52398
52697
  advanceTaskState(session, taskId, "tests_run");
52399
52698
  } catch (err2) {
52400
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52699
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52401
52700
  }
52402
52701
  }
52403
52702
  }
@@ -52413,12 +52712,12 @@ function createDelegationGateHook(config3, directory) {
52413
52712
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52414
52713
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
52415
52714
  }
52416
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52417
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52715
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52716
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52418
52717
  try {
52419
52718
  advanceTaskState(otherSession, taskId, "reviewer_run");
52420
52719
  } catch (err2) {
52421
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52720
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52422
52721
  }
52423
52722
  }
52424
52723
  }
@@ -52428,12 +52727,12 @@ function createDelegationGateHook(config3, directory) {
52428
52727
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52429
52728
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
52430
52729
  }
52431
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52432
- if (state2 === "reviewer_run") {
52730
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52731
+ if (state === "reviewer_run") {
52433
52732
  try {
52434
52733
  advanceTaskState(otherSession, taskId, "tests_run");
52435
52734
  } catch (err2) {
52436
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52735
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52437
52736
  }
52438
52737
  }
52439
52738
  }
@@ -52497,23 +52796,23 @@ function createDelegationGateHook(config3, directory) {
52497
52796
  session.qaSkipTaskIds = [];
52498
52797
  }
52499
52798
  if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
52500
- for (const [taskId, state2] of session.taskWorkflowStates) {
52501
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52799
+ for (const [taskId, state] of session.taskWorkflowStates) {
52800
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52502
52801
  try {
52503
52802
  advanceTaskState(session, taskId, "reviewer_run");
52504
52803
  } catch (err2) {
52505
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52804
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52506
52805
  }
52507
52806
  }
52508
52807
  }
52509
52808
  }
52510
52809
  if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
52511
- for (const [taskId, state2] of session.taskWorkflowStates) {
52512
- if (state2 === "reviewer_run") {
52810
+ for (const [taskId, state] of session.taskWorkflowStates) {
52811
+ if (state === "reviewer_run") {
52513
52812
  try {
52514
52813
  advanceTaskState(session, taskId, "tests_run");
52515
52814
  } catch (err2) {
52516
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52815
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52517
52816
  }
52518
52817
  }
52519
52818
  }
@@ -52528,12 +52827,12 @@ function createDelegationGateHook(config3, directory) {
52528
52827
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52529
52828
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
52530
52829
  }
52531
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52532
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52830
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52831
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52533
52832
  try {
52534
52833
  advanceTaskState(otherSession, taskId, "reviewer_run");
52535
52834
  } catch (err2) {
52536
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52835
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52537
52836
  }
52538
52837
  }
52539
52838
  }
@@ -52549,12 +52848,12 @@ function createDelegationGateHook(config3, directory) {
52549
52848
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52550
52849
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
52551
52850
  }
52552
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52553
- if (state2 === "reviewer_run") {
52851
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52852
+ if (state === "reviewer_run") {
52554
52853
  try {
52555
52854
  advanceTaskState(otherSession, taskId, "tests_run");
52556
52855
  } catch (err2) {
52557
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52856
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52558
52857
  }
52559
52858
  }
52560
52859
  }
@@ -55295,7 +55594,7 @@ ${errorSummary}`);
55295
55594
 
55296
55595
  // src/hooks/knowledge-reader.ts
55297
55596
  init_knowledge_store();
55298
- import { existsSync as existsSync22 } from "fs";
55597
+ import { existsSync as existsSync23 } from "fs";
55299
55598
  import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
55300
55599
  import * as path38 from "path";
55301
55600
  var JACCARD_THRESHOLD = 0.6;
@@ -55348,7 +55647,7 @@ async function recordLessonsShown(directory, lessonIds, currentPhase) {
55348
55647
  const shownFile = path38.join(directory, ".swarm", ".knowledge-shown.json");
55349
55648
  try {
55350
55649
  let shownData = {};
55351
- if (existsSync22(shownFile)) {
55650
+ if (existsSync23(shownFile)) {
55352
55651
  const content = await readFile6(shownFile, "utf-8");
55353
55652
  shownData = JSON.parse(content);
55354
55653
  }
@@ -55452,7 +55751,7 @@ async function readMergedKnowledge(directory, config3, context) {
55452
55751
  async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
55453
55752
  const shownFile = path38.join(directory, ".swarm", ".knowledge-shown.json");
55454
55753
  try {
55455
- if (!existsSync22(shownFile)) {
55754
+ if (!existsSync23(shownFile)) {
55456
55755
  return;
55457
55756
  }
55458
55757
  const content = await readFile6(shownFile, "utf-8");
@@ -55917,7 +56216,7 @@ Use this data to avoid repeating known failure patterns.`;
55917
56216
  const availableContentTokens = MAX_SUMMARY_TOKENS - prefixTokens - suffixTokens;
55918
56217
  if (availableContentTokens > 0) {
55919
56218
  const maxContentChars = Math.floor(availableContentTokens / 0.33);
55920
- summaryText = summaryText.slice(-maxContentChars);
56219
+ summaryText = summaryText.slice(0, maxContentChars);
55921
56220
  } else {
55922
56221
  summaryText = "";
55923
56222
  }
@@ -56466,6 +56765,19 @@ init_config_doctor();
56466
56765
 
56467
56766
  // src/session/snapshot-reader.ts
56468
56767
  init_utils2();
56768
+ var TRANSIENT_SESSION_FIELDS = [
56769
+ { name: "revisionLimitHit", resetValue: false },
56770
+ { name: "coderRevisions", resetValue: 0 },
56771
+ { name: "selfFixAttempted", resetValue: false },
56772
+ { name: "lastGateFailure", resetValue: null },
56773
+ { name: "architectWriteCount", resetValue: 0 },
56774
+ { name: "selfCodingWarnedAtCount", resetValue: 0 },
56775
+ { name: "pendingAdvisoryMessages", resetValue: [] },
56776
+ { name: "model_fallback_index", resetValue: 0 },
56777
+ { name: "modelFallbackExhausted", resetValue: false },
56778
+ { name: "scopeViolationDetected", resetValue: false },
56779
+ { name: "delegationActive", resetValue: false }
56780
+ ];
56469
56781
  var VALID_TASK_WORKFLOW_STATES = [
56470
56782
  "idle",
56471
56783
  "coder_delegated",
@@ -56612,17 +56924,9 @@ async function rehydrateState(snapshot) {
56612
56924
  window2.warningReason = "";
56613
56925
  }
56614
56926
  }
56615
- session.revisionLimitHit = false;
56616
- session.coderRevisions = 0;
56617
- session.selfFixAttempted = false;
56618
- session.lastGateFailure = null;
56619
- session.architectWriteCount = 0;
56620
- session.selfCodingWarnedAtCount = 0;
56621
- session.pendingAdvisoryMessages = [];
56622
- session.model_fallback_index = 0;
56623
- session.modelFallbackExhausted = false;
56624
- session.scopeViolationDetected = false;
56625
- session.delegationActive = false;
56927
+ for (const field of TRANSIENT_SESSION_FIELDS) {
56928
+ session[field.name] = field.resetValue;
56929
+ }
56626
56930
  swarmState.agentSessions.set(sessionId, session);
56627
56931
  }
56628
56932
  }
@@ -57106,7 +57410,7 @@ async function executeCompletionVerify(args2, directory) {
57106
57410
  return JSON.stringify(result2, null, 2);
57107
57411
  }
57108
57412
  let tasksChecked = 0;
57109
- let tasksSkipped = 0;
57413
+ const tasksSkipped = 0;
57110
57414
  let tasksBlocked = 0;
57111
57415
  const blockedTasks = [];
57112
57416
  for (const task of targetPhase.tasks) {
@@ -57117,17 +57421,30 @@ async function executeCompletionVerify(args2, directory) {
57117
57421
  const fileTargets = parseFilePaths(task.description, task.files_touched);
57118
57422
  const identifiers = parseIdentifiers(task.description);
57119
57423
  if (fileTargets.length === 0) {
57120
- tasksSkipped++;
57424
+ blockedTasks.push({
57425
+ task_id: task.id,
57426
+ identifier: "",
57427
+ file_path: "",
57428
+ reason: "No file targets \u2014 cannot verify completion without files_touched"
57429
+ });
57430
+ tasksBlocked++;
57121
57431
  continue;
57122
57432
  }
57123
57433
  if (identifiers.length === 0) {
57124
- tasksSkipped++;
57434
+ blockedTasks.push({
57435
+ task_id: task.id,
57436
+ identifier: "",
57437
+ file_path: "",
57438
+ reason: "No identifiers \u2014 cannot verify completion without identifiers"
57439
+ });
57440
+ tasksBlocked++;
57125
57441
  continue;
57126
57442
  }
57127
- let foundCount = 0;
57443
+ const foundIdentifiers = new Set;
57128
57444
  let hasFileReadFailure = false;
57129
57445
  for (const filePath of fileTargets) {
57130
- const resolvedPath = path43.resolve(directory, filePath);
57446
+ const normalizedPath = filePath.replace(/\\/g, "/");
57447
+ const resolvedPath = path43.resolve(directory, normalizedPath);
57131
57448
  let fileContent;
57132
57449
  try {
57133
57450
  fileContent = fs32.readFileSync(resolvedPath, "utf-8");
@@ -57143,11 +57460,11 @@ async function executeCompletionVerify(args2, directory) {
57143
57460
  }
57144
57461
  for (const identifier of identifiers) {
57145
57462
  if (fileContent.includes(identifier)) {
57146
- foundCount++;
57147
- break;
57463
+ foundIdentifiers.add(identifier);
57148
57464
  }
57149
57465
  }
57150
57466
  }
57467
+ const foundCount = foundIdentifiers.size;
57151
57468
  if (hasFileReadFailure || foundCount === 0) {
57152
57469
  if (!hasFileReadFailure) {
57153
57470
  blockedTasks.push({
@@ -57810,6 +58127,11 @@ var languageDefinitions = [
57810
58127
  id: "rust",
57811
58128
  extensions: [".rs"],
57812
58129
  commentNodes: ["line_comment", "block_comment"]
58130
+ },
58131
+ {
58132
+ id: "php",
58133
+ extensions: [".php", ".phtml"],
58134
+ commentNodes: ["comment"]
57813
58135
  }
57814
58136
  ];
57815
58137
  var extensionMap = new Map;
@@ -57873,10 +58195,16 @@ async function computeASTDiff(filePath, oldContent, newContent) {
57873
58195
  };
57874
58196
  }
57875
58197
  try {
58198
+ let timeoutId;
57876
58199
  const parser = await Promise.race([
57877
58200
  loadGrammar(language.id),
57878
- new Promise((_, reject) => setTimeout(() => reject(new Error("AST_TIMEOUT")), AST_TIMEOUT_MS))
57879
- ]);
58201
+ new Promise((_, reject) => {
58202
+ timeoutId = setTimeout(() => reject(new Error("AST_TIMEOUT")), AST_TIMEOUT_MS);
58203
+ })
58204
+ ]).finally(() => {
58205
+ if (timeoutId)
58206
+ clearTimeout(timeoutId);
58207
+ });
57880
58208
  const oldTree = parser.parse(oldContent);
57881
58209
  const newTree = parser.parse(newContent);
57882
58210
  if (!oldTree || !newTree) {
@@ -59063,7 +59391,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
59063
59391
  return files;
59064
59392
  }
59065
59393
  var imports = createSwarmTool({
59066
- description: "Find all consumers that import from a given file. Returns JSON with file path, line numbers, and import metadata for each consumer. Useful for understanding dependency relationships.",
59394
+ description: "Find all reverse dependencies (consumers) that import from a given file. Returns JSON with file path, line numbers, and import metadata for each consumer. Use this to understand who depends on a module before refactoring.",
59067
59395
  args: {
59068
59396
  file: tool.schema.string().describe('Source file path to find importers for (e.g., "./src/utils.ts")'),
59069
59397
  symbol: tool.schema.string().optional().describe('Optional specific symbol to filter imports (e.g., "MyClass")')
@@ -59218,7 +59546,9 @@ var imports = createSwarmTool({
59218
59546
  });
59219
59547
  // src/tools/knowledge-add.ts
59220
59548
  init_dist();
59549
+ init_config();
59221
59550
  init_knowledge_store();
59551
+ import { randomUUID as randomUUID4 } from "crypto";
59222
59552
  init_manager2();
59223
59553
  init_create_tool();
59224
59554
  var VALID_CATEGORIES2 = [
@@ -59293,7 +59623,7 @@ var knowledgeAdd = createSwarmTool({
59293
59623
  project_name = plan?.title ?? "";
59294
59624
  } catch {}
59295
59625
  const entry = {
59296
- id: crypto.randomUUID(),
59626
+ id: randomUUID4(),
59297
59627
  tier: "swarm",
59298
59628
  lesson,
59299
59629
  category,
@@ -59314,6 +59644,22 @@ var knowledgeAdd = createSwarmTool({
59314
59644
  auto_generated: true,
59315
59645
  hive_eligible: false
59316
59646
  };
59647
+ try {
59648
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
59649
+ if (config3.knowledge?.validation_enabled !== false) {
59650
+ const validation = validateLesson(lesson, [], {
59651
+ category,
59652
+ scope,
59653
+ confidence: 0.5
59654
+ });
59655
+ if (!validation.valid) {
59656
+ return JSON.stringify({
59657
+ success: false,
59658
+ error: `Validation failed: ${validation.reason}`
59659
+ });
59660
+ }
59661
+ }
59662
+ } catch {}
59317
59663
  try {
59318
59664
  await appendKnowledge(resolveSwarmKnowledgePath(directory), entry);
59319
59665
  } catch (err2) {
@@ -59332,9 +59678,10 @@ var knowledgeAdd = createSwarmTool({
59332
59678
  });
59333
59679
  // src/tools/knowledge-query.ts
59334
59680
  init_dist();
59681
+ init_config();
59335
59682
  init_knowledge_store();
59336
59683
  init_create_tool();
59337
- import { existsSync as existsSync30 } from "fs";
59684
+ import { existsSync as existsSync31 } from "fs";
59338
59685
  var DEFAULT_LIMIT = 10;
59339
59686
  var MAX_LESSON_LENGTH = 200;
59340
59687
  var VALID_CATEGORIES3 = [
@@ -59403,19 +59750,19 @@ function validateLimit(limit) {
59403
59750
  }
59404
59751
  async function readSwarmKnowledge(directory) {
59405
59752
  const swarmPath = resolveSwarmKnowledgePath(directory);
59406
- if (!existsSync30(swarmPath)) {
59753
+ if (!existsSync31(swarmPath)) {
59407
59754
  return [];
59408
59755
  }
59409
59756
  return readKnowledge(swarmPath);
59410
59757
  }
59411
59758
  async function readHiveKnowledge() {
59412
59759
  const hivePath = resolveHiveKnowledgePath();
59413
- if (!existsSync30(hivePath)) {
59760
+ if (!existsSync31(hivePath)) {
59414
59761
  return [];
59415
59762
  }
59416
59763
  return readKnowledge(hivePath);
59417
59764
  }
59418
- function filterSwarmEntries(entries, filters) {
59765
+ function filterSwarmEntries(entries, filters, scopeFilter) {
59419
59766
  return entries.filter((entry) => {
59420
59767
  if (filters.status && entry.status !== filters.status) {
59421
59768
  return false;
@@ -59426,6 +59773,12 @@ function filterSwarmEntries(entries, filters) {
59426
59773
  if (filters.minScore !== undefined && entry.confidence < filters.minScore) {
59427
59774
  return false;
59428
59775
  }
59776
+ if (scopeFilter && scopeFilter.length > 0) {
59777
+ const entryScope = entry.scope ?? "global";
59778
+ if (!scopeFilter.some((pattern) => entryScope === pattern)) {
59779
+ return false;
59780
+ }
59781
+ }
59429
59782
  return true;
59430
59783
  });
59431
59784
  }
@@ -59510,9 +59863,14 @@ var knowledge_query = createSwarmTool({
59510
59863
  minScore: minScore ?? undefined
59511
59864
  };
59512
59865
  const results = [];
59866
+ let scopeFilter;
59867
+ try {
59868
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
59869
+ scopeFilter = config3.knowledge?.scope_filter;
59870
+ } catch {}
59513
59871
  if (tier === "swarm" || tier === "all") {
59514
59872
  const swarmEntries = await readSwarmKnowledge(directory);
59515
- const filtered = filterSwarmEntries(swarmEntries, filters);
59873
+ const filtered = filterSwarmEntries(swarmEntries, filters, scopeFilter);
59516
59874
  for (const entry of filtered) {
59517
59875
  results.push({ entry, tier: "swarm" });
59518
59876
  }
@@ -60042,7 +60400,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60042
60400
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
60043
60401
  try {
60044
60402
  const projectName = path49.basename(dir);
60045
- await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
60403
+ const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
60404
+ if (curationResult) {
60405
+ const sessionState = swarmState.agentSessions.get(sessionID);
60406
+ if (sessionState) {
60407
+ sessionState.pendingAdvisoryMessages ??= [];
60408
+ sessionState.pendingAdvisoryMessages.push(`[CURATOR] Knowledge curation: ${curationResult.stored} stored, ${curationResult.skipped} skipped, ${curationResult.rejected} rejected.`);
60409
+ }
60410
+ }
60046
60411
  } catch (error93) {
60047
60412
  safeWarn("[phase_complete] Failed to curate lessons from retrospective:", error93);
60048
60413
  }
@@ -60076,7 +60441,17 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60076
60441
  } catch (curatorError) {
60077
60442
  safeWarn("[phase_complete] Curator pipeline error (non-blocking):", curatorError);
60078
60443
  }
60079
- const effectiveRequired = [...phaseCompleteConfig.required_agents];
60444
+ let phaseRequiredAgents;
60445
+ try {
60446
+ const planPath = validateSwarmPath(dir, "plan.json");
60447
+ const planRaw = fs38.readFileSync(planPath, "utf-8");
60448
+ const plan = JSON.parse(planRaw);
60449
+ const phaseObj = plan.phases.find((p) => p.id === phase);
60450
+ phaseRequiredAgents = phaseObj?.required_agents;
60451
+ } catch {}
60452
+ const effectiveRequired = [
60453
+ ...phaseRequiredAgents ?? phaseCompleteConfig.required_agents
60454
+ ];
60080
60455
  if (phaseCompleteConfig.require_docs && !effectiveRequired.includes("docs")) {
60081
60456
  effectiveRequired.push("docs");
60082
60457
  }
@@ -62541,7 +62916,7 @@ var javascriptRules = [
62541
62916
  languages: ["javascript", "typescript"],
62542
62917
  description: "Potential command injection via child_process.exec() with unsanitized input",
62543
62918
  remediation: "Never pass user input directly to exec(). Use execFile() with arguments array or sanitize input thoroughly.",
62544
- pattern: /exec\s*\(\s*[`'"]/,
62919
+ pattern: /exec\s*\(\s*(?:[`'"]|\w)/,
62545
62920
  validate: (_match, _context) => {
62546
62921
  return true;
62547
62922
  }
@@ -62864,9 +63239,7 @@ function executeRulesSync(filePath, content, language) {
62864
63239
  }
62865
63240
 
62866
63241
  // src/sast/semgrep.ts
62867
- import { execFile as execFile2, execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
62868
- import { promisify as promisify2 } from "util";
62869
- var _execFileAsync = promisify2(execFile2);
63242
+ import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
62870
63243
  var semgrepAvailableCache = null;
62871
63244
  var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
62872
63245
  var DEFAULT_TIMEOUT_MS3 = 30000;
@@ -62891,7 +63264,12 @@ function parseSemgrepResults(semgrepOutput) {
62891
63264
  try {
62892
63265
  const parsed = JSON.parse(semgrepOutput);
62893
63266
  const results = parsed.results || parsed;
63267
+ if (!Array.isArray(results)) {
63268
+ return [];
63269
+ }
62894
63270
  for (const result of results) {
63271
+ if (!result || typeof result !== "object")
63272
+ continue;
62895
63273
  const severity = mapSemgrepSeverity(result.extra?.severity || result.severity);
62896
63274
  findings.push({
62897
63275
  rule_id: result.check_id || result.rule_id || "unknown",
@@ -64041,10 +64419,11 @@ import * as path54 from "path";
64041
64419
  var LOCKS_DIR = ".swarm/locks";
64042
64420
  var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
64043
64421
  function getLockFilePath(directory, filePath) {
64044
- if (filePath.includes("..")) {
64422
+ const normalized = path54.resolve(directory, filePath);
64423
+ if (!normalized.startsWith(path54.resolve(directory))) {
64045
64424
  throw new Error("Invalid file path: path traversal not allowed");
64046
64425
  }
64047
- const hash3 = Buffer.from(filePath).toString("base64").replace(/[/+=]/g, "_");
64426
+ const hash3 = Buffer.from(normalized).toString("base64").replace(/[/+=]/g, "_");
64048
64427
  return path54.join(directory, LOCKS_DIR, `${hash3}.lock`);
64049
64428
  }
64050
64429
  function tryAcquireLock(directory, filePath, agent, taskId) {
@@ -64224,12 +64603,24 @@ async function executeSavePlan(args2, fallbackDir) {
64224
64603
  });
64225
64604
  await fs44.promises.writeFile(markerPath, marker, "utf8");
64226
64605
  } catch {}
64606
+ const warnings = [];
64607
+ let criticReviewFound = false;
64608
+ for (const [, session] of swarmState.agentSessions) {
64609
+ if (session.phaseAgentsDispatched?.has("critic") || session.lastCompletedPhaseAgentsDispatched?.has("critic")) {
64610
+ criticReviewFound = true;
64611
+ break;
64612
+ }
64613
+ }
64614
+ if (!criticReviewFound) {
64615
+ warnings.push("No critic review detected before plan save. Consider delegating to critic for plan validation.");
64616
+ }
64227
64617
  return {
64228
64618
  success: true,
64229
64619
  message: "Plan saved successfully",
64230
64620
  plan_path: path55.join(dir, ".swarm", "plan.json"),
64231
64621
  phases_count: plan.phases.length,
64232
- tasks_count: tasksCount
64622
+ tasks_count: tasksCount,
64623
+ ...warnings.length > 0 ? { warnings } : {}
64233
64624
  };
64234
64625
  } finally {
64235
64626
  releaseLock(dir, planFilePath, lockTaskId);
@@ -64699,7 +65090,7 @@ function parseYarnLock(content) {
64699
65090
  const components = [];
64700
65091
  const blocks = content.split(/^(?=@?[\w-]+@)/m);
64701
65092
  for (const block of blocks) {
64702
- const pkgMatch = block.match(/^(@?[\w-]+)@([\d.]+)/m);
65093
+ const pkgMatch = block.match(/^(@?[\w-]+)@(\d+\.\d+\.\d+(?:-[\w.]+)?)/m);
64703
65094
  if (!pkgMatch)
64704
65095
  continue;
64705
65096
  const name2 = pkgMatch[1];
@@ -64963,8 +65354,8 @@ function parsePackageResolved(content) {
64963
65354
  const pins = resolved.pins || [];
64964
65355
  for (const pin of pins) {
64965
65356
  const identity = pin.identity || pin.package || "";
64966
- const state2 = pin.state || {};
64967
- const version3 = state2.version || state2.revision || "";
65357
+ const state = pin.state || {};
65358
+ const version3 = state.version || state.revision || "";
64968
65359
  let org = "";
64969
65360
  const location = pin.location || "";
64970
65361
  const orgMatch = location.match(/github\.com\/([^/]+)\//);
@@ -65050,7 +65441,9 @@ function detectComponents(filePath, content) {
65050
65441
  if (components.length > 0) {
65051
65442
  return components;
65052
65443
  }
65053
- } catch {}
65444
+ } catch (error93) {
65445
+ console.warn(`[sbom] Detector failed for ${filePath}:`, error93 instanceof Error ? error93.message : String(error93));
65446
+ }
65054
65447
  }
65055
65448
  return [];
65056
65449
  }
@@ -66439,8 +66832,8 @@ function checkReviewerGate(taskId, workingDirectory) {
66439
66832
  continue;
66440
66833
  }
66441
66834
  validSessionCount++;
66442
- const state2 = getTaskState(session, taskId);
66443
- if (state2 === "tests_run" || state2 === "complete") {
66835
+ const state = getTaskState(session, taskId);
66836
+ if (state === "tests_run" || state === "complete") {
66444
66837
  return { blocked: false, reason: "" };
66445
66838
  }
66446
66839
  }
@@ -66451,8 +66844,8 @@ function checkReviewerGate(taskId, workingDirectory) {
66451
66844
  for (const [sessionId, session] of swarmState.agentSessions) {
66452
66845
  if (!(session.taskWorkflowStates instanceof Map))
66453
66846
  continue;
66454
- const state2 = getTaskState(session, taskId);
66455
- stateEntries.push(`${sessionId}: ${state2}`);
66847
+ const state = getTaskState(session, taskId);
66848
+ stateEntries.push(`${sessionId}: ${state}`);
66456
66849
  }
66457
66850
  try {
66458
66851
  const resolvedDir2 = workingDirectory;
@@ -66675,19 +67068,35 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
66675
67068
  }
66676
67069
  } else {
66677
67070
  if (!fallbackDir) {
66678
- console.warn("[update-task-status] fallbackDir is undefined");
67071
+ return {
67072
+ success: false,
67073
+ message: "No working_directory provided and fallbackDir is undefined",
67074
+ errors: ["Cannot resolve directory for task status update"]
67075
+ };
66679
67076
  }
66680
67077
  directory = fallbackDir;
66681
67078
  }
66682
67079
  if (args2.status === "completed") {
66683
67080
  recoverTaskStateFromDelegations(args2.task_id);
66684
- const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
66685
- if (reviewerCheck.blocked) {
66686
- return {
66687
- success: false,
66688
- message: "Gate check failed: reviewer delegation required before marking task as completed",
66689
- errors: [reviewerCheck.reason]
66690
- };
67081
+ let phaseRequiresReviewer = true;
67082
+ try {
67083
+ const planPath = path61.join(directory, ".swarm", "plan.json");
67084
+ const planRaw = fs50.readFileSync(planPath, "utf-8");
67085
+ const plan = JSON.parse(planRaw);
67086
+ const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
67087
+ if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
67088
+ phaseRequiresReviewer = false;
67089
+ }
67090
+ } catch {}
67091
+ if (phaseRequiresReviewer) {
67092
+ const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
67093
+ if (reviewerCheck.blocked) {
67094
+ return {
67095
+ success: false,
67096
+ message: "Gate check failed: reviewer delegation required before marking task as completed",
67097
+ errors: [reviewerCheck.reason]
67098
+ };
67099
+ }
66691
67100
  }
66692
67101
  }
66693
67102
  try {