opencode-swarm 7.47.0 → 7.48.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
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.47.0",
72
+ version: "7.48.0",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -616,6 +616,7 @@ var init_constants = __esm(() => {
616
616
  "syntax_check",
617
617
  "search",
618
618
  "summarize_work",
619
+ "knowledge_recall",
619
620
  "swarm_command"
620
621
  ],
621
622
  sme: [
@@ -81429,17 +81430,13 @@ var init_tool_policy = __esm(() => {
81429
81430
  "agents",
81430
81431
  "config",
81431
81432
  "config doctor",
81432
- "config-doctor",
81433
- "doctor",
81434
81433
  "doctor tools",
81435
81434
  "status",
81436
81435
  "show-plan",
81437
- "plan",
81438
81436
  "help",
81439
81437
  "history",
81440
81438
  "evidence",
81441
81439
  "evidence summary",
81442
- "evidence-summary",
81443
81440
  "retrieve",
81444
81441
  "diagnose",
81445
81442
  "preflight",
@@ -81456,8 +81453,7 @@ var init_tool_policy = __esm(() => {
81456
81453
  "memory import",
81457
81454
  "memory migrate",
81458
81455
  "sync-plan",
81459
- "export",
81460
- "list-agents"
81456
+ "export"
81461
81457
  ];
81462
81458
  SWARM_COMMAND_TOOL_ALLOWLIST = new Set([
81463
81459
  "agents",
@@ -83441,7 +83437,7 @@ the safe \`spec_write\` tool. Use it when:
83441
83437
  - requirements decomposition is non-trivial,
83442
83438
  - you would otherwise inline-author \`.swarm/spec.md\` yourself.
83443
83439
 
83444
- Continue handling small touch-ups (typos, cross-references) inline.
83440
+ Continue handling small touch-ups (typos, cross-references) via the spec_writer agent — the architect lacks the spec_write tool and must delegate all spec changes.
83445
83441
 
83446
83442
  ### ANTI-RATIONALIZATION
83447
83443
  - ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
@@ -83745,6 +83741,24 @@ HARD CONSTRAINTS (apply regardless of skill load success):
83745
83741
  - Do NOT touch .swarm/spec.md, CHANGELOG.md, or docs/releases/pending/* in this mode.
83746
83742
  - Requires design_docs.enabled: true — if the docs_design agent is not registered, instruct the user to enable it and stop.
83747
83743
 
83744
+ ### MODE: PR_REVIEW
83745
+ Activates when: architect receives \`[MODE: PR_REVIEW pr="https://github.com/..." council=true/false]\` signal from the pr-review command handler.
83746
+
83747
+ Purpose: Read-only structured PR review using parallel explorer lanes, independent reviewer validation, critic challenge, and synthesis. Does NOT mutate source code. Does NOT delegate to coder.
83748
+
83749
+ ACTION: Load skill file:.opencode/skills/swarm-pr-review/SKILL.md immediately and follow its protocol.
83750
+
83751
+ HARD CONSTRAINTS (apply regardless of skill load success):
83752
+ - Do NOT delegate to coder
83753
+ - Do NOT call declare_scope
83754
+ - Do NOT mutate source code
83755
+ - Do NOT create or modify files outside .swarm/
83756
+ - The orchestrator MUST NOT classify, confirm, disprove, or judge explorer candidates — validation is exclusively the reviewer's job
83757
+ - Explorers produce candidates only — reviewers verify or reject — critics challenge HIGH/CRITICAL and borderline findings
83758
+ - No finding may appear as CONFIRMED in the final report without reviewer validation provenance
83759
+ - Test execution, explorer lanes, reviewer dispatch, and critic challenge are all permitted within this mode
83760
+ - Quality is the only metric — time, tokens, and agent dispatches are irrelevant to correctness
83761
+
83748
83762
  ### MODE: ISSUE_INGEST
83749
83763
  Activates when the user invokes /swarm issue <url> or the architect receives an ISSUE_INGEST signal.
83750
83764
 
@@ -92278,11 +92292,11 @@ __export(exports_design_doc_drift, {
92278
92292
  runDesignDocDriftCheck: () => runDesignDocDriftCheck,
92279
92293
  _internals: () => _internals55
92280
92294
  });
92281
- import * as fs89 from "node:fs";
92282
- import * as path126 from "node:path";
92295
+ import * as fs94 from "node:fs";
92296
+ import * as path131 from "node:path";
92283
92297
  function mtimeMsOrNull(absPath) {
92284
92298
  try {
92285
- return fs89.statSync(absPath).mtimeMs;
92299
+ return fs94.statSync(absPath).mtimeMs;
92286
92300
  } catch {
92287
92301
  return null;
92288
92302
  }
@@ -92290,40 +92304,40 @@ function mtimeMsOrNull(absPath) {
92290
92304
  function resolveAnchorWithin(directory, anchor) {
92291
92305
  if (!anchor || typeof anchor !== "string")
92292
92306
  return null;
92293
- const root = path126.resolve(directory);
92294
- const resolved = path126.resolve(root, anchor);
92295
- const rel = path126.relative(root, resolved);
92296
- if (rel.startsWith("..") || path126.isAbsolute(rel))
92307
+ const root = path131.resolve(directory);
92308
+ const resolved = path131.resolve(root, anchor);
92309
+ const rel = path131.relative(root, resolved);
92310
+ if (rel.startsWith("..") || path131.isAbsolute(rel))
92297
92311
  return null;
92298
92312
  return resolved;
92299
92313
  }
92300
92314
  async function runDesignDocDriftCheck(directory, phase, outDir) {
92301
92315
  try {
92302
- const root = path126.resolve(directory);
92303
- const outAbs = path126.resolve(root, outDir);
92304
- const outRel = path126.relative(root, outAbs);
92305
- if (outRel.startsWith("..") || path126.isAbsolute(outRel)) {
92316
+ const root = path131.resolve(directory);
92317
+ const outAbs = path131.resolve(root, outDir);
92318
+ const outRel = path131.relative(root, outAbs);
92319
+ if (outRel.startsWith("..") || path131.isAbsolute(outRel)) {
92306
92320
  return null;
92307
92321
  }
92308
92322
  const docMtimes = new Map;
92309
92323
  const checkedDocs = [];
92310
92324
  const missingDocs = [];
92311
92325
  for (const [docName, relFile] of Object.entries(DESIGN_DOC_FILES)) {
92312
- const abs = path126.join(outAbs, relFile);
92326
+ const abs = path131.join(outAbs, relFile);
92313
92327
  const mtime = mtimeMsOrNull(abs);
92314
92328
  docMtimes.set(docName, mtime);
92315
92329
  if (mtime === null) {
92316
- missingDocs.push(path126.join(outDir, relFile));
92330
+ missingDocs.push(path131.join(outDir, relFile));
92317
92331
  } else {
92318
- checkedDocs.push(path126.join(outDir, relFile));
92332
+ checkedDocs.push(path131.join(outDir, relFile));
92319
92333
  }
92320
92334
  }
92321
- const traceabilityAbs = path126.join(outAbs, TRACEABILITY_REL);
92335
+ const traceabilityAbs = path131.join(outAbs, TRACEABILITY_REL);
92322
92336
  let registry3 = null;
92323
92337
  try {
92324
- const stat9 = await fs89.promises.stat(traceabilityAbs);
92338
+ const stat9 = await fs94.promises.stat(traceabilityAbs);
92325
92339
  if (stat9.size <= MAX_TRACEABILITY_BYTES) {
92326
- const raw = await fs89.promises.readFile(traceabilityAbs, "utf-8");
92340
+ const raw = await fs94.promises.readFile(traceabilityAbs, "utf-8");
92327
92341
  const parsed = JSON.parse(raw);
92328
92342
  registry3 = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
92329
92343
  }
@@ -92331,7 +92345,7 @@ async function runDesignDocDriftCheck(directory, phase, outDir) {
92331
92345
  registry3 = null;
92332
92346
  }
92333
92347
  const noDocs = checkedDocs.length === 0 || registry3 === null;
92334
- const specMtime = mtimeMsOrNull(path126.join(root, ".swarm", "spec.md"));
92348
+ const specMtime = mtimeMsOrNull(path131.join(root, ".swarm", "spec.md"));
92335
92349
  const staleSections = [];
92336
92350
  if (!noDocs && Array.isArray(registry3?.sections)) {
92337
92351
  for (const section of registry3.sections) {
@@ -92387,8 +92401,8 @@ async function runDesignDocDriftCheck(directory, phase, outDir) {
92387
92401
  };
92388
92402
  const filename = `${DOC_DRIFT_REPORT_PREFIX}${phase}.json`;
92389
92403
  const filePath = validateSwarmPath(directory, filename);
92390
- await fs89.promises.mkdir(path126.dirname(filePath), { recursive: true });
92391
- await fs89.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
92404
+ await fs94.promises.mkdir(path131.dirname(filePath), { recursive: true });
92405
+ await fs94.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
92392
92406
  getGlobalEventBus().publish("curator.docdrift.completed", {
92393
92407
  phase,
92394
92408
  verdict,
@@ -92418,10 +92432,10 @@ var init_design_doc_drift = __esm(() => {
92418
92432
  domain: "domain.md",
92419
92433
  "technical-spec": "technical-spec.md",
92420
92434
  "behavior-spec": "behavior-spec.md",
92421
- "reference-impl": path126.join("reference", "reference-impl.md"),
92422
- "idiom-notes": path126.join("reference", "idiom-notes.md")
92435
+ "reference-impl": path131.join("reference", "reference-impl.md"),
92436
+ "idiom-notes": path131.join("reference", "idiom-notes.md")
92423
92437
  };
92424
- TRACEABILITY_REL = path126.join("reference", "traceability.json");
92438
+ TRACEABILITY_REL = path131.join("reference", "traceability.json");
92425
92439
  _internals55 = {
92426
92440
  mtimeMsOrNull,
92427
92441
  resolveAnchorWithin,
@@ -92436,12 +92450,12 @@ __export(exports_project_context, {
92436
92450
  _internals: () => _internals71,
92437
92451
  LANG_BACKEND_DETECTION_TIMEOUT_MS: () => LANG_BACKEND_DETECTION_TIMEOUT_MS
92438
92452
  });
92439
- import * as fs118 from "node:fs";
92440
- import * as path161 from "node:path";
92453
+ import * as fs123 from "node:fs";
92454
+ import * as path166 from "node:path";
92441
92455
  function detectFileExists2(directory, pattern) {
92442
92456
  if (pattern.includes("*") || pattern.includes("?")) {
92443
92457
  try {
92444
- const files = fs118.readdirSync(directory);
92458
+ const files = fs123.readdirSync(directory);
92445
92459
  const regex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
92446
92460
  return files.some((f) => regex.test(f));
92447
92461
  } catch {
@@ -92449,7 +92463,7 @@ function detectFileExists2(directory, pattern) {
92449
92463
  }
92450
92464
  }
92451
92465
  try {
92452
- fs118.accessSync(path161.join(directory, pattern));
92466
+ fs123.accessSync(path166.join(directory, pattern));
92453
92467
  return true;
92454
92468
  } catch {
92455
92469
  return false;
@@ -92458,7 +92472,7 @@ function detectFileExists2(directory, pattern) {
92458
92472
  function selectTestCommandFromScriptsTest(backend, directory) {
92459
92473
  let pkgRaw;
92460
92474
  try {
92461
- pkgRaw = fs118.readFileSync(path161.join(directory, "package.json"), "utf-8");
92475
+ pkgRaw = fs123.readFileSync(path166.join(directory, "package.json"), "utf-8");
92462
92476
  } catch {
92463
92477
  return null;
92464
92478
  }
@@ -92567,7 +92581,7 @@ var init_project_context = __esm(() => {
92567
92581
  init_package();
92568
92582
  init_agents2();
92569
92583
  init_critic();
92570
- import * as path162 from "node:path";
92584
+ import * as path167 from "node:path";
92571
92585
 
92572
92586
  // src/background/index.ts
92573
92587
  init_event_bus();
@@ -111461,10 +111475,9 @@ init_lint();
111461
111475
  init_zod();
111462
111476
  init_config();
111463
111477
  init_schema();
111464
- init_qa_gate_profile();
111465
111478
  init_manager2();
111466
- import * as fs90 from "node:fs";
111467
- import * as path127 from "node:path";
111479
+ import * as fs95 from "node:fs";
111480
+ import * as path132 from "node:path";
111468
111481
 
111469
111482
  // src/full-auto/phase-approval.ts
111470
111483
  init_utils2();
@@ -111643,7 +111656,6 @@ init_ledger();
111643
111656
  init_manager();
111644
111657
  init_snapshot_writer();
111645
111658
  init_state();
111646
- init_store();
111647
111659
  init_telemetry();
111648
111660
 
111649
111661
  // src/turbo/lean/phase-ready.ts
@@ -112137,12 +112149,765 @@ function verifyLeanTurboPhaseReady(directory, phase, sessionIDOrConfig, config3)
112137
112149
  // src/tools/phase-complete.ts
112138
112150
  init_logger();
112139
112151
  init_create_tool();
112152
+
112153
+ // src/tools/phase-complete/gates/architecture-supervisor-gate.ts
112154
+ init_store();
112155
+ async function runArchitectureSupervisorGate(ctx) {
112156
+ const { phase, dir, pluginConfig, agentsDispatched, safeWarn } = ctx;
112157
+ const asConfig = pluginConfig.architectural_supervision;
112158
+ const summarizeFindings = (findings) => {
112159
+ if (!Array.isArray(findings) || findings.length === 0)
112160
+ return "";
112161
+ const details = findings.map((f) => f && typeof f === "object" && typeof f.description === "string" ? f.description : undefined).filter((d) => Boolean(d));
112162
+ return details.length > 0 ? `
112163
+ Findings: ${details.join("; ")}` : "";
112164
+ };
112165
+ const asBlocked = (reason, message) => ({
112166
+ blocked: true,
112167
+ reason,
112168
+ message,
112169
+ agentsDispatched,
112170
+ agentsMissing: [],
112171
+ warnings: []
112172
+ });
112173
+ let asEntry = null;
112174
+ try {
112175
+ asEntry = readSupervisorReportRaw(dir, phase);
112176
+ } catch (asError) {
112177
+ return asBlocked("ARCH_SUPERVISOR_ERROR", `Phase ${phase} cannot be completed: architecture supervisor gate encountered an error. Error: ${String(asError)}`);
112178
+ }
112179
+ if (!asEntry) {
112180
+ return asBlocked("ARCH_SUPERVISOR_REQUIRED", `Phase ${phase} cannot be completed: architectural_supervision gate mode is enabled and no architecture supervisor evidence was found at .swarm/evidence/${phase}/architecture-supervisor.json. Dispatch critic_architecture_supervisor with the phase + agent summaries, then call write_architecture_supervisor_evidence.`);
112181
+ }
112182
+ const now = new Date;
112183
+ const asTime = asEntry.timestamp ? new Date(asEntry.timestamp) : null;
112184
+ if (!asTime || Number.isNaN(asTime.getTime())) {
112185
+ return asBlocked("ARCH_SUPERVISOR_INVALID_TIMESTAMP", `Phase ${phase} cannot be completed: architecture supervisor evidence has a missing or invalid timestamp.`);
112186
+ }
112187
+ if (asTime.getTime() > now.getTime()) {
112188
+ return asBlocked("ARCH_SUPERVISOR_FUTURE_TIMESTAMP", `Phase ${phase} cannot be completed: architecture supervisor evidence timestamp is in the future.`);
112189
+ }
112190
+ if (now.getTime() - asTime.getTime() > 24 * 60 * 60 * 1000) {
112191
+ return asBlocked("ARCH_SUPERVISOR_STALE_EVIDENCE", `Phase ${phase} cannot be completed: architecture supervisor evidence is older than 24 hours. Re-run the supervisor for fresh review.`);
112192
+ }
112193
+ if (typeof asEntry.phase_number !== "number" || asEntry.phase_number !== phase) {
112194
+ return asBlocked("ARCH_SUPERVISOR_PHASE_MISMATCH", `Phase ${phase} cannot be completed: architecture supervisor evidence is for phase ${String(asEntry.phase_number)}, not phase ${phase}.`);
112195
+ }
112196
+ const asVerdict = asEntry.verdict;
112197
+ if (asVerdict === "REJECT") {
112198
+ return asBlocked("ARCH_SUPERVISOR_REJECTED", `Phase ${phase} cannot be completed: architecture supervisor returned verdict 'REJECT'. Address the system-level findings before completing the phase.${summarizeFindings(asEntry.findings)}`);
112199
+ }
112200
+ if (asVerdict === "CONCERNS") {
112201
+ if (asConfig?.allow_concerns_to_complete === false) {
112202
+ return asBlocked("ARCH_SUPERVISOR_CONCERNS", `Phase ${phase} cannot be completed: architecture supervisor returned verdict 'CONCERNS' and allow_concerns_to_complete is disabled.${summarizeFindings(asEntry.findings)}`);
112203
+ }
112204
+ safeWarn(`[phase_complete] Architecture supervisor returned CONCERNS for phase ${phase} — proceeding (allow_concerns_to_complete is enabled)`, undefined);
112205
+ } else if (asVerdict !== "APPROVE") {
112206
+ return asBlocked("ARCH_SUPERVISOR_INVALID", `Phase ${phase} cannot be completed: architecture supervisor evidence contains unrecognized verdict '${String(asVerdict)}'. Expected one of: APPROVE, CONCERNS, REJECT.`);
112207
+ }
112208
+ return { blocked: false, agentsDispatched, agentsMissing: [], warnings: [] };
112209
+ }
112210
+ // src/tools/phase-complete/gates/completion-verify-gate.ts
112211
+ async function runCompletionVerifyGate(ctx) {
112212
+ const { phase, dir, agentsDispatched, safeWarn } = ctx;
112213
+ try {
112214
+ const completionResultRaw = await executeCompletionVerify({ phase }, dir);
112215
+ const completionResult = JSON.parse(completionResultRaw);
112216
+ if (completionResult.status === "blocked") {
112217
+ return {
112218
+ blocked: true,
112219
+ reason: "COMPLETION_INCOMPLETE",
112220
+ message: `Phase ${phase} cannot be completed: ${completionResult.reason}`,
112221
+ agentsDispatched,
112222
+ agentsMissing: [],
112223
+ warnings: completionResult.blockedTasks ? [
112224
+ `Blocked tasks: ${completionResult.blockedTasks.map((t) => t.task_id).join(", ")}`
112225
+ ] : []
112226
+ };
112227
+ }
112228
+ return {
112229
+ blocked: false,
112230
+ agentsDispatched,
112231
+ agentsMissing: [],
112232
+ warnings: []
112233
+ };
112234
+ } catch (completionError) {
112235
+ safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
112236
+ return {
112237
+ blocked: false,
112238
+ agentsDispatched,
112239
+ agentsMissing: [],
112240
+ warnings: []
112241
+ };
112242
+ }
112243
+ }
112244
+ // src/tools/phase-complete/gates/drift-gate.ts
112245
+ init_qa_gate_profile();
112246
+ init_manager();
112247
+ init_state();
112248
+ import * as fs89 from "node:fs";
112249
+ import * as path126 from "node:path";
112250
+ async function runDriftGate(ctx) {
112251
+ const { phase, dir, sessionID, agentsDispatched, safeWarn } = ctx;
112252
+ let driftCheckEnabled = true;
112253
+ let driftHasSpecMd = false;
112254
+ try {
112255
+ const specMdPath = path126.join(dir, ".swarm", "spec.md");
112256
+ driftHasSpecMd = fs89.existsSync(specMdPath);
112257
+ const gatePlan = await loadPlan(dir);
112258
+ if (gatePlan) {
112259
+ const gatePlanId = derivePlanId(gatePlan);
112260
+ const gateProfile = getProfile(dir, gatePlanId);
112261
+ if (gateProfile) {
112262
+ const gateSession = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112263
+ const gateOverrides = gateSession?.qaGateSessionOverrides ?? {};
112264
+ const gateEffective = getEffectiveGates(gateProfile, gateOverrides);
112265
+ driftCheckEnabled = gateEffective.drift_check === true;
112266
+ }
112267
+ }
112268
+ } catch (gateLoadError) {
112269
+ safeWarn(`[phase_complete] QA gate profile load error, drift_check defaults to enabled:`, gateLoadError);
112270
+ }
112271
+ if (!driftCheckEnabled) {
112272
+ return {
112273
+ blocked: false,
112274
+ agentsDispatched,
112275
+ agentsMissing: [],
112276
+ warnings: [
112277
+ `drift_check gate is disabled. Drift verification was skipped for phase ${phase}.`
112278
+ ]
112279
+ };
112280
+ }
112281
+ let phaseType;
112282
+ try {
112283
+ const planPath = path126.join(dir, ".swarm", "plan.json");
112284
+ if (fs89.existsSync(planPath)) {
112285
+ const planRaw = fs89.readFileSync(planPath, "utf-8");
112286
+ const plan = JSON.parse(planRaw);
112287
+ const targetPhase = plan.phases?.find((p) => p.id === phase);
112288
+ phaseType = targetPhase?.type;
112289
+ }
112290
+ } catch {}
112291
+ if (phaseType === "non-code") {
112292
+ return {
112293
+ blocked: false,
112294
+ agentsDispatched,
112295
+ agentsMissing: [],
112296
+ warnings: [
112297
+ `Phase ${phase} is annotated as 'non-code'. Drift verification was skipped per phase type annotation.`
112298
+ ]
112299
+ };
112300
+ }
112301
+ try {
112302
+ const driftEvidencePath = path126.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
112303
+ let driftVerdictFound = false;
112304
+ let driftVerdictApproved = false;
112305
+ try {
112306
+ const driftEvidenceContent = fs89.readFileSync(driftEvidencePath, "utf-8");
112307
+ const driftEvidence = JSON.parse(driftEvidenceContent);
112308
+ const entries = driftEvidence.entries ?? [];
112309
+ for (const entry of entries) {
112310
+ if (typeof entry.type === "string" && entry.type.includes("drift") && typeof entry.verdict === "string") {
112311
+ driftVerdictFound = true;
112312
+ if (entry.verdict === "approved") {
112313
+ driftVerdictApproved = true;
112314
+ }
112315
+ if (entry.verdict === "rejected" || typeof entry.summary === "string" && entry.summary.includes("NEEDS_REVISION")) {
112316
+ return {
112317
+ blocked: true,
112318
+ reason: "DRIFT_VERIFICATION_REJECTED",
112319
+ message: `Phase ${phase} cannot be completed: drift verifier returned verdict '${entry.verdict}'. Address the drift issues before completing the phase.`,
112320
+ agentsDispatched,
112321
+ agentsMissing: [],
112322
+ warnings: []
112323
+ };
112324
+ }
112325
+ }
112326
+ }
112327
+ } catch (readError) {
112328
+ if (readError.code !== "ENOENT") {
112329
+ safeWarn(`[phase_complete] Drift verifier evidence unreadable:`, readError);
112330
+ }
112331
+ driftVerdictFound = false;
112332
+ }
112333
+ if (!driftVerdictFound) {
112334
+ if (!driftHasSpecMd) {
112335
+ let incompleteTaskCount = 0;
112336
+ let planParseable = false;
112337
+ try {
112338
+ const planPath = path126.join(dir, ".swarm", "plan.json");
112339
+ if (fs89.existsSync(planPath)) {
112340
+ const planRaw = fs89.readFileSync(planPath, "utf-8");
112341
+ const plan = JSON.parse(planRaw);
112342
+ planParseable = true;
112343
+ const planPhase = plan.phases?.find((p) => p.id === phase);
112344
+ if (planPhase?.tasks) {
112345
+ incompleteTaskCount = planPhase.tasks.filter((t) => t.status !== "completed" && t.status !== "closed").length;
112346
+ }
112347
+ }
112348
+ } catch {}
112349
+ if (!planParseable) {
112350
+ return {
112351
+ blocked: false,
112352
+ agentsDispatched,
112353
+ agentsMissing: [],
112354
+ warnings: [
112355
+ `No spec.md found and drift verification evidence missing — consider running critic_drift_verifier before phase completion.`
112356
+ ]
112357
+ };
112358
+ } else if (incompleteTaskCount > 0) {
112359
+ return {
112360
+ blocked: false,
112361
+ agentsDispatched,
112362
+ agentsMissing: [],
112363
+ warnings: [
112364
+ `No spec.md found and drift verification evidence missing. Phase ${phase} has ${incompleteTaskCount} incomplete task(s) in plan.json — consider running critic_drift_verifier before phase completion.`
112365
+ ]
112366
+ };
112367
+ } else {
112368
+ return {
112369
+ blocked: false,
112370
+ agentsDispatched,
112371
+ agentsMissing: [],
112372
+ warnings: [
112373
+ `No spec.md found. Phase ${phase} tasks are all completed in plan.json. Drift verification was skipped.`
112374
+ ]
112375
+ };
112376
+ }
112377
+ } else {
112378
+ return {
112379
+ blocked: true,
112380
+ reason: "DRIFT_VERIFICATION_MISSING",
112381
+ message: `Phase ${phase} cannot be completed: drift_check is enabled and drift verifier evidence not found at .swarm/evidence/${phase}/drift-verifier.json. Run drift verification before completing the phase.`,
112382
+ agentsDispatched,
112383
+ agentsMissing: [],
112384
+ warnings: []
112385
+ };
112386
+ }
112387
+ }
112388
+ if (!driftVerdictApproved && driftVerdictFound) {
112389
+ return {
112390
+ blocked: true,
112391
+ reason: "DRIFT_VERIFICATION_REJECTED",
112392
+ message: `Phase ${phase} cannot be completed: drift verifier verdict is not approved.`,
112393
+ agentsDispatched,
112394
+ agentsMissing: [],
112395
+ warnings: []
112396
+ };
112397
+ }
112398
+ return {
112399
+ blocked: false,
112400
+ agentsDispatched,
112401
+ agentsMissing: [],
112402
+ warnings: []
112403
+ };
112404
+ } catch (driftError) {
112405
+ return {
112406
+ blocked: true,
112407
+ reason: "DRIFT_VERIFICATION_ERROR",
112408
+ message: `Phase ${phase} cannot be completed: drift verification encountered an error: ${driftError instanceof Error ? driftError.message : String(driftError)}. This is a hard block — resolve the error before completing the phase.`,
112409
+ agentsDispatched,
112410
+ agentsMissing: [],
112411
+ warnings: []
112412
+ };
112413
+ }
112414
+ }
112415
+ // src/tools/phase-complete/gates/final-council-gate.ts
112416
+ init_qa_gate_profile();
112417
+ init_manager();
112418
+ init_state();
112419
+ import * as fs90 from "node:fs";
112420
+ import * as path127 from "node:path";
112421
+ async function runFinalCouncilGate(ctx) {
112422
+ const { phase, dir, sessionID, agentsDispatched, safeWarn } = ctx;
112423
+ let finalCouncilEnabled = false;
112424
+ try {
112425
+ const plan = await loadPlan(dir);
112426
+ if (plan) {
112427
+ const lastPhaseId = plan.phases[plan.phases.length - 1]?.id;
112428
+ if (lastPhaseId !== undefined && phase === lastPhaseId) {
112429
+ const planId = derivePlanId(plan);
112430
+ const profile = getProfile(dir, planId);
112431
+ if (profile) {
112432
+ const session = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112433
+ const overrides = session?.qaGateSessionOverrides ?? {};
112434
+ const effective = getEffectiveGates(profile, overrides);
112435
+ if (effective.final_council === true) {
112436
+ finalCouncilEnabled = true;
112437
+ const fcPath = path127.join(dir, ".swarm", "evidence", "final-council.json");
112438
+ let fcVerdictFound = false;
112439
+ let _fcVerdict;
112440
+ try {
112441
+ const fcContent = fs90.readFileSync(fcPath, "utf-8");
112442
+ const fcBundle = JSON.parse(fcContent);
112443
+ for (const entry of fcBundle.entries ?? []) {
112444
+ if (typeof entry.type === "string" && entry.type === "final-council" && typeof entry.verdict === "string") {
112445
+ fcVerdictFound = true;
112446
+ _fcVerdict = entry.verdict;
112447
+ if (plan) {
112448
+ const currentPlanId = derivePlanId(plan);
112449
+ if (entry.plan_id && entry.plan_id !== currentPlanId) {
112450
+ return {
112451
+ blocked: true,
112452
+ reason: "final_council_plan_mismatch",
112453
+ message: `Final council evidence belongs to a different plan (evidence: ${entry.plan_id}, current: ${currentPlanId}). Re-run the final council.`,
112454
+ agentsDispatched,
112455
+ agentsMissing: [],
112456
+ warnings: []
112457
+ };
112458
+ }
112459
+ if (!entry.plan_id) {
112460
+ return {
112461
+ blocked: true,
112462
+ reason: "FINAL_COUNCIL_PLAN_ID_REQUIRED",
112463
+ message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing plan_id binding. Re-run the final council to generate evidence with plan identity.`,
112464
+ agentsDispatched,
112465
+ agentsMissing: [],
112466
+ warnings: []
112467
+ };
112468
+ }
112469
+ }
112470
+ if (typeof entry.quorumSize !== "number" || !Number.isFinite(entry.quorumSize) || entry.quorumSize < 5) {
112471
+ return {
112472
+ blocked: true,
112473
+ reason: "FINAL_COUNCIL_MISSING_QUORUM",
112474
+ message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing valid quorum metadata. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate quorumed evidence.`,
112475
+ agentsDispatched,
112476
+ agentsMissing: [],
112477
+ warnings: []
112478
+ };
112479
+ }
112480
+ const requiredFinalCouncilMembers = [
112481
+ "critic",
112482
+ "reviewer",
112483
+ "sme",
112484
+ "test_engineer",
112485
+ "explorer"
112486
+ ];
112487
+ const membersVoted = Array.isArray(entry.membersVoted) ? entry.membersVoted.filter((member) => typeof member === "string") : [];
112488
+ const membersAbsent = Array.isArray(entry.membersAbsent) ? entry.membersAbsent.filter((member) => typeof member === "string") : [];
112489
+ const distinctMembersVoted = new Set(membersVoted);
112490
+ const hasAllRequiredMembers = requiredFinalCouncilMembers.every((member) => distinctMembersVoted.has(member)) && distinctMembersVoted.size === requiredFinalCouncilMembers.length && membersAbsent.length === 0;
112491
+ if (!hasAllRequiredMembers) {
112492
+ return {
112493
+ blocked: true,
112494
+ reason: "FINAL_COUNCIL_MISSING_QUORUM",
112495
+ message: `Phase ${phase} (last phase) cannot be completed: final council evidence does not prove all five required members voted. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate complete evidence.`,
112496
+ agentsDispatched,
112497
+ agentsMissing: [],
112498
+ warnings: []
112499
+ };
112500
+ }
112501
+ if (entry.verdict === "rejected" || entry.verdict === "REJECTED") {
112502
+ return {
112503
+ blocked: true,
112504
+ reason: "FINAL_COUNCIL_REJECTED",
112505
+ message: `Phase ${phase} (last phase) cannot be completed: final council returned verdict 'REJECTED'. Address the required fixes before completing the project.`,
112506
+ agentsDispatched,
112507
+ agentsMissing: [],
112508
+ warnings: []
112509
+ };
112510
+ }
112511
+ if (entry.verdict !== "approved" && entry.verdict !== "APPROVED") {
112512
+ return {
112513
+ blocked: true,
112514
+ reason: "FINAL_COUNCIL_INVALID_VERDICT",
112515
+ message: `Phase ${phase} (last phase) cannot be completed: final council evidence contains unrecognized verdict '${entry.verdict}'. Expected 'approved'.`,
112516
+ agentsDispatched,
112517
+ agentsMissing: [],
112518
+ warnings: []
112519
+ };
112520
+ }
112521
+ }
112522
+ }
112523
+ } catch (readErr) {
112524
+ if (readErr.code !== "ENOENT") {
112525
+ safeWarn(`[phase_complete] Final council evidence unreadable:`, readErr);
112526
+ }
112527
+ fcVerdictFound = false;
112528
+ }
112529
+ if (!fcVerdictFound) {
112530
+ return {
112531
+ blocked: true,
112532
+ reason: "FINAL_COUNCIL_REQUIRED",
112533
+ final_council_required: true,
112534
+ message: `Phase ${phase} (last phase) cannot be completed: final_council is enabled and final council evidence not found at .swarm/evidence/final-council.json. Dispatch critic, reviewer, sme, test_engineer, and explorer with project-scoped context, collect their CouncilMemberVerdict JSON, and call write_final_council_evidence before completing the project. Do not use convene_general_council for this gate.`,
112535
+ agentsDispatched,
112536
+ agentsMissing: [],
112537
+ warnings: [
112538
+ `Final council required - dispatch the five project-scoped council members, then call write_final_council_evidence to persist quorumed evidence.`
112539
+ ]
112540
+ };
112541
+ }
112542
+ }
112543
+ }
112544
+ }
112545
+ }
112546
+ } catch (fcError) {
112547
+ if (finalCouncilEnabled) {
112548
+ return {
112549
+ blocked: true,
112550
+ reason: "FINAL_COUNCIL_ERROR",
112551
+ message: `Phase ${phase} (last phase) cannot be completed: final council gate encountered an error. Error: ${String(fcError)}`,
112552
+ agentsDispatched,
112553
+ agentsMissing: [],
112554
+ warnings: [`FINAL_COUNCIL_ERROR: ${String(fcError)}`]
112555
+ };
112556
+ } else {
112557
+ safeWarn(`[phase_complete] Final council gate error (non-blocking):`, fcError);
112558
+ }
112559
+ }
112560
+ return { blocked: false, agentsDispatched, agentsMissing: [], warnings: [] };
112561
+ }
112562
+ // src/tools/phase-complete/gates/hallucination-gate.ts
112563
+ init_qa_gate_profile();
112564
+ init_manager();
112565
+ init_state();
112566
+ import * as fs91 from "node:fs";
112567
+ import * as path128 from "node:path";
112568
+ async function runHallucinationGate(ctx) {
112569
+ const { phase, dir, sessionID, agentsDispatched, safeWarn } = ctx;
112570
+ try {
112571
+ const plan = await loadPlan(dir);
112572
+ if (plan) {
112573
+ const planId = derivePlanId(plan);
112574
+ const profile = getProfile(dir, planId);
112575
+ if (profile) {
112576
+ const session = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112577
+ const overrides = session?.qaGateSessionOverrides ?? {};
112578
+ const effective = getEffectiveGates(profile, overrides);
112579
+ if (effective.hallucination_guard === true) {
112580
+ const hgPath = path128.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
112581
+ let hgVerdictFound = false;
112582
+ let hgVerdictApproved = false;
112583
+ try {
112584
+ const hgContent = fs91.readFileSync(hgPath, "utf-8");
112585
+ const hgBundle = JSON.parse(hgContent);
112586
+ for (const entry of hgBundle.entries ?? []) {
112587
+ if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
112588
+ hgVerdictFound = true;
112589
+ if (entry.verdict === "approved") {
112590
+ hgVerdictApproved = true;
112591
+ }
112592
+ if (entry.verdict === "rejected" || typeof entry.summary === "string" && entry.summary.includes("NEEDS_REVISION")) {
112593
+ return {
112594
+ blocked: true,
112595
+ reason: "HALLUCINATION_VERIFICATION_REJECTED",
112596
+ message: `Phase ${phase} cannot be completed: hallucination verifier returned verdict '${entry.verdict}'. Remove fabricated APIs/signatures and fix broken citations before completing the phase.`,
112597
+ agentsDispatched,
112598
+ agentsMissing: [],
112599
+ warnings: []
112600
+ };
112601
+ }
112602
+ }
112603
+ }
112604
+ } catch (readErr) {
112605
+ if (readErr.code !== "ENOENT") {
112606
+ safeWarn(`[phase_complete] Hallucination guard evidence unreadable:`, readErr);
112607
+ }
112608
+ hgVerdictFound = false;
112609
+ }
112610
+ if (!hgVerdictFound) {
112611
+ return {
112612
+ blocked: true,
112613
+ reason: "HALLUCINATION_VERIFICATION_MISSING",
112614
+ message: `Phase ${phase} cannot be completed: hallucination_guard is enabled and evidence not found at .swarm/evidence/${phase}/hallucination-guard.json. Delegate to critic_hallucination_verifier and call write_hallucination_evidence before completing the phase.`,
112615
+ agentsDispatched,
112616
+ agentsMissing: [],
112617
+ warnings: []
112618
+ };
112619
+ }
112620
+ if (!hgVerdictApproved) {
112621
+ return {
112622
+ blocked: true,
112623
+ reason: "HALLUCINATION_VERIFICATION_REJECTED",
112624
+ message: `Phase ${phase} cannot be completed: hallucination verifier verdict is not approved.`,
112625
+ agentsDispatched,
112626
+ agentsMissing: [],
112627
+ warnings: []
112628
+ };
112629
+ }
112630
+ }
112631
+ }
112632
+ }
112633
+ } catch (hgError) {
112634
+ safeWarn(`[phase_complete] Hallucination guard error (non-blocking):`, hgError);
112635
+ }
112636
+ return { blocked: false, agentsDispatched, agentsMissing: [], warnings: [] };
112637
+ }
112638
+ // src/tools/phase-complete/gates/mutation-gate.ts
112639
+ init_qa_gate_profile();
112640
+ init_manager();
112641
+ init_state();
112642
+ import * as fs92 from "node:fs";
112643
+ import * as path129 from "node:path";
112644
+ async function runMutationGate(ctx) {
112645
+ const { phase, dir, sessionID, agentsDispatched, safeWarn } = ctx;
112646
+ try {
112647
+ const plan = await loadPlan(dir);
112648
+ if (plan) {
112649
+ const planId = derivePlanId(plan);
112650
+ const profile = getProfile(dir, planId);
112651
+ if (profile) {
112652
+ const session = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112653
+ const overrides = session?.qaGateSessionOverrides ?? {};
112654
+ const effective = getEffectiveGates(profile, overrides);
112655
+ if (effective.mutation_test === true) {
112656
+ const mgPath = path129.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
112657
+ let mgVerdictFound = false;
112658
+ let mgVerdict;
112659
+ try {
112660
+ const mgContent = fs92.readFileSync(mgPath, "utf-8");
112661
+ const mgBundle = JSON.parse(mgContent);
112662
+ for (const entry of mgBundle.entries ?? []) {
112663
+ if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
112664
+ mgVerdictFound = true;
112665
+ mgVerdict = entry.verdict;
112666
+ if (entry.verdict === "fail") {
112667
+ return {
112668
+ blocked: true,
112669
+ reason: "MUTATION_GATE_FAIL",
112670
+ message: `Phase ${phase} cannot be completed: mutation gate returned verdict 'fail'. Resolve surviving mutants or lower the kill-rate threshold before completing the phase.`,
112671
+ agentsDispatched,
112672
+ agentsMissing: [],
112673
+ warnings: []
112674
+ };
112675
+ } else if (!["pass", "warn", "skip"].includes(entry.verdict)) {
112676
+ return {
112677
+ blocked: true,
112678
+ reason: "MUTATION_GATE_FAIL",
112679
+ message: `Phase ${phase} cannot be completed: mutation gate evidence contains unrecognized verdict '${entry.verdict}'. Expected one of: pass, warn, fail, skip.`,
112680
+ agentsDispatched,
112681
+ agentsMissing: [],
112682
+ warnings: []
112683
+ };
112684
+ }
112685
+ }
112686
+ }
112687
+ } catch (readErr) {
112688
+ if (readErr.code !== "ENOENT") {
112689
+ safeWarn(`[phase_complete] Mutation gate evidence unreadable:`, readErr);
112690
+ }
112691
+ mgVerdictFound = false;
112692
+ }
112693
+ if (!mgVerdictFound) {
112694
+ return {
112695
+ blocked: true,
112696
+ reason: "MUTATION_GATE_MISSING",
112697
+ message: `Phase ${phase} cannot be completed: mutation_test is enabled and evidence not found at .swarm/evidence/${phase}/mutation-gate.json. Run mutation_test, then call write_mutation_evidence before completing the phase.`,
112698
+ agentsDispatched,
112699
+ agentsMissing: [],
112700
+ warnings: []
112701
+ };
112702
+ }
112703
+ if (mgVerdict === "warn") {
112704
+ safeWarn(`[phase_complete] Mutation gate verdict is 'warn' for phase ${phase} — proceeding with warning`, undefined);
112705
+ }
112706
+ }
112707
+ }
112708
+ }
112709
+ } catch (mgError) {
112710
+ safeWarn(`[phase_complete] Mutation gate error (non-blocking):`, mgError);
112711
+ }
112712
+ return { blocked: false, agentsDispatched, agentsMissing: [], warnings: [] };
112713
+ }
112714
+ // src/tools/phase-complete/gates/phase-council-gate.ts
112715
+ init_qa_gate_profile();
112716
+ init_manager();
112717
+ init_state();
112718
+ import * as fs93 from "node:fs";
112719
+ import * as path130 from "node:path";
112720
+ async function runPhaseCouncilGate(ctx) {
112721
+ const { phase, dir, sessionID, pluginConfig, agentsDispatched, safeWarn } = ctx;
112722
+ let councilModeEnabled = false;
112723
+ try {
112724
+ const plan = await loadPlan(dir);
112725
+ if (plan) {
112726
+ const planId = derivePlanId(plan);
112727
+ const profile = getProfile(dir, planId);
112728
+ if (profile) {
112729
+ const session = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112730
+ const overrides = session?.qaGateSessionOverrides ?? {};
112731
+ const effective = getEffectiveGates(profile, overrides);
112732
+ if (effective.council_mode === true) {
112733
+ councilModeEnabled = true;
112734
+ const pcPath = path130.join(dir, ".swarm", "evidence", String(phase), "phase-council.json");
112735
+ let pcVerdictFound = false;
112736
+ let _pcVerdict;
112737
+ let pcQuorumSize;
112738
+ let pcTimestamp;
112739
+ let pcPhaseNumber;
112740
+ try {
112741
+ const pcContent = fs93.readFileSync(pcPath, "utf-8");
112742
+ const pcBundle = JSON.parse(pcContent);
112743
+ for (const entry of pcBundle.entries ?? []) {
112744
+ if (typeof entry.type === "string" && entry.type === "phase-council" && typeof entry.verdict === "string") {
112745
+ pcVerdictFound = true;
112746
+ _pcVerdict = entry.verdict;
112747
+ pcQuorumSize = typeof entry.quorumSize === "number" ? entry.quorumSize : undefined;
112748
+ pcTimestamp = typeof entry.timestamp === "string" ? entry.timestamp : undefined;
112749
+ pcPhaseNumber = typeof entry.phase_number === "number" ? entry.phase_number : typeof entry.phase === "number" ? entry.phase : undefined;
112750
+ const now = new Date;
112751
+ const pcTime = pcTimestamp ? new Date(pcTimestamp) : null;
112752
+ if (!pcTime || Number.isNaN(pcTime.getTime())) {
112753
+ return {
112754
+ blocked: true,
112755
+ reason: "PHASE_COUNCIL_INVALID_TIMESTAMP",
112756
+ message: `Phase ${phase} cannot be completed: phase council evidence has missing or invalid timestamp.`,
112757
+ agentsDispatched,
112758
+ agentsMissing: [],
112759
+ warnings: []
112760
+ };
112761
+ }
112762
+ const maxAge = 24 * 60 * 60 * 1000;
112763
+ if (pcTime.getTime() > now.getTime()) {
112764
+ return {
112765
+ blocked: true,
112766
+ reason: "PHASE_COUNCIL_FUTURE_TIMESTAMP",
112767
+ message: `Phase ${phase} cannot be completed: phase council evidence timestamp is in the future.`,
112768
+ agentsDispatched,
112769
+ agentsMissing: [],
112770
+ warnings: []
112771
+ };
112772
+ }
112773
+ if (now.getTime() - pcTime.getTime() > maxAge) {
112774
+ return {
112775
+ blocked: true,
112776
+ reason: "PHASE_COUNCIL_STALE_EVIDENCE",
112777
+ message: `Phase ${phase} cannot be completed: phase council evidence is older than 24 hours. Re-convene council for fresh review.`,
112778
+ agentsDispatched,
112779
+ agentsMissing: [],
112780
+ warnings: []
112781
+ };
112782
+ }
112783
+ if (entry.verdict === "REJECT" || entry.verdict === "reject") {
112784
+ const requiredFixes = entry.requiredFixes ?? entry.required_fixes ?? [];
112785
+ const fixesDetail = Array.isArray(requiredFixes) && requiredFixes.length > 0 ? `
112786
+ Required fixes: ${requiredFixes.map((f) => f.detail ?? JSON.stringify(f)).join("; ")}` : "";
112787
+ return {
112788
+ blocked: true,
112789
+ reason: "PHASE_COUNCIL_REJECTED",
112790
+ message: `Phase ${phase} cannot be completed: phase council returned verdict 'REJECT'. Address the required fixes before completing the phase.${fixesDetail}`,
112791
+ agentsDispatched,
112792
+ agentsMissing: [],
112793
+ warnings: []
112794
+ };
112795
+ }
112796
+ if (entry.verdict === "CONCERNS" || entry.verdict === "concerns") {
112797
+ const phaseConcernsAllow = pluginConfig.council?.phaseConcernsAllowComplete ?? true;
112798
+ if (!phaseConcernsAllow) {
112799
+ const advisoryNotes = entry.advisoryNotes ?? entry.advisory_notes ?? [];
112800
+ const notesDetail = Array.isArray(advisoryNotes) && advisoryNotes.length > 0 ? `
112801
+ Advisory notes: ${advisoryNotes.join("; ")}` : "";
112802
+ return {
112803
+ blocked: true,
112804
+ reason: "PHASE_COUNCIL_CONCERNS",
112805
+ message: `Phase ${phase} cannot be completed: phase council returned verdict 'CONCERNS'.${notesDetail}`,
112806
+ agentsDispatched,
112807
+ agentsMissing: [],
112808
+ warnings: []
112809
+ };
112810
+ }
112811
+ safeWarn(`[phase_complete] Phase council returned CONCERNS for phase ${phase} — proceeding (phaseConcernsAllowComplete is enabled)`, undefined);
112812
+ }
112813
+ if (entry.verdict !== "APPROVE" && entry.verdict !== "approve" && entry.verdict !== "CONCERNS" && entry.verdict !== "concerns") {
112814
+ return {
112815
+ blocked: true,
112816
+ reason: "PHASE_COUNCIL_INVALID",
112817
+ message: `Phase ${phase} cannot be completed: phase council evidence contains unrecognized verdict '${entry.verdict}'. Expected one of: APPROVE, CONCERNS, REJECT.`,
112818
+ agentsDispatched,
112819
+ agentsMissing: [],
112820
+ warnings: []
112821
+ };
112822
+ }
112823
+ }
112824
+ }
112825
+ } catch (readErr) {
112826
+ if (readErr.code !== "ENOENT") {
112827
+ safeWarn(`[phase_complete] Phase council evidence unreadable:`, readErr);
112828
+ }
112829
+ pcVerdictFound = false;
112830
+ }
112831
+ if (!pcVerdictFound) {
112832
+ return {
112833
+ blocked: true,
112834
+ reason: "PHASE_COUNCIL_REQUIRED",
112835
+ phase_council_required: true,
112836
+ message: `Phase ${phase} cannot be completed: council_mode is enabled and phase council evidence not found at .swarm/evidence/${phase}/phase-council.json. Convene a phase-level council (dispatch 5 members, collect verdicts, call submit_phase_council_verdicts) before completing the phase.`,
112837
+ agentsDispatched,
112838
+ agentsMissing: [],
112839
+ warnings: [
112840
+ `Phase council required — convene 5 council members (critic, reviewer, sme, test_engineer, explorer) for holistic phase review. Call submit_phase_council_verdicts to synthesize verdicts and write phase-council.json evidence.`
112841
+ ]
112842
+ };
112843
+ }
112844
+ if (pcQuorumSize === undefined || typeof pcQuorumSize !== "number") {
112845
+ return {
112846
+ blocked: true,
112847
+ reason: "PHASE_COUNCIL_MISSING_QUORUM",
112848
+ message: `Phase ${phase} cannot be completed: phase council evidence is missing quorumSize field.`,
112849
+ agentsDispatched,
112850
+ agentsMissing: [],
112851
+ warnings: []
112852
+ };
112853
+ }
112854
+ if (pcQuorumSize < 3) {
112855
+ return {
112856
+ blocked: true,
112857
+ reason: "PHASE_COUNCIL_INSUFFICIENT_QUORUM",
112858
+ message: `Phase ${phase} cannot be completed: phase council quorum (${pcQuorumSize}) is below minimum (3). Re-convene council with sufficient members.`,
112859
+ agentsDispatched,
112860
+ agentsMissing: [],
112861
+ warnings: []
112862
+ };
112863
+ }
112864
+ if (pcPhaseNumber === undefined || typeof pcPhaseNumber !== "number") {
112865
+ return {
112866
+ blocked: true,
112867
+ reason: "PHASE_COUNCIL_MISSING_PHASE",
112868
+ message: `Phase ${phase} cannot be completed: phase council evidence is missing phase_number field.`,
112869
+ agentsDispatched,
112870
+ agentsMissing: [],
112871
+ warnings: []
112872
+ };
112873
+ }
112874
+ if (pcPhaseNumber !== phase) {
112875
+ return {
112876
+ blocked: true,
112877
+ reason: "PHASE_COUNCIL_PHASE_MISMATCH",
112878
+ message: `Phase ${phase} cannot be completed: phase council evidence is for phase ${pcPhaseNumber}, not phase ${phase}. Run council for the correct phase.`,
112879
+ agentsDispatched,
112880
+ agentsMissing: [],
112881
+ warnings: []
112882
+ };
112883
+ }
112884
+ }
112885
+ }
112886
+ }
112887
+ } catch (pcError) {
112888
+ if (councilModeEnabled) {
112889
+ return {
112890
+ blocked: true,
112891
+ reason: "PHASE_COUNCIL_ERROR",
112892
+ message: `Phase ${phase} cannot be completed: phase council gate encountered an error when council_mode was enabled. Error: ${String(pcError)}`,
112893
+ agentsDispatched,
112894
+ agentsMissing: [],
112895
+ warnings: [`PHASE_COUNCIL_ERROR: ${String(pcError)}`]
112896
+ };
112897
+ } else {
112898
+ safeWarn(`[phase_complete] Phase council gate error (non-blocking):`, pcError);
112899
+ }
112900
+ }
112901
+ return { blocked: false, agentsDispatched, agentsMissing: [], warnings: [] };
112902
+ }
112903
+ // src/tools/phase-complete.ts
112140
112904
  init_resolve_working_directory();
112141
112905
  function safeWarn(message, error93) {
112142
112906
  try {
112143
112907
  warn(message, error93 instanceof Error ? error93.message : String(error93));
112144
112908
  } catch {}
112145
112909
  }
112910
+ var MAX_OUTPUT_BYTES5 = 512000;
112146
112911
  var TASK_GATE_INFERABLE_AGENTS = new Set([
112147
112912
  "coder",
112148
112913
  "reviewer",
@@ -112213,6 +112978,28 @@ function _getDelegationsSince(sessionID, sinceTimestamp) {
112213
112978
  function isValidRetroEntry(entry, phase) {
112214
112979
  return entry.type === "retrospective" && "phase_number" in entry && entry.phase_number === phase && "verdict" in entry && entry.verdict === "pass";
112215
112980
  }
112981
+ function blockedResult(phase, gateResult) {
112982
+ const {
112983
+ reason,
112984
+ message,
112985
+ agentsDispatched,
112986
+ agentsMissing,
112987
+ warnings,
112988
+ blocked: _blocked,
112989
+ ...extra
112990
+ } = gateResult;
112991
+ return JSON.stringify({
112992
+ success: false,
112993
+ phase,
112994
+ status: "blocked",
112995
+ reason,
112996
+ message,
112997
+ agentsDispatched,
112998
+ agentsMissing,
112999
+ warnings,
113000
+ ...extra
113001
+ }, null, 2);
113002
+ }
112216
113003
  async function executePhaseComplete(args2, workingDirectory, directory) {
112217
113004
  const phase = Number(args2.phase);
112218
113005
  const summary = args2.summary;
@@ -112351,719 +113138,66 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
112351
113138
  ]
112352
113139
  }, null, 2);
112353
113140
  }
113141
+ const gateCtx = {
113142
+ phase,
113143
+ dir,
113144
+ sessionID,
113145
+ pluginConfig: config3,
113146
+ agentsDispatched,
113147
+ safeWarn
113148
+ };
112354
113149
  if (hasActiveTurboMode(sessionID)) {
112355
113150
  warnings.push(`Turbo mode active — skipped completion-verify, drift-verifier, hallucination-guard, mutation-gate, phase-council, and final-council gates for phase ${phase}.`);
112356
113151
  } else {
112357
- try {
112358
- const completionResultRaw = await executeCompletionVerify({ phase }, dir);
112359
- const completionResult = JSON.parse(completionResultRaw);
112360
- if (completionResult.status === "blocked") {
112361
- return JSON.stringify({
112362
- success: false,
112363
- phase,
112364
- status: "blocked",
112365
- reason: "COMPLETION_INCOMPLETE",
112366
- message: `Phase ${phase} cannot be completed: ${completionResult.reason}`,
112367
- agentsDispatched,
112368
- agentsMissing: [],
112369
- warnings: completionResult.blockedTasks ? [
112370
- `Blocked tasks: ${completionResult.blockedTasks.map((t) => t.task_id).join(", ")}`
112371
- ] : []
112372
- }, null, 2);
113152
+ {
113153
+ const gateResult = await runCompletionVerifyGate(gateCtx);
113154
+ if (gateResult.blocked) {
113155
+ return blockedResult(phase, gateResult);
112373
113156
  }
112374
- } catch (completionError) {
112375
- safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
113157
+ warnings.push(...gateResult.warnings);
112376
113158
  }
112377
- let driftCheckEnabled = true;
112378
- let driftHasSpecMd = false;
112379
- try {
112380
- const specMdPath = path127.join(dir, ".swarm", "spec.md");
112381
- driftHasSpecMd = fs90.existsSync(specMdPath);
112382
- const gatePlan = await loadPlan(dir);
112383
- if (gatePlan) {
112384
- const gatePlanId = derivePlanId(gatePlan);
112385
- const gateProfile = getProfile(dir, gatePlanId);
112386
- if (gateProfile) {
112387
- const gateSession = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112388
- const gateOverrides = gateSession?.qaGateSessionOverrides ?? {};
112389
- const gateEffective = getEffectiveGates(gateProfile, gateOverrides);
112390
- driftCheckEnabled = gateEffective.drift_check === true;
112391
- }
112392
- }
112393
- } catch (gateLoadError) {
112394
- safeWarn(`[phase_complete] QA gate profile load error, drift_check defaults to enabled:`, gateLoadError);
112395
- }
112396
- if (!driftCheckEnabled) {
112397
- warnings.push(`drift_check gate is disabled. Drift verification was skipped for phase ${phase}.`);
112398
- } else {
112399
- let phaseType;
112400
- try {
112401
- const planPath = path127.join(dir, ".swarm", "plan.json");
112402
- if (fs90.existsSync(planPath)) {
112403
- const planRaw = fs90.readFileSync(planPath, "utf-8");
112404
- const plan = JSON.parse(planRaw);
112405
- const targetPhase = plan.phases?.find((p) => p.id === phase);
112406
- phaseType = targetPhase?.type;
112407
- }
112408
- } catch {}
112409
- if (phaseType === "non-code") {
112410
- warnings.push(`Phase ${phase} is annotated as 'non-code'. Drift verification was skipped per phase type annotation.`);
112411
- } else {
112412
- try {
112413
- const driftEvidencePath = path127.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
112414
- let driftVerdictFound = false;
112415
- let driftVerdictApproved = false;
112416
- try {
112417
- const driftEvidenceContent = fs90.readFileSync(driftEvidencePath, "utf-8");
112418
- const driftEvidence = JSON.parse(driftEvidenceContent);
112419
- const entries = driftEvidence.entries ?? [];
112420
- for (const entry of entries) {
112421
- if (typeof entry.type === "string" && entry.type.includes("drift") && typeof entry.verdict === "string") {
112422
- driftVerdictFound = true;
112423
- if (entry.verdict === "approved") {
112424
- driftVerdictApproved = true;
112425
- }
112426
- if (entry.verdict === "rejected" || typeof entry.summary === "string" && entry.summary.includes("NEEDS_REVISION")) {
112427
- return JSON.stringify({
112428
- success: false,
112429
- phase,
112430
- status: "blocked",
112431
- reason: "DRIFT_VERIFICATION_REJECTED",
112432
- message: `Phase ${phase} cannot be completed: drift verifier returned verdict '${entry.verdict}'. Address the drift issues before completing the phase.`,
112433
- agentsDispatched,
112434
- agentsMissing: [],
112435
- warnings: []
112436
- }, null, 2);
112437
- }
112438
- }
112439
- }
112440
- } catch (readError) {
112441
- if (readError.code !== "ENOENT") {
112442
- safeWarn(`[phase_complete] Drift verifier evidence unreadable:`, readError);
112443
- }
112444
- driftVerdictFound = false;
112445
- }
112446
- if (!driftVerdictFound) {
112447
- if (!driftHasSpecMd) {
112448
- let incompleteTaskCount = 0;
112449
- let planParseable = false;
112450
- try {
112451
- const planPath = path127.join(dir, ".swarm", "plan.json");
112452
- if (fs90.existsSync(planPath)) {
112453
- const planRaw = fs90.readFileSync(planPath, "utf-8");
112454
- const plan = JSON.parse(planRaw);
112455
- planParseable = true;
112456
- const planPhase = plan.phases?.find((p) => p.id === phase);
112457
- if (planPhase?.tasks) {
112458
- incompleteTaskCount = planPhase.tasks.filter((t) => t.status !== "completed" && t.status !== "closed").length;
112459
- }
112460
- }
112461
- } catch {}
112462
- if (!planParseable) {
112463
- warnings.push(`No spec.md found and drift verification evidence missing — consider running critic_drift_verifier before phase completion.`);
112464
- } else if (incompleteTaskCount > 0) {
112465
- warnings.push(`No spec.md found and drift verification evidence missing. Phase ${phase} has ${incompleteTaskCount} incomplete task(s) in plan.json — consider running critic_drift_verifier before phase completion.`);
112466
- } else {
112467
- warnings.push(`No spec.md found. Phase ${phase} tasks are all completed in plan.json. Drift verification was skipped.`);
112468
- }
112469
- } else {
112470
- return JSON.stringify({
112471
- success: false,
112472
- phase,
112473
- status: "blocked",
112474
- reason: "DRIFT_VERIFICATION_MISSING",
112475
- message: `Phase ${phase} cannot be completed: drift_check is enabled and drift verifier evidence not found at .swarm/evidence/${phase}/drift-verifier.json. Run drift verification before completing the phase.`,
112476
- agentsDispatched,
112477
- agentsMissing: [],
112478
- warnings: []
112479
- }, null, 2);
112480
- }
112481
- }
112482
- if (!driftVerdictApproved && driftVerdictFound) {
112483
- return JSON.stringify({
112484
- success: false,
112485
- phase,
112486
- status: "blocked",
112487
- reason: "DRIFT_VERIFICATION_REJECTED",
112488
- message: `Phase ${phase} cannot be completed: drift verifier verdict is not approved.`,
112489
- agentsDispatched,
112490
- agentsMissing: [],
112491
- warnings: []
112492
- }, null, 2);
112493
- }
112494
- } catch (driftError) {
112495
- return JSON.stringify({
112496
- success: false,
112497
- phase,
112498
- status: "blocked",
112499
- reason: "DRIFT_VERIFICATION_ERROR",
112500
- message: `Phase ${phase} cannot be completed: drift verification encountered an error: ${driftError instanceof Error ? driftError.message : String(driftError)}. This is a hard block — resolve the error before completing the phase.`,
112501
- agentsDispatched,
112502
- agentsMissing: [],
112503
- warnings: []
112504
- }, null, 2);
112505
- }
113159
+ {
113160
+ const gateResult = await runDriftGate(gateCtx);
113161
+ if (gateResult.blocked) {
113162
+ return blockedResult(phase, gateResult);
112506
113163
  }
113164
+ warnings.push(...gateResult.warnings);
112507
113165
  }
112508
- try {
112509
- const plan = await loadPlan(dir);
112510
- if (plan) {
112511
- const planId = derivePlanId(plan);
112512
- const profile = getProfile(dir, planId);
112513
- if (profile) {
112514
- const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112515
- const overrides = session2?.qaGateSessionOverrides ?? {};
112516
- const effective = getEffectiveGates(profile, overrides);
112517
- if (effective.hallucination_guard === true) {
112518
- const hgPath = path127.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
112519
- let hgVerdictFound = false;
112520
- let hgVerdictApproved = false;
112521
- try {
112522
- const hgContent = fs90.readFileSync(hgPath, "utf-8");
112523
- const hgBundle = JSON.parse(hgContent);
112524
- for (const entry of hgBundle.entries ?? []) {
112525
- if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
112526
- hgVerdictFound = true;
112527
- if (entry.verdict === "approved") {
112528
- hgVerdictApproved = true;
112529
- }
112530
- if (entry.verdict === "rejected" || typeof entry.summary === "string" && entry.summary.includes("NEEDS_REVISION")) {
112531
- return JSON.stringify({
112532
- success: false,
112533
- phase,
112534
- status: "blocked",
112535
- reason: "HALLUCINATION_VERIFICATION_REJECTED",
112536
- message: `Phase ${phase} cannot be completed: hallucination verifier returned verdict '${entry.verdict}'. Remove fabricated APIs/signatures and fix broken citations before completing the phase.`,
112537
- agentsDispatched,
112538
- agentsMissing: [],
112539
- warnings: []
112540
- }, null, 2);
112541
- }
112542
- }
112543
- }
112544
- } catch (readErr) {
112545
- if (readErr.code !== "ENOENT") {
112546
- safeWarn(`[phase_complete] Hallucination guard evidence unreadable:`, readErr);
112547
- }
112548
- hgVerdictFound = false;
112549
- }
112550
- if (!hgVerdictFound) {
112551
- return JSON.stringify({
112552
- success: false,
112553
- phase,
112554
- status: "blocked",
112555
- reason: "HALLUCINATION_VERIFICATION_MISSING",
112556
- message: `Phase ${phase} cannot be completed: hallucination_guard is enabled and evidence not found at .swarm/evidence/${phase}/hallucination-guard.json. Delegate to critic_hallucination_verifier and call write_hallucination_evidence before completing the phase.`,
112557
- agentsDispatched,
112558
- agentsMissing: [],
112559
- warnings: []
112560
- }, null, 2);
112561
- }
112562
- if (!hgVerdictApproved) {
112563
- return JSON.stringify({
112564
- success: false,
112565
- phase,
112566
- status: "blocked",
112567
- reason: "HALLUCINATION_VERIFICATION_REJECTED",
112568
- message: `Phase ${phase} cannot be completed: hallucination verifier verdict is not approved.`,
112569
- agentsDispatched,
112570
- agentsMissing: [],
112571
- warnings: []
112572
- }, null, 2);
112573
- }
112574
- }
112575
- }
113166
+ {
113167
+ const gateResult = await runHallucinationGate(gateCtx);
113168
+ if (gateResult.blocked) {
113169
+ return blockedResult(phase, gateResult);
112576
113170
  }
112577
- } catch (hgError) {
112578
- safeWarn(`[phase_complete] Hallucination guard error (non-blocking):`, hgError);
113171
+ warnings.push(...gateResult.warnings);
112579
113172
  }
112580
- try {
112581
- const plan = await loadPlan(dir);
112582
- if (plan) {
112583
- const planId = derivePlanId(plan);
112584
- const profile = getProfile(dir, planId);
112585
- if (profile) {
112586
- const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112587
- const overrides = session2?.qaGateSessionOverrides ?? {};
112588
- const effective = getEffectiveGates(profile, overrides);
112589
- if (effective.mutation_test === true) {
112590
- const mgPath = path127.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
112591
- let mgVerdictFound = false;
112592
- let mgVerdict;
112593
- try {
112594
- const mgContent = fs90.readFileSync(mgPath, "utf-8");
112595
- const mgBundle = JSON.parse(mgContent);
112596
- for (const entry of mgBundle.entries ?? []) {
112597
- if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
112598
- mgVerdictFound = true;
112599
- mgVerdict = entry.verdict;
112600
- if (entry.verdict === "fail") {
112601
- return JSON.stringify({
112602
- success: false,
112603
- phase,
112604
- status: "blocked",
112605
- reason: "MUTATION_GATE_FAIL",
112606
- message: `Phase ${phase} cannot be completed: mutation gate returned verdict 'fail'. Resolve surviving mutants or lower the kill-rate threshold before completing the phase.`,
112607
- agentsDispatched,
112608
- agentsMissing: [],
112609
- warnings: []
112610
- }, null, 2);
112611
- } else if (!["pass", "warn", "skip"].includes(entry.verdict)) {
112612
- return JSON.stringify({
112613
- success: false,
112614
- phase,
112615
- status: "blocked",
112616
- reason: "MUTATION_GATE_FAIL",
112617
- message: `Phase ${phase} cannot be completed: mutation gate evidence contains unrecognized verdict '${entry.verdict}'. Expected one of: pass, warn, fail, skip.`,
112618
- agentsDispatched,
112619
- agentsMissing: [],
112620
- warnings: []
112621
- }, null, 2);
112622
- }
112623
- }
112624
- }
112625
- } catch (readErr) {
112626
- if (readErr.code !== "ENOENT") {
112627
- safeWarn(`[phase_complete] Mutation gate evidence unreadable:`, readErr);
112628
- }
112629
- mgVerdictFound = false;
112630
- }
112631
- if (!mgVerdictFound) {
112632
- return JSON.stringify({
112633
- success: false,
112634
- phase,
112635
- status: "blocked",
112636
- reason: "MUTATION_GATE_MISSING",
112637
- message: `Phase ${phase} cannot be completed: mutation_test is enabled and evidence not found at .swarm/evidence/${phase}/mutation-gate.json. Run mutation_test, then call write_mutation_evidence before completing the phase.`,
112638
- agentsDispatched,
112639
- agentsMissing: [],
112640
- warnings: []
112641
- }, null, 2);
112642
- }
112643
- if (mgVerdict === "warn") {
112644
- safeWarn(`[phase_complete] Mutation gate verdict is 'warn' for phase ${phase} — proceeding with warning`, undefined);
112645
- }
112646
- }
112647
- }
113173
+ {
113174
+ const gateResult = await runMutationGate(gateCtx);
113175
+ if (gateResult.blocked) {
113176
+ return blockedResult(phase, gateResult);
112648
113177
  }
112649
- } catch (mgError) {
112650
- safeWarn(`[phase_complete] Mutation gate error (non-blocking):`, mgError);
113178
+ warnings.push(...gateResult.warnings);
112651
113179
  }
112652
- let councilModeEnabled = false;
112653
- try {
112654
- const plan = await loadPlan(dir);
112655
- if (plan) {
112656
- const planId = derivePlanId(plan);
112657
- const profile = getProfile(dir, planId);
112658
- if (profile) {
112659
- const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112660
- const overrides = session2?.qaGateSessionOverrides ?? {};
112661
- const effective = getEffectiveGates(profile, overrides);
112662
- if (effective.council_mode === true) {
112663
- councilModeEnabled = true;
112664
- const pcPath = path127.join(dir, ".swarm", "evidence", String(phase), "phase-council.json");
112665
- let pcVerdictFound = false;
112666
- let _pcVerdict;
112667
- let pcQuorumSize;
112668
- let pcTimestamp;
112669
- let pcPhaseNumber;
112670
- try {
112671
- const pcContent = fs90.readFileSync(pcPath, "utf-8");
112672
- const pcBundle = JSON.parse(pcContent);
112673
- for (const entry of pcBundle.entries ?? []) {
112674
- if (typeof entry.type === "string" && entry.type === "phase-council" && typeof entry.verdict === "string") {
112675
- pcVerdictFound = true;
112676
- _pcVerdict = entry.verdict;
112677
- pcQuorumSize = typeof entry.quorumSize === "number" ? entry.quorumSize : undefined;
112678
- pcTimestamp = typeof entry.timestamp === "string" ? entry.timestamp : undefined;
112679
- pcPhaseNumber = typeof entry.phase_number === "number" ? entry.phase_number : typeof entry.phase === "number" ? entry.phase : undefined;
112680
- const now2 = new Date;
112681
- const pcTime = pcTimestamp ? new Date(pcTimestamp) : null;
112682
- if (!pcTime || Number.isNaN(pcTime.getTime())) {
112683
- return JSON.stringify({
112684
- success: false,
112685
- phase,
112686
- status: "blocked",
112687
- reason: "PHASE_COUNCIL_INVALID_TIMESTAMP",
112688
- message: `Phase ${phase} cannot be completed: phase council evidence has missing or invalid timestamp.`,
112689
- agentsDispatched,
112690
- agentsMissing: [],
112691
- warnings: []
112692
- }, null, 2);
112693
- }
112694
- const maxAge = 24 * 60 * 60 * 1000;
112695
- if (pcTime.getTime() > now2.getTime()) {
112696
- return JSON.stringify({
112697
- success: false,
112698
- phase,
112699
- status: "blocked",
112700
- reason: "PHASE_COUNCIL_FUTURE_TIMESTAMP",
112701
- message: `Phase ${phase} cannot be completed: phase council evidence timestamp is in the future.`,
112702
- agentsDispatched,
112703
- agentsMissing: [],
112704
- warnings: []
112705
- }, null, 2);
112706
- }
112707
- if (now2.getTime() - pcTime.getTime() > maxAge) {
112708
- return JSON.stringify({
112709
- success: false,
112710
- phase,
112711
- status: "blocked",
112712
- reason: "PHASE_COUNCIL_STALE_EVIDENCE",
112713
- message: `Phase ${phase} cannot be completed: phase council evidence is older than 24 hours. Re-convene council for fresh review.`,
112714
- agentsDispatched,
112715
- agentsMissing: [],
112716
- warnings: []
112717
- }, null, 2);
112718
- }
112719
- if (entry.verdict === "REJECT" || entry.verdict === "reject") {
112720
- const requiredFixes = entry.requiredFixes ?? entry.required_fixes ?? [];
112721
- const fixesDetail = Array.isArray(requiredFixes) && requiredFixes.length > 0 ? `
112722
- Required fixes: ${requiredFixes.map((f) => f.detail ?? JSON.stringify(f)).join("; ")}` : "";
112723
- return JSON.stringify({
112724
- success: false,
112725
- phase,
112726
- status: "blocked",
112727
- reason: "PHASE_COUNCIL_REJECTED",
112728
- message: `Phase ${phase} cannot be completed: phase council returned verdict 'REJECT'. Address the required fixes before completing the phase.${fixesDetail}`,
112729
- agentsDispatched,
112730
- agentsMissing: [],
112731
- warnings: []
112732
- }, null, 2);
112733
- }
112734
- if (entry.verdict === "CONCERNS" || entry.verdict === "concerns") {
112735
- const phaseConcernsAllow = config3.council?.phaseConcernsAllowComplete ?? true;
112736
- if (!phaseConcernsAllow) {
112737
- const advisoryNotes = entry.advisoryNotes ?? entry.advisory_notes ?? [];
112738
- const notesDetail = Array.isArray(advisoryNotes) && advisoryNotes.length > 0 ? `
112739
- Advisory notes: ${advisoryNotes.join("; ")}` : "";
112740
- return JSON.stringify({
112741
- success: false,
112742
- phase,
112743
- status: "blocked",
112744
- reason: "PHASE_COUNCIL_CONCERNS",
112745
- message: `Phase ${phase} cannot be completed: phase council returned verdict 'CONCERNS'.${notesDetail}`,
112746
- agentsDispatched,
112747
- agentsMissing: [],
112748
- warnings: []
112749
- }, null, 2);
112750
- }
112751
- safeWarn(`[phase_complete] Phase council returned CONCERNS for phase ${phase} — proceeding (phaseConcernsAllowComplete is enabled)`, undefined);
112752
- }
112753
- if (entry.verdict !== "APPROVE" && entry.verdict !== "approve" && entry.verdict !== "CONCERNS" && entry.verdict !== "concerns") {
112754
- return JSON.stringify({
112755
- success: false,
112756
- phase,
112757
- status: "blocked",
112758
- reason: "PHASE_COUNCIL_INVALID",
112759
- message: `Phase ${phase} cannot be completed: phase council evidence contains unrecognized verdict '${entry.verdict}'. Expected one of: APPROVE, CONCERNS, REJECT.`,
112760
- agentsDispatched,
112761
- agentsMissing: [],
112762
- warnings: []
112763
- }, null, 2);
112764
- }
112765
- }
112766
- }
112767
- } catch (readErr) {
112768
- if (readErr.code !== "ENOENT") {
112769
- safeWarn(`[phase_complete] Phase council evidence unreadable:`, readErr);
112770
- }
112771
- pcVerdictFound = false;
112772
- }
112773
- if (!pcVerdictFound) {
112774
- return JSON.stringify({
112775
- success: false,
112776
- phase,
112777
- status: "blocked",
112778
- reason: "PHASE_COUNCIL_REQUIRED",
112779
- phase_council_required: true,
112780
- message: `Phase ${phase} cannot be completed: council_mode is enabled and phase council evidence not found at .swarm/evidence/${phase}/phase-council.json. Convene a phase-level council (dispatch 5 members, collect verdicts, call submit_phase_council_verdicts) before completing the phase.`,
112781
- agentsDispatched,
112782
- agentsMissing: [],
112783
- warnings: [
112784
- `Phase council required — convene 5 council members (critic, reviewer, sme, test_engineer, explorer) for holistic phase review. Call submit_phase_council_verdicts to synthesize verdicts and write phase-council.json evidence.`
112785
- ]
112786
- }, null, 2);
112787
- }
112788
- if (pcQuorumSize === undefined || typeof pcQuorumSize !== "number") {
112789
- return JSON.stringify({
112790
- success: false,
112791
- phase,
112792
- status: "blocked",
112793
- reason: "PHASE_COUNCIL_MISSING_QUORUM",
112794
- message: `Phase ${phase} cannot be completed: phase council evidence is missing quorumSize field.`,
112795
- agentsDispatched,
112796
- agentsMissing: [],
112797
- warnings: []
112798
- }, null, 2);
112799
- }
112800
- if (pcQuorumSize < 3) {
112801
- return JSON.stringify({
112802
- success: false,
112803
- phase,
112804
- status: "blocked",
112805
- reason: "PHASE_COUNCIL_INSUFFICIENT_QUORUM",
112806
- message: `Phase ${phase} cannot be completed: phase council quorum (${pcQuorumSize}) is below minimum (3). Re-convene council with sufficient members.`,
112807
- agentsDispatched,
112808
- agentsMissing: [],
112809
- warnings: []
112810
- }, null, 2);
112811
- }
112812
- if (pcPhaseNumber === undefined || typeof pcPhaseNumber !== "number") {
112813
- return JSON.stringify({
112814
- success: false,
112815
- phase,
112816
- status: "blocked",
112817
- reason: "PHASE_COUNCIL_MISSING_PHASE",
112818
- message: `Phase ${phase} cannot be completed: phase council evidence is missing phase_number field.`,
112819
- agentsDispatched,
112820
- agentsMissing: [],
112821
- warnings: []
112822
- }, null, 2);
112823
- }
112824
- if (pcPhaseNumber !== phase) {
112825
- return JSON.stringify({
112826
- success: false,
112827
- phase,
112828
- status: "blocked",
112829
- reason: "PHASE_COUNCIL_PHASE_MISMATCH",
112830
- message: `Phase ${phase} cannot be completed: phase council evidence is for phase ${pcPhaseNumber}, not phase ${phase}. Run council for the correct phase.`,
112831
- agentsDispatched,
112832
- agentsMissing: [],
112833
- warnings: []
112834
- }, null, 2);
112835
- }
112836
- }
112837
- }
112838
- }
112839
- } catch (pcError) {
112840
- if (councilModeEnabled) {
112841
- warnings.push(`PHASE_COUNCIL_ERROR: ${String(pcError)}`);
112842
- return JSON.stringify({
112843
- success: false,
112844
- phase,
112845
- status: "blocked",
112846
- reason: "PHASE_COUNCIL_ERROR",
112847
- message: `Phase ${phase} cannot be completed: phase council gate encountered an error when council_mode was enabled. Error: ${String(pcError)}`,
112848
- agentsDispatched,
112849
- agentsMissing: [],
112850
- warnings: [`PHASE_COUNCIL_ERROR: ${String(pcError)}`]
112851
- }, null, 2);
112852
- } else {
112853
- safeWarn(`[phase_complete] Phase council gate error (non-blocking):`, pcError);
113180
+ {
113181
+ const gateResult = await runPhaseCouncilGate(gateCtx);
113182
+ if (gateResult.blocked) {
113183
+ return blockedResult(phase, gateResult);
112854
113184
  }
113185
+ warnings.push(...gateResult.warnings);
112855
113186
  }
112856
113187
  }
112857
113188
  if (config3.architectural_supervision?.enabled && config3.architectural_supervision.mode === "gate") {
112858
- const asConfig = config3.architectural_supervision;
112859
- const summarizeFindings = (findings) => {
112860
- if (!Array.isArray(findings) || findings.length === 0)
112861
- return "";
112862
- const details = findings.map((f) => f && typeof f === "object" && typeof f.description === "string" ? f.description : undefined).filter((d) => Boolean(d));
112863
- return details.length > 0 ? `
112864
- Findings: ${details.join("; ")}` : "";
112865
- };
112866
- const asBlocked = (reason, message2) => JSON.stringify({
112867
- success: false,
112868
- phase,
112869
- status: "blocked",
112870
- reason,
112871
- message: message2,
112872
- agentsDispatched,
112873
- agentsMissing: [],
112874
- warnings: []
112875
- }, null, 2);
112876
- let asEntry = null;
112877
- try {
112878
- asEntry = readSupervisorReportRaw(dir, phase);
112879
- } catch (asError) {
112880
- return asBlocked("ARCH_SUPERVISOR_ERROR", `Phase ${phase} cannot be completed: architecture supervisor gate encountered an error. Error: ${String(asError)}`);
112881
- }
112882
- if (!asEntry) {
112883
- return asBlocked("ARCH_SUPERVISOR_REQUIRED", `Phase ${phase} cannot be completed: architectural_supervision gate mode is enabled and no architecture supervisor evidence was found at .swarm/evidence/${phase}/architecture-supervisor.json. Dispatch critic_architecture_supervisor with the phase + agent summaries, then call write_architecture_supervisor_evidence.`);
112884
- }
112885
- const now2 = new Date;
112886
- const asTime = asEntry.timestamp ? new Date(asEntry.timestamp) : null;
112887
- if (!asTime || Number.isNaN(asTime.getTime())) {
112888
- return asBlocked("ARCH_SUPERVISOR_INVALID_TIMESTAMP", `Phase ${phase} cannot be completed: architecture supervisor evidence has a missing or invalid timestamp.`);
112889
- }
112890
- if (asTime.getTime() > now2.getTime()) {
112891
- return asBlocked("ARCH_SUPERVISOR_FUTURE_TIMESTAMP", `Phase ${phase} cannot be completed: architecture supervisor evidence timestamp is in the future.`);
112892
- }
112893
- if (now2.getTime() - asTime.getTime() > 24 * 60 * 60 * 1000) {
112894
- return asBlocked("ARCH_SUPERVISOR_STALE_EVIDENCE", `Phase ${phase} cannot be completed: architecture supervisor evidence is older than 24 hours. Re-run the supervisor for fresh review.`);
112895
- }
112896
- if (typeof asEntry.phase_number !== "number" || asEntry.phase_number !== phase) {
112897
- return asBlocked("ARCH_SUPERVISOR_PHASE_MISMATCH", `Phase ${phase} cannot be completed: architecture supervisor evidence is for phase ${String(asEntry.phase_number)}, not phase ${phase}.`);
112898
- }
112899
- const asVerdict = asEntry.verdict;
112900
- if (asVerdict === "REJECT") {
112901
- return asBlocked("ARCH_SUPERVISOR_REJECTED", `Phase ${phase} cannot be completed: architecture supervisor returned verdict 'REJECT'. Address the system-level findings before completing the phase.${summarizeFindings(asEntry.findings)}`);
112902
- }
112903
- if (asVerdict === "CONCERNS") {
112904
- if (asConfig.allow_concerns_to_complete === false) {
112905
- return asBlocked("ARCH_SUPERVISOR_CONCERNS", `Phase ${phase} cannot be completed: architecture supervisor returned verdict 'CONCERNS' and allow_concerns_to_complete is disabled.${summarizeFindings(asEntry.findings)}`);
112906
- }
112907
- safeWarn(`[phase_complete] Architecture supervisor returned CONCERNS for phase ${phase} — proceeding (allow_concerns_to_complete is enabled)`, undefined);
112908
- } else if (asVerdict !== "APPROVE") {
112909
- return asBlocked("ARCH_SUPERVISOR_INVALID", `Phase ${phase} cannot be completed: architecture supervisor evidence contains unrecognized verdict '${String(asVerdict)}'. Expected one of: APPROVE, CONCERNS, REJECT.`);
113189
+ const gateResult = await runArchitectureSupervisorGate(gateCtx);
113190
+ if (gateResult.blocked) {
113191
+ return blockedResult(phase, gateResult);
112910
113192
  }
113193
+ warnings.push(...gateResult.warnings);
112911
113194
  }
112912
113195
  if (!hasActiveTurboMode(sessionID)) {
112913
- let finalCouncilEnabled = false;
112914
- try {
112915
- const plan = await loadPlan(dir);
112916
- if (plan) {
112917
- const lastPhaseId = plan.phases[plan.phases.length - 1]?.id;
112918
- if (lastPhaseId !== undefined && phase === lastPhaseId) {
112919
- const planId = derivePlanId(plan);
112920
- const profile = getProfile(dir, planId);
112921
- if (profile) {
112922
- const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
112923
- const overrides = session2?.qaGateSessionOverrides ?? {};
112924
- const effective = getEffectiveGates(profile, overrides);
112925
- if (effective.final_council === true) {
112926
- finalCouncilEnabled = true;
112927
- const fcPath = path127.join(dir, ".swarm", "evidence", "final-council.json");
112928
- let fcVerdictFound = false;
112929
- let _fcVerdict;
112930
- try {
112931
- const fcContent = fs90.readFileSync(fcPath, "utf-8");
112932
- const fcBundle = JSON.parse(fcContent);
112933
- for (const entry of fcBundle.entries ?? []) {
112934
- if (typeof entry.type === "string" && entry.type === "final-council" && typeof entry.verdict === "string") {
112935
- fcVerdictFound = true;
112936
- _fcVerdict = entry.verdict;
112937
- if (plan) {
112938
- const currentPlanId = derivePlanId(plan);
112939
- if (entry.plan_id && entry.plan_id !== currentPlanId) {
112940
- return JSON.stringify({
112941
- success: false,
112942
- phase,
112943
- status: "blocked",
112944
- reason: "final_council_plan_mismatch",
112945
- message: `Final council evidence belongs to a different plan (evidence: ${entry.plan_id}, current: ${currentPlanId}). Re-run the final council.`,
112946
- agentsDispatched,
112947
- agentsMissing: [],
112948
- warnings: []
112949
- }, null, 2);
112950
- }
112951
- if (!entry.plan_id) {
112952
- return JSON.stringify({
112953
- success: false,
112954
- phase,
112955
- status: "blocked",
112956
- reason: "FINAL_COUNCIL_PLAN_ID_REQUIRED",
112957
- message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing plan_id binding. Re-run the final council to generate evidence with plan identity.`,
112958
- agentsDispatched,
112959
- agentsMissing: [],
112960
- warnings: []
112961
- }, null, 2);
112962
- }
112963
- }
112964
- if (typeof entry.quorumSize !== "number" || !Number.isFinite(entry.quorumSize) || entry.quorumSize < 5) {
112965
- return JSON.stringify({
112966
- success: false,
112967
- phase,
112968
- status: "blocked",
112969
- reason: "FINAL_COUNCIL_MISSING_QUORUM",
112970
- message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing valid quorum metadata. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate quorumed evidence.`,
112971
- agentsDispatched,
112972
- agentsMissing: [],
112973
- warnings: []
112974
- }, null, 2);
112975
- }
112976
- const requiredFinalCouncilMembers = [
112977
- "critic",
112978
- "reviewer",
112979
- "sme",
112980
- "test_engineer",
112981
- "explorer"
112982
- ];
112983
- const membersVoted = Array.isArray(entry.membersVoted) ? entry.membersVoted.filter((member) => typeof member === "string") : [];
112984
- const membersAbsent = Array.isArray(entry.membersAbsent) ? entry.membersAbsent.filter((member) => typeof member === "string") : [];
112985
- const distinctMembersVoted = new Set(membersVoted);
112986
- const hasAllRequiredMembers = requiredFinalCouncilMembers.every((member) => distinctMembersVoted.has(member)) && distinctMembersVoted.size === requiredFinalCouncilMembers.length && membersAbsent.length === 0;
112987
- if (!hasAllRequiredMembers) {
112988
- return JSON.stringify({
112989
- success: false,
112990
- phase,
112991
- status: "blocked",
112992
- reason: "FINAL_COUNCIL_MISSING_QUORUM",
112993
- message: `Phase ${phase} (last phase) cannot be completed: final council evidence does not prove all five required members voted. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate complete evidence.`,
112994
- agentsDispatched,
112995
- agentsMissing: [],
112996
- warnings: []
112997
- }, null, 2);
112998
- }
112999
- if (entry.verdict === "rejected" || entry.verdict === "REJECTED") {
113000
- return JSON.stringify({
113001
- success: false,
113002
- phase,
113003
- status: "blocked",
113004
- reason: "FINAL_COUNCIL_REJECTED",
113005
- message: `Phase ${phase} (last phase) cannot be completed: final council returned verdict 'REJECTED'. Address the required fixes before completing the project.`,
113006
- agentsDispatched,
113007
- agentsMissing: [],
113008
- warnings: []
113009
- }, null, 2);
113010
- }
113011
- if (entry.verdict !== "approved" && entry.verdict !== "APPROVED") {
113012
- return JSON.stringify({
113013
- success: false,
113014
- phase,
113015
- status: "blocked",
113016
- reason: "FINAL_COUNCIL_INVALID_VERDICT",
113017
- message: `Phase ${phase} (last phase) cannot be completed: final council evidence contains unrecognized verdict '${entry.verdict}'. Expected 'approved'.`,
113018
- agentsDispatched,
113019
- agentsMissing: [],
113020
- warnings: []
113021
- }, null, 2);
113022
- }
113023
- }
113024
- }
113025
- } catch (readErr) {
113026
- if (readErr.code !== "ENOENT") {
113027
- safeWarn(`[phase_complete] Final council evidence unreadable:`, readErr);
113028
- }
113029
- fcVerdictFound = false;
113030
- }
113031
- if (!fcVerdictFound) {
113032
- return JSON.stringify({
113033
- success: false,
113034
- phase,
113035
- status: "blocked",
113036
- reason: "FINAL_COUNCIL_REQUIRED",
113037
- final_council_required: true,
113038
- message: `Phase ${phase} (last phase) cannot be completed: final_council is enabled and final council evidence not found at .swarm/evidence/final-council.json. Dispatch critic, reviewer, sme, test_engineer, and explorer with project-scoped context, collect their CouncilMemberVerdict JSON, and call write_final_council_evidence before completing the project. Do not use convene_general_council for this gate.`,
113039
- agentsDispatched,
113040
- agentsMissing: [],
113041
- warnings: [
113042
- `Final council required - dispatch the five project-scoped council members, then call write_final_council_evidence to persist quorumed evidence.`
113043
- ]
113044
- }, null, 2);
113045
- }
113046
- }
113047
- }
113048
- }
113049
- }
113050
- } catch (fcError) {
113051
- if (finalCouncilEnabled) {
113052
- warnings.push(`FINAL_COUNCIL_ERROR: ${String(fcError)}`);
113053
- return JSON.stringify({
113054
- success: false,
113055
- phase,
113056
- status: "blocked",
113057
- reason: "FINAL_COUNCIL_ERROR",
113058
- message: `Phase ${phase} (last phase) cannot be completed: final council gate encountered an error. Error: ${String(fcError)}`,
113059
- agentsDispatched,
113060
- agentsMissing: [],
113061
- warnings: [`FINAL_COUNCIL_ERROR: ${String(fcError)}`]
113062
- }, null, 2);
113063
- } else {
113064
- safeWarn(`[phase_complete] Final council gate error (non-blocking):`, fcError);
113065
- }
113196
+ const gateResult = await runFinalCouncilGate(gateCtx);
113197
+ if (gateResult.blocked) {
113198
+ return blockedResult(phase, gateResult);
113066
113199
  }
113200
+ warnings.push(...gateResult.warnings);
113067
113201
  }
113068
113202
  {
113069
113203
  const approval = verifyFullAutoPhaseApproval(dir, sessionID, phase, config3);
@@ -113112,7 +113246,7 @@ Findings: ${details.join("; ")}` : "";
113112
113246
  }
113113
113247
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
113114
113248
  try {
113115
- const projectName = path127.basename(dir);
113249
+ const projectName = path132.basename(dir);
113116
113250
  const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
113117
113251
  if (curationResult) {
113118
113252
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -113212,14 +113346,14 @@ Findings: ${details.join("; ")}` : "";
113212
113346
  const markerPath = validateSwarmPath(dir, "skill-usage-last-processed.json");
113213
113347
  let sinceTimestamp;
113214
113348
  try {
113215
- const markerData = JSON.parse(fs90.readFileSync(markerPath, "utf-8"));
113349
+ const markerData = JSON.parse(fs95.readFileSync(markerPath, "utf-8"));
113216
113350
  sinceTimestamp = markerData.lastProcessedTimestamp;
113217
113351
  } catch {}
113218
113352
  const feedbackResult = await applySkillUsageFeedback(dir, {
113219
113353
  sinceTimestamp
113220
113354
  });
113221
113355
  try {
113222
- fs90.writeFileSync(markerPath, JSON.stringify({ lastProcessedTimestamp: new Date().toISOString() }), "utf-8");
113356
+ fs95.writeFileSync(markerPath, JSON.stringify({ lastProcessedTimestamp: new Date().toISOString() }), "utf-8");
113223
113357
  } catch {}
113224
113358
  if (feedbackResult.processed > 0) {
113225
113359
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -113239,7 +113373,7 @@ Findings: ${details.join("; ")}` : "";
113239
113373
  let phaseRequiredAgents;
113240
113374
  try {
113241
113375
  const planPath = validateSwarmPath(dir, "plan.json");
113242
- const planRaw = fs90.readFileSync(planPath, "utf-8");
113376
+ const planRaw = fs95.readFileSync(planPath, "utf-8");
113243
113377
  const plan = JSON.parse(planRaw);
113244
113378
  const phaseObj = plan.phases.find((p) => p.id === phase);
113245
113379
  phaseRequiredAgents = phaseObj?.required_agents;
@@ -113254,7 +113388,7 @@ Findings: ${details.join("; ")}` : "";
113254
113388
  if (agentsMissing.length > 0) {
113255
113389
  try {
113256
113390
  const planPath = validateSwarmPath(dir, "plan.json");
113257
- const planRaw = fs90.readFileSync(planPath, "utf-8");
113391
+ const planRaw = fs95.readFileSync(planPath, "utf-8");
113258
113392
  const plan = JSON.parse(planRaw);
113259
113393
  const targetPhase = plan.phases.find((p) => p.id === phase);
113260
113394
  if (targetPhase && targetPhase.tasks.length > 0 && canInferMissingAgentsFromTaskGates(agentsMissing) && await allCompletedTasksHavePassedGateEvidence(dir, targetPhase.tasks)) {
@@ -113294,7 +113428,7 @@ Findings: ${details.join("; ")}` : "";
113294
113428
  if (phaseCompleteConfig.regression_sweep?.enforce) {
113295
113429
  try {
113296
113430
  const planPath = validateSwarmPath(dir, "plan.json");
113297
- const planRaw = fs90.readFileSync(planPath, "utf-8");
113431
+ const planRaw = fs95.readFileSync(planPath, "utf-8");
113298
113432
  const plan = JSON.parse(planRaw);
113299
113433
  const targetPhase = plan.phases.find((p) => p.id === phase);
113300
113434
  if (targetPhase) {
@@ -113348,7 +113482,7 @@ Findings: ${details.join("; ")}` : "";
113348
113482
  }
113349
113483
  try {
113350
113484
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
113351
- fs90.appendFileSync(eventsPath, `${JSON.stringify(event)}
113485
+ fs95.appendFileSync(eventsPath, `${JSON.stringify(event)}
113352
113486
  `, "utf-8");
113353
113487
  } catch (writeError) {
113354
113488
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -113423,12 +113557,12 @@ Findings: ${details.join("; ")}` : "";
113423
113557
  warnings.push(`Warning: failed to update plan.json phase status`);
113424
113558
  try {
113425
113559
  const planPath = validateSwarmPath(dir, "plan.json");
113426
- const planRaw = fs90.readFileSync(planPath, "utf-8");
113560
+ const planRaw = fs95.readFileSync(planPath, "utf-8");
113427
113561
  const plan2 = JSON.parse(planRaw);
113428
113562
  const phaseObj = plan2.phases.find((p) => p.id === phase);
113429
113563
  if (phaseObj) {
113430
113564
  phaseObj.status = "complete";
113431
- fs90.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
113565
+ fs95.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
113432
113566
  }
113433
113567
  } catch {}
113434
113568
  } else if (plan) {
@@ -113465,12 +113599,12 @@ Findings: ${details.join("; ")}` : "";
113465
113599
  warnings.push(`Warning: failed to update plan.json phase status`);
113466
113600
  try {
113467
113601
  const planPath = validateSwarmPath(dir, "plan.json");
113468
- const planRaw = fs90.readFileSync(planPath, "utf-8");
113602
+ const planRaw = fs95.readFileSync(planPath, "utf-8");
113469
113603
  const plan = JSON.parse(planRaw);
113470
113604
  const phaseObj = plan.phases.find((p) => p.id === phase);
113471
113605
  if (phaseObj) {
113472
113606
  phaseObj.status = "complete";
113473
- fs90.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
113607
+ fs95.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
113474
113608
  }
113475
113609
  } catch {}
113476
113610
  }
@@ -113480,7 +113614,32 @@ Findings: ${details.join("; ")}` : "";
113480
113614
  }
113481
113615
  await flushPendingSnapshot(dir);
113482
113616
  await writeCheckpoint(dir).catch(() => {});
113483
- return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
113617
+ const outputData = {
113618
+ ...result,
113619
+ timestamp: event.timestamp,
113620
+ duration_ms: durationMs
113621
+ };
113622
+ return _buildOutputJson(outputData);
113623
+ }
113624
+ function _buildOutputJson(outputData) {
113625
+ let json3 = JSON.stringify(outputData, null, 2);
113626
+ if (json3.length > MAX_OUTPUT_BYTES5) {
113627
+ const truncated = {
113628
+ _truncated: true,
113629
+ _truncation_reason: `Output exceeded MAX_OUTPUT_BYTES (${MAX_OUTPUT_BYTES5}) limit`,
113630
+ phase: outputData.phase,
113631
+ success: outputData.success,
113632
+ status: outputData.status,
113633
+ message: outputData.message,
113634
+ agentsDispatched: outputData.agentsDispatched?.slice(0, 10),
113635
+ agentsMissing: outputData.agentsMissing?.slice(0, 10),
113636
+ warnings: ["(output truncated — full output exceeded size limit)"],
113637
+ timestamp: outputData.timestamp,
113638
+ duration_ms: outputData.duration_ms
113639
+ };
113640
+ json3 = JSON.stringify(truncated, null, 2);
113641
+ }
113642
+ return json3;
113484
113643
  }
113485
113644
  var phase_complete = createSwarmTool({
113486
113645
  description: "Mark a phase as complete and track which agents were dispatched. Used for phase completion gating and tracking. Accepts phase number and optional summary. Returns list of agents that were dispatched.",
@@ -113528,9 +113687,9 @@ init_discovery();
113528
113687
  init_utils();
113529
113688
  init_bun_compat();
113530
113689
  init_create_tool();
113531
- import * as fs91 from "node:fs";
113532
- import * as path128 from "node:path";
113533
- var MAX_OUTPUT_BYTES5 = 52428800;
113690
+ import * as fs96 from "node:fs";
113691
+ import * as path133 from "node:path";
113692
+ var MAX_OUTPUT_BYTES6 = 52428800;
113534
113693
  var AUDIT_TIMEOUT_MS = 120000;
113535
113694
  function isValidEcosystem(value) {
113536
113695
  return typeof value === "string" && [
@@ -113557,31 +113716,31 @@ function validateArgs3(args2) {
113557
113716
  function detectEcosystems(directory) {
113558
113717
  const ecosystems = [];
113559
113718
  const cwd = directory;
113560
- if (fs91.existsSync(path128.join(cwd, "package.json"))) {
113719
+ if (fs96.existsSync(path133.join(cwd, "package.json"))) {
113561
113720
  ecosystems.push("npm");
113562
113721
  }
113563
- if (fs91.existsSync(path128.join(cwd, "pyproject.toml")) || fs91.existsSync(path128.join(cwd, "requirements.txt"))) {
113722
+ if (fs96.existsSync(path133.join(cwd, "pyproject.toml")) || fs96.existsSync(path133.join(cwd, "requirements.txt"))) {
113564
113723
  ecosystems.push("pip");
113565
113724
  }
113566
- if (fs91.existsSync(path128.join(cwd, "Cargo.toml"))) {
113725
+ if (fs96.existsSync(path133.join(cwd, "Cargo.toml"))) {
113567
113726
  ecosystems.push("cargo");
113568
113727
  }
113569
- if (fs91.existsSync(path128.join(cwd, "go.mod"))) {
113728
+ if (fs96.existsSync(path133.join(cwd, "go.mod"))) {
113570
113729
  ecosystems.push("go");
113571
113730
  }
113572
113731
  try {
113573
- const files = fs91.readdirSync(cwd);
113732
+ const files = fs96.readdirSync(cwd);
113574
113733
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
113575
113734
  ecosystems.push("dotnet");
113576
113735
  }
113577
113736
  } catch {}
113578
- if (fs91.existsSync(path128.join(cwd, "Gemfile")) || fs91.existsSync(path128.join(cwd, "Gemfile.lock"))) {
113737
+ if (fs96.existsSync(path133.join(cwd, "Gemfile")) || fs96.existsSync(path133.join(cwd, "Gemfile.lock"))) {
113579
113738
  ecosystems.push("ruby");
113580
113739
  }
113581
- if (fs91.existsSync(path128.join(cwd, "pubspec.yaml"))) {
113740
+ if (fs96.existsSync(path133.join(cwd, "pubspec.yaml"))) {
113582
113741
  ecosystems.push("dart");
113583
113742
  }
113584
- if (fs91.existsSync(path128.join(cwd, "composer.lock"))) {
113743
+ if (fs96.existsSync(path133.join(cwd, "composer.lock"))) {
113585
113744
  ecosystems.push("composer");
113586
113745
  }
113587
113746
  return ecosystems;
@@ -113613,8 +113772,8 @@ async function runNpmAudit(directory) {
113613
113772
  };
113614
113773
  }
113615
113774
  let { stdout, stderr } = result;
113616
- if (stdout.length > MAX_OUTPUT_BYTES5) {
113617
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
113775
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
113776
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
113618
113777
  }
113619
113778
  const exitCode = await proc.exited;
113620
113779
  if (exitCode === 0) {
@@ -113733,8 +113892,8 @@ async function runPipAudit(directory) {
113733
113892
  };
113734
113893
  }
113735
113894
  let { stdout, stderr } = result;
113736
- if (stdout.length > MAX_OUTPUT_BYTES5) {
113737
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
113895
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
113896
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
113738
113897
  }
113739
113898
  const exitCode = await proc.exited;
113740
113899
  if (exitCode === 0 && !stdout.trim()) {
@@ -113861,8 +114020,8 @@ async function runCargoAudit(directory) {
113861
114020
  };
113862
114021
  }
113863
114022
  let { stdout, stderr: _stderr } = result;
113864
- if (stdout.length > MAX_OUTPUT_BYTES5) {
113865
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114023
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114024
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
113866
114025
  }
113867
114026
  const exitCode = await proc.exited;
113868
114027
  if (exitCode === 0) {
@@ -113985,8 +114144,8 @@ async function runGoAudit(directory) {
113985
114144
  };
113986
114145
  }
113987
114146
  let { stdout } = result;
113988
- if (stdout.length > MAX_OUTPUT_BYTES5) {
113989
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114147
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114148
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
113990
114149
  }
113991
114150
  const exitCode = await proc.exited;
113992
114151
  if (exitCode !== 0 && exitCode !== 3) {
@@ -114118,8 +114277,8 @@ async function runDotnetAudit(directory) {
114118
114277
  };
114119
114278
  }
114120
114279
  let { stdout } = result;
114121
- if (stdout.length > MAX_OUTPUT_BYTES5) {
114122
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114280
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114281
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
114123
114282
  }
114124
114283
  const exitCode = await proc.exited;
114125
114284
  if (exitCode !== 0 && !stdout.includes("has the following vulnerable packages")) {
@@ -114234,8 +114393,8 @@ async function runBundleAudit(directory) {
114234
114393
  };
114235
114394
  }
114236
114395
  let { stdout } = result;
114237
- if (stdout.length > MAX_OUTPUT_BYTES5) {
114238
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114396
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114397
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
114239
114398
  }
114240
114399
  const exitCode = await proc.exited;
114241
114400
  if (exitCode !== 0 && exitCode !== 1) {
@@ -114379,8 +114538,8 @@ async function runDartAudit(directory) {
114379
114538
  };
114380
114539
  }
114381
114540
  let { stdout } = result;
114382
- if (stdout.length > MAX_OUTPUT_BYTES5) {
114383
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114541
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114542
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
114384
114543
  }
114385
114544
  const exitCode = await proc.exited;
114386
114545
  if (exitCode !== 0) {
@@ -114494,8 +114653,8 @@ async function runComposerAudit(directory) {
114494
114653
  };
114495
114654
  }
114496
114655
  let { stdout } = result;
114497
- if (stdout.length > MAX_OUTPUT_BYTES5) {
114498
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES5);
114656
+ if (stdout.length > MAX_OUTPUT_BYTES6) {
114657
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES6);
114499
114658
  }
114500
114659
  const exitCode = await proc.exited;
114501
114660
  const hasVulnerabilities = (exitCode & 1) !== 0;
@@ -114716,8 +114875,8 @@ var pkg_audit = createSwarmTool({
114716
114875
  // src/tools/placeholder-scan.ts
114717
114876
  init_zod();
114718
114877
  init_manager2();
114719
- import * as fs92 from "node:fs";
114720
- import * as path129 from "node:path";
114878
+ import * as fs97 from "node:fs";
114879
+ import * as path134 from "node:path";
114721
114880
  init_utils();
114722
114881
  init_create_tool();
114723
114882
  var MAX_FILE_SIZE = 1024 * 1024;
@@ -114840,7 +114999,7 @@ function isScaffoldFile(filePath) {
114840
114999
  if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
114841
115000
  return true;
114842
115001
  }
114843
- const filename = path129.basename(filePath);
115002
+ const filename = path134.basename(filePath);
114844
115003
  if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
114845
115004
  return true;
114846
115005
  }
@@ -114857,7 +115016,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
114857
115016
  if (regex.test(normalizedPath)) {
114858
115017
  return true;
114859
115018
  }
114860
- const filename = path129.basename(filePath);
115019
+ const filename = path134.basename(filePath);
114861
115020
  const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
114862
115021
  if (filenameRegex.test(filename)) {
114863
115022
  return true;
@@ -114866,7 +115025,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
114866
115025
  return false;
114867
115026
  }
114868
115027
  function isParserSupported(filePath) {
114869
- const ext = path129.extname(filePath).toLowerCase();
115028
+ const ext = path134.extname(filePath).toLowerCase();
114870
115029
  return SUPPORTED_PARSER_EXTENSIONS.has(ext);
114871
115030
  }
114872
115031
  function isPlanFile(filePath) {
@@ -115113,28 +115272,28 @@ async function placeholderScan(input, directory) {
115113
115272
  let filesScanned = 0;
115114
115273
  const filesWithFindings = new Set;
115115
115274
  for (const filePath of changed_files) {
115116
- const fullPath = path129.isAbsolute(filePath) ? filePath : path129.resolve(directory, filePath);
115117
- const resolvedDirectory = path129.resolve(directory);
115118
- if (!fullPath.startsWith(resolvedDirectory + path129.sep) && fullPath !== resolvedDirectory) {
115275
+ const fullPath = path134.isAbsolute(filePath) ? filePath : path134.resolve(directory, filePath);
115276
+ const resolvedDirectory = path134.resolve(directory);
115277
+ if (!fullPath.startsWith(resolvedDirectory + path134.sep) && fullPath !== resolvedDirectory) {
115119
115278
  continue;
115120
115279
  }
115121
- if (!fs92.existsSync(fullPath)) {
115280
+ if (!fs97.existsSync(fullPath)) {
115122
115281
  continue;
115123
115282
  }
115124
115283
  if (isAllowedByGlobs(filePath, allow_globs)) {
115125
115284
  continue;
115126
115285
  }
115127
- const relativeFilePath = path129.relative(directory, fullPath).replace(/\\/g, "/");
115286
+ const relativeFilePath = path134.relative(directory, fullPath).replace(/\\/g, "/");
115128
115287
  if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
115129
115288
  continue;
115130
115289
  }
115131
115290
  let content;
115132
115291
  try {
115133
- const stat9 = fs92.statSync(fullPath);
115292
+ const stat9 = fs97.statSync(fullPath);
115134
115293
  if (stat9.size > MAX_FILE_SIZE) {
115135
115294
  continue;
115136
115295
  }
115137
- content = fs92.readFileSync(fullPath, "utf-8");
115296
+ content = fs97.readFileSync(fullPath, "utf-8");
115138
115297
  } catch {
115139
115298
  continue;
115140
115299
  }
@@ -115195,8 +115354,8 @@ var placeholder_scan = createSwarmTool({
115195
115354
  }
115196
115355
  });
115197
115356
  // src/tools/pre-check-batch.ts
115198
- import * as fs96 from "node:fs";
115199
- import * as path133 from "node:path";
115357
+ import * as fs101 from "node:fs";
115358
+ import * as path138 from "node:path";
115200
115359
  init_zod();
115201
115360
  init_manager2();
115202
115361
  init_utils();
@@ -115336,8 +115495,8 @@ var _internals56 = {
115336
115495
  init_zod();
115337
115496
  init_manager2();
115338
115497
  init_detector();
115339
- import * as fs95 from "node:fs";
115340
- import * as path132 from "node:path";
115498
+ import * as fs100 from "node:fs";
115499
+ import * as path137 from "node:path";
115341
115500
  import { extname as extname20 } from "node:path";
115342
115501
 
115343
115502
  // src/sast/rules/c.ts
@@ -116052,8 +116211,8 @@ function executeRulesSync(filePath, content, language) {
116052
116211
 
116053
116212
  // src/sast/semgrep.ts
116054
116213
  import * as child_process9 from "node:child_process";
116055
- import * as fs93 from "node:fs";
116056
- import * as path130 from "node:path";
116214
+ import * as fs98 from "node:fs";
116215
+ import * as path135 from "node:path";
116057
116216
  var semgrepAvailableCache = null;
116058
116217
  var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
116059
116218
  var DEFAULT_TIMEOUT_MS3 = 30000;
@@ -116240,14 +116399,14 @@ async function runSemgrep(options) {
116240
116399
  }
116241
116400
  function getRulesDirectory(projectRoot) {
116242
116401
  if (projectRoot) {
116243
- return path130.resolve(projectRoot, DEFAULT_RULES_DIR);
116402
+ return path135.resolve(projectRoot, DEFAULT_RULES_DIR);
116244
116403
  }
116245
116404
  return DEFAULT_RULES_DIR;
116246
116405
  }
116247
116406
  function hasBundledRules(projectRoot) {
116248
116407
  const rulesDir = getRulesDirectory(projectRoot);
116249
116408
  try {
116250
- return fs93.existsSync(rulesDir);
116409
+ return fs98.existsSync(rulesDir);
116251
116410
  } catch {
116252
116411
  return false;
116253
116412
  }
@@ -116260,25 +116419,25 @@ init_create_tool();
116260
116419
  // src/tools/sast-baseline.ts
116261
116420
  init_utils2();
116262
116421
  import * as crypto11 from "node:crypto";
116263
- import * as fs94 from "node:fs";
116264
- import * as path131 from "node:path";
116422
+ import * as fs99 from "node:fs";
116423
+ import * as path136 from "node:path";
116265
116424
  var BASELINE_SCHEMA_VERSION = "1.0.0";
116266
116425
  var MAX_BASELINE_FINDINGS = 2000;
116267
116426
  var MAX_BASELINE_BYTES = 2 * 1048576;
116268
116427
  var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
116269
116428
  function normalizeFindingPath(directory, file3) {
116270
- const resolved = path131.isAbsolute(file3) ? file3 : path131.resolve(directory, file3);
116271
- const rel = path131.relative(path131.resolve(directory), resolved);
116429
+ const resolved = path136.isAbsolute(file3) ? file3 : path136.resolve(directory, file3);
116430
+ const rel = path136.relative(path136.resolve(directory), resolved);
116272
116431
  return rel.replace(/\\/g, "/");
116273
116432
  }
116274
116433
  function baselineRelPath(phase) {
116275
- return path131.join("evidence", String(phase), "sast-baseline.json");
116434
+ return path136.join("evidence", String(phase), "sast-baseline.json");
116276
116435
  }
116277
116436
  function tempRelPath(phase) {
116278
- return path131.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
116437
+ return path136.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
116279
116438
  }
116280
116439
  function lockRelPath(phase) {
116281
- return path131.join("evidence", String(phase), "sast-baseline.json.lock");
116440
+ return path136.join("evidence", String(phase), "sast-baseline.json.lock");
116282
116441
  }
116283
116442
  function getLine(lines, idx) {
116284
116443
  if (idx < 0 || idx >= lines.length)
@@ -116295,7 +116454,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
116295
116454
  }
116296
116455
  const lineNum = finding.location.line;
116297
116456
  try {
116298
- const content = fs94.readFileSync(finding.location.file, "utf-8");
116457
+ const content = fs99.readFileSync(finding.location.file, "utf-8");
116299
116458
  const lines = content.split(`
116300
116459
  `);
116301
116460
  const idx = lineNum - 1;
@@ -116326,7 +116485,7 @@ function assignOccurrenceIndices(findings, directory) {
116326
116485
  try {
116327
116486
  if (relFile.startsWith(".."))
116328
116487
  throw new Error("escapes workspace");
116329
- const content = fs94.readFileSync(finding.location.file, "utf-8");
116488
+ const content = fs99.readFileSync(finding.location.file, "utf-8");
116330
116489
  const lines = content.split(`
116331
116490
  `);
116332
116491
  const idx = lineNum - 1;
@@ -116355,11 +116514,11 @@ function assignOccurrenceIndices(findings, directory) {
116355
116514
  async function acquireLock2(lockPath) {
116356
116515
  for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
116357
116516
  try {
116358
- const fd = fs94.openSync(lockPath, "wx");
116359
- fs94.closeSync(fd);
116517
+ const fd = fs99.openSync(lockPath, "wx");
116518
+ fs99.closeSync(fd);
116360
116519
  return () => {
116361
116520
  try {
116362
- fs94.unlinkSync(lockPath);
116521
+ fs99.unlinkSync(lockPath);
116363
116522
  } catch {}
116364
116523
  };
116365
116524
  } catch {
@@ -116399,13 +116558,13 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
116399
116558
  message: e instanceof Error ? e.message : "Path validation failed"
116400
116559
  };
116401
116560
  }
116402
- fs94.mkdirSync(path131.dirname(baselinePath), { recursive: true });
116403
- fs94.mkdirSync(path131.dirname(tempPath), { recursive: true });
116561
+ fs99.mkdirSync(path136.dirname(baselinePath), { recursive: true });
116562
+ fs99.mkdirSync(path136.dirname(tempPath), { recursive: true });
116404
116563
  const releaseLock = await acquireLock2(lockPath);
116405
116564
  try {
116406
116565
  let existing = null;
116407
116566
  try {
116408
- const raw = fs94.readFileSync(baselinePath, "utf-8");
116567
+ const raw = fs99.readFileSync(baselinePath, "utf-8");
116409
116568
  const parsed = JSON.parse(raw);
116410
116569
  if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
116411
116570
  existing = parsed;
@@ -116465,8 +116624,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
116465
116624
  message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
116466
116625
  };
116467
116626
  }
116468
- fs94.writeFileSync(tempPath, json4, "utf-8");
116469
- fs94.renameSync(tempPath, baselinePath);
116627
+ fs99.writeFileSync(tempPath, json4, "utf-8");
116628
+ fs99.renameSync(tempPath, baselinePath);
116470
116629
  return {
116471
116630
  status: "merged",
116472
116631
  path: baselinePath,
@@ -116497,8 +116656,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
116497
116656
  message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
116498
116657
  };
116499
116658
  }
116500
- fs94.writeFileSync(tempPath, json3, "utf-8");
116501
- fs94.renameSync(tempPath, baselinePath);
116659
+ fs99.writeFileSync(tempPath, json3, "utf-8");
116660
+ fs99.renameSync(tempPath, baselinePath);
116502
116661
  return {
116503
116662
  status: "written",
116504
116663
  path: baselinePath,
@@ -116523,7 +116682,7 @@ function loadBaseline(directory, phase) {
116523
116682
  };
116524
116683
  }
116525
116684
  try {
116526
- const raw = fs94.readFileSync(baselinePath, "utf-8");
116685
+ const raw = fs99.readFileSync(baselinePath, "utf-8");
116527
116686
  const parsed = JSON.parse(raw);
116528
116687
  if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
116529
116688
  return {
@@ -116571,17 +116730,17 @@ var SEVERITY_ORDER = {
116571
116730
  };
116572
116731
  function shouldSkipFile(filePath) {
116573
116732
  try {
116574
- const stats = fs95.statSync(filePath);
116733
+ const stats = fs100.statSync(filePath);
116575
116734
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
116576
116735
  return { skip: true, reason: "file too large" };
116577
116736
  }
116578
116737
  if (stats.size === 0) {
116579
116738
  return { skip: true, reason: "empty file" };
116580
116739
  }
116581
- const fd = fs95.openSync(filePath, "r");
116740
+ const fd = fs100.openSync(filePath, "r");
116582
116741
  const buffer = Buffer.alloc(8192);
116583
- const bytesRead = fs95.readSync(fd, buffer, 0, 8192, 0);
116584
- fs95.closeSync(fd);
116742
+ const bytesRead = fs100.readSync(fd, buffer, 0, 8192, 0);
116743
+ fs100.closeSync(fd);
116585
116744
  if (bytesRead > 0) {
116586
116745
  let nullCount = 0;
116587
116746
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -116620,7 +116779,7 @@ function countBySeverity(findings) {
116620
116779
  }
116621
116780
  function scanFileWithTierA(filePath, language) {
116622
116781
  try {
116623
- const content = fs95.readFileSync(filePath, "utf-8");
116782
+ const content = fs100.readFileSync(filePath, "utf-8");
116624
116783
  const findings = executeRulesSync(filePath, content, language);
116625
116784
  return findings.map((f) => ({
116626
116785
  rule_id: f.rule_id,
@@ -116673,13 +116832,13 @@ async function sastScan(input, directory, config3) {
116673
116832
  _filesSkipped++;
116674
116833
  continue;
116675
116834
  }
116676
- const resolvedPath = path132.isAbsolute(filePath) ? filePath : path132.resolve(directory, filePath);
116677
- const resolvedDirectory = path132.resolve(directory);
116678
- if (!resolvedPath.startsWith(resolvedDirectory + path132.sep) && resolvedPath !== resolvedDirectory) {
116835
+ const resolvedPath = path137.isAbsolute(filePath) ? filePath : path137.resolve(directory, filePath);
116836
+ const resolvedDirectory = path137.resolve(directory);
116837
+ if (!resolvedPath.startsWith(resolvedDirectory + path137.sep) && resolvedPath !== resolvedDirectory) {
116679
116838
  _filesSkipped++;
116680
116839
  continue;
116681
116840
  }
116682
- if (!fs95.existsSync(resolvedPath)) {
116841
+ if (!fs100.existsSync(resolvedPath)) {
116683
116842
  _filesSkipped++;
116684
116843
  continue;
116685
116844
  }
@@ -116990,18 +117149,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
116990
117149
  let resolved;
116991
117150
  const isWinAbs = isWindowsAbsolutePath(inputPath);
116992
117151
  if (isWinAbs) {
116993
- resolved = path133.win32.resolve(inputPath);
116994
- } else if (path133.isAbsolute(inputPath)) {
116995
- resolved = path133.resolve(inputPath);
117152
+ resolved = path138.win32.resolve(inputPath);
117153
+ } else if (path138.isAbsolute(inputPath)) {
117154
+ resolved = path138.resolve(inputPath);
116996
117155
  } else {
116997
- resolved = path133.resolve(baseDir, inputPath);
117156
+ resolved = path138.resolve(baseDir, inputPath);
116998
117157
  }
116999
- const workspaceResolved = path133.resolve(workspaceDir);
117158
+ const workspaceResolved = path138.resolve(workspaceDir);
117000
117159
  let relative27;
117001
117160
  if (isWinAbs) {
117002
- relative27 = path133.win32.relative(workspaceResolved, resolved);
117161
+ relative27 = path138.win32.relative(workspaceResolved, resolved);
117003
117162
  } else {
117004
- relative27 = path133.relative(workspaceResolved, resolved);
117163
+ relative27 = path138.relative(workspaceResolved, resolved);
117005
117164
  }
117006
117165
  if (relative27.startsWith("..")) {
117007
117166
  return "path traversal detected";
@@ -117066,7 +117225,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
117066
117225
  if (typeof file3 !== "string") {
117067
117226
  continue;
117068
117227
  }
117069
- const resolvedPath = path133.resolve(file3);
117228
+ const resolvedPath = path138.resolve(file3);
117070
117229
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
117071
117230
  if (validationError) {
117072
117231
  continue;
@@ -117223,7 +117382,7 @@ async function runSecretscanWithFiles(files, directory) {
117223
117382
  skippedFiles++;
117224
117383
  continue;
117225
117384
  }
117226
- const resolvedPath = path133.resolve(file3);
117385
+ const resolvedPath = path138.resolve(file3);
117227
117386
  const validationError = validatePath(resolvedPath, directory, directory);
117228
117387
  if (validationError) {
117229
117388
  skippedFiles++;
@@ -117241,14 +117400,14 @@ async function runSecretscanWithFiles(files, directory) {
117241
117400
  };
117242
117401
  }
117243
117402
  for (const file3 of validatedFiles) {
117244
- const ext = path133.extname(file3).toLowerCase();
117403
+ const ext = path138.extname(file3).toLowerCase();
117245
117404
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
117246
117405
  skippedFiles++;
117247
117406
  continue;
117248
117407
  }
117249
117408
  let stat9;
117250
117409
  try {
117251
- stat9 = fs96.statSync(file3);
117410
+ stat9 = fs101.statSync(file3);
117252
117411
  } catch {
117253
117412
  skippedFiles++;
117254
117413
  continue;
@@ -117259,7 +117418,7 @@ async function runSecretscanWithFiles(files, directory) {
117259
117418
  }
117260
117419
  let content;
117261
117420
  try {
117262
- const buffer = fs96.readFileSync(file3);
117421
+ const buffer = fs101.readFileSync(file3);
117263
117422
  if (buffer.includes(0)) {
117264
117423
  skippedFiles++;
117265
117424
  continue;
@@ -117460,7 +117619,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
117460
117619
  const preexistingFindings = [];
117461
117620
  for (const finding of findings) {
117462
117621
  const filePath = finding.location.file;
117463
- const normalised = path133.relative(directory, filePath).replace(/\\/g, "/");
117622
+ const normalised = path138.relative(directory, filePath).replace(/\\/g, "/");
117464
117623
  const changedLines = changedLineRanges.get(normalised);
117465
117624
  if (changedLines?.has(finding.location.line)) {
117466
117625
  newFindings.push(finding);
@@ -117511,7 +117670,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
117511
117670
  warn(`pre_check_batch: Invalid file path: ${file3}`);
117512
117671
  continue;
117513
117672
  }
117514
- changedFiles.push(path133.resolve(directory, file3));
117673
+ changedFiles.push(path138.resolve(directory, file3));
117515
117674
  }
117516
117675
  if (changedFiles.length === 0) {
117517
117676
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -117712,9 +117871,9 @@ var pre_check_batch = createSwarmTool({
117712
117871
  };
117713
117872
  return JSON.stringify(errorResult, null, 2);
117714
117873
  }
117715
- const resolvedDirectory = path133.resolve(typedArgs.directory);
117716
- const workspaceAnchor = path133.resolve(directory);
117717
- if (resolvedDirectory !== workspaceAnchor && resolvedDirectory.startsWith(workspaceAnchor + path133.sep)) {
117874
+ const resolvedDirectory = path138.resolve(typedArgs.directory);
117875
+ const workspaceAnchor = path138.resolve(directory);
117876
+ if (resolvedDirectory !== workspaceAnchor && resolvedDirectory.startsWith(workspaceAnchor + path138.sep)) {
117718
117877
  const subDirError = `directory "${typedArgs.directory}" is a subdirectory of the project root — pre_check_batch requires the project root directory "${workspaceAnchor}"`;
117719
117878
  const subDirResult = {
117720
117879
  gates_passed: false,
@@ -117765,7 +117924,7 @@ var pre_check_batch = createSwarmTool({
117765
117924
  });
117766
117925
  // src/tools/repo-map.ts
117767
117926
  init_zod();
117768
- import * as path134 from "node:path";
117927
+ import * as path139 from "node:path";
117769
117928
  init_path_security();
117770
117929
  init_create_tool();
117771
117930
  var VALID_ACTIONS = [
@@ -117790,7 +117949,7 @@ function validateFile(p) {
117790
117949
  return "file contains control characters";
117791
117950
  if (containsPathTraversal(p))
117792
117951
  return "file contains path traversal";
117793
- if (path134.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
117952
+ if (path139.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
117794
117953
  return "file must be a workspace-relative path, not absolute";
117795
117954
  }
117796
117955
  return null;
@@ -117813,8 +117972,8 @@ function ok(action, payload) {
117813
117972
  }
117814
117973
  function toRelativeGraphPath(input, workspaceRoot) {
117815
117974
  const normalized = input.replace(/\\/g, "/");
117816
- if (path134.isAbsolute(normalized)) {
117817
- const rel = path134.relative(workspaceRoot, normalized).replace(/\\/g, "/");
117975
+ if (path139.isAbsolute(normalized)) {
117976
+ const rel = path139.relative(workspaceRoot, normalized).replace(/\\/g, "/");
117818
117977
  return normalizeGraphPath2(rel);
117819
117978
  }
117820
117979
  return normalizeGraphPath2(normalized);
@@ -117958,8 +118117,8 @@ var repo_map = createSwarmTool({
117958
118117
  // src/tools/req-coverage.ts
117959
118118
  init_zod();
117960
118119
  init_create_tool();
117961
- import * as fs97 from "node:fs";
117962
- import * as path135 from "node:path";
118120
+ import * as fs102 from "node:fs";
118121
+ import * as path140 from "node:path";
117963
118122
  var SPEC_FILE = ".swarm/spec.md";
117964
118123
  var EVIDENCE_DIR4 = ".swarm/evidence";
117965
118124
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -118018,19 +118177,19 @@ function extractObligationAndText(id, lineText) {
118018
118177
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
118019
118178
  function readTouchedFiles(evidenceDir, phase, cwd) {
118020
118179
  const touchedFiles = new Set;
118021
- if (!fs97.existsSync(evidenceDir) || !fs97.statSync(evidenceDir).isDirectory()) {
118180
+ if (!fs102.existsSync(evidenceDir) || !fs102.statSync(evidenceDir).isDirectory()) {
118022
118181
  return [];
118023
118182
  }
118024
118183
  let entries;
118025
118184
  try {
118026
- entries = fs97.readdirSync(evidenceDir);
118185
+ entries = fs102.readdirSync(evidenceDir);
118027
118186
  } catch {
118028
118187
  return [];
118029
118188
  }
118030
118189
  for (const entry of entries) {
118031
- const entryPath = path135.join(evidenceDir, entry);
118190
+ const entryPath = path140.join(evidenceDir, entry);
118032
118191
  try {
118033
- const stat9 = fs97.statSync(entryPath);
118192
+ const stat9 = fs102.statSync(entryPath);
118034
118193
  if (!stat9.isDirectory()) {
118035
118194
  continue;
118036
118195
  }
@@ -118044,14 +118203,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
118044
118203
  if (entryPhase !== String(phase)) {
118045
118204
  continue;
118046
118205
  }
118047
- const evidenceFilePath = path135.join(entryPath, "evidence.json");
118206
+ const evidenceFilePath = path140.join(entryPath, "evidence.json");
118048
118207
  try {
118049
- const resolvedPath = path135.resolve(evidenceFilePath);
118050
- const evidenceDirResolved = path135.resolve(evidenceDir);
118051
- if (!resolvedPath.startsWith(evidenceDirResolved + path135.sep)) {
118208
+ const resolvedPath = path140.resolve(evidenceFilePath);
118209
+ const evidenceDirResolved = path140.resolve(evidenceDir);
118210
+ if (!resolvedPath.startsWith(evidenceDirResolved + path140.sep)) {
118052
118211
  continue;
118053
118212
  }
118054
- const stat9 = fs97.lstatSync(evidenceFilePath);
118213
+ const stat9 = fs102.lstatSync(evidenceFilePath);
118055
118214
  if (!stat9.isFile()) {
118056
118215
  continue;
118057
118216
  }
@@ -118063,7 +118222,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
118063
118222
  }
118064
118223
  let content;
118065
118224
  try {
118066
- content = fs97.readFileSync(evidenceFilePath, "utf-8");
118225
+ content = fs102.readFileSync(evidenceFilePath, "utf-8");
118067
118226
  } catch {
118068
118227
  continue;
118069
118228
  }
@@ -118082,7 +118241,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
118082
118241
  if (Array.isArray(diffEntry.files_changed)) {
118083
118242
  for (const file3 of diffEntry.files_changed) {
118084
118243
  if (typeof file3 === "string") {
118085
- touchedFiles.add(path135.resolve(cwd, file3));
118244
+ touchedFiles.add(path140.resolve(cwd, file3));
118086
118245
  }
118087
118246
  }
118088
118247
  }
@@ -118095,12 +118254,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
118095
118254
  }
118096
118255
  function searchFileForKeywords(filePath, keywords, cwd) {
118097
118256
  try {
118098
- const resolvedPath = path135.resolve(filePath);
118099
- const cwdResolved = path135.resolve(cwd);
118257
+ const resolvedPath = path140.resolve(filePath);
118258
+ const cwdResolved = path140.resolve(cwd);
118100
118259
  if (!resolvedPath.startsWith(cwdResolved)) {
118101
118260
  return false;
118102
118261
  }
118103
- const content = fs97.readFileSync(resolvedPath, "utf-8");
118262
+ const content = fs102.readFileSync(resolvedPath, "utf-8");
118104
118263
  for (const keyword of keywords) {
118105
118264
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
118106
118265
  if (regex.test(content)) {
@@ -118230,10 +118389,10 @@ var req_coverage = createSwarmTool({
118230
118389
  }, null, 2);
118231
118390
  }
118232
118391
  const cwd = inputDirectory || directory;
118233
- const specPath = path135.join(cwd, SPEC_FILE);
118392
+ const specPath = path140.join(cwd, SPEC_FILE);
118234
118393
  let specContent;
118235
118394
  try {
118236
- specContent = fs97.readFileSync(specPath, "utf-8");
118395
+ specContent = fs102.readFileSync(specPath, "utf-8");
118237
118396
  } catch (readError) {
118238
118397
  return JSON.stringify({
118239
118398
  success: false,
@@ -118257,7 +118416,7 @@ var req_coverage = createSwarmTool({
118257
118416
  message: "No FR requirements found in spec.md"
118258
118417
  }, null, 2);
118259
118418
  }
118260
- const evidenceDir = path135.join(cwd, EVIDENCE_DIR4);
118419
+ const evidenceDir = path140.join(cwd, EVIDENCE_DIR4);
118261
118420
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
118262
118421
  const analyzedRequirements = [];
118263
118422
  let coveredCount = 0;
@@ -118283,12 +118442,12 @@ var req_coverage = createSwarmTool({
118283
118442
  requirements: analyzedRequirements
118284
118443
  };
118285
118444
  const reportFilename = `req-coverage-phase-${phase}.json`;
118286
- const reportPath = path135.join(evidenceDir, reportFilename);
118445
+ const reportPath = path140.join(evidenceDir, reportFilename);
118287
118446
  try {
118288
- if (!fs97.existsSync(evidenceDir)) {
118289
- fs97.mkdirSync(evidenceDir, { recursive: true });
118447
+ if (!fs102.existsSync(evidenceDir)) {
118448
+ fs102.mkdirSync(evidenceDir, { recursive: true });
118290
118449
  }
118291
- fs97.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
118450
+ fs102.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
118292
118451
  } catch (writeError) {
118293
118452
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
118294
118453
  }
@@ -118370,8 +118529,8 @@ init_plan_schema();
118370
118529
  init_qa_gate_profile();
118371
118530
  init_file_locks();
118372
118531
  import * as crypto12 from "node:crypto";
118373
- import * as fs98 from "node:fs";
118374
- import * as path136 from "node:path";
118532
+ import * as fs103 from "node:fs";
118533
+ import * as path141 from "node:path";
118375
118534
  init_ledger();
118376
118535
  init_manager();
118377
118536
  init_state();
@@ -118452,17 +118611,17 @@ async function executeSavePlan(args2, fallbackDir) {
118452
118611
  };
118453
118612
  }
118454
118613
  if (args2.working_directory && fallbackDir) {
118455
- const resolvedTarget = path136.resolve(args2.working_directory);
118456
- const resolvedRoot = path136.resolve(fallbackDir);
118614
+ const resolvedTarget = path141.resolve(args2.working_directory);
118615
+ const resolvedRoot = path141.resolve(fallbackDir);
118457
118616
  let fallbackExists = false;
118458
118617
  try {
118459
- fs98.accessSync(resolvedRoot, fs98.constants.F_OK);
118618
+ fs103.accessSync(resolvedRoot, fs103.constants.F_OK);
118460
118619
  fallbackExists = true;
118461
118620
  } catch {
118462
118621
  fallbackExists = false;
118463
118622
  }
118464
118623
  if (fallbackExists) {
118465
- const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path136.sep);
118624
+ const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path141.sep);
118466
118625
  if (isSubdirectory) {
118467
118626
  return {
118468
118627
  success: false,
@@ -118478,11 +118637,11 @@ async function executeSavePlan(args2, fallbackDir) {
118478
118637
  let specMtime;
118479
118638
  let specHash;
118480
118639
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
118481
- const specPath = path136.join(targetWorkspace, ".swarm", "spec.md");
118640
+ const specPath = path141.join(targetWorkspace, ".swarm", "spec.md");
118482
118641
  try {
118483
- const stat9 = await fs98.promises.stat(specPath);
118642
+ const stat9 = await fs103.promises.stat(specPath);
118484
118643
  specMtime = stat9.mtime.toISOString();
118485
- const content = await fs98.promises.readFile(specPath, "utf8");
118644
+ const content = await fs103.promises.readFile(specPath, "utf8");
118486
118645
  specHash = crypto12.createHash("sha256").update(content).digest("hex");
118487
118646
  } catch {
118488
118647
  return {
@@ -118494,10 +118653,10 @@ async function executeSavePlan(args2, fallbackDir) {
118494
118653
  }
118495
118654
  }
118496
118655
  if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
118497
- const contextPath = path136.join(targetWorkspace, ".swarm", "context.md");
118656
+ const contextPath = path141.join(targetWorkspace, ".swarm", "context.md");
118498
118657
  let contextContent = "";
118499
118658
  try {
118500
- contextContent = await fs98.promises.readFile(contextPath, "utf8");
118659
+ contextContent = await fs103.promises.readFile(contextPath, "utf8");
118501
118660
  } catch {}
118502
118661
  const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
118503
118662
  if (!hasPendingSection) {
@@ -118784,14 +118943,14 @@ async function executeSavePlan(args2, fallbackDir) {
118784
118943
  }
118785
118944
  await writeCheckpoint(dir).catch(() => {});
118786
118945
  try {
118787
- const markerPath = path136.join(dir, ".swarm", ".plan-write-marker");
118946
+ const markerPath = path141.join(dir, ".swarm", ".plan-write-marker");
118788
118947
  const marker = JSON.stringify({
118789
118948
  source: "save_plan",
118790
118949
  timestamp: new Date().toISOString(),
118791
118950
  phases_count: plan.phases.length,
118792
118951
  tasks_count: tasksCount
118793
118952
  });
118794
- await fs98.promises.writeFile(markerPath, marker, "utf8");
118953
+ await fs103.promises.writeFile(markerPath, marker, "utf8");
118795
118954
  } catch {}
118796
118955
  const warnings = [];
118797
118956
  let criticReviewFound = false;
@@ -118807,7 +118966,7 @@ async function executeSavePlan(args2, fallbackDir) {
118807
118966
  return {
118808
118967
  success: true,
118809
118968
  message: "Plan saved successfully",
118810
- plan_path: path136.join(dir, ".swarm", "plan.json"),
118969
+ plan_path: path141.join(dir, ".swarm", "plan.json"),
118811
118970
  phases_count: plan.phases.length,
118812
118971
  tasks_count: tasksCount,
118813
118972
  ...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
@@ -118872,8 +119031,8 @@ var save_plan = createSwarmTool({
118872
119031
  // src/tools/sbom-generate.ts
118873
119032
  init_zod();
118874
119033
  init_manager2();
118875
- import * as fs99 from "node:fs";
118876
- import * as path137 from "node:path";
119034
+ import * as fs104 from "node:fs";
119035
+ import * as path142 from "node:path";
118877
119036
 
118878
119037
  // src/sbom/detectors/index.ts
118879
119038
  init_utils();
@@ -119721,9 +119880,9 @@ function findManifestFiles(rootDir) {
119721
119880
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
119722
119881
  function searchDir(dir) {
119723
119882
  try {
119724
- const entries = fs99.readdirSync(dir, { withFileTypes: true });
119883
+ const entries = fs104.readdirSync(dir, { withFileTypes: true });
119725
119884
  for (const entry of entries) {
119726
- const fullPath = path137.join(dir, entry.name);
119885
+ const fullPath = path142.join(dir, entry.name);
119727
119886
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
119728
119887
  continue;
119729
119888
  }
@@ -119732,7 +119891,7 @@ function findManifestFiles(rootDir) {
119732
119891
  } else if (entry.isFile()) {
119733
119892
  for (const pattern of patterns) {
119734
119893
  if (simpleGlobToRegex(pattern).test(entry.name)) {
119735
- manifestFiles.push(path137.relative(rootDir, fullPath));
119894
+ manifestFiles.push(path142.relative(rootDir, fullPath));
119736
119895
  break;
119737
119896
  }
119738
119897
  }
@@ -119748,13 +119907,13 @@ function findManifestFilesInDirs(directories, workingDir) {
119748
119907
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
119749
119908
  for (const dir of directories) {
119750
119909
  try {
119751
- const entries = fs99.readdirSync(dir, { withFileTypes: true });
119910
+ const entries = fs104.readdirSync(dir, { withFileTypes: true });
119752
119911
  for (const entry of entries) {
119753
- const fullPath = path137.join(dir, entry.name);
119912
+ const fullPath = path142.join(dir, entry.name);
119754
119913
  if (entry.isFile()) {
119755
119914
  for (const pattern of patterns) {
119756
119915
  if (simpleGlobToRegex(pattern).test(entry.name)) {
119757
- found.push(path137.relative(workingDir, fullPath));
119916
+ found.push(path142.relative(workingDir, fullPath));
119758
119917
  break;
119759
119918
  }
119760
119919
  }
@@ -119767,11 +119926,11 @@ function findManifestFilesInDirs(directories, workingDir) {
119767
119926
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
119768
119927
  const dirs = new Set;
119769
119928
  for (const file3 of changedFiles) {
119770
- let currentDir = path137.dirname(file3);
119929
+ let currentDir = path142.dirname(file3);
119771
119930
  while (true) {
119772
- if (currentDir && currentDir !== "." && currentDir !== path137.sep) {
119773
- dirs.add(path137.join(workingDir, currentDir));
119774
- const parent = path137.dirname(currentDir);
119931
+ if (currentDir && currentDir !== "." && currentDir !== path142.sep) {
119932
+ dirs.add(path142.join(workingDir, currentDir));
119933
+ const parent = path142.dirname(currentDir);
119775
119934
  if (parent === currentDir)
119776
119935
  break;
119777
119936
  currentDir = parent;
@@ -119785,7 +119944,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
119785
119944
  }
119786
119945
  function ensureOutputDir(outputDir) {
119787
119946
  try {
119788
- fs99.mkdirSync(outputDir, { recursive: true });
119947
+ fs104.mkdirSync(outputDir, { recursive: true });
119789
119948
  } catch (error93) {
119790
119949
  if (!error93 || error93.code !== "EEXIST") {
119791
119950
  throw error93;
@@ -119855,7 +120014,7 @@ var sbom_generate = createSwarmTool({
119855
120014
  const changedFiles = obj.changed_files;
119856
120015
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
119857
120016
  const workingDir = directory;
119858
- const outputDir = path137.isAbsolute(relativeOutputDir) ? relativeOutputDir : path137.join(workingDir, relativeOutputDir);
120017
+ const outputDir = path142.isAbsolute(relativeOutputDir) ? relativeOutputDir : path142.join(workingDir, relativeOutputDir);
119859
120018
  let manifestFiles = [];
119860
120019
  if (scope === "all") {
119861
120020
  manifestFiles = findManifestFiles(workingDir);
@@ -119878,11 +120037,11 @@ var sbom_generate = createSwarmTool({
119878
120037
  const processedFiles = [];
119879
120038
  for (const manifestFile of manifestFiles) {
119880
120039
  try {
119881
- const fullPath = path137.isAbsolute(manifestFile) ? manifestFile : path137.join(workingDir, manifestFile);
119882
- if (!fs99.existsSync(fullPath)) {
120040
+ const fullPath = path142.isAbsolute(manifestFile) ? manifestFile : path142.join(workingDir, manifestFile);
120041
+ if (!fs104.existsSync(fullPath)) {
119883
120042
  continue;
119884
120043
  }
119885
- const content = fs99.readFileSync(fullPath, "utf-8");
120044
+ const content = fs104.readFileSync(fullPath, "utf-8");
119886
120045
  const components = detectComponents(manifestFile, content);
119887
120046
  processedFiles.push(manifestFile);
119888
120047
  if (components.length > 0) {
@@ -119895,8 +120054,8 @@ var sbom_generate = createSwarmTool({
119895
120054
  const bom = generateCycloneDX(allComponents);
119896
120055
  const bomJson = serializeCycloneDX(bom);
119897
120056
  const filename = generateSbomFilename();
119898
- const outputPath = path137.join(outputDir, filename);
119899
- fs99.writeFileSync(outputPath, bomJson, "utf-8");
120057
+ const outputPath = path142.join(outputDir, filename);
120058
+ fs104.writeFileSync(outputPath, bomJson, "utf-8");
119900
120059
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
119901
120060
  try {
119902
120061
  const timestamp = new Date().toISOString();
@@ -119938,8 +120097,8 @@ var sbom_generate = createSwarmTool({
119938
120097
  // src/tools/schema-drift.ts
119939
120098
  init_zod();
119940
120099
  init_create_tool();
119941
- import * as fs100 from "node:fs";
119942
- import * as path138 from "node:path";
120100
+ import * as fs105 from "node:fs";
120101
+ import * as path143 from "node:path";
119943
120102
  var SPEC_CANDIDATES = [
119944
120103
  "openapi.json",
119945
120104
  "openapi.yaml",
@@ -119971,28 +120130,28 @@ function normalizePath4(p) {
119971
120130
  }
119972
120131
  function discoverSpecFile(cwd, specFileArg) {
119973
120132
  if (specFileArg) {
119974
- const resolvedPath = path138.resolve(cwd, specFileArg);
119975
- const normalizedCwd = cwd.endsWith(path138.sep) ? cwd : cwd + path138.sep;
120133
+ const resolvedPath = path143.resolve(cwd, specFileArg);
120134
+ const normalizedCwd = cwd.endsWith(path143.sep) ? cwd : cwd + path143.sep;
119976
120135
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
119977
120136
  throw new Error("Invalid spec_file: path traversal detected");
119978
120137
  }
119979
- const ext = path138.extname(resolvedPath).toLowerCase();
120138
+ const ext = path143.extname(resolvedPath).toLowerCase();
119980
120139
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
119981
120140
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
119982
120141
  }
119983
- const stats = fs100.statSync(resolvedPath);
120142
+ const stats = fs105.statSync(resolvedPath);
119984
120143
  if (stats.size > MAX_SPEC_SIZE) {
119985
120144
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
119986
120145
  }
119987
- if (!fs100.existsSync(resolvedPath)) {
120146
+ if (!fs105.existsSync(resolvedPath)) {
119988
120147
  throw new Error(`Spec file not found: ${resolvedPath}`);
119989
120148
  }
119990
120149
  return resolvedPath;
119991
120150
  }
119992
120151
  for (const candidate of SPEC_CANDIDATES) {
119993
- const candidatePath = path138.resolve(cwd, candidate);
119994
- if (fs100.existsSync(candidatePath)) {
119995
- const stats = fs100.statSync(candidatePath);
120152
+ const candidatePath = path143.resolve(cwd, candidate);
120153
+ if (fs105.existsSync(candidatePath)) {
120154
+ const stats = fs105.statSync(candidatePath);
119996
120155
  if (stats.size <= MAX_SPEC_SIZE) {
119997
120156
  return candidatePath;
119998
120157
  }
@@ -120001,8 +120160,8 @@ function discoverSpecFile(cwd, specFileArg) {
120001
120160
  return null;
120002
120161
  }
120003
120162
  function parseSpec(specFile) {
120004
- const content = fs100.readFileSync(specFile, "utf-8");
120005
- const ext = path138.extname(specFile).toLowerCase();
120163
+ const content = fs105.readFileSync(specFile, "utf-8");
120164
+ const ext = path143.extname(specFile).toLowerCase();
120006
120165
  if (ext === ".json") {
120007
120166
  return parseJsonSpec(content);
120008
120167
  }
@@ -120073,12 +120232,12 @@ function extractRoutes(cwd) {
120073
120232
  function walkDir(dir) {
120074
120233
  let entries;
120075
120234
  try {
120076
- entries = fs100.readdirSync(dir, { withFileTypes: true });
120235
+ entries = fs105.readdirSync(dir, { withFileTypes: true });
120077
120236
  } catch {
120078
120237
  return;
120079
120238
  }
120080
120239
  for (const entry of entries) {
120081
- const fullPath = path138.join(dir, entry.name);
120240
+ const fullPath = path143.join(dir, entry.name);
120082
120241
  if (entry.isSymbolicLink()) {
120083
120242
  continue;
120084
120243
  }
@@ -120088,7 +120247,7 @@ function extractRoutes(cwd) {
120088
120247
  }
120089
120248
  walkDir(fullPath);
120090
120249
  } else if (entry.isFile()) {
120091
- const ext = path138.extname(entry.name).toLowerCase();
120250
+ const ext = path143.extname(entry.name).toLowerCase();
120092
120251
  const baseName = entry.name.toLowerCase();
120093
120252
  if (![".ts", ".js", ".mjs"].includes(ext)) {
120094
120253
  continue;
@@ -120106,7 +120265,7 @@ function extractRoutes(cwd) {
120106
120265
  }
120107
120266
  function extractRoutesFromFile(filePath) {
120108
120267
  const routes = [];
120109
- const content = fs100.readFileSync(filePath, "utf-8");
120268
+ const content = fs105.readFileSync(filePath, "utf-8");
120110
120269
  const lines = content.split(/\r?\n/);
120111
120270
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
120112
120271
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -120255,8 +120414,8 @@ init_zod();
120255
120414
  init_bun_compat();
120256
120415
  init_path_security();
120257
120416
  init_create_tool();
120258
- import * as fs101 from "node:fs";
120259
- import * as path139 from "node:path";
120417
+ import * as fs106 from "node:fs";
120418
+ import * as path144 from "node:path";
120260
120419
  var DEFAULT_MAX_RESULTS = 100;
120261
120420
  var DEFAULT_MAX_LINES = 200;
120262
120421
  var REGEX_TIMEOUT_MS = 5000;
@@ -120292,11 +120451,11 @@ function containsWindowsAttacks3(str) {
120292
120451
  }
120293
120452
  function isPathInWorkspace3(filePath, workspace) {
120294
120453
  try {
120295
- const resolvedPath = path139.resolve(workspace, filePath);
120296
- const realWorkspace = fs101.realpathSync(workspace);
120297
- const realResolvedPath = fs101.realpathSync(resolvedPath);
120298
- const relativePath = path139.relative(realWorkspace, realResolvedPath);
120299
- if (relativePath.startsWith("..") || path139.isAbsolute(relativePath)) {
120454
+ const resolvedPath = path144.resolve(workspace, filePath);
120455
+ const realWorkspace = fs106.realpathSync(workspace);
120456
+ const realResolvedPath = fs106.realpathSync(resolvedPath);
120457
+ const relativePath = path144.relative(realWorkspace, realResolvedPath);
120458
+ if (relativePath.startsWith("..") || path144.isAbsolute(relativePath)) {
120300
120459
  return false;
120301
120460
  }
120302
120461
  return true;
@@ -120309,12 +120468,12 @@ function validatePathForRead2(filePath, workspace) {
120309
120468
  }
120310
120469
  function findRgInEnvPath() {
120311
120470
  const searchPath = process.env.PATH ?? "";
120312
- for (const dir of searchPath.split(path139.delimiter)) {
120471
+ for (const dir of searchPath.split(path144.delimiter)) {
120313
120472
  if (!dir)
120314
120473
  continue;
120315
120474
  const isWindows = process.platform === "win32";
120316
- const candidate = path139.join(dir, isWindows ? "rg.exe" : "rg");
120317
- if (fs101.existsSync(candidate))
120475
+ const candidate = path144.join(dir, isWindows ? "rg.exe" : "rg");
120476
+ if (fs106.existsSync(candidate))
120318
120477
  return candidate;
120319
120478
  }
120320
120479
  return null;
@@ -120441,10 +120600,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
120441
120600
  return files;
120442
120601
  }
120443
120602
  try {
120444
- const entries = fs101.readdirSync(dir, { withFileTypes: true });
120603
+ const entries = fs106.readdirSync(dir, { withFileTypes: true });
120445
120604
  for (const entry of entries) {
120446
- const fullPath = path139.join(dir, entry.name);
120447
- const relativePath = path139.relative(workspace, fullPath);
120605
+ const fullPath = path144.join(dir, entry.name);
120606
+ const relativePath = path144.relative(workspace, fullPath);
120448
120607
  if (!validatePathForRead2(fullPath, workspace)) {
120449
120608
  continue;
120450
120609
  }
@@ -120485,13 +120644,13 @@ async function fallbackSearch(opts) {
120485
120644
  const matches = [];
120486
120645
  let total = 0;
120487
120646
  for (const file3 of files) {
120488
- const fullPath = path139.join(opts.workspace, file3);
120647
+ const fullPath = path144.join(opts.workspace, file3);
120489
120648
  if (!validatePathForRead2(fullPath, opts.workspace)) {
120490
120649
  continue;
120491
120650
  }
120492
120651
  let stats;
120493
120652
  try {
120494
- stats = fs101.statSync(fullPath);
120653
+ stats = fs106.statSync(fullPath);
120495
120654
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
120496
120655
  continue;
120497
120656
  }
@@ -120500,7 +120659,7 @@ async function fallbackSearch(opts) {
120500
120659
  }
120501
120660
  let content;
120502
120661
  try {
120503
- content = fs101.readFileSync(fullPath, "utf-8");
120662
+ content = fs106.readFileSync(fullPath, "utf-8");
120504
120663
  } catch {
120505
120664
  continue;
120506
120665
  }
@@ -120612,7 +120771,7 @@ var search = createSwarmTool({
120612
120771
  message: "Exclude pattern contains invalid Windows-specific sequence"
120613
120772
  }, null, 2);
120614
120773
  }
120615
- if (!fs101.existsSync(directory)) {
120774
+ if (!fs106.existsSync(directory)) {
120616
120775
  return JSON.stringify({
120617
120776
  error: true,
120618
120777
  type: "unknown",
@@ -120890,7 +121049,7 @@ init_config();
120890
121049
  init_schema();
120891
121050
  init_create_tool();
120892
121051
  import { mkdir as mkdir23, rename as rename9, writeFile as writeFile18 } from "node:fs/promises";
120893
- import * as path140 from "node:path";
121052
+ import * as path145 from "node:path";
120894
121053
  var MAX_SPEC_BYTES = 256 * 1024;
120895
121054
  var spec_write = createSwarmTool({
120896
121055
  description: "Write the canonical project spec to .swarm/spec.md. Atomic write, size-bounded (256 KiB), heading-required. Honors spec_writer.allow_spec_write.",
@@ -120931,14 +121090,14 @@ var spec_write = createSwarmTool({
120931
121090
  reason: 'spec must contain at least one top-level "# Heading"'
120932
121091
  }, null, 2);
120933
121092
  }
120934
- const target = path140.join(directory, ".swarm", "spec.md");
120935
- await mkdir23(path140.dirname(target), { recursive: true });
121093
+ const target = path145.join(directory, ".swarm", "spec.md");
121094
+ await mkdir23(path145.dirname(target), { recursive: true });
120936
121095
  const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
120937
121096
  let finalContent = content;
120938
121097
  if (mode === "append") {
120939
121098
  try {
120940
- const fs102 = await import("node:fs/promises");
120941
- const prior = await fs102.readFile(target, "utf-8");
121099
+ const fs107 = await import("node:fs/promises");
121100
+ const prior = await fs107.readFile(target, "utf-8");
120942
121101
  finalContent = `${prior.replace(/\s+$/, "")}
120943
121102
 
120944
121103
  ${content}
@@ -120962,12 +121121,12 @@ init_loader();
120962
121121
  import {
120963
121122
  existsSync as existsSync82,
120964
121123
  mkdirSync as mkdirSync35,
120965
- readFileSync as readFileSync63,
121124
+ readFileSync as readFileSync68,
120966
121125
  renameSync as renameSync21,
120967
121126
  unlinkSync as unlinkSync18,
120968
121127
  writeFileSync as writeFileSync28
120969
121128
  } from "node:fs";
120970
- import path141 from "node:path";
121129
+ import path146 from "node:path";
120971
121130
  init_create_tool();
120972
121131
  init_resolve_working_directory();
120973
121132
  var VerdictSchema2 = exports_external.object({
@@ -121097,9 +121256,9 @@ var submit_phase_council_verdicts = createSwarmTool({
121097
121256
  }
121098
121257
  });
121099
121258
  function getPhaseMutationGapFinding(phaseNumber, workingDir) {
121100
- const mutationGatePath = path141.join(workingDir, ".swarm", "evidence", String(phaseNumber), "mutation-gate.json");
121259
+ const mutationGatePath = path146.join(workingDir, ".swarm", "evidence", String(phaseNumber), "mutation-gate.json");
121101
121260
  try {
121102
- const raw = readFileSync63(mutationGatePath, "utf-8");
121261
+ const raw = readFileSync68(mutationGatePath, "utf-8");
121103
121262
  const parsed = JSON.parse(raw);
121104
121263
  const gateEntry = (parsed.entries ?? []).find((entry) => entry?.type === "mutation-gate");
121105
121264
  if (!gateEntry) {
@@ -121159,9 +121318,9 @@ function getPhaseMutationGapFinding(phaseNumber, workingDir) {
121159
121318
  }
121160
121319
  }
121161
121320
  function writePhaseCouncilEvidence(workingDir, synthesis) {
121162
- const evidenceDir = path141.join(workingDir, ".swarm", "evidence", String(synthesis.phaseNumber));
121321
+ const evidenceDir = path146.join(workingDir, ".swarm", "evidence", String(synthesis.phaseNumber));
121163
121322
  mkdirSync35(evidenceDir, { recursive: true });
121164
- const evidenceFile = path141.join(evidenceDir, "phase-council.json");
121323
+ const evidenceFile = path146.join(evidenceDir, "phase-council.json");
121165
121324
  const evidenceBundle = {
121166
121325
  entries: [
121167
121326
  {
@@ -121530,7 +121689,7 @@ init_schema3();
121530
121689
  init_store();
121531
121690
  init_create_tool();
121532
121691
  init_resolve_working_directory();
121533
- import * as path142 from "node:path";
121692
+ import * as path147 from "node:path";
121534
121693
  var FindingSchema2 = exports_external.object({
121535
121694
  severity: exports_external.enum(["low", "medium", "high", "critical"]),
121536
121695
  category: exports_external.string().min(1),
@@ -121594,7 +121753,7 @@ var write_architecture_supervisor_evidence = createSwarmTool({
121594
121753
  if (config3.architectural_supervision?.persist_knowledge_recommendations && args2.knowledge_recommendations.length > 0) {
121595
121754
  const knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
121596
121755
  const lessons = args2.knowledge_recommendations.map((r) => r.lesson);
121597
- const result = await curateAndStoreSwarm(lessons, path142.basename(dirResult.directory), { phase_number: args2.phase }, dirResult.directory, knowledgeConfig, { skipAutoPromotion: true });
121756
+ const result = await curateAndStoreSwarm(lessons, path147.basename(dirResult.directory), { phase_number: args2.phase }, dirResult.directory, knowledgeConfig, { skipAutoPromotion: true });
121598
121757
  knowledgeProposed = result.stored;
121599
121758
  }
121600
121759
  } catch {}
@@ -121625,8 +121784,8 @@ var write_architecture_supervisor_evidence = createSwarmTool({
121625
121784
  init_zod();
121626
121785
  init_path_security();
121627
121786
  init_create_tool();
121628
- import * as fs102 from "node:fs";
121629
- import * as path143 from "node:path";
121787
+ import * as fs107 from "node:fs";
121788
+ import * as path148 from "node:path";
121630
121789
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
121631
121790
  function containsWindowsAttacks4(str) {
121632
121791
  if (/:[^\\/]/.test(str))
@@ -121640,14 +121799,14 @@ function containsWindowsAttacks4(str) {
121640
121799
  }
121641
121800
  function isPathInWorkspace4(filePath, workspace) {
121642
121801
  try {
121643
- const resolvedPath = path143.resolve(workspace, filePath);
121644
- if (!fs102.existsSync(resolvedPath)) {
121802
+ const resolvedPath = path148.resolve(workspace, filePath);
121803
+ if (!fs107.existsSync(resolvedPath)) {
121645
121804
  return true;
121646
121805
  }
121647
- const realWorkspace = fs102.realpathSync(workspace);
121648
- const realResolvedPath = fs102.realpathSync(resolvedPath);
121649
- const relativePath = path143.relative(realWorkspace, realResolvedPath);
121650
- if (relativePath.startsWith("..") || path143.isAbsolute(relativePath)) {
121806
+ const realWorkspace = fs107.realpathSync(workspace);
121807
+ const realResolvedPath = fs107.realpathSync(resolvedPath);
121808
+ const relativePath = path148.relative(realWorkspace, realResolvedPath);
121809
+ if (relativePath.startsWith("..") || path148.isAbsolute(relativePath)) {
121651
121810
  return false;
121652
121811
  }
121653
121812
  return true;
@@ -121819,7 +121978,7 @@ var suggestPatch = createSwarmTool({
121819
121978
  message: "changes cannot be empty"
121820
121979
  }, null, 2);
121821
121980
  }
121822
- if (!fs102.existsSync(directory)) {
121981
+ if (!fs107.existsSync(directory)) {
121823
121982
  return JSON.stringify({
121824
121983
  success: false,
121825
121984
  error: true,
@@ -121855,8 +122014,8 @@ var suggestPatch = createSwarmTool({
121855
122014
  });
121856
122015
  continue;
121857
122016
  }
121858
- const fullPath = path143.resolve(directory, change.file);
121859
- if (!fs102.existsSync(fullPath)) {
122017
+ const fullPath = path148.resolve(directory, change.file);
122018
+ if (!fs107.existsSync(fullPath)) {
121860
122019
  errors5.push({
121861
122020
  success: false,
121862
122021
  error: true,
@@ -121870,7 +122029,7 @@ var suggestPatch = createSwarmTool({
121870
122029
  }
121871
122030
  let content;
121872
122031
  try {
121873
- content = fs102.readFileSync(fullPath, "utf-8");
122032
+ content = fs107.readFileSync(fullPath, "utf-8");
121874
122033
  } catch (err3) {
121875
122034
  errors5.push({
121876
122035
  success: false,
@@ -122158,12 +122317,12 @@ var lean_turbo_acquire_locks = createSwarmTool({
122158
122317
  // src/tools/lean-turbo-plan-lanes.ts
122159
122318
  init_zod();
122160
122319
  init_constants();
122161
- import * as fs104 from "node:fs";
122162
- import * as path145 from "node:path";
122320
+ import * as fs109 from "node:fs";
122321
+ import * as path150 from "node:path";
122163
122322
 
122164
122323
  // src/turbo/lean/conflicts.ts
122165
- import * as fs103 from "node:fs";
122166
- import * as path144 from "node:path";
122324
+ import * as fs108 from "node:fs";
122325
+ import * as path149 from "node:path";
122167
122326
  var DEFAULT_GLOBAL_FILES = [
122168
122327
  "package.json",
122169
122328
  "package-lock.json",
@@ -122290,12 +122449,12 @@ function isProtectedPath2(normalizedPath) {
122290
122449
  return false;
122291
122450
  }
122292
122451
  function readTaskScopes(directory, taskId) {
122293
- const scopePath = path144.join(directory, ".swarm", "scopes", `scope-${taskId}.json`);
122452
+ const scopePath = path149.join(directory, ".swarm", "scopes", `scope-${taskId}.json`);
122294
122453
  try {
122295
- if (!fs103.existsSync(scopePath)) {
122454
+ if (!fs108.existsSync(scopePath)) {
122296
122455
  return null;
122297
122456
  }
122298
- const raw = fs103.readFileSync(scopePath, "utf-8");
122457
+ const raw = fs108.readFileSync(scopePath, "utf-8");
122299
122458
  const parsed = JSON.parse(raw);
122300
122459
  if (!parsed || !Array.isArray(parsed.files)) {
122301
122460
  return null;
@@ -122678,12 +122837,12 @@ function createEmptyPlan(phaseNumber, planId) {
122678
122837
  // src/tools/lean-turbo-plan-lanes.ts
122679
122838
  init_create_tool();
122680
122839
  function readPlanJson(directory) {
122681
- const planPath = path145.join(directory, ".swarm", "plan.json");
122682
- if (!fs104.existsSync(planPath)) {
122840
+ const planPath = path150.join(directory, ".swarm", "plan.json");
122841
+ if (!fs109.existsSync(planPath)) {
122683
122842
  return null;
122684
122843
  }
122685
122844
  try {
122686
- return JSON.parse(fs104.readFileSync(planPath, "utf-8"));
122845
+ return JSON.parse(fs109.readFileSync(planPath, "utf-8"));
122687
122846
  } catch {
122688
122847
  return null;
122689
122848
  }
@@ -122732,8 +122891,8 @@ init_config();
122732
122891
 
122733
122892
  // src/turbo/lean/reviewer.ts
122734
122893
  init_state();
122735
- import * as fs105 from "node:fs/promises";
122736
- import * as path146 from "node:path";
122894
+ import * as fs110 from "node:fs/promises";
122895
+ import * as path151 from "node:path";
122737
122896
  init_state3();
122738
122897
  var DEFAULT_CONFIG3 = {
122739
122898
  reviewerAgent: "",
@@ -122849,9 +123008,9 @@ function parseReviewerVerdict(responseText) {
122849
123008
  return { verdict, reason };
122850
123009
  }
122851
123010
  async function writeReviewerEvidence(directory, phase, verdict, reason) {
122852
- const evidenceDir = path146.join(directory, ".swarm", "evidence", String(phase));
122853
- await fs105.mkdir(evidenceDir, { recursive: true });
122854
- const evidencePath = path146.join(evidenceDir, "lean-turbo-reviewer.json");
123011
+ const evidenceDir = path151.join(directory, ".swarm", "evidence", String(phase));
123012
+ await fs110.mkdir(evidenceDir, { recursive: true });
123013
+ const evidencePath = path151.join(evidenceDir, "lean-turbo-reviewer.json");
122855
123014
  const content = JSON.stringify({
122856
123015
  phase,
122857
123016
  verdict,
@@ -122860,11 +123019,11 @@ async function writeReviewerEvidence(directory, phase, verdict, reason) {
122860
123019
  }, null, 2);
122861
123020
  const tempPath = `${evidencePath}.tmp.${process.pid}.${Date.now()}`;
122862
123021
  try {
122863
- await fs105.writeFile(tempPath, content, "utf-8");
122864
- await fs105.rename(tempPath, evidencePath);
123022
+ await fs110.writeFile(tempPath, content, "utf-8");
123023
+ await fs110.rename(tempPath, evidencePath);
122865
123024
  } catch (error93) {
122866
123025
  try {
122867
- await fs105.unlink(tempPath);
123026
+ await fs110.unlink(tempPath);
122868
123027
  } catch {}
122869
123028
  throw error93;
122870
123029
  }
@@ -123655,8 +123814,8 @@ var lean_turbo_status = createSwarmTool({
123655
123814
  // src/tools/lint-spec.ts
123656
123815
  init_spec_schema();
123657
123816
  init_create_tool();
123658
- import * as fs106 from "node:fs";
123659
- import * as path147 from "node:path";
123817
+ import * as fs111 from "node:fs";
123818
+ import * as path152 from "node:path";
123660
123819
  var SPEC_FILE_NAME = "spec.md";
123661
123820
  var SWARM_DIR2 = ".swarm";
123662
123821
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -123709,8 +123868,8 @@ var lint_spec = createSwarmTool({
123709
123868
  async execute(_args, directory) {
123710
123869
  const errors5 = [];
123711
123870
  const warnings = [];
123712
- const specPath = path147.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
123713
- if (!fs106.existsSync(specPath)) {
123871
+ const specPath = path152.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
123872
+ if (!fs111.existsSync(specPath)) {
123714
123873
  const result2 = {
123715
123874
  valid: false,
123716
123875
  specMtime: null,
@@ -123729,12 +123888,12 @@ var lint_spec = createSwarmTool({
123729
123888
  }
123730
123889
  let specMtime = null;
123731
123890
  try {
123732
- const stats = fs106.statSync(specPath);
123891
+ const stats = fs111.statSync(specPath);
123733
123892
  specMtime = stats.mtime.toISOString();
123734
123893
  } catch {}
123735
123894
  let content;
123736
123895
  try {
123737
- content = fs106.readFileSync(specPath, "utf-8");
123896
+ content = fs111.readFileSync(specPath, "utf-8");
123738
123897
  } catch (e) {
123739
123898
  const result2 = {
123740
123899
  valid: false,
@@ -123779,13 +123938,13 @@ var lint_spec = createSwarmTool({
123779
123938
  });
123780
123939
  // src/tools/mutation-test.ts
123781
123940
  init_zod();
123782
- import * as fs107 from "node:fs";
123783
- import * as path149 from "node:path";
123941
+ import * as fs112 from "node:fs";
123942
+ import * as path154 from "node:path";
123784
123943
 
123785
123944
  // src/mutation/engine.ts
123786
123945
  import { spawnSync as spawnSync8 } from "node:child_process";
123787
123946
  import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync29 } from "node:fs";
123788
- import * as path148 from "node:path";
123947
+ import * as path153 from "node:path";
123789
123948
 
123790
123949
  // src/mutation/equivalence.ts
123791
123950
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -123931,7 +124090,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
123931
124090
  let patchFile;
123932
124091
  try {
123933
124092
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
123934
- patchFile = path148.join(workingDir, `.mutation_patch_${safeId2}.diff`);
124093
+ patchFile = path153.join(workingDir, `.mutation_patch_${safeId2}.diff`);
123935
124094
  try {
123936
124095
  writeFileSync29(patchFile, patch.patch);
123937
124096
  } catch (writeErr) {
@@ -124335,8 +124494,8 @@ var mutation_test = createSwarmTool({
124335
124494
  ];
124336
124495
  for (const filePath of uniquePaths) {
124337
124496
  try {
124338
- const resolvedPath = path149.resolve(cwd, filePath);
124339
- sourceFiles.set(filePath, fs107.readFileSync(resolvedPath, "utf-8"));
124497
+ const resolvedPath = path154.resolve(cwd, filePath);
124498
+ sourceFiles.set(filePath, fs112.readFileSync(resolvedPath, "utf-8"));
124340
124499
  } catch {}
124341
124500
  }
124342
124501
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -124354,8 +124513,8 @@ var mutation_test = createSwarmTool({
124354
124513
  init_zod();
124355
124514
  init_manager2();
124356
124515
  init_detector();
124357
- import * as fs108 from "node:fs";
124358
- import * as path150 from "node:path";
124516
+ import * as fs113 from "node:fs";
124517
+ import * as path155 from "node:path";
124359
124518
  init_create_tool();
124360
124519
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
124361
124520
  var BINARY_CHECK_BYTES = 8192;
@@ -124421,7 +124580,7 @@ async function syntaxCheck(input, directory, config3) {
124421
124580
  if (languages?.length) {
124422
124581
  const lowerLangs = languages.map((l) => l.toLowerCase());
124423
124582
  filesToCheck = filesToCheck.filter((file3) => {
124424
- const ext = path150.extname(file3.path).toLowerCase();
124583
+ const ext = path155.extname(file3.path).toLowerCase();
124425
124584
  const langDef = getLanguageForExtension(ext);
124426
124585
  const fileProfile = getProfileForFile(file3.path);
124427
124586
  const langId = fileProfile?.id || langDef?.id;
@@ -124434,7 +124593,7 @@ async function syntaxCheck(input, directory, config3) {
124434
124593
  let skippedCount = 0;
124435
124594
  for (const fileInfo of filesToCheck) {
124436
124595
  const { path: filePath } = fileInfo;
124437
- const fullPath = path150.isAbsolute(filePath) ? filePath : path150.join(directory, filePath);
124596
+ const fullPath = path155.isAbsolute(filePath) ? filePath : path155.join(directory, filePath);
124438
124597
  const result = {
124439
124598
  path: filePath,
124440
124599
  language: "",
@@ -124464,7 +124623,7 @@ async function syntaxCheck(input, directory, config3) {
124464
124623
  }
124465
124624
  let content;
124466
124625
  try {
124467
- content = fs108.readFileSync(fullPath, "utf8");
124626
+ content = fs113.readFileSync(fullPath, "utf8");
124468
124627
  } catch {
124469
124628
  result.skipped_reason = "file_read_error";
124470
124629
  skippedCount++;
@@ -124483,7 +124642,7 @@ async function syntaxCheck(input, directory, config3) {
124483
124642
  results.push(result);
124484
124643
  continue;
124485
124644
  }
124486
- const ext = path150.extname(filePath).toLowerCase();
124645
+ const ext = path155.extname(filePath).toLowerCase();
124487
124646
  const langDef = getLanguageForExtension(ext);
124488
124647
  result.language = profile?.id || langDef?.id || "unknown";
124489
124648
  const errors5 = extractSyntaxErrors(parser, content);
@@ -124580,8 +124739,8 @@ init_zod();
124580
124739
  init_utils();
124581
124740
  init_create_tool();
124582
124741
  init_path_security();
124583
- import * as fs109 from "node:fs";
124584
- import * as path151 from "node:path";
124742
+ import * as fs114 from "node:fs";
124743
+ import * as path156 from "node:path";
124585
124744
  var MAX_TEXT_LENGTH = 200;
124586
124745
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
124587
124746
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -124647,9 +124806,9 @@ function validatePathsInput(paths, cwd) {
124647
124806
  return { error: "paths contains path traversal", resolvedPath: null };
124648
124807
  }
124649
124808
  try {
124650
- const resolvedPath = path151.resolve(paths);
124651
- const normalizedCwd = path151.resolve(cwd);
124652
- const normalizedResolved = path151.resolve(resolvedPath);
124809
+ const resolvedPath = path156.resolve(paths);
124810
+ const normalizedCwd = path156.resolve(cwd);
124811
+ const normalizedResolved = path156.resolve(resolvedPath);
124653
124812
  if (!normalizedResolved.startsWith(normalizedCwd)) {
124654
124813
  return {
124655
124814
  error: "paths must be within the current working directory",
@@ -124665,13 +124824,13 @@ function validatePathsInput(paths, cwd) {
124665
124824
  }
124666
124825
  }
124667
124826
  function isSupportedExtension(filePath) {
124668
- const ext = path151.extname(filePath).toLowerCase();
124827
+ const ext = path156.extname(filePath).toLowerCase();
124669
124828
  return SUPPORTED_EXTENSIONS4.has(ext);
124670
124829
  }
124671
124830
  function findSourceFiles3(dir, files = []) {
124672
124831
  let entries;
124673
124832
  try {
124674
- entries = fs109.readdirSync(dir);
124833
+ entries = fs114.readdirSync(dir);
124675
124834
  } catch {
124676
124835
  return files;
124677
124836
  }
@@ -124680,10 +124839,10 @@ function findSourceFiles3(dir, files = []) {
124680
124839
  if (SKIP_DIRECTORIES5.has(entry)) {
124681
124840
  continue;
124682
124841
  }
124683
- const fullPath = path151.join(dir, entry);
124842
+ const fullPath = path156.join(dir, entry);
124684
124843
  let stat9;
124685
124844
  try {
124686
- stat9 = fs109.statSync(fullPath);
124845
+ stat9 = fs114.statSync(fullPath);
124687
124846
  } catch {
124688
124847
  continue;
124689
124848
  }
@@ -124776,7 +124935,7 @@ var todo_extract = createSwarmTool({
124776
124935
  return JSON.stringify(errorResult, null, 2);
124777
124936
  }
124778
124937
  const scanPath = resolvedPath;
124779
- if (!fs109.existsSync(scanPath)) {
124938
+ if (!fs114.existsSync(scanPath)) {
124780
124939
  const errorResult = {
124781
124940
  error: `path not found: ${pathsInput}`,
124782
124941
  total: 0,
@@ -124786,13 +124945,13 @@ var todo_extract = createSwarmTool({
124786
124945
  return JSON.stringify(errorResult, null, 2);
124787
124946
  }
124788
124947
  const filesToScan = [];
124789
- const stat9 = fs109.statSync(scanPath);
124948
+ const stat9 = fs114.statSync(scanPath);
124790
124949
  if (stat9.isFile()) {
124791
124950
  if (isSupportedExtension(scanPath)) {
124792
124951
  filesToScan.push(scanPath);
124793
124952
  } else {
124794
124953
  const errorResult = {
124795
- error: `unsupported file extension: ${path151.extname(scanPath)}`,
124954
+ error: `unsupported file extension: ${path156.extname(scanPath)}`,
124796
124955
  total: 0,
124797
124956
  byPriority: { high: 0, medium: 0, low: 0 },
124798
124957
  entries: []
@@ -124805,11 +124964,11 @@ var todo_extract = createSwarmTool({
124805
124964
  const allEntries = [];
124806
124965
  for (const filePath of filesToScan) {
124807
124966
  try {
124808
- const fileStat = fs109.statSync(filePath);
124967
+ const fileStat = fs114.statSync(filePath);
124809
124968
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
124810
124969
  continue;
124811
124970
  }
124812
- const content = fs109.readFileSync(filePath, "utf-8");
124971
+ const content = fs114.readFileSync(filePath, "utf-8");
124813
124972
  const entries = parseTodoComments(content, filePath, tagsSet);
124814
124973
  allEntries.push(...entries);
124815
124974
  } catch {}
@@ -124840,18 +124999,18 @@ init_loader();
124840
124999
  init_schema();
124841
125000
  init_qa_gate_profile();
124842
125001
  init_gate_evidence();
124843
- import * as fs113 from "node:fs";
124844
- import * as path155 from "node:path";
125002
+ import * as fs118 from "node:fs";
125003
+ import * as path160 from "node:path";
124845
125004
 
124846
125005
  // src/hooks/diff-scope.ts
124847
125006
  init_bun_compat();
124848
- import * as fs111 from "node:fs";
124849
- import * as path153 from "node:path";
125007
+ import * as fs116 from "node:fs";
125008
+ import * as path158 from "node:path";
124850
125009
 
124851
125010
  // src/utils/gitignore-warning.ts
124852
125011
  init_bun_compat();
124853
- import * as fs110 from "node:fs";
124854
- import * as path152 from "node:path";
125012
+ import * as fs115 from "node:fs";
125013
+ import * as path157 from "node:path";
124855
125014
  var _internals67 = { bunSpawn };
124856
125015
  var _swarmGitExcludedChecked = false;
124857
125016
  function fileCoversSwarm(content) {
@@ -124925,16 +125084,16 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
124925
125084
  const excludeRelPath = excludePathRaw.trim();
124926
125085
  if (!excludeRelPath)
124927
125086
  return;
124928
- const excludePath = path152.isAbsolute(excludeRelPath) ? excludeRelPath : path152.join(directory, excludeRelPath);
125087
+ const excludePath = path157.isAbsolute(excludeRelPath) ? excludeRelPath : path157.join(directory, excludeRelPath);
124929
125088
  if (checkIgnoreExitCode !== 0) {
124930
125089
  try {
124931
- fs110.mkdirSync(path152.dirname(excludePath), { recursive: true });
125090
+ fs115.mkdirSync(path157.dirname(excludePath), { recursive: true });
124932
125091
  let existing = "";
124933
125092
  try {
124934
- existing = fs110.readFileSync(excludePath, "utf8");
125093
+ existing = fs115.readFileSync(excludePath, "utf8");
124935
125094
  } catch {}
124936
125095
  if (!fileCoversSwarm(existing)) {
124937
- fs110.appendFileSync(excludePath, `
125096
+ fs115.appendFileSync(excludePath, `
124938
125097
  # opencode-swarm local runtime state
124939
125098
  .swarm/
124940
125099
  `, "utf8");
@@ -124972,10 +125131,10 @@ async function ensureSwarmGitExcluded(directory, options = {}) {
124972
125131
  var _internals68 = { bunSpawn };
124973
125132
  function getDeclaredScope(taskId, directory) {
124974
125133
  try {
124975
- const planPath = path153.join(directory, ".swarm", "plan.json");
124976
- if (!fs111.existsSync(planPath))
125134
+ const planPath = path158.join(directory, ".swarm", "plan.json");
125135
+ if (!fs116.existsSync(planPath))
124977
125136
  return null;
124978
- const raw = fs111.readFileSync(planPath, "utf-8");
125137
+ const raw = fs116.readFileSync(planPath, "utf-8");
124979
125138
  const plan = JSON.parse(raw);
124980
125139
  for (const phase of plan.phases ?? []) {
124981
125140
  for (const task of phase.tasks ?? []) {
@@ -125077,8 +125236,8 @@ init_telemetry();
125077
125236
 
125078
125237
  // src/turbo/lean/task-completion.ts
125079
125238
  init_file_locks();
125080
- import * as fs112 from "node:fs";
125081
- import * as path154 from "node:path";
125239
+ import * as fs117 from "node:fs";
125240
+ import * as path159 from "node:path";
125082
125241
  var _internals69 = {
125083
125242
  listActiveLocks,
125084
125243
  verifyLeanTurboTaskCompletion
@@ -125097,7 +125256,7 @@ var TIER_3_PATTERNS = [
125097
125256
  ];
125098
125257
  function matchesTier3Pattern(files) {
125099
125258
  for (const file3 of files) {
125100
- const fileName = path154.basename(file3);
125259
+ const fileName = path159.basename(file3);
125101
125260
  for (const pattern of TIER_3_PATTERNS) {
125102
125261
  if (pattern.test(fileName)) {
125103
125262
  return true;
@@ -125109,14 +125268,14 @@ function matchesTier3Pattern(files) {
125109
125268
  function verifyLeanTurboTaskCompletion(directory, taskId, sessionID) {
125110
125269
  let persisted = null;
125111
125270
  try {
125112
- const statePath = path154.join(directory, ".swarm", "turbo-state.json");
125113
- if (!fs112.existsSync(statePath)) {
125271
+ const statePath = path159.join(directory, ".swarm", "turbo-state.json");
125272
+ if (!fs117.existsSync(statePath)) {
125114
125273
  return {
125115
125274
  ok: false,
125116
125275
  reason: "Lean Turbo state file not found"
125117
125276
  };
125118
125277
  }
125119
- const raw = fs112.readFileSync(statePath, "utf-8");
125278
+ const raw = fs117.readFileSync(statePath, "utf-8");
125120
125279
  persisted = JSON.parse(raw);
125121
125280
  } catch {
125122
125281
  return {
@@ -125193,11 +125352,11 @@ function verifyLeanTurboTaskCompletion(directory, taskId, sessionID) {
125193
125352
  };
125194
125353
  }
125195
125354
  const phase = runState.phase ?? 0;
125196
- const evidencePath = path154.join(directory, ".swarm", "evidence", String(phase), "lean-turbo", `${lane.laneId}.json`);
125197
- const expectedDir = path154.join(directory, ".swarm", "evidence", String(phase), "lean-turbo");
125198
- const resolvedPath = path154.resolve(evidencePath);
125199
- const resolvedDir = path154.resolve(expectedDir);
125200
- if (!resolvedPath.startsWith(resolvedDir + path154.sep) && resolvedPath !== resolvedDir) {
125355
+ const evidencePath = path159.join(directory, ".swarm", "evidence", String(phase), "lean-turbo", `${lane.laneId}.json`);
125356
+ const expectedDir = path159.join(directory, ".swarm", "evidence", String(phase), "lean-turbo");
125357
+ const resolvedPath = path159.resolve(evidencePath);
125358
+ const resolvedDir = path159.resolve(expectedDir);
125359
+ if (!resolvedPath.startsWith(resolvedDir + path159.sep) && resolvedPath !== resolvedDir) {
125201
125360
  return {
125202
125361
  ok: false,
125203
125362
  reason: `Lane ID causes path traversal: ${lane.laneId}`,
@@ -125209,7 +125368,7 @@ function verifyLeanTurboTaskCompletion(directory, taskId, sessionID) {
125209
125368
  }
125210
125369
  };
125211
125370
  }
125212
- if (!fs112.existsSync(evidencePath)) {
125371
+ if (!fs117.existsSync(evidencePath)) {
125213
125372
  return {
125214
125373
  ok: false,
125215
125374
  reason: `Lane ${lane.laneId} evidence file not found: ${evidencePath}`,
@@ -125237,8 +125396,8 @@ function verifyLeanTurboTaskCompletion(directory, taskId, sessionID) {
125237
125396
  }
125238
125397
  let filesTouched = [];
125239
125398
  try {
125240
- const planPath = path154.join(directory, ".swarm", "plan.json");
125241
- const planRaw = fs112.readFileSync(planPath, "utf-8");
125399
+ const planPath = path159.join(directory, ".swarm", "plan.json");
125400
+ const planRaw = fs117.readFileSync(planPath, "utf-8");
125242
125401
  const plan = JSON.parse(planRaw);
125243
125402
  for (const planPhase of plan.phases ?? []) {
125244
125403
  for (const task of planPhase.tasks ?? []) {
@@ -125321,7 +125480,7 @@ var TIER_3_PATTERNS2 = [
125321
125480
  ];
125322
125481
  function matchesTier3Pattern2(files) {
125323
125482
  for (const file3 of files) {
125324
- const fileName = path155.basename(file3);
125483
+ const fileName = path160.basename(file3);
125325
125484
  for (const pattern of TIER_3_PATTERNS2) {
125326
125485
  if (pattern.test(fileName)) {
125327
125486
  return true;
@@ -125360,8 +125519,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
125360
125519
  if (!skipStandardTurboBypass && hasActiveTurboMode()) {
125361
125520
  const resolvedDir2 = workingDirectory;
125362
125521
  try {
125363
- const planPath = path155.join(resolvedDir2, ".swarm", "plan.json");
125364
- const planRaw = fs113.readFileSync(planPath, "utf-8");
125522
+ const planPath = path160.join(resolvedDir2, ".swarm", "plan.json");
125523
+ const planRaw = fs118.readFileSync(planPath, "utf-8");
125365
125524
  const plan = JSON.parse(planRaw);
125366
125525
  for (const planPhase of plan.phases ?? []) {
125367
125526
  for (const task of planPhase.tasks ?? []) {
@@ -125438,8 +125597,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
125438
125597
  }
125439
125598
  if (resolvedDir) {
125440
125599
  try {
125441
- const planPath = path155.join(resolvedDir, ".swarm", "plan.json");
125442
- const planRaw = fs113.readFileSync(planPath, "utf-8");
125600
+ const planPath = path160.join(resolvedDir, ".swarm", "plan.json");
125601
+ const planRaw = fs118.readFileSync(planPath, "utf-8");
125443
125602
  const plan = JSON.parse(planRaw);
125444
125603
  for (const planPhase of plan.phases ?? []) {
125445
125604
  for (const task of planPhase.tasks ?? []) {
@@ -125657,72 +125816,35 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
125657
125816
  session.currentTaskId = args2.task_id;
125658
125817
  }
125659
125818
  }
125660
- let normalizedDir;
125661
125819
  let directory;
125662
- if (args2.working_directory != null) {
125663
- if (args2.working_directory.includes("\x00")) {
125664
- return {
125665
- success: false,
125666
- message: "Invalid working_directory: null bytes are not allowed"
125667
- };
125668
- }
125669
- {
125670
- const devicePathPattern = /^\\\\|^(NUL|CON|AUX|COM[1-9]|LPT[1-9])(\..*)?$/i;
125671
- if (devicePathPattern.test(args2.working_directory)) {
125672
- return {
125673
- success: false,
125674
- message: "Invalid working_directory: Windows device paths are not allowed"
125675
- };
125676
- }
125677
- }
125678
- normalizedDir = path155.normalize(args2.working_directory);
125679
- const pathParts = normalizedDir.split(path155.sep);
125680
- if (pathParts.includes("..")) {
125681
- return {
125682
- success: false,
125683
- message: "Invalid working_directory: path traversal sequences (..) are not allowed",
125684
- errors: [
125685
- "Invalid working_directory: path traversal sequences (..) are not allowed"
125686
- ]
125687
- };
125688
- }
125689
- const resolvedDir = path155.resolve(normalizedDir);
125690
- try {
125691
- const realPath = fs113.realpathSync(resolvedDir);
125692
- const planPath = path155.join(realPath, ".swarm", "plan.json");
125693
- if (!fs113.existsSync(planPath)) {
125694
- return {
125695
- success: false,
125696
- message: `Invalid working_directory: plan not found in "${realPath}"`,
125697
- errors: [
125698
- `Invalid working_directory: plan not found in "${realPath}"`
125699
- ]
125700
- };
125701
- }
125702
- directory = realPath;
125703
- } catch {
125704
- return {
125705
- success: false,
125706
- message: `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`,
125707
- errors: [
125708
- `Invalid working_directory: path "${resolvedDir}" does not exist or is inaccessible`
125709
- ]
125710
- };
125711
- }
125712
- } else {
125713
- if (!fallbackDir) {
125714
- return {
125715
- success: false,
125716
- message: "No working_directory provided and fallbackDir is undefined",
125717
- errors: ["Cannot resolve directory for task status update"]
125718
- };
125719
- }
125720
- directory = fallbackDir;
125820
+ if (!args2.working_directory && !fallbackDir) {
125821
+ return {
125822
+ success: false,
125823
+ message: "No working_directory provided and fallbackDir is undefined",
125824
+ errors: ["Cannot resolve directory for task status update"]
125825
+ };
125826
+ }
125827
+ const resolveResult = resolveWorkingDirectory(args2.working_directory ?? fallbackDir, fallbackDir);
125828
+ if (!resolveResult.success) {
125829
+ return {
125830
+ success: false,
125831
+ message: resolveResult.message,
125832
+ errors: [resolveResult.message]
125833
+ };
125834
+ }
125835
+ directory = resolveResult.directory;
125836
+ const planPath = path160.join(directory, ".swarm", "plan.json");
125837
+ if (!fs118.existsSync(planPath)) {
125838
+ return {
125839
+ success: false,
125840
+ message: `Invalid working_directory: plan not found in "${directory}"`,
125841
+ errors: [`Invalid working_directory: plan not found in "${directory}"`]
125842
+ };
125721
125843
  }
125722
125844
  if (fallbackDir && directory !== fallbackDir) {
125723
- const canonicalDir = fs113.realpathSync(path155.resolve(directory));
125724
- const canonicalRoot = fs113.realpathSync(path155.resolve(fallbackDir));
125725
- if (canonicalDir.startsWith(canonicalRoot + path155.sep)) {
125845
+ const canonicalDir = fs118.realpathSync(path160.resolve(directory));
125846
+ const canonicalRoot = fs118.realpathSync(path160.resolve(fallbackDir));
125847
+ if (canonicalDir.startsWith(canonicalRoot + path160.sep)) {
125726
125848
  return {
125727
125849
  success: false,
125728
125850
  message: `Invalid working_directory: "${directory}" is a subdirectory of ` + `the project root "${fallbackDir}". Pass the project root path or ` + `omit working_directory entirely.`,
@@ -125734,22 +125856,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
125734
125856
  }
125735
125857
  if (args2.status === "in_progress") {
125736
125858
  try {
125737
- const evidencePath = path155.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
125738
- fs113.mkdirSync(path155.dirname(evidencePath), { recursive: true });
125739
- const fd = fs113.openSync(evidencePath, "wx");
125859
+ const evidencePath = path160.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
125860
+ fs118.mkdirSync(path160.dirname(evidencePath), { recursive: true });
125861
+ const fd = fs118.openSync(evidencePath, "wx");
125740
125862
  let writeOk = false;
125741
125863
  try {
125742
- fs113.writeSync(fd, JSON.stringify({
125864
+ fs118.writeSync(fd, JSON.stringify({
125743
125865
  taskId: args2.task_id,
125744
125866
  required_gates: [],
125745
125867
  gates: {}
125746
125868
  }, null, 2));
125747
125869
  writeOk = true;
125748
125870
  } finally {
125749
- fs113.closeSync(fd);
125871
+ fs118.closeSync(fd);
125750
125872
  if (!writeOk) {
125751
125873
  try {
125752
- fs113.unlinkSync(evidencePath);
125874
+ fs118.unlinkSync(evidencePath);
125753
125875
  } catch {}
125754
125876
  }
125755
125877
  }
@@ -125759,8 +125881,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
125759
125881
  recoverTaskStateFromDelegations(args2.task_id, directory);
125760
125882
  let phaseRequiresReviewer = true;
125761
125883
  try {
125762
- const planPath = path155.join(directory, ".swarm", "plan.json");
125763
- const planRaw = fs113.readFileSync(planPath, "utf-8");
125884
+ const planPath2 = path160.join(directory, ".swarm", "plan.json");
125885
+ const planRaw = fs118.readFileSync(planPath2, "utf-8");
125764
125886
  const plan = JSON.parse(planRaw);
125765
125887
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
125766
125888
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -125987,7 +126109,7 @@ init_utils2();
125987
126109
  init_redaction();
125988
126110
  import { createHash as createHash12 } from "node:crypto";
125989
126111
  import { appendFile as appendFile14, mkdir as mkdir25 } from "node:fs/promises";
125990
- import * as path156 from "node:path";
126112
+ import * as path161 from "node:path";
125991
126113
  var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
125992
126114
  var MAX_EVIDENCE_TEXT_LENGTH = 4000;
125993
126115
  async function writeEvidenceDocuments(directory, inputs, now = () => new Date) {
@@ -125995,7 +126117,7 @@ async function writeEvidenceDocuments(directory, inputs, now = () => new Date) {
125995
126117
  const capturedAt = now().toISOString();
125996
126118
  const records = inputs.map((input) => createEvidenceDocumentRecord(input, capturedAt)).filter((record3) => record3 !== null);
125997
126119
  if (records.length > 0) {
125998
- await mkdir25(path156.dirname(filePath), { recursive: true });
126120
+ await mkdir25(path161.dirname(filePath), { recursive: true });
125999
126121
  await appendFile14(filePath, `${records.map((record3) => JSON.stringify(record3)).join(`
126000
126122
  `)}
126001
126123
  `, "utf-8");
@@ -126188,8 +126310,8 @@ init_utils2();
126188
126310
  init_ledger();
126189
126311
  init_manager();
126190
126312
  init_create_tool();
126191
- import fs114 from "node:fs";
126192
- import path157 from "node:path";
126313
+ import fs119 from "node:fs";
126314
+ import path162 from "node:path";
126193
126315
  function normalizeVerdict(verdict) {
126194
126316
  switch (verdict) {
126195
126317
  case "APPROVED":
@@ -126237,7 +126359,7 @@ async function executeWriteDriftEvidence(args2, directory) {
126237
126359
  entries: [evidenceEntry]
126238
126360
  };
126239
126361
  const filename = "drift-verifier.json";
126240
- const relativePath = path157.join("evidence", String(phase), filename);
126362
+ const relativePath = path162.join("evidence", String(phase), filename);
126241
126363
  let validatedPath;
126242
126364
  try {
126243
126365
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -126248,12 +126370,12 @@ async function executeWriteDriftEvidence(args2, directory) {
126248
126370
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
126249
126371
  }, null, 2);
126250
126372
  }
126251
- const evidenceDir = path157.dirname(validatedPath);
126373
+ const evidenceDir = path162.dirname(validatedPath);
126252
126374
  try {
126253
- await fs114.promises.mkdir(evidenceDir, { recursive: true });
126254
- const tempPath = path157.join(evidenceDir, `.${filename}.tmp`);
126255
- await fs114.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126256
- await fs114.promises.rename(tempPath, validatedPath);
126375
+ await fs119.promises.mkdir(evidenceDir, { recursive: true });
126376
+ const tempPath = path162.join(evidenceDir, `.${filename}.tmp`);
126377
+ await fs119.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126378
+ await fs119.promises.rename(tempPath, validatedPath);
126257
126379
  let snapshotInfo;
126258
126380
  let snapshotError;
126259
126381
  let qaProfileLocked;
@@ -126346,8 +126468,8 @@ var write_drift_evidence = createSwarmTool({
126346
126468
  // src/tools/write-final-council-evidence.ts
126347
126469
  init_zod();
126348
126470
  init_loader();
126349
- import fs115 from "node:fs";
126350
- import path158 from "node:path";
126471
+ import fs120 from "node:fs";
126472
+ import path163 from "node:path";
126351
126473
  init_utils2();
126352
126474
  init_manager();
126353
126475
  init_create_tool();
@@ -126435,7 +126557,7 @@ async function executeWriteFinalCouncilEvidence(args2, directory) {
126435
126557
  timestamp: synthesis.timestamp
126436
126558
  };
126437
126559
  const filename = "final-council.json";
126438
- const relativePath = path158.join("evidence", filename);
126560
+ const relativePath = path163.join("evidence", filename);
126439
126561
  let validatedPath;
126440
126562
  try {
126441
126563
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -126449,12 +126571,12 @@ async function executeWriteFinalCouncilEvidence(args2, directory) {
126449
126571
  const evidenceContent = {
126450
126572
  entries: [evidenceEntry]
126451
126573
  };
126452
- const evidenceDir = path158.dirname(validatedPath);
126574
+ const evidenceDir = path163.dirname(validatedPath);
126453
126575
  try {
126454
- await fs115.promises.mkdir(evidenceDir, { recursive: true });
126455
- const tempPath = path158.join(evidenceDir, `.${filename}.tmp`);
126456
- await fs115.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126457
- await fs115.promises.rename(tempPath, validatedPath);
126576
+ await fs120.promises.mkdir(evidenceDir, { recursive: true });
126577
+ const tempPath = path163.join(evidenceDir, `.${filename}.tmp`);
126578
+ await fs120.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126579
+ await fs120.promises.rename(tempPath, validatedPath);
126458
126580
  return JSON.stringify({
126459
126581
  success: true,
126460
126582
  phase: input.phase,
@@ -126510,8 +126632,8 @@ var write_final_council_evidence = createSwarmTool({
126510
126632
  init_zod();
126511
126633
  init_utils2();
126512
126634
  init_create_tool();
126513
- import fs116 from "node:fs";
126514
- import path159 from "node:path";
126635
+ import fs121 from "node:fs";
126636
+ import path164 from "node:path";
126515
126637
  function normalizeVerdict2(verdict) {
126516
126638
  switch (verdict) {
126517
126639
  case "APPROVED":
@@ -126559,7 +126681,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
126559
126681
  entries: [evidenceEntry]
126560
126682
  };
126561
126683
  const filename = "hallucination-guard.json";
126562
- const relativePath = path159.join("evidence", String(phase), filename);
126684
+ const relativePath = path164.join("evidence", String(phase), filename);
126563
126685
  let validatedPath;
126564
126686
  try {
126565
126687
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -126570,12 +126692,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
126570
126692
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
126571
126693
  }, null, 2);
126572
126694
  }
126573
- const evidenceDir = path159.dirname(validatedPath);
126695
+ const evidenceDir = path164.dirname(validatedPath);
126574
126696
  try {
126575
- await fs116.promises.mkdir(evidenceDir, { recursive: true });
126576
- const tempPath = path159.join(evidenceDir, `.${filename}.tmp`);
126577
- await fs116.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126578
- await fs116.promises.rename(tempPath, validatedPath);
126697
+ await fs121.promises.mkdir(evidenceDir, { recursive: true });
126698
+ const tempPath = path164.join(evidenceDir, `.${filename}.tmp`);
126699
+ await fs121.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126700
+ await fs121.promises.rename(tempPath, validatedPath);
126579
126701
  return JSON.stringify({
126580
126702
  success: true,
126581
126703
  phase,
@@ -126621,8 +126743,8 @@ var write_hallucination_evidence = createSwarmTool({
126621
126743
  init_zod();
126622
126744
  init_utils2();
126623
126745
  init_create_tool();
126624
- import fs117 from "node:fs";
126625
- import path160 from "node:path";
126746
+ import fs122 from "node:fs";
126747
+ import path165 from "node:path";
126626
126748
  function normalizeVerdict3(verdict) {
126627
126749
  switch (verdict) {
126628
126750
  case "PASS":
@@ -126696,7 +126818,7 @@ async function executeWriteMutationEvidence(args2, directory) {
126696
126818
  entries: [evidenceEntry]
126697
126819
  };
126698
126820
  const filename = "mutation-gate.json";
126699
- const relativePath = path160.join("evidence", String(phase), filename);
126821
+ const relativePath = path165.join("evidence", String(phase), filename);
126700
126822
  let validatedPath;
126701
126823
  try {
126702
126824
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -126707,12 +126829,12 @@ async function executeWriteMutationEvidence(args2, directory) {
126707
126829
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
126708
126830
  }, null, 2);
126709
126831
  }
126710
- const evidenceDir = path160.dirname(validatedPath);
126832
+ const evidenceDir = path165.dirname(validatedPath);
126711
126833
  try {
126712
- await fs117.promises.mkdir(evidenceDir, { recursive: true });
126713
- const tempPath = path160.join(evidenceDir, `.${filename}.tmp`);
126714
- await fs117.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126715
- await fs117.promises.rename(tempPath, validatedPath);
126834
+ await fs122.promises.mkdir(evidenceDir, { recursive: true });
126835
+ const tempPath = path165.join(evidenceDir, `.${filename}.tmp`);
126836
+ await fs122.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
126837
+ await fs122.promises.rename(tempPath, validatedPath);
126716
126838
  return JSON.stringify({
126717
126839
  success: true,
126718
126840
  phase,
@@ -127060,7 +127182,7 @@ async function initializeOpenCodeSwarm(ctx) {
127060
127182
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
127061
127183
  preflightTriggerManager = new PTM(automationConfig);
127062
127184
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
127063
- const swarmDir = path162.resolve(ctx.directory, ".swarm");
127185
+ const swarmDir = path167.resolve(ctx.directory, ".swarm");
127064
127186
  statusArtifact = new ASA(swarmDir);
127065
127187
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
127066
127188
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -127657,7 +127779,7 @@ ${promptRaw}`;
127657
127779
  const meta3 = readSkillMetadata(s.skillPath, ctx.directory);
127658
127780
  let desc = meta3.description || "";
127659
127781
  if (!desc || desc === "No description provided") {
127660
- desc = path162.basename(path162.dirname(s.skillPath));
127782
+ desc = path167.basename(path167.dirname(s.skillPath));
127661
127783
  }
127662
127784
  desc = desc.replace(/,/g, ";");
127663
127785
  return `file:${s.skillPath} (-- ${desc})`;
@@ -127667,7 +127789,7 @@ ${promptRaw}`;
127667
127789
 
127668
127790
  ${promptRaw}`;
127669
127791
  argsRecord.prompt = newPrompt;
127670
- const skillNames = topSkills.map((s) => `${path162.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
127792
+ const skillNames = topSkills.map((s) => `${path167.basename(s.skillPath)} (score: ${s.score.toFixed(2)})`).join(", ");
127671
127793
  console.warn(`[skill-propagation-gate] Injected skills: ${skillNames}`);
127672
127794
  for (const skill of topSkills) {
127673
127795
  try {