opencode-swarm 6.82.2 → 6.84.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
@@ -87,7 +87,9 @@ var init_tool_names = __esm(() => {
87
87
  "get_approved_plan",
88
88
  "repo_map",
89
89
  "get_qa_gate_profile",
90
- "set_qa_gates"
90
+ "set_qa_gates",
91
+ "web_search",
92
+ "convene_general_council"
91
93
  ];
92
94
  TOOL_NAME_SET = new Set(TOOL_NAMES);
93
95
  });
@@ -126,9 +128,7 @@ function isSubagent(name2) {
126
128
  return ALL_SUBAGENT_NAMES.includes(name2);
127
129
  }
128
130
  function isLowCapabilityModel(modelId) {
129
- if (!modelId)
130
- return false;
131
- const lower = modelId.toLowerCase();
131
+ const lower = (modelId || "").toLowerCase();
132
132
  return LOW_CAPABILITY_MODELS.some((substr) => lower.includes(substr));
133
133
  }
134
134
  var QA_AGENTS, PIPELINE_AGENTS, ORCHESTRATOR_NAME = "architect", ALL_SUBAGENT_NAMES, ALL_AGENT_NAMES, OPENCODE_NATIVE_AGENTS, AGENT_TOOL_MAP, WRITE_TOOL_NAMES, TOOL_DESCRIPTIONS, DEFAULT_MODELS, DEFAULT_SCORING_CONFIG, LOW_CAPABILITY_MODELS, TURBO_MODE_BANNER = `## \uD83D\uDE80 TURBO MODE ACTIVE
@@ -172,6 +172,8 @@ var init_constants = __esm(() => {
172
172
  "critic_hallucination_verifier",
173
173
  "curator_init",
174
174
  "curator_phase",
175
+ "council_member",
176
+ "council_moderator",
175
177
  ...QA_AGENTS,
176
178
  ...PIPELINE_AGENTS
177
179
  ];
@@ -243,7 +245,8 @@ var init_constants = __esm(() => {
243
245
  "suggest_patch",
244
246
  "repo_map",
245
247
  "get_qa_gate_profile",
246
- "set_qa_gates"
248
+ "set_qa_gates",
249
+ "convene_general_council"
247
250
  ],
248
251
  explorer: [
249
252
  "complexity_hotspots",
@@ -330,6 +333,7 @@ var init_constants = __esm(() => {
330
333
  "symbols",
331
334
  "knowledge_recall",
332
335
  "req_coverage",
336
+ "get_approved_plan",
333
337
  "repo_map"
334
338
  ],
335
339
  critic_sounding_board: [
@@ -392,7 +396,9 @@ var init_constants = __esm(() => {
392
396
  "knowledge_recall"
393
397
  ],
394
398
  curator_init: ["knowledge_recall"],
395
- curator_phase: ["knowledge_recall"]
399
+ curator_phase: ["knowledge_recall"],
400
+ council_member: ["web_search"],
401
+ council_moderator: []
396
402
  };
397
403
  WRITE_TOOL_NAMES = [
398
404
  "write",
@@ -412,7 +418,7 @@ var init_constants = __esm(() => {
412
418
  diff_summary: "filter classified AST changes by category, risk level, or file for reviewer drill-down",
413
419
  imports: "dependency audit",
414
420
  lint: "code quality",
415
- placeholder_scan: "placeholder/todo detection",
421
+ placeholder_scan: "todo and FIXME comment detection",
416
422
  secretscan: "secret detection",
417
423
  sast_scan: "static analysis security scan",
418
424
  syntax_check: "syntax validation",
@@ -454,13 +460,15 @@ var init_constants = __esm(() => {
454
460
  gitingest: "fetch a GitHub repository full content via gitingest.com",
455
461
  retrieve_summary: "retrieve the full content of a stored tool output summary",
456
462
  search: "Workspace-scoped ripgrep-style text search with structured JSON output. Supports literal and regex modes, glob filtering, and result limits. NOTE: This is text search, not structural AST search \u2014 use symbols and imports tools for structural queries.",
463
+ web_search: "External web search (Tavily or Brave) for General Council member agents. Returns titled results with snippets and URLs. Restricted to council_member agents via AGENT_TOOL_MAP. Config-gated on council.general.enabled; requires a search API key.",
464
+ convene_general_council: "Synthesize responses from a multi-model General Council. Accepts parallel member responses (Round 1, optionally Round 2), detects disagreements, and returns consensus points, persisting disagreements, a structured synthesis, and an optional moderator prompt. Architect-only. Config-gated on council.general.enabled.",
457
465
  batch_symbols: "Batched symbol extraction across multiple files. Returns per-file symbol summaries with isolated error handling.",
458
466
  suggest_patch: "Reviewer-safe structured patch suggestion tool. Produces context-anchored patch artifacts without file modification. Returns structured diagnostics on context mismatch.",
459
467
  lint_spec: "validate .swarm/spec.md format and required fields",
460
468
  get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
461
469
  repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
462
- get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test), lock state, and profile hash. Read-only.",
463
- set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only \u2014 rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test.",
470
+ get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review), lock state, and profile hash. Read-only.",
471
+ set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only \u2014 rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review.",
464
472
  req_coverage: "query requirement coverage status for tracked functional requirements"
465
473
  };
466
474
  for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
@@ -484,6 +492,8 @@ var init_constants = __esm(() => {
484
492
  designer: "opencode/trinity-large-preview-free",
485
493
  curator_init: "opencode/trinity-large-preview-free",
486
494
  curator_phase: "opencode/trinity-large-preview-free",
495
+ council_member: "opencode/trinity-large-preview-free",
496
+ council_moderator: "opencode/trinity-large-preview-free",
487
497
  default: "opencode/trinity-large-preview-free"
488
498
  };
489
499
  DEFAULT_SCORING_CONFIG = {
@@ -14691,7 +14701,7 @@ function resolveGuardrailsConfig(config2, agentName) {
14691
14701
  };
14692
14702
  return resolved;
14693
14703
  }
14694
- var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, CouncilConfigSchema, ParallelizationConfigSchema, PluginConfigSchema;
14704
+ var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, ParallelizationConfigSchema, PluginConfigSchema;
14695
14705
  var init_schema = __esm(() => {
14696
14706
  init_zod();
14697
14707
  init_constants();
@@ -15208,13 +15218,37 @@ var init_schema = __esm(() => {
15208
15218
  rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({}),
15209
15219
  universal_deny_prefixes: exports_external.array(exports_external.string().min(1)).default([])
15210
15220
  });
15221
+ GeneralCouncilMemberConfigSchema = exports_external.object({
15222
+ memberId: exports_external.string().min(1),
15223
+ model: exports_external.string().min(1),
15224
+ role: exports_external.enum([
15225
+ "generalist",
15226
+ "skeptic",
15227
+ "domain_expert",
15228
+ "devil_advocate",
15229
+ "synthesizer"
15230
+ ]),
15231
+ persona: exports_external.string().optional()
15232
+ }).strict();
15233
+ GeneralCouncilConfigSchema = exports_external.object({
15234
+ enabled: exports_external.boolean().default(false),
15235
+ searchProvider: exports_external.enum(["tavily", "brave"]).default("tavily"),
15236
+ searchApiKey: exports_external.string().optional(),
15237
+ members: exports_external.array(GeneralCouncilMemberConfigSchema).default([]),
15238
+ presets: exports_external.record(exports_external.string(), exports_external.array(GeneralCouncilMemberConfigSchema)).default({}),
15239
+ deliberate: exports_external.boolean().default(true),
15240
+ moderator: exports_external.boolean().default(true),
15241
+ moderatorModel: exports_external.string().optional(),
15242
+ maxSourcesPerMember: exports_external.number().int().min(1).max(20).default(5)
15243
+ }).strict();
15211
15244
  CouncilConfigSchema = exports_external.object({
15212
15245
  enabled: exports_external.boolean().default(false),
15213
15246
  maxRounds: exports_external.number().int().min(1).max(10).default(3),
15214
15247
  parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
15215
15248
  vetoPriority: exports_external.boolean().default(true),
15216
15249
  requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
15217
- escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet.")
15250
+ escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
15251
+ general: GeneralCouncilConfigSchema.optional()
15218
15252
  }).strict();
15219
15253
  ParallelizationConfigSchema = exports_external.object({
15220
15254
  enabled: exports_external.boolean().default(false),
@@ -19708,7 +19742,8 @@ var init_qa_gate_profile = __esm(() => {
19708
19742
  critic_pre_plan: true,
19709
19743
  hallucination_guard: false,
19710
19744
  sast_enabled: true,
19711
- mutation_test: false
19745
+ mutation_test: false,
19746
+ council_general_review: false
19712
19747
  };
19713
19748
  });
19714
19749
 
@@ -41865,6 +41900,76 @@ var init_config2 = __esm(() => {
41865
41900
  init_loader();
41866
41901
  });
41867
41902
 
41903
+ // src/commands/council.ts
41904
+ function sanitizeQuestion(raw) {
41905
+ const collapsed = raw.replace(/\s+/g, " ").trim();
41906
+ const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
41907
+ const normalized = stripped.replace(/\s+/g, " ").trim();
41908
+ if (normalized.length <= MAX_QUESTION_LEN)
41909
+ return normalized;
41910
+ return `${normalized.slice(0, MAX_QUESTION_LEN)}\u2026`;
41911
+ }
41912
+ function sanitizePresetName(raw) {
41913
+ const trimmed = raw.trim();
41914
+ if (!trimmed)
41915
+ return null;
41916
+ if (trimmed.length > 64)
41917
+ return null;
41918
+ if (!/^[A-Za-z0-9_-]+$/.test(trimmed))
41919
+ return null;
41920
+ return trimmed;
41921
+ }
41922
+ function parseArgs(args2) {
41923
+ const out2 = { specReview: false, rest: [] };
41924
+ for (let i2 = 0;i2 < args2.length; i2++) {
41925
+ const token = args2[i2];
41926
+ if (token === "--spec-review") {
41927
+ out2.specReview = true;
41928
+ continue;
41929
+ }
41930
+ if (token === "--preset") {
41931
+ const next = args2[i2 + 1];
41932
+ if (next !== undefined) {
41933
+ const sanitized = sanitizePresetName(next);
41934
+ if (sanitized)
41935
+ out2.preset = sanitized;
41936
+ i2++;
41937
+ }
41938
+ continue;
41939
+ }
41940
+ out2.rest.push(token);
41941
+ }
41942
+ return out2;
41943
+ }
41944
+ async function handleCouncilCommand(_directory, args2) {
41945
+ const parsed = parseArgs(args2);
41946
+ const question = sanitizeQuestion(parsed.rest.join(" "));
41947
+ if (!question) {
41948
+ return USAGE;
41949
+ }
41950
+ const tokens = ["MODE: COUNCIL"];
41951
+ if (parsed.preset) {
41952
+ tokens.push(`preset=${parsed.preset}`);
41953
+ }
41954
+ if (parsed.specReview) {
41955
+ tokens.push("spec_review");
41956
+ }
41957
+ return `[${tokens.join(" ")}] ${question}`;
41958
+ }
41959
+ var MAX_QUESTION_LEN = 2000, USAGE;
41960
+ var init_council = __esm(() => {
41961
+ USAGE = [
41962
+ "Usage: /swarm council <question> [--preset <name>] [--spec-review]",
41963
+ "",
41964
+ " question The question to put to the council",
41965
+ " --preset <name> Use a named member preset from council.general.presets",
41966
+ " --spec-review Use spec_review mode (single advisory pass on a draft spec)",
41967
+ "",
41968
+ "Requires council.general.enabled: true and a configured search API key in opencode-swarm.json."
41969
+ ].join(`
41970
+ `);
41971
+ });
41972
+
41868
41973
  // src/agents/explorer.ts
41869
41974
  function createExplorerAgent(model, customPrompt, customAppendPrompt) {
41870
41975
  let prompt = EXPLORER_PROMPT;
@@ -52151,7 +52256,8 @@ var init_qa_gates = __esm(() => {
52151
52256
  "critic_pre_plan",
52152
52257
  "hallucination_guard",
52153
52258
  "sast_enabled",
52154
- "mutation_test"
52259
+ "mutation_test",
52260
+ "council_general_review"
52155
52261
  ];
52156
52262
  });
52157
52263
 
@@ -53777,6 +53883,7 @@ var init_registry = __esm(() => {
53777
53883
  init_checkpoint2();
53778
53884
  init_close();
53779
53885
  init_config2();
53886
+ init_council();
53780
53887
  init_curate();
53781
53888
  init_dark_matter();
53782
53889
  init_diagnose();
@@ -53932,11 +54039,17 @@ var init_registry = __esm(() => {
53932
54039
  args: "[topic-text]",
53933
54040
  details: "Triggers the architect to run the brainstorm workflow: CONTEXT SCAN, single-question DIALOGUE, APPROACHES, DESIGN SECTIONS, SPEC WRITE + SELF-REVIEW, QA GATE SELECTION, TRANSITION. Use for new plans where requirements need to be drawn out before writing spec.md / plan.md."
53934
54041
  },
54042
+ council: {
54043
+ handler: (ctx) => handleCouncilCommand(ctx.directory, ctx.args),
54044
+ description: "Enter architect MODE: COUNCIL \u2014 multi-model deliberation [question] [--preset <name>] [--spec-review]",
54045
+ args: "<question> [--preset <name>] [--spec-review]",
54046
+ details: "Triggers the architect to convene a configurable General Council: each member independently web-searches, answers, and engages in one structured deliberation round on disagreements; an optional moderator pass synthesizes the final answer. --preset <name> selects a member group from council.general.presets. --spec-review switches to single-pass advisory mode for spec review. Requires council.general.enabled: true and a search API key in opencode-swarm.json."
54047
+ },
53935
54048
  "qa-gates": {
53936
54049
  handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
53937
54050
  description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
53938
54051
  args: "[show|enable|override] <gate>...",
53939
- details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test."
54052
+ details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, council_general_review."
53940
54053
  },
53941
54054
  promote: {
53942
54055
  handler: (ctx) => handlePromoteCommand(ctx.directory, ctx.args),
@@ -54046,7 +54159,7 @@ follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
54046
54159
  ### Phase 1 \u2014 Parallel dispatch (when the coder signals the task is complete)
54047
54160
  Dispatch all FIVE council members IN PARALLEL \u2014 do not run them sequentially.
54048
54161
  Each receives ONLY their role-relevant context, not the full conversation:
54049
- - \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results
54162
+ - \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results + approved-plan baseline comparison (via \`get_approved_plan\`) and spec-intent drift analysis against the approved baseline
54050
54163
  - \`reviewer\` \u2014 semantic diff summary + blast radius (files importing changed files) + style guide
54051
54164
  - \`sme\` \u2014 task domain context + relevant knowledge base entries
54052
54165
  - \`test_engineer\` \u2014 changed test files + coverage delta + known mutation gaps
@@ -54092,14 +54205,24 @@ from different members.`;
54092
54205
  function buildYourToolsList(council) {
54093
54206
  const tools = AGENT_TOOL_MAP.architect ?? [];
54094
54207
  const sorted = [...tools].sort();
54095
- const filtered = council?.enabled === true ? sorted : sorted.filter((t) => t !== "convene_council" && t !== "declare_council_criteria");
54208
+ const qaCouncilEnabled = council?.enabled === true;
54209
+ const generalCouncilEnabled = council?.general?.enabled === true;
54210
+ const filtered = sorted.filter((t) => {
54211
+ if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
54212
+ return false;
54213
+ }
54214
+ if (!generalCouncilEnabled && t === "convene_general_council") {
54215
+ return false;
54216
+ }
54217
+ return true;
54218
+ });
54096
54219
  return `Task (delegation), ${filtered.join(", ")}.`;
54097
54220
  }
54098
54221
  function buildQaGateSelectionDialogue(modeLabel) {
54099
54222
  const leadIn = modeLabel === "BRAINSTORM" ? "Now ask the user which QA gates to enable for this plan \u2014 do not select on their behalf." : modeLabel === "SPECIFY" ? "Ask the user which QA gates to enable for this plan before suggesting the next step." : "No pending gate selection found in `.swarm/context.md`. Ask the user inline now.";
54100
54223
  return `${leadIn}
54101
54224
 
54102
- Present the eight gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The eight gates are:
54225
+ Present the nine gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The nine gates are:
54103
54226
  - reviewer (default: ON) \u2014 code review of coder output
54104
54227
  - test_engineer (default: ON) \u2014 test verification of coder output
54105
54228
  - sme_enabled (default: ON) \u2014 SME consultation during planning/clarification
@@ -54108,13 +54231,24 @@ Present the eight gates with their defaults (DEFAULT_QA_GATES) as a single user-
54108
54231
  - council_mode (default: OFF) \u2014 multi-member council gate (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
54109
54232
  - hallucination_guard (default: OFF) \u2014 when enabled, mandatory per-phase API/signature/claim/citation verification via critic_hallucination_verifier at PHASE-WRAP; phase_complete will REJECT phase completion unless .swarm/evidence/{phase}/hallucination-guard.json exists with an APPROVED verdict (recommended for claim-heavy or research-heavy work)
54110
54233
  - mutation_test (default: OFF) \u2014 when enabled, runs mutation testing on source files touched this phase via generate_mutants + mutation_test + write_mutation_evidence at PHASE-WRAP; FAIL verdict blocks phase_complete; WARN is non-blocking (recommended for projects with coverage gaps or safety-critical code)
54234
+ - council_general_review (default: OFF) \u2014 when enabled, MODE: SPECIFY runs convene_general_council on the draft spec before the critic-gate; multiple models each independently search the web, deliberate on disagreements, and a moderator synthesizes a final answer that the architect folds into the spec (recommended for novel architecture, unclear best practices, or high-risk design decisions). Requires council.general.enabled: true and a configured search API key.
54111
54235
 
54112
54236
  One question, one message, defaults pre-stated. Wait for the user's answer.`;
54113
54237
  }
54114
54238
  function buildAvailableToolsList(council) {
54115
54239
  const tools = AGENT_TOOL_MAP.architect ?? [];
54116
54240
  const sorted = [...tools].sort();
54117
- const filtered = council?.enabled === true ? sorted : sorted.filter((t) => t !== "convene_council" && t !== "declare_council_criteria");
54241
+ const qaCouncilEnabled = council?.enabled === true;
54242
+ const generalCouncilEnabled = council?.general?.enabled === true;
54243
+ const filtered = sorted.filter((t) => {
54244
+ if (!qaCouncilEnabled && (t === "convene_council" || t === "declare_council_criteria")) {
54245
+ return false;
54246
+ }
54247
+ if (!generalCouncilEnabled && t === "convene_general_council") {
54248
+ return false;
54249
+ }
54250
+ return true;
54251
+ });
54118
54252
  return filtered.map((t) => {
54119
54253
  const desc = TOOL_DESCRIPTIONS[t];
54120
54254
  return desc ? `${t} (${desc})` : t;
@@ -54155,7 +54289,8 @@ function buildSlashCommandsList() {
54155
54289
  "analyze",
54156
54290
  "plan",
54157
54291
  "sync-plan",
54158
- "acknowledge-spec-drift"
54292
+ "acknowledge-spec-drift",
54293
+ "council"
54159
54294
  ],
54160
54295
  "Execution Modes": ["turbo", "full-auto"],
54161
54296
  Observation: [
@@ -54800,6 +54935,29 @@ MODE: BRAINSTORM runs seven phases in strict order. Do not skip phases. Do not c
54800
54935
  **Phase 6: QA GATE SELECTION (architect, dialogue only).**
54801
54936
  {{QA_GATE_DIALOGUE_BRAINSTORM}}
54802
54937
 
54938
+ <!-- BEHAVIORAL_GUIDANCE_START -->
54939
+ GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
54940
+ \u2717 "I'll use the defaults \u2014 they're probably fine"
54941
+ \u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
54942
+ \u2717 "The user didn't mention gates, so defaults are fine"
54943
+ \u2192 WRONG: silence is not consent. The gate dialogue is not optional.
54944
+ \u2717 "I'll handle it in MODE: PLAN after the spec is done"
54945
+ \u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
54946
+ save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
54947
+ \u2717 "This feature is simple \u2014 gates are obvious"
54948
+ \u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
54949
+ \u2717 "I already know which gates are right for this project"
54950
+ \u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
54951
+ \u2717 "council_general_review is off by default, I don't need to mention it"
54952
+ \u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
54953
+
54954
+ MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
54955
+ You are BLOCKED until ALL THREE of these conditions are met:
54956
+ (1) The gate selection question has been presented to the user in a single message
54957
+ (2) The user has responded (accept defaults OR customized list)
54958
+ (3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
54959
+ <!-- BEHAVIORAL_GUIDANCE_END -->
54960
+
54803
54961
  Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
54804
54962
  \`\`\`
54805
54963
  ## Pending QA Gate Selection
@@ -54811,6 +54969,7 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
54811
54969
  - council_mode: <true|false>
54812
54970
  - hallucination_guard: <true|false>
54813
54971
  - mutation_test: <true|false>
54972
+ - council_general_review: <true|false>
54814
54973
  - recorded_at: <ISO timestamp>
54815
54974
  \`\`\`
54816
54975
  MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
@@ -54853,6 +55012,29 @@ Activates when: user asks to "specify", "define requirements", "write a spec", o
54853
55012
  5b. **QA GATE SELECTION (dialogue only).**
54854
55013
  {{QA_GATE_DIALOGUE_SPECIFY}}
54855
55014
 
55015
+ <!-- BEHAVIORAL_GUIDANCE_START -->
55016
+ GATE SELECTION IS MANDATORY \u2014 these thoughts are WRONG and must be ignored:
55017
+ \u2717 "I'll use the defaults \u2014 they're probably fine"
55018
+ \u2192 WRONG: defaults are not the user's decision. The user must be asked every time.
55019
+ \u2717 "The user didn't mention gates, so defaults are fine"
55020
+ \u2192 WRONG: silence is not consent. The gate dialogue is not optional.
55021
+ \u2717 "I'll handle it in MODE: PLAN after the spec is done"
55022
+ \u2192 WRONG: ## Pending QA Gate Selection must exist in context.md BEFORE save_plan is called.
55023
+ save_plan will reject with QA_GATE_SELECTION_REQUIRED if this section is absent.
55024
+ \u2717 "This feature is simple \u2014 gates are obvious"
55025
+ \u2192 WRONG: complexity does not exempt this step. Gate selection is mandatory for ALL plans.
55026
+ \u2717 "I already know which gates are right for this project"
55027
+ \u2192 WRONG: the architect does not configure gates. The user configures gates. Always ask.
55028
+ \u2717 "council_general_review is off by default, I don't need to mention it"
55029
+ \u2192 WRONG: every gate is presented with its default stated. The user opts in or accepts the default explicitly.
55030
+
55031
+ MANDATORY PAUSE: Do NOT write the spec summary (step 7). Do NOT suggest next steps.
55032
+ You are BLOCKED until ALL THREE of these conditions are met:
55033
+ (1) The gate selection question has been presented to the user in a single message
55034
+ (2) The user has responded (accept defaults OR customized list)
55035
+ (3) The elected gates have been written to .swarm/context.md under "## Pending QA Gate Selection"
55036
+ <!-- BEHAVIORAL_GUIDANCE_END -->
55037
+
54856
55038
  Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this point. Once the user answers, write the elected gates to \`.swarm/context.md\` under a new section:
54857
55039
  \`\`\`
54858
55040
  ## Pending QA Gate Selection
@@ -54864,9 +55046,37 @@ Do NOT call \`set_qa_gates\` yet \u2014 \`plan.json\` does not exist at this poi
54864
55046
  - council_mode: <true|false>
54865
55047
  - hallucination_guard: <true|false>
54866
55048
  - mutation_test: <true|false>
55049
+ - council_general_review: <true|false>
54867
55050
  - recorded_at: <ISO timestamp>
54868
55051
  \`\`\`
54869
55052
  MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
55053
+
55054
+ 5c. **SPECIFY-COUNCIL-REVIEW (fires ONLY when council_general_review gate is true).**
55055
+ Read the elected QA gates (parse the \`## Pending QA Gate Selection\` section from \`.swarm/context.md\` you just wrote, OR call \`get_qa_gate_profile\` if a profile already exists). If \`council_general_review\` is false or absent, skip directly to step 7.
55056
+
55057
+ If \`council_general_review\` is true:
55058
+ 1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured, surface to the user: "council_general_review gate is enabled but the General Council is not configured. Set council.general.enabled: true and configure a search API key in opencode-swarm.json, or unset council_general_review and re-run." Then stop.
55059
+ 2. Determine the council members from \`council.general.members\` (or \`council.general.presets[<name>]\` if you were invoked via \`/swarm council --preset <name>\` originally).
55060
+ 3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait. Pass: the spec text as the question, the member's role/persona, round number 1. Do NOT share other members' perspectives at this stage.
55061
+ 4. Collect all member JSON responses.
55062
+ 5. Call \`convene_general_council\` with mode: 'spec_review', the spec as question, and the collected \`round1Responses\`. Omit \`round2Responses\` \u2014 spec review is a single-pass advisory, not a full deliberation.
55063
+ 6. Read \`consensusPoints\` \u2014 incorporate unambiguous consensus directly into the spec.
55064
+ 7. Read \`disagreements\` \u2014 for each: (a) accept one position with rationale, (b) mark as \`[NEEDS CLARIFICATION]\` in the spec, or (c) schedule an SME consultation.
55065
+ 8. If \`council.general.moderator\` is true, the tool returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. Use the moderator's output to refine the spec further.
55066
+ 9. Revise \`.swarm/spec.md\` to reflect the council input.
55067
+
55068
+ <!-- BEHAVIORAL_GUIDANCE_START -->
55069
+ SPECIFY-COUNCIL-REVIEW RULES:
55070
+ \u2717 "council_general_review is off by default, I'll skip this"
55071
+ \u2192 CORRECT only when the gate is explicitly false or absent. Do NOT assume false. Read the actual gate value before deciding to skip.
55072
+ \u2717 "The spec is already good, no need to ask the council"
55073
+ \u2192 WRONG when gate is true: the user enabled this gate for a reason. Run it regardless.
55074
+ \u2717 "I'll include round2Responses for spec_review \u2014 more is better"
55075
+ \u2192 WRONG: spec review is a single advisory pass. Omit \`round2Responses\` for spec_review mode.
55076
+ \u2717 "I'll skip the moderator pass to save time"
55077
+ \u2192 WRONG when council.general.moderator is true: invoke \`{{AGENT_PREFIX}}council_moderator\` with the moderatorPrompt the tool returns.
55078
+ <!-- BEHAVIORAL_GUIDANCE_END -->
55079
+
54870
55080
  7. Report a summary to the user (MUST count, SHALL count, scenario count, clarification markers, elected QA gates) and suggest the next step: \`CLARIFY-SPEC\` (if markers exist) or \`PLAN\`.
54871
55081
 
54872
55082
  SPEC CONTENT RULES \u2014 the spec MUST NOT contain:
@@ -55035,6 +55245,39 @@ This check fires automatically in:
55035
55245
 
55036
55246
  GREENFIELD EXEMPTION: If the work is purely greenfield (new project, no existing codebase references), skip this check.
55037
55247
 
55248
+ ### MODE: COUNCIL
55249
+
55250
+ Activates when: user invokes \`/swarm council <question>\` (optionally with \`--preset <name>\` or \`--spec-review\`).
55251
+
55252
+ Purpose: convene a configurable multi-model General Council for an advisory deliberation. Each member independently web-searches and answers; the architect routes any disagreements back for one targeted reconciliation round; an optional moderator pass synthesizes the final user-facing answer.
55253
+
55254
+ This mode is ADVISORY \u2014 it does NOT block any other workflow and does NOT modify code, plans, or specs. The output is for the user (general mode) or for the spec being drafted in MODE: SPECIFY (spec_review mode, gated by \`council_general_review\`).
55255
+
55256
+ #### Pre-flight (always run first)
55257
+ 1. Read \`council.general\` config. If \`council.general.enabled\` is not true OR no search API key is configured (neither \`council.general.searchApiKey\` nor the corresponding env var \`TAVILY_API_KEY\` / \`BRAVE_SEARCH_API_KEY\`), surface to the user: "General Council is not enabled. Set council.general.enabled: true and configure a search API key in opencode-swarm.json." Then STOP.
55258
+
55259
+ #### Round 1 \u2014 Parallel Independent Search
55260
+ 2. Determine council members. Default: \`council.general.members\`. If invoked with \`--preset <name>\`: \`council.general.presets[<name>]\`. If a named preset is missing, surface a clear error and stop.
55261
+ 3. Delegate to each council member in PARALLEL \u2014 one message per member, then STOP and wait for all responses to come back. Pass: the question, the member's role/persona, round number 1. Do NOT share other members' responses at this stage.
55262
+ 4. Collect all member JSON responses (each member returns a fenced JSON block per the council_member prompt).
55263
+
55264
+ #### Synthesis and Deliberation (when council.general.deliberate is true; default true)
55265
+ 5. Call \`convene_general_council\` with mode set from the command (\`general\` or \`spec_review\`), \`question\`, and the collected \`round1Responses\` only (omit \`round2Responses\`). Inspect the returned \`disagreementsCount\`.
55266
+ 6. If \`disagreementsCount > 0\`:
55267
+ a. For each disagreement in the tool's response, identify the disputing members (the members listed in the disagreement's positions).
55268
+ b. Re-delegate ONLY to the disputing members \u2014 one message per member \u2014 passing: their Round 1 response, the disagreement topic, the opposing position(s), round number 2.
55269
+ c. Collect the Round 2 responses.
55270
+ d. Call \`convene_general_council\` AGAIN with both \`round1Responses\` AND \`round2Responses\` populated.
55271
+
55272
+ #### Moderator Pass (when council.general.moderator is true; default true)
55273
+ 7. The most recent \`convene_general_council\` call returned a \`moderatorPrompt\` field. Delegate this prompt to \`{{AGENT_PREFIX}}council_moderator\`. The moderator agent has no tools and no web access \u2014 it synthesizes a final user-facing answer from the council output you give it. Collect the moderator's markdown output.
55274
+
55275
+ #### Output
55276
+ 8. Present the final answer to the user:
55277
+ - If the moderator pass ran: present the moderator's output verbatim, prefaced with the participating models (one line).
55278
+ - If no moderator: present the structural \`synthesis\` markdown from the tool's return.
55279
+ In either case, do NOT present the raw per-member JSON. Do NOT silently pick a winner among persisting disagreements \u2014 surface them honestly.
55280
+
55038
55281
  ### MODE: PLAN
55039
55282
 
55040
55283
  SPEC GATE (soft \u2014 check before planning):
@@ -55113,7 +55356,18 @@ save_plan({
55113
55356
  **POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
55114
55357
  After \`save_plan\` succeeds, read \`.swarm/context.md\`:
55115
55358
  - If a \`## Pending QA Gate Selection\` section exists: parse the gate values, call \`set_qa_gates\` with those flags, confirm with the user ("QA gates applied: <list>"), then remove the section from context.md.
55116
- - If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}} Then call \`set_qa_gates\` with the user's chosen flags.
55359
+ - If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
55360
+ <!-- BEHAVIORAL_GUIDANCE_START -->
55361
+ INLINE GATE SELECTION \u2014 no pending section found in context.md. You MUST ask now.
55362
+ \u2717 "I'll call set_qa_gates with defaults and move on"
55363
+ \u2192 WRONG: set_qa_gates with assumed values is a gate violation. The user must answer first.
55364
+ \u2717 "The user provided a plan \u2014 they know what gates they want"
55365
+ \u2192 WRONG: providing a plan is not the same as configuring gates. Always ask.
55366
+
55367
+ MANDATORY PAUSE: Present the gate question. Wait for the user's answer.
55368
+ Do NOT call \`set_qa_gates\` until the user has responded.
55369
+ <!-- BEHAVIORAL_GUIDANCE_END -->
55370
+ Then call \`set_qa_gates\` with the user's chosen flags.
55117
55371
  Either path must yield a persisted QA gate profile before the first task dispatches.
55118
55372
 
55119
55373
  \u26A0\uFE0F If \`save_plan\` is unavailable, delegate plan writing to {{AGENT_PREFIX}}coder:
@@ -55702,6 +55956,189 @@ META.SUMMARY CONVENTION \u2014 When reporting task completion, include:
55702
55956
 
55703
55957
  `;
55704
55958
 
55959
+ // src/agents/council-member.ts
55960
+ function createCouncilMemberAgent(model, customPrompt, customAppendPrompt) {
55961
+ let prompt = COUNCIL_MEMBER_PROMPT;
55962
+ if (customPrompt) {
55963
+ prompt = customPrompt;
55964
+ } else if (customAppendPrompt) {
55965
+ prompt = `${COUNCIL_MEMBER_PROMPT}
55966
+
55967
+ ${customAppendPrompt}`;
55968
+ }
55969
+ return {
55970
+ name: "council_member",
55971
+ description: "General Council deliberation member. Independently web-searches and answers in Round 1; " + "targeted MAINTAIN/CONCEDE/NUANCE deliberation in Round 2. Tool-restricted to web_search only.",
55972
+ config: {
55973
+ model,
55974
+ temperature: 0.4,
55975
+ prompt,
55976
+ tools: {
55977
+ write: false,
55978
+ edit: false,
55979
+ patch: false
55980
+ }
55981
+ }
55982
+ };
55983
+ }
55984
+ var COUNCIL_MEMBER_PROMPT = `You are Council Member {{MEMBER_ID}} ({{ROLE}}) on a multi-model General Council.
55985
+
55986
+ {{PERSONA_BLOCK}}
55987
+
55988
+ You are participating in Round {{ROUND}} of a structured deliberation. Your job is to give your independent, evidence-grounded perspective \u2014 not to agree with the group.
55989
+
55990
+ ================================================================
55991
+ ROUND {{ROUND}} PROTOCOL
55992
+ ================================================================
55993
+
55994
+ ROUND 1 \u2014 Independent Research and Answer
55995
+ - Issue 1\u20133 targeted web_search calls to gather evidence relevant to the question.
55996
+ - Cite EVERY factual claim with a source URL from your search results.
55997
+ - State your confidence (0.0\u20131.0) explicitly. Be honest \u2014 overconfident answers hurt the council.
55998
+ - Enumerate areas of uncertainty so the architect knows where you're guessing vs. where you're sure.
55999
+ - Do NOT coordinate with other members. You will not see their responses until Round 2.
56000
+ - Do NOT pad. Be concise. Substance over volume.
56001
+
56002
+ ROUND 2 \u2014 Targeted Deliberation (ONLY when this round is invoked for you)
56003
+ - {{DISAGREEMENT_BLOCK}}
56004
+ - Issue at most 1 additional web_search call.
56005
+ - Declare your stance explicitly using one of these keywords as the FIRST word of a paragraph:
56006
+ MAINTAIN \u2014 your Round 1 position holds; cite the new evidence supporting it
56007
+ CONCEDE \u2014 the opposing position is correct; state specifically what you got wrong
56008
+ NUANCE \u2014 both positions are partially right; state the boundary condition that distinguishes them
56009
+ - Never CONCEDE without evidence. Sycophantic capitulation degrades the council below an individual member's baseline (NSED arXiv:2601.16863).
56010
+ - Never MAINTAIN without engaging the opposing argument on its merits.
56011
+
56012
+ ================================================================
56013
+ RESPONSE FORMAT (always \u2014 both rounds)
56014
+ ================================================================
56015
+
56016
+ Reply with a single fenced JSON block. No prose outside the block.
56017
+
56018
+ \`\`\`json
56019
+ {
56020
+ "memberId": "{{MEMBER_ID}}",
56021
+ "role": "{{ROLE}}",
56022
+ "round": {{ROUND}},
56023
+ "response": "Your full answer (Round 1) or stance + reasoning (Round 2). Markdown OK inside the string.",
56024
+ "searchQueries": ["query 1", "query 2"],
56025
+ "sources": [
56026
+ { "title": "...", "url": "...", "snippet": "...", "query": "..." }
56027
+ ],
56028
+ "confidence": 0.85,
56029
+ "areasOfUncertainty": [
56030
+ "What I'm not sure about, in plain language."
56031
+ ],
56032
+ "disagreementTopics": []
56033
+ }
56034
+ \`\`\`
56035
+
56036
+ For Round 1: leave \`disagreementTopics\` as []. For Round 2: list the specific disagreement topics this response addresses.
56037
+
56038
+ ================================================================
56039
+ HARD RULES
56040
+ ================================================================
56041
+ - web_search is your ONLY tool. You cannot read or write files, run commands, or delegate.
56042
+ - Never invent sources. If a search returns nothing useful, say so in \`areasOfUncertainty\`.
56043
+ - Never echo other members' responses verbatim. Paraphrase or quote with attribution.
56044
+ - Stay within your role and persona. The architect chose you for a specific perspective.
56045
+ `;
56046
+
56047
+ // src/agents/council-moderator.ts
56048
+ function createCouncilModeratorAgent(model, customPrompt, customAppendPrompt) {
56049
+ let prompt = COUNCIL_MODERATOR_PROMPT;
56050
+ if (customPrompt) {
56051
+ prompt = customPrompt;
56052
+ } else if (customAppendPrompt) {
56053
+ prompt = `${COUNCIL_MODERATOR_PROMPT}
56054
+
56055
+ ${customAppendPrompt}`;
56056
+ }
56057
+ return {
56058
+ name: "council_moderator",
56059
+ description: "General Council moderator. Synthesizes a coherent final answer from member " + "responses; no web search (works on already-gathered content).",
56060
+ config: {
56061
+ model,
56062
+ temperature: 0.3,
56063
+ prompt,
56064
+ tools: {
56065
+ write: false,
56066
+ edit: false,
56067
+ patch: false
56068
+ }
56069
+ }
56070
+ };
56071
+ }
56072
+ var COUNCIL_MODERATOR_PROMPT = `You are the General Council Moderator.
56073
+
56074
+ You are receiving the structural synthesis from a multi-model council deliberation:
56075
+ - Question (and mode: general or spec_review)
56076
+ - All member Round 1 responses with sources
56077
+ - Detected disagreements
56078
+ - Round 2 deliberation responses (if any)
56079
+ - Confidence-weighted consensus claims
56080
+ - Persisting disagreements after deliberation
56081
+
56082
+ Your job: produce a coherent, well-structured final answer for the user.
56083
+
56084
+ ================================================================
56085
+ RULES
56086
+ ================================================================
56087
+
56088
+ 1. LEAD WITH CONSENSUS \u2014 open with the strongest consensus position. Use the
56089
+ confidence-weighted ordering (Quadratic Voting): higher-confidence claims
56090
+ from multiple members rank higher, but evidence quality outranks raw
56091
+ confidence. Never elevate a single confident voice over a well-evidenced
56092
+ contrary majority.
56093
+
56094
+ 2. ACKNOWLEDGE DISAGREEMENT HONESTLY \u2014 for each persisting disagreement, write
56095
+ "experts disagree on X because\u2026" and present the strongest version of each
56096
+ side. Do NOT pretend disagreements are resolved when they are not. Do NOT
56097
+ silently pick a winner.
56098
+
56099
+ 3. CITE THE STRONGEST SOURCES \u2014 link key claims with [title](url) format from
56100
+ the deduplicated source list. Pick the most reputable source for each claim;
56101
+ do not cite duplicates.
56102
+
56103
+ 4. BE CONCISE \u2014 the user wants an answer, not a committee report. Default
56104
+ length: a few short paragraphs plus a bulleted summary. Expand only when
56105
+ the question genuinely requires it.
56106
+
56107
+ ================================================================
56108
+ HARD CONSTRAINTS
56109
+ ================================================================
56110
+
56111
+ - You MUST NOT invent claims that are not present in the council's responses.
56112
+ - You MUST NOT add new web research. If something was missed, say so.
56113
+ - You MUST NOT favor a position based on member confidence alone \u2014 evidence
56114
+ quality is the tie-breaker.
56115
+ - You have NO tools. You write the final synthesis from the input given.
56116
+
56117
+ ================================================================
56118
+ OUTPUT FORMAT
56119
+ ================================================================
56120
+
56121
+ Plain markdown. No code fences. No JSON. Suggested structure:
56122
+
56123
+ # Answer
56124
+
56125
+ <lead consensus position with citation(s)>
56126
+
56127
+ <remaining consensus / context paragraphs as needed>
56128
+
56129
+ ## Where Experts Disagree
56130
+
56131
+ - <topic 1>: <position A> vs <position B>, with sources for each
56132
+ - <topic 2>: ...
56133
+
56134
+ ## Sources
56135
+
56136
+ - [title](url)
56137
+ - ...
56138
+
56139
+ (Omit any section that is empty.)
56140
+ `;
56141
+
55705
56142
  // src/agents/critic.ts
55706
56143
  function parseSoundingBoardResponse(raw) {
55707
56144
  if (typeof raw !== "string" || raw.trim().length === 0)
@@ -55841,6 +56278,18 @@ EXECUTION PROFILE CHECK (when plan includes execution_profile):
55841
56278
  - Task Atomicity: Does any single task touch 2+ files or mix unrelated concerns ("implement auth and add logging and refactor config")? Flag as MAJOR \u2014 oversized tasks blow coder's context and cause downstream gate failures. Suggested fix: Split into sequential single-file tasks grouped by concern, not per-file subtasks.
55842
56279
  - Governance Compliance (conditional): If \`.swarm/context.md\` contains a \`## Project Governance\` section, read the MUST and SHOULD rules and validate the plan against them. MUST rule violations are CRITICAL severity. SHOULD rule violations are recommendation-level (note them but do not block approval). If no \`## Project Governance\` section exists in context.md, skip this check silently.
55843
56280
 
56281
+ ## BASELINE COMPARISON (mandatory before plan review)
56282
+
56283
+ Before reviewing the plan, check whether it was silently mutated since last critic approval.
56284
+
56285
+ 1. Call the \`get_approved_plan\` tool (no arguments required \u2014 it derives identity internally).
56286
+ 2. Examine the response:
56287
+ - If \`success: false\` with \`reason: "no_approved_snapshot"\`: this is the first plan or no prior approval exists. Note this and proceed with plan review.
56288
+ - If \`drift_detected: false\`: baseline integrity confirmed \u2014 the plan has not been mutated since the last critic approval. Proceed with plan review.
56289
+ - If \`drift_detected: true\`: CRITICAL finding \u2014 plan mutated after approval. Compare \`approved_plan\` vs \`current_plan\` to identify what changed (phases added/removed, tasks modified, scope changes). Report findings in a \`## BASELINE DRIFT\` section before the rubric assessment.
56290
+ - If \`drift_detected: "unknown"\`: flag as warning and proceed with caution.
56291
+ 3. Report spec-intent divergence: compare the approved baseline intent against what the current plan actually does, not just structural diff. Identify if the plan's purpose or scope has drifted from the original approved intent.
56292
+
55844
56293
  ## PLAN ASSESSMENT DIMENSIONS
55845
56294
  Evaluate ALL seven dimensions. Report any that fail:
55846
56295
  1. TASK ATOMICITY: Can each task be completed and QA'd independently?
@@ -57334,6 +57783,19 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
57334
57783
  testEngineer.name = prefixName("test_engineer");
57335
57784
  agents.push(applyOverrides(testEngineer, swarmAgents, swarmPrefix));
57336
57785
  }
57786
+ if (pluginConfig?.council?.general?.enabled === true && !isAgentDisabled("council_member", swarmAgents, swarmPrefix)) {
57787
+ const councilMemberPrompts = getPrompts("council_member");
57788
+ const councilMember = createCouncilMemberAgent(getModel("council_member"), councilMemberPrompts.prompt, councilMemberPrompts.appendPrompt);
57789
+ councilMember.name = prefixName("council_member");
57790
+ agents.push(applyOverrides(councilMember, swarmAgents, swarmPrefix));
57791
+ }
57792
+ if (pluginConfig?.council?.general?.enabled === true && pluginConfig?.council?.general?.moderator === true && !isAgentDisabled("council_moderator", swarmAgents, swarmPrefix)) {
57793
+ const moderatorPrompts = getPrompts("council_moderator");
57794
+ const moderatorModel = pluginConfig?.council?.general?.moderatorModel ?? getModel("council_moderator");
57795
+ const councilModerator = createCouncilModeratorAgent(moderatorModel, moderatorPrompts.prompt, moderatorPrompts.appendPrompt);
57796
+ councilModerator.name = prefixName("council_moderator");
57797
+ agents.push(applyOverrides(councilModerator, swarmAgents, swarmPrefix));
57798
+ }
57337
57799
  if (!isAgentDisabled("docs", swarmAgents, swarmPrefix)) {
57338
57800
  const docsPrompts = getPrompts("docs");
57339
57801
  const docs = createDocsAgent(getModel("docs"), docsPrompts.prompt, docsPrompts.appendPrompt);
@@ -62346,7 +62808,7 @@ var init_curator_drift = __esm(() => {
62346
62808
 
62347
62809
  // src/index.ts
62348
62810
  init_agents();
62349
- import * as path102 from "path";
62811
+ import * as path103 from "path";
62350
62812
 
62351
62813
  // src/background/index.ts
62352
62814
  init_event_bus();
@@ -62645,6 +63107,7 @@ init_benchmark();
62645
63107
  init_checkpoint2();
62646
63108
  init_close();
62647
63109
  init_config2();
63110
+ init_council();
62648
63111
  init_curate();
62649
63112
  init_dark_matter();
62650
63113
  init_diagnose();
@@ -74003,6 +74466,496 @@ var convene_council = createSwarmTool({
74003
74466
  }, null, 2);
74004
74467
  }
74005
74468
  });
74469
+ // src/tools/convene-general-council.ts
74470
+ init_dist();
74471
+ init_zod();
74472
+ init_loader();
74473
+ import * as fs59 from "fs";
74474
+ import * as path73 from "path";
74475
+
74476
+ // src/council/general-council-advisory.ts
74477
+ var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
74478
+ function pushGeneralCouncilAdvisory(session, result) {
74479
+ if (!session)
74480
+ return;
74481
+ const body2 = renderAdvisoryBody(result);
74482
+ if (!body2)
74483
+ return;
74484
+ session.pendingAdvisoryMessages ??= [];
74485
+ session.pendingAdvisoryMessages.push(`${ADVISORY_HEADER}
74486
+ ${body2}`);
74487
+ }
74488
+ function renderAdvisoryBody(result) {
74489
+ const parts2 = [result.synthesis];
74490
+ if (result.moderatorOutput && result.moderatorOutput.trim().length > 0) {
74491
+ parts2.push("", "### Moderator Output", result.moderatorOutput);
74492
+ }
74493
+ return parts2.join(`
74494
+ `).trim();
74495
+ }
74496
+
74497
+ // src/council/disagreement-detector.ts
74498
+ var MAX_DISAGREEMENTS = 10;
74499
+ var EXPLICIT_DISAGREEMENT_MARKERS = [
74500
+ "i disagree with",
74501
+ "i would push back on",
74502
+ "contrary to",
74503
+ "this contradicts",
74504
+ "unlike "
74505
+ ];
74506
+ var STRONG_RECOMMENDATION_MARKERS = [
74507
+ "recommend",
74508
+ "best approach",
74509
+ "should use",
74510
+ "i suggest",
74511
+ "the answer is",
74512
+ "the right choice is"
74513
+ ];
74514
+ function tokenize(text) {
74515
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
74516
+ }
74517
+ function termOverlap(a, b) {
74518
+ const tokensA = new Set(tokenize(a));
74519
+ const tokensB = new Set(tokenize(b));
74520
+ if (tokensA.size === 0 || tokensB.size === 0)
74521
+ return 0;
74522
+ let intersection3 = 0;
74523
+ for (const t of tokensA) {
74524
+ if (tokensB.has(t))
74525
+ intersection3++;
74526
+ }
74527
+ const union3 = tokensA.size + tokensB.size - intersection3;
74528
+ return union3 === 0 ? 0 : intersection3 / union3;
74529
+ }
74530
+ function extractMarkerSentence(response, markers) {
74531
+ const lower = response.toLowerCase();
74532
+ const sentences = response.split(/(?<=[.!?])\s+/);
74533
+ for (const sentence of sentences) {
74534
+ const sentLower = sentence.toLowerCase();
74535
+ if (markers.some((m) => sentLower.includes(m))) {
74536
+ return sentence.trim();
74537
+ }
74538
+ }
74539
+ for (const marker of markers) {
74540
+ const idx = lower.indexOf(marker);
74541
+ if (idx !== -1) {
74542
+ const slice = response.slice(idx, idx + 200);
74543
+ return slice.split(/\n/)[0]?.trim() ?? slice.trim();
74544
+ }
74545
+ }
74546
+ return null;
74547
+ }
74548
+ function dedupeByTopic(disagreements) {
74549
+ const result = [];
74550
+ for (const d of disagreements) {
74551
+ const topicLower = d.topic.toLowerCase();
74552
+ const existing = result.find((r) => r.topic.toLowerCase().includes(topicLower) || topicLower.includes(r.topic.toLowerCase()));
74553
+ if (existing) {
74554
+ for (const pos of d.positions) {
74555
+ if (!existing.positions.some((p) => p.memberId === pos.memberId)) {
74556
+ existing.positions.push(pos);
74557
+ }
74558
+ }
74559
+ } else {
74560
+ result.push(d);
74561
+ }
74562
+ }
74563
+ return result;
74564
+ }
74565
+ function detectExplicitMarkers(responses) {
74566
+ const out2 = [];
74567
+ for (const member of responses) {
74568
+ const markerSentence = extractMarkerSentence(member.response, EXPLICIT_DISAGREEMENT_MARKERS);
74569
+ if (!markerSentence)
74570
+ continue;
74571
+ const position = {
74572
+ memberId: member.memberId,
74573
+ claim: markerSentence,
74574
+ evidence: member.sources[0]?.url ?? "(no source cited in marker sentence)"
74575
+ };
74576
+ out2.push({
74577
+ topic: markerSentence.slice(0, 80),
74578
+ positions: [position]
74579
+ });
74580
+ }
74581
+ return out2;
74582
+ }
74583
+ function extractRecommendation(response) {
74584
+ return extractMarkerSentence(response, STRONG_RECOMMENDATION_MARKERS);
74585
+ }
74586
+ function detectClaimDivergence(responses) {
74587
+ const recommendations = [];
74588
+ for (const member of responses) {
74589
+ const rec = extractRecommendation(member.response);
74590
+ if (!rec)
74591
+ continue;
74592
+ recommendations.push({
74593
+ memberId: member.memberId,
74594
+ text: rec,
74595
+ evidence: member.sources[0]?.url ?? "(no source cited)"
74596
+ });
74597
+ }
74598
+ const out2 = [];
74599
+ for (let i2 = 0;i2 < recommendations.length; i2++) {
74600
+ for (let j = i2 + 1;j < recommendations.length; j++) {
74601
+ const a = recommendations[i2];
74602
+ const b = recommendations[j];
74603
+ if (!a || !b)
74604
+ continue;
74605
+ const topicOverlap = termOverlap(a.text, b.text);
74606
+ if (topicOverlap > 0.4)
74607
+ continue;
74608
+ if (topicOverlap > 0 && topicOverlap < 0.3) {
74609
+ const topic = `${a.text.slice(0, 50)} vs ${b.text.slice(0, 50)}`;
74610
+ out2.push({
74611
+ topic,
74612
+ positions: [
74613
+ { memberId: a.memberId, claim: a.text, evidence: a.evidence },
74614
+ { memberId: b.memberId, claim: b.text, evidence: b.evidence }
74615
+ ]
74616
+ });
74617
+ }
74618
+ }
74619
+ }
74620
+ return out2;
74621
+ }
74622
+ function detectDisagreements(responses) {
74623
+ if (!Array.isArray(responses) || responses.length < 2)
74624
+ return [];
74625
+ const safeResponses = responses.filter((r) => typeof r?.memberId === "string" && typeof r?.response === "string");
74626
+ const explicit = detectExplicitMarkers(safeResponses);
74627
+ const divergent = detectClaimDivergence(safeResponses);
74628
+ const combined = [...explicit, ...divergent];
74629
+ const deduped = dedupeByTopic(combined);
74630
+ return deduped.slice(0, MAX_DISAGREEMENTS);
74631
+ }
74632
+
74633
+ // src/council/general-council-service.ts
74634
+ var CONSENSUS_WEIGHT_THRESHOLD = 0.6;
74635
+ function tokenize2(text) {
74636
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4);
74637
+ }
74638
+ function similarity(a, b) {
74639
+ const tokensA = new Set(tokenize2(a));
74640
+ const tokensB = new Set(tokenize2(b));
74641
+ if (tokensA.size === 0 || tokensB.size === 0)
74642
+ return 0;
74643
+ let intersection3 = 0;
74644
+ for (const t of tokensA)
74645
+ if (tokensB.has(t))
74646
+ intersection3++;
74647
+ const union3 = tokensA.size + tokensB.size - intersection3;
74648
+ return union3 === 0 ? 0 : intersection3 / union3;
74649
+ }
74650
+ function extractClaims(response) {
74651
+ return response.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length >= 30 && s.length <= 400);
74652
+ }
74653
+ function buildConsensusClusters(responses) {
74654
+ if (responses.length < 2)
74655
+ return [];
74656
+ const totalMembers = responses.length;
74657
+ const clusters = [];
74658
+ for (const member of responses) {
74659
+ const confidence = clamp01(member.confidence ?? 0.5);
74660
+ const claims = extractClaims(member.response ?? "");
74661
+ for (const claim of claims) {
74662
+ let assigned = false;
74663
+ for (const cluster of clusters) {
74664
+ if (similarity(cluster.representative, claim) >= 0.5) {
74665
+ if (!cluster.memberIds.has(member.memberId)) {
74666
+ cluster.weightedAgreement += confidence;
74667
+ cluster.memberIds.add(member.memberId);
74668
+ }
74669
+ if (claim.length > cluster.representative.length) {
74670
+ cluster.representative = claim;
74671
+ }
74672
+ assigned = true;
74673
+ break;
74674
+ }
74675
+ }
74676
+ if (!assigned) {
74677
+ clusters.push({
74678
+ representative: claim,
74679
+ weightedAgreement: confidence,
74680
+ memberIds: new Set([member.memberId])
74681
+ });
74682
+ }
74683
+ }
74684
+ }
74685
+ return clusters.filter((c) => c.memberIds.size >= 2 && c.weightedAgreement / totalMembers >= CONSENSUS_WEIGHT_THRESHOLD).sort((a, b) => b.weightedAgreement - a.weightedAgreement || b.memberIds.size - a.memberIds.size).map((c) => c.representative);
74686
+ }
74687
+ function clamp01(n) {
74688
+ if (typeof n !== "number" || Number.isNaN(n))
74689
+ return 0;
74690
+ if (n < 0)
74691
+ return 0;
74692
+ if (n > 1)
74693
+ return 1;
74694
+ return n;
74695
+ }
74696
+ function computePersistingDisagreements(disagreements, round2) {
74697
+ if (disagreements.length === 0)
74698
+ return [];
74699
+ if (round2.length === 0)
74700
+ return disagreements;
74701
+ return disagreements.filter((d) => {
74702
+ const disputants = new Set(d.positions.map((p) => p.memberId));
74703
+ const conceded = round2.some((r) => {
74704
+ if (!disputants.has(r.memberId))
74705
+ return false;
74706
+ if (!r.disagreementTopics?.includes(d.topic))
74707
+ return false;
74708
+ return /\bconcede\b/i.test(r.response ?? "");
74709
+ });
74710
+ return !conceded;
74711
+ });
74712
+ }
74713
+ function dedupeSources(round1, round2) {
74714
+ const seen = new Set;
74715
+ const out2 = [];
74716
+ const allSources = [...round1, ...round2].flatMap((r) => r.sources ?? []);
74717
+ for (const src of allSources) {
74718
+ if (!src?.url)
74719
+ continue;
74720
+ if (seen.has(src.url))
74721
+ continue;
74722
+ seen.add(src.url);
74723
+ out2.push(src);
74724
+ }
74725
+ return out2;
74726
+ }
74727
+ function renderSynthesisMarkdown(question, mode, roundsCompleted, members, consensusPoints, persistingDisagreements, allSources) {
74728
+ const memberLines = members.map((m) => `- ${m.memberId} (${m.model}, ${m.role})`).join(`
74729
+ `);
74730
+ const consensusBlock = consensusPoints.length > 0 ? consensusPoints.map((c) => `- ${c}`).join(`
74731
+ `) : "_No consensus claims reached the weighted-agreement threshold._";
74732
+ const disagreementsBlock = persistingDisagreements.length > 0 ? persistingDisagreements.map((d) => `- **${d.topic}**
74733
+ ` + d.positions.map((p) => ` - ${p.memberId}: ${p.claim}`).join(`
74734
+ `)).join(`
74735
+ `) : "_No persisting disagreements after deliberation._";
74736
+ const sourcesBlock = allSources.length > 0 ? allSources.map((s) => `- [${s.title || s.url}](${s.url})`).join(`
74737
+ `) : "_No sources cited._";
74738
+ return [
74739
+ "## General Council Synthesis",
74740
+ "",
74741
+ `**Question:** ${question}`,
74742
+ `**Mode:** ${mode}`,
74743
+ `**Members:**
74744
+ ${memberLines}`,
74745
+ `**Rounds:** ${roundsCompleted}`,
74746
+ "",
74747
+ "### Consensus",
74748
+ consensusBlock,
74749
+ "",
74750
+ "### Persistent Disagreements",
74751
+ disagreementsBlock,
74752
+ "",
74753
+ "### Sources",
74754
+ sourcesBlock
74755
+ ].join(`
74756
+ `);
74757
+ }
74758
+ function synthesizeGeneralCouncil(question, mode, round1Responses, round2Responses) {
74759
+ const safeRound1 = Array.isArray(round1Responses) ? round1Responses : [];
74760
+ const safeRound2 = Array.isArray(round2Responses) ? round2Responses : [];
74761
+ const disagreements = detectDisagreements(safeRound1);
74762
+ const consensusPoints = buildConsensusClusters(safeRound1);
74763
+ const persistingDisagreements = computePersistingDisagreements(disagreements, safeRound2);
74764
+ const allSources = dedupeSources(safeRound1, safeRound2);
74765
+ const roundsCompleted = safeRound2.length > 0 ? 2 : 1;
74766
+ const synthesis = renderSynthesisMarkdown(question, mode, roundsCompleted, safeRound1, consensusPoints, persistingDisagreements, allSources);
74767
+ return {
74768
+ question,
74769
+ mode,
74770
+ round1Responses: safeRound1,
74771
+ disagreements,
74772
+ round2Responses: safeRound2,
74773
+ synthesis,
74774
+ consensusPoints,
74775
+ persistingDisagreements: persistingDisagreements.map((d) => d.topic),
74776
+ allSources,
74777
+ timestamp: new Date().toISOString()
74778
+ };
74779
+ }
74780
+
74781
+ // src/tools/convene-general-council.ts
74782
+ init_state();
74783
+ init_create_tool();
74784
+ init_resolve_working_directory();
74785
+ var WebSearchResultSchema = exports_external.object({
74786
+ title: exports_external.string(),
74787
+ url: exports_external.string(),
74788
+ snippet: exports_external.string(),
74789
+ query: exports_external.string()
74790
+ });
74791
+ var MemberRoleEnum = exports_external.enum([
74792
+ "generalist",
74793
+ "skeptic",
74794
+ "domain_expert",
74795
+ "devil_advocate",
74796
+ "synthesizer"
74797
+ ]);
74798
+ var Round1ResponseSchema = exports_external.object({
74799
+ memberId: exports_external.string().min(1),
74800
+ model: exports_external.string().min(1),
74801
+ role: MemberRoleEnum,
74802
+ response: exports_external.string(),
74803
+ sources: exports_external.array(WebSearchResultSchema).default([]),
74804
+ searchQueries: exports_external.array(exports_external.string()).default([]),
74805
+ confidence: exports_external.number().min(0).max(1),
74806
+ areasOfUncertainty: exports_external.array(exports_external.string()).default([]),
74807
+ durationMs: exports_external.number().nonnegative().default(0)
74808
+ });
74809
+ var Round2ResponseSchema = Round1ResponseSchema.extend({
74810
+ disagreementTopics: exports_external.array(exports_external.string()).default([])
74811
+ });
74812
+ var ArgsSchema2 = exports_external.object({
74813
+ question: exports_external.string().min(1).max(8000),
74814
+ mode: exports_external.enum(["general", "spec_review"]).default("general"),
74815
+ members: exports_external.array(exports_external.string()).default([]),
74816
+ round1Responses: exports_external.array(Round1ResponseSchema).min(1),
74817
+ round2Responses: exports_external.array(Round2ResponseSchema).optional(),
74818
+ working_directory: exports_external.string().optional()
74819
+ });
74820
+ function buildModeratorPrompt(question, synthesis) {
74821
+ return [
74822
+ "A multi-model council has deliberated on the following question. Your job is to synthesize",
74823
+ "the council output into a single coherent answer for the user, following the rules in your",
74824
+ "system prompt (lead with consensus, acknowledge persisting disagreement honestly, cite the",
74825
+ "strongest sources, be concise, do not invent claims, do not run new searches).",
74826
+ "",
74827
+ `QUESTION:
74828
+ ${question}`,
74829
+ "",
74830
+ "COUNCIL OUTPUT:",
74831
+ synthesis
74832
+ ].join(`
74833
+ `);
74834
+ }
74835
+ var convene_general_council = createSwarmTool({
74836
+ description: "Synthesize responses from a multi-model General Council. Accepts parallel member " + "responses (Round 1, optionally Round 2), detects disagreements, and returns " + "consensus points, persisting disagreements, a structured synthesis, and an optional " + "moderator prompt. Architect-only. Config-gated on council.general.enabled.",
74837
+ args: {
74838
+ question: tool.schema.string().min(1).max(8000).describe("The question put to the council, or the spec text to review."),
74839
+ mode: tool.schema.enum(["general", "spec_review"]).optional().describe('"general" for /swarm council; "spec_review" for SPECIFY-COUNCIL-REVIEW gate.'),
74840
+ members: tool.schema.array(tool.schema.string()).optional().describe("Optional list of member IDs convened (for evidence/audit)."),
74841
+ round1Responses: tool.schema.array(tool.schema.object({
74842
+ memberId: tool.schema.string().min(1),
74843
+ model: tool.schema.string().min(1),
74844
+ role: tool.schema.enum([
74845
+ "generalist",
74846
+ "skeptic",
74847
+ "domain_expert",
74848
+ "devil_advocate",
74849
+ "synthesizer"
74850
+ ]),
74851
+ response: tool.schema.string(),
74852
+ sources: tool.schema.array(tool.schema.object({
74853
+ title: tool.schema.string(),
74854
+ url: tool.schema.string(),
74855
+ snippet: tool.schema.string(),
74856
+ query: tool.schema.string()
74857
+ })).optional(),
74858
+ searchQueries: tool.schema.array(tool.schema.string()).optional(),
74859
+ confidence: tool.schema.number().min(0).max(1),
74860
+ areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
74861
+ durationMs: tool.schema.number().nonnegative().optional()
74862
+ })).describe("Round 1 member responses (one per council member)."),
74863
+ round2Responses: tool.schema.array(tool.schema.object({
74864
+ memberId: tool.schema.string().min(1),
74865
+ model: tool.schema.string().min(1),
74866
+ role: tool.schema.enum([
74867
+ "generalist",
74868
+ "skeptic",
74869
+ "domain_expert",
74870
+ "devil_advocate",
74871
+ "synthesizer"
74872
+ ]),
74873
+ response: tool.schema.string(),
74874
+ sources: tool.schema.array(tool.schema.object({
74875
+ title: tool.schema.string(),
74876
+ url: tool.schema.string(),
74877
+ snippet: tool.schema.string(),
74878
+ query: tool.schema.string()
74879
+ })).optional(),
74880
+ searchQueries: tool.schema.array(tool.schema.string()).optional(),
74881
+ confidence: tool.schema.number().min(0).max(1),
74882
+ areasOfUncertainty: tool.schema.array(tool.schema.string()).optional(),
74883
+ durationMs: tool.schema.number().nonnegative().optional(),
74884
+ disagreementTopics: tool.schema.array(tool.schema.string()).optional()
74885
+ })).optional().describe("Round 2 deliberation responses (omit when no deliberation has occurred)."),
74886
+ working_directory: tool.schema.string().optional().describe("Project root for config + evidence path resolution.")
74887
+ },
74888
+ execute: async (args2, directory, ctx) => {
74889
+ const parsed = ArgsSchema2.safeParse(args2);
74890
+ if (!parsed.success) {
74891
+ const fail = {
74892
+ success: false,
74893
+ reason: "invalid_args",
74894
+ message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
74895
+ };
74896
+ return JSON.stringify(fail, null, 2);
74897
+ }
74898
+ const input = parsed.data;
74899
+ const dirResult = resolveWorkingDirectory(input.working_directory, directory);
74900
+ if (!dirResult.success) {
74901
+ const fail = {
74902
+ success: false,
74903
+ reason: "invalid_working_directory",
74904
+ message: dirResult.message
74905
+ };
74906
+ return JSON.stringify(fail, null, 2);
74907
+ }
74908
+ const workingDir = dirResult.directory;
74909
+ const config3 = loadPluginConfig(workingDir);
74910
+ const generalConfig = config3.council?.general;
74911
+ if (!generalConfig || generalConfig.enabled !== true) {
74912
+ const fail = {
74913
+ success: false,
74914
+ reason: "council_general_disabled",
74915
+ message: "convene_general_council requires council.general.enabled: true in opencode-swarm.json."
74916
+ };
74917
+ return JSON.stringify(fail, null, 2);
74918
+ }
74919
+ const round1 = input.round1Responses;
74920
+ const round2 = input.round2Responses ?? [];
74921
+ const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
74922
+ const evidenceDir = path73.join(workingDir, ".swarm", "council", "general");
74923
+ const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
74924
+ const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
74925
+ const evidencePath = path73.join(evidenceDir, evidenceFile);
74926
+ try {
74927
+ await fs59.promises.mkdir(evidenceDir, { recursive: true });
74928
+ await fs59.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
74929
+ } catch (err2) {
74930
+ const message = err2 instanceof Error ? err2.message : String(err2);
74931
+ console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
74932
+ }
74933
+ try {
74934
+ const sessionID = ctx?.sessionID;
74935
+ if (sessionID) {
74936
+ const session = getAgentSession(sessionID);
74937
+ if (session) {
74938
+ pushGeneralCouncilAdvisory(session, result);
74939
+ }
74940
+ }
74941
+ } catch {}
74942
+ const moderatorPrompt = generalConfig.moderator === true ? buildModeratorPrompt(input.question, result.synthesis) : undefined;
74943
+ const ok = {
74944
+ success: true,
74945
+ question: input.question,
74946
+ mode: input.mode,
74947
+ roundsCompleted: round2.length > 0 ? 2 : 1,
74948
+ consensusPoints: result.consensusPoints,
74949
+ disagreementsCount: result.disagreements.length,
74950
+ persistingDisagreements: result.persistingDisagreements,
74951
+ allSourcesCount: result.allSources.length,
74952
+ synthesis: result.synthesis,
74953
+ ...moderatorPrompt !== undefined && { moderatorPrompt },
74954
+ evidencePath
74955
+ };
74956
+ return JSON.stringify(ok, null, 2);
74957
+ }
74958
+ });
74006
74959
  // src/tools/curator-analyze.ts
74007
74960
  init_dist();
74008
74961
  init_config();
@@ -74132,7 +75085,7 @@ var CriteriaItemSchema = exports_external.object({
74132
75085
  description: exports_external.string().min(10).max(500),
74133
75086
  mandatory: exports_external.boolean()
74134
75087
  });
74135
- var ArgsSchema2 = exports_external.object({
75088
+ var ArgsSchema3 = exports_external.object({
74136
75089
  taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format"),
74137
75090
  criteria: exports_external.array(CriteriaItemSchema).min(1).max(20),
74138
75091
  working_directory: exports_external.string().optional()
@@ -74149,7 +75102,7 @@ var declare_council_criteria = createSwarmTool({
74149
75102
  working_directory: tool.schema.string().optional().describe("Explicit project root directory. When provided, .swarm/council/ is resolved relative to this path instead of the plugin context directory.")
74150
75103
  },
74151
75104
  async execute(args2, directory) {
74152
- const parsed = ArgsSchema2.safeParse(args2);
75105
+ const parsed = ArgsSchema3.safeParse(args2);
74153
75106
  if (!parsed.success) {
74154
75107
  return JSON.stringify({
74155
75108
  success: false,
@@ -74210,8 +75163,8 @@ init_scope_persistence();
74210
75163
  init_state();
74211
75164
  init_task_id();
74212
75165
  init_create_tool();
74213
- import * as fs59 from "fs";
74214
- import * as path73 from "path";
75166
+ import * as fs60 from "fs";
75167
+ import * as path74 from "path";
74215
75168
  function validateTaskIdFormat2(taskId) {
74216
75169
  return validateTaskIdFormat(taskId);
74217
75170
  }
@@ -74285,8 +75238,8 @@ async function executeDeclareScope(args2, fallbackDir) {
74285
75238
  };
74286
75239
  }
74287
75240
  }
74288
- normalizedDir = path73.normalize(args2.working_directory);
74289
- const pathParts = normalizedDir.split(path73.sep);
75241
+ normalizedDir = path74.normalize(args2.working_directory);
75242
+ const pathParts = normalizedDir.split(path74.sep);
74290
75243
  if (pathParts.includes("..")) {
74291
75244
  return {
74292
75245
  success: false,
@@ -74296,11 +75249,11 @@ async function executeDeclareScope(args2, fallbackDir) {
74296
75249
  ]
74297
75250
  };
74298
75251
  }
74299
- const resolvedDir = path73.resolve(normalizedDir);
75252
+ const resolvedDir = path74.resolve(normalizedDir);
74300
75253
  try {
74301
- const realPath = fs59.realpathSync(resolvedDir);
74302
- const planPath2 = path73.join(realPath, ".swarm", "plan.json");
74303
- if (!fs59.existsSync(planPath2)) {
75254
+ const realPath = fs60.realpathSync(resolvedDir);
75255
+ const planPath2 = path74.join(realPath, ".swarm", "plan.json");
75256
+ if (!fs60.existsSync(planPath2)) {
74304
75257
  return {
74305
75258
  success: false,
74306
75259
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -74323,8 +75276,8 @@ async function executeDeclareScope(args2, fallbackDir) {
74323
75276
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
74324
75277
  }
74325
75278
  const directory = normalizedDir || fallbackDir;
74326
- const planPath = path73.resolve(directory, ".swarm", "plan.json");
74327
- if (!fs59.existsSync(planPath)) {
75279
+ const planPath = path74.resolve(directory, ".swarm", "plan.json");
75280
+ if (!fs60.existsSync(planPath)) {
74328
75281
  return {
74329
75282
  success: false,
74330
75283
  message: "No plan found",
@@ -74333,7 +75286,7 @@ async function executeDeclareScope(args2, fallbackDir) {
74333
75286
  }
74334
75287
  let planContent;
74335
75288
  try {
74336
- planContent = JSON.parse(fs59.readFileSync(planPath, "utf-8"));
75289
+ planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
74337
75290
  } catch {
74338
75291
  return {
74339
75292
  success: false,
@@ -74363,8 +75316,8 @@ async function executeDeclareScope(args2, fallbackDir) {
74363
75316
  const normalizeErrors = [];
74364
75317
  const dir = normalizedDir || fallbackDir || process.cwd();
74365
75318
  const mergedFiles = rawMergedFiles.map((file3) => {
74366
- if (path73.isAbsolute(file3)) {
74367
- const relativePath = path73.relative(dir, file3).replace(/\\/g, "/");
75319
+ if (path74.isAbsolute(file3)) {
75320
+ const relativePath = path74.relative(dir, file3).replace(/\\/g, "/");
74368
75321
  if (relativePath.startsWith("..")) {
74369
75322
  normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
74370
75323
  return file3;
@@ -74424,8 +75377,8 @@ var declare_scope = createSwarmTool({
74424
75377
  // src/tools/diff.ts
74425
75378
  init_dist();
74426
75379
  import * as child_process7 from "child_process";
74427
- import * as fs60 from "fs";
74428
- import * as path74 from "path";
75380
+ import * as fs61 from "fs";
75381
+ import * as path75 from "path";
74429
75382
  init_create_tool();
74430
75383
  var MAX_DIFF_LINES = 500;
74431
75384
  var DIFF_TIMEOUT_MS = 30000;
@@ -74454,20 +75407,20 @@ function validateBase(base) {
74454
75407
  function validatePaths(paths) {
74455
75408
  if (!paths)
74456
75409
  return null;
74457
- for (const path75 of paths) {
74458
- if (!path75 || path75.length === 0) {
75410
+ for (const path76 of paths) {
75411
+ if (!path76 || path76.length === 0) {
74459
75412
  return "empty path not allowed";
74460
75413
  }
74461
- if (path75.length > MAX_PATH_LENGTH) {
75414
+ if (path76.length > MAX_PATH_LENGTH) {
74462
75415
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
74463
75416
  }
74464
- if (SHELL_METACHARACTERS2.test(path75)) {
75417
+ if (SHELL_METACHARACTERS2.test(path76)) {
74465
75418
  return "path contains shell metacharacters";
74466
75419
  }
74467
- if (path75.startsWith("-")) {
75420
+ if (path76.startsWith("-")) {
74468
75421
  return 'path cannot start with "-" (option-like arguments not allowed)';
74469
75422
  }
74470
- if (CONTROL_CHAR_PATTERN2.test(path75)) {
75423
+ if (CONTROL_CHAR_PATTERN2.test(path76)) {
74471
75424
  return "path contains control characters";
74472
75425
  }
74473
75426
  }
@@ -74573,8 +75526,8 @@ var diff = createSwarmTool({
74573
75526
  if (parts2.length >= 3) {
74574
75527
  const additions = parseInt(parts2[0], 10) || 0;
74575
75528
  const deletions = parseInt(parts2[1], 10) || 0;
74576
- const path75 = parts2[2];
74577
- files.push({ path: path75, additions, deletions });
75529
+ const path76 = parts2[2];
75530
+ files.push({ path: path76, additions, deletions });
74578
75531
  }
74579
75532
  }
74580
75533
  const contractChanges = [];
@@ -74614,7 +75567,7 @@ var diff = createSwarmTool({
74614
75567
  } else if (base === "unstaged") {
74615
75568
  const oldRef = `:${file3.path}`;
74616
75569
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
74617
- newContent = fs60.readFileSync(path74.join(directory, file3.path), "utf-8");
75570
+ newContent = fs61.readFileSync(path75.join(directory, file3.path), "utf-8");
74618
75571
  } else {
74619
75572
  const oldRef = `${base}:${file3.path}`;
74620
75573
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
@@ -74688,8 +75641,8 @@ var diff = createSwarmTool({
74688
75641
  // src/tools/diff-summary.ts
74689
75642
  init_dist();
74690
75643
  import * as child_process8 from "child_process";
74691
- import * as fs61 from "fs";
74692
- import * as path75 from "path";
75644
+ import * as fs62 from "fs";
75645
+ import * as path76 from "path";
74693
75646
  init_create_tool();
74694
75647
  var diff_summary = createSwarmTool({
74695
75648
  description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
@@ -74737,7 +75690,7 @@ var diff_summary = createSwarmTool({
74737
75690
  }
74738
75691
  try {
74739
75692
  let oldContent;
74740
- const newContent = fs61.readFileSync(path75.join(workingDir, filePath), "utf-8");
75693
+ const newContent = fs62.readFileSync(path76.join(workingDir, filePath), "utf-8");
74741
75694
  if (fileExistsInHead) {
74742
75695
  oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
74743
75696
  encoding: "utf-8",
@@ -74964,8 +75917,8 @@ Use these as DOMAIN values when delegating to @sme.`;
74964
75917
  init_dist();
74965
75918
  init_create_tool();
74966
75919
  init_path_security();
74967
- import * as fs62 from "fs";
74968
- import * as path76 from "path";
75920
+ import * as fs63 from "fs";
75921
+ import * as path77 from "path";
74969
75922
  var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
74970
75923
  var MAX_EVIDENCE_FILES = 1000;
74971
75924
  var EVIDENCE_DIR3 = ".swarm/evidence";
@@ -74992,9 +75945,9 @@ function validateRequiredTypes(input) {
74992
75945
  return null;
74993
75946
  }
74994
75947
  function isPathWithinSwarm2(filePath, cwd) {
74995
- const normalizedCwd = path76.resolve(cwd);
74996
- const swarmPath = path76.join(normalizedCwd, ".swarm");
74997
- const normalizedPath = path76.resolve(filePath);
75948
+ const normalizedCwd = path77.resolve(cwd);
75949
+ const swarmPath = path77.join(normalizedCwd, ".swarm");
75950
+ const normalizedPath = path77.resolve(filePath);
74998
75951
  return normalizedPath.startsWith(swarmPath);
74999
75952
  }
75000
75953
  function parseCompletedTasks(planContent) {
@@ -75010,12 +75963,12 @@ function parseCompletedTasks(planContent) {
75010
75963
  }
75011
75964
  function readEvidenceFiles(evidenceDir, _cwd) {
75012
75965
  const evidence = [];
75013
- if (!fs62.existsSync(evidenceDir) || !fs62.statSync(evidenceDir).isDirectory()) {
75966
+ if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
75014
75967
  return evidence;
75015
75968
  }
75016
75969
  let files;
75017
75970
  try {
75018
- files = fs62.readdirSync(evidenceDir);
75971
+ files = fs63.readdirSync(evidenceDir);
75019
75972
  } catch {
75020
75973
  return evidence;
75021
75974
  }
@@ -75024,14 +75977,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
75024
75977
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
75025
75978
  continue;
75026
75979
  }
75027
- const filePath = path76.join(evidenceDir, filename);
75980
+ const filePath = path77.join(evidenceDir, filename);
75028
75981
  try {
75029
- const resolvedPath = path76.resolve(filePath);
75030
- const evidenceDirResolved = path76.resolve(evidenceDir);
75982
+ const resolvedPath = path77.resolve(filePath);
75983
+ const evidenceDirResolved = path77.resolve(evidenceDir);
75031
75984
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
75032
75985
  continue;
75033
75986
  }
75034
- const stat4 = fs62.lstatSync(filePath);
75987
+ const stat4 = fs63.lstatSync(filePath);
75035
75988
  if (!stat4.isFile()) {
75036
75989
  continue;
75037
75990
  }
@@ -75040,7 +75993,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
75040
75993
  }
75041
75994
  let fileStat;
75042
75995
  try {
75043
- fileStat = fs62.statSync(filePath);
75996
+ fileStat = fs63.statSync(filePath);
75044
75997
  if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
75045
75998
  continue;
75046
75999
  }
@@ -75049,7 +76002,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
75049
76002
  }
75050
76003
  let content;
75051
76004
  try {
75052
- content = fs62.readFileSync(filePath, "utf-8");
76005
+ content = fs63.readFileSync(filePath, "utf-8");
75053
76006
  } catch {
75054
76007
  continue;
75055
76008
  }
@@ -75145,7 +76098,7 @@ var evidence_check = createSwarmTool({
75145
76098
  return JSON.stringify(errorResult, null, 2);
75146
76099
  }
75147
76100
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
75148
- const planPath = path76.join(cwd, PLAN_FILE);
76101
+ const planPath = path77.join(cwd, PLAN_FILE);
75149
76102
  if (!isPathWithinSwarm2(planPath, cwd)) {
75150
76103
  const errorResult = {
75151
76104
  error: "plan file path validation failed",
@@ -75159,7 +76112,7 @@ var evidence_check = createSwarmTool({
75159
76112
  }
75160
76113
  let planContent;
75161
76114
  try {
75162
- planContent = fs62.readFileSync(planPath, "utf-8");
76115
+ planContent = fs63.readFileSync(planPath, "utf-8");
75163
76116
  } catch {
75164
76117
  const result2 = {
75165
76118
  message: "No completed tasks found in plan.",
@@ -75177,7 +76130,7 @@ var evidence_check = createSwarmTool({
75177
76130
  };
75178
76131
  return JSON.stringify(result2, null, 2);
75179
76132
  }
75180
- const evidenceDir = path76.join(cwd, EVIDENCE_DIR3);
76133
+ const evidenceDir = path77.join(cwd, EVIDENCE_DIR3);
75181
76134
  const evidence = readEvidenceFiles(evidenceDir, cwd);
75182
76135
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
75183
76136
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -75194,8 +76147,8 @@ var evidence_check = createSwarmTool({
75194
76147
  // src/tools/file-extractor.ts
75195
76148
  init_tool();
75196
76149
  init_create_tool();
75197
- import * as fs63 from "fs";
75198
- import * as path77 from "path";
76150
+ import * as fs64 from "fs";
76151
+ import * as path78 from "path";
75199
76152
  var EXT_MAP = {
75200
76153
  python: ".py",
75201
76154
  py: ".py",
@@ -75257,8 +76210,8 @@ var extract_code_blocks = createSwarmTool({
75257
76210
  execute: async (args2, directory) => {
75258
76211
  const { content, output_dir, prefix } = args2;
75259
76212
  const targetDir = output_dir || directory;
75260
- if (!fs63.existsSync(targetDir)) {
75261
- fs63.mkdirSync(targetDir, { recursive: true });
76213
+ if (!fs64.existsSync(targetDir)) {
76214
+ fs64.mkdirSync(targetDir, { recursive: true });
75262
76215
  }
75263
76216
  if (!content) {
75264
76217
  return "Error: content is required";
@@ -75276,16 +76229,16 @@ var extract_code_blocks = createSwarmTool({
75276
76229
  if (prefix) {
75277
76230
  filename = `${prefix}_${filename}`;
75278
76231
  }
75279
- let filepath = path77.join(targetDir, filename);
75280
- const base = path77.basename(filepath, path77.extname(filepath));
75281
- const ext = path77.extname(filepath);
76232
+ let filepath = path78.join(targetDir, filename);
76233
+ const base = path78.basename(filepath, path78.extname(filepath));
76234
+ const ext = path78.extname(filepath);
75282
76235
  let counter = 1;
75283
- while (fs63.existsSync(filepath)) {
75284
- filepath = path77.join(targetDir, `${base}_${counter}${ext}`);
76236
+ while (fs64.existsSync(filepath)) {
76237
+ filepath = path78.join(targetDir, `${base}_${counter}${ext}`);
75285
76238
  counter++;
75286
76239
  }
75287
76240
  try {
75288
- fs63.writeFileSync(filepath, code.trim(), "utf-8");
76241
+ fs64.writeFileSync(filepath, code.trim(), "utf-8");
75289
76242
  savedFiles.push(filepath);
75290
76243
  } catch (error93) {
75291
76244
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -75544,8 +76497,8 @@ var gitingest = createSwarmTool({
75544
76497
  init_dist();
75545
76498
  init_create_tool();
75546
76499
  init_path_security();
75547
- import * as fs64 from "fs";
75548
- import * as path78 from "path";
76500
+ import * as fs65 from "fs";
76501
+ import * as path79 from "path";
75549
76502
  var MAX_FILE_PATH_LENGTH2 = 500;
75550
76503
  var MAX_SYMBOL_LENGTH = 256;
75551
76504
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
@@ -75593,7 +76546,7 @@ function validateSymbolInput(symbol3) {
75593
76546
  return null;
75594
76547
  }
75595
76548
  function isBinaryFile2(filePath, buffer) {
75596
- const ext = path78.extname(filePath).toLowerCase();
76549
+ const ext = path79.extname(filePath).toLowerCase();
75597
76550
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
75598
76551
  return false;
75599
76552
  }
@@ -75617,15 +76570,15 @@ function parseImports(content, targetFile, targetSymbol) {
75617
76570
  const imports = [];
75618
76571
  let _resolvedTarget;
75619
76572
  try {
75620
- _resolvedTarget = path78.resolve(targetFile);
76573
+ _resolvedTarget = path79.resolve(targetFile);
75621
76574
  } catch {
75622
76575
  _resolvedTarget = targetFile;
75623
76576
  }
75624
- const targetBasename = path78.basename(targetFile, path78.extname(targetFile));
76577
+ const targetBasename = path79.basename(targetFile, path79.extname(targetFile));
75625
76578
  const targetWithExt = targetFile;
75626
76579
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
75627
- const normalizedTargetWithExt = path78.normalize(targetWithExt).replace(/\\/g, "/");
75628
- const normalizedTargetWithoutExt = path78.normalize(targetWithoutExt).replace(/\\/g, "/");
76580
+ const normalizedTargetWithExt = path79.normalize(targetWithExt).replace(/\\/g, "/");
76581
+ const normalizedTargetWithoutExt = path79.normalize(targetWithoutExt).replace(/\\/g, "/");
75629
76582
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
75630
76583
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
75631
76584
  const modulePath = match[1] || match[2] || match[3];
@@ -75648,9 +76601,9 @@ function parseImports(content, targetFile, targetSymbol) {
75648
76601
  }
75649
76602
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
75650
76603
  let isMatch = false;
75651
- const _targetDir = path78.dirname(targetFile);
75652
- const targetExt = path78.extname(targetFile);
75653
- const targetBasenameNoExt = path78.basename(targetFile, targetExt);
76604
+ const _targetDir = path79.dirname(targetFile);
76605
+ const targetExt = path79.extname(targetFile);
76606
+ const targetBasenameNoExt = path79.basename(targetFile, targetExt);
75654
76607
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
75655
76608
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
75656
76609
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -75707,7 +76660,7 @@ var SKIP_DIRECTORIES4 = new Set([
75707
76660
  function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
75708
76661
  let entries;
75709
76662
  try {
75710
- entries = fs64.readdirSync(dir);
76663
+ entries = fs65.readdirSync(dir);
75711
76664
  } catch (e) {
75712
76665
  stats.fileErrors.push({
75713
76666
  path: dir,
@@ -75718,13 +76671,13 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
75718
76671
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
75719
76672
  for (const entry of entries) {
75720
76673
  if (SKIP_DIRECTORIES4.has(entry)) {
75721
- stats.skippedDirs.push(path78.join(dir, entry));
76674
+ stats.skippedDirs.push(path79.join(dir, entry));
75722
76675
  continue;
75723
76676
  }
75724
- const fullPath = path78.join(dir, entry);
76677
+ const fullPath = path79.join(dir, entry);
75725
76678
  let stat4;
75726
76679
  try {
75727
- stat4 = fs64.statSync(fullPath);
76680
+ stat4 = fs65.statSync(fullPath);
75728
76681
  } catch (e) {
75729
76682
  stats.fileErrors.push({
75730
76683
  path: fullPath,
@@ -75735,7 +76688,7 @@ function findSourceFiles3(dir, files = [], stats = { skippedDirs: [], skippedFil
75735
76688
  if (stat4.isDirectory()) {
75736
76689
  findSourceFiles3(fullPath, files, stats);
75737
76690
  } else if (stat4.isFile()) {
75738
- const ext = path78.extname(fullPath).toLowerCase();
76691
+ const ext = path79.extname(fullPath).toLowerCase();
75739
76692
  if (SUPPORTED_EXTENSIONS3.includes(ext)) {
75740
76693
  files.push(fullPath);
75741
76694
  }
@@ -75792,8 +76745,8 @@ var imports = createSwarmTool({
75792
76745
  return JSON.stringify(errorResult, null, 2);
75793
76746
  }
75794
76747
  try {
75795
- const targetFile = path78.resolve(file3);
75796
- if (!fs64.existsSync(targetFile)) {
76748
+ const targetFile = path79.resolve(file3);
76749
+ if (!fs65.existsSync(targetFile)) {
75797
76750
  const errorResult = {
75798
76751
  error: `target file not found: ${file3}`,
75799
76752
  target: file3,
@@ -75803,7 +76756,7 @@ var imports = createSwarmTool({
75803
76756
  };
75804
76757
  return JSON.stringify(errorResult, null, 2);
75805
76758
  }
75806
- const targetStat = fs64.statSync(targetFile);
76759
+ const targetStat = fs65.statSync(targetFile);
75807
76760
  if (!targetStat.isFile()) {
75808
76761
  const errorResult = {
75809
76762
  error: "target must be a file, not a directory",
@@ -75814,7 +76767,7 @@ var imports = createSwarmTool({
75814
76767
  };
75815
76768
  return JSON.stringify(errorResult, null, 2);
75816
76769
  }
75817
- const baseDir = path78.dirname(targetFile);
76770
+ const baseDir = path79.dirname(targetFile);
75818
76771
  const scanStats = {
75819
76772
  skippedDirs: [],
75820
76773
  skippedFiles: 0,
@@ -75829,12 +76782,12 @@ var imports = createSwarmTool({
75829
76782
  if (consumers.length >= MAX_CONSUMERS)
75830
76783
  break;
75831
76784
  try {
75832
- const stat4 = fs64.statSync(filePath);
76785
+ const stat4 = fs65.statSync(filePath);
75833
76786
  if (stat4.size > MAX_FILE_SIZE_BYTES7) {
75834
76787
  skippedFileCount++;
75835
76788
  continue;
75836
76789
  }
75837
- const buffer = fs64.readFileSync(filePath);
76790
+ const buffer = fs65.readFileSync(filePath);
75838
76791
  if (isBinaryFile2(filePath, buffer)) {
75839
76792
  skippedFileCount++;
75840
76793
  continue;
@@ -76346,8 +77299,8 @@ init_schema();
76346
77299
  init_qa_gate_profile();
76347
77300
  init_manager2();
76348
77301
  init_curator();
76349
- import * as fs65 from "fs";
76350
- import * as path79 from "path";
77302
+ import * as fs66 from "fs";
77303
+ import * as path80 from "path";
76351
77304
  init_knowledge_curator();
76352
77305
  init_knowledge_reader();
76353
77306
  init_knowledge_store();
@@ -76578,11 +77531,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76578
77531
  safeWarn(`[phase_complete] Completion verify error (non-blocking):`, completionError);
76579
77532
  }
76580
77533
  try {
76581
- const driftEvidencePath = path79.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
77534
+ const driftEvidencePath = path80.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
76582
77535
  let driftVerdictFound = false;
76583
77536
  let driftVerdictApproved = false;
76584
77537
  try {
76585
- const driftEvidenceContent = fs65.readFileSync(driftEvidencePath, "utf-8");
77538
+ const driftEvidenceContent = fs66.readFileSync(driftEvidencePath, "utf-8");
76586
77539
  const driftEvidence = JSON.parse(driftEvidenceContent);
76587
77540
  const entries = driftEvidence.entries ?? [];
76588
77541
  for (const entry of entries) {
@@ -76612,14 +77565,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76612
77565
  driftVerdictFound = false;
76613
77566
  }
76614
77567
  if (!driftVerdictFound) {
76615
- const specPath = path79.join(dir, ".swarm", "spec.md");
76616
- const specExists = fs65.existsSync(specPath);
77568
+ const specPath = path80.join(dir, ".swarm", "spec.md");
77569
+ const specExists = fs66.existsSync(specPath);
76617
77570
  if (!specExists) {
76618
77571
  let incompleteTaskCount = 0;
76619
77572
  let planPhaseFound = false;
76620
77573
  try {
76621
77574
  const planPath = validateSwarmPath(dir, "plan.json");
76622
- const planRaw = fs65.readFileSync(planPath, "utf-8");
77575
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
76623
77576
  const plan = JSON.parse(planRaw);
76624
77577
  const targetPhase = plan.phases.find((p) => p.id === phase);
76625
77578
  if (targetPhase) {
@@ -76670,11 +77623,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76670
77623
  const overrides = session2?.qaGateSessionOverrides ?? {};
76671
77624
  const effective = getEffectiveGates(profile, overrides);
76672
77625
  if (effective.hallucination_guard === true) {
76673
- const hgPath = path79.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
77626
+ const hgPath = path80.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
76674
77627
  let hgVerdictFound = false;
76675
77628
  let hgVerdictApproved = false;
76676
77629
  try {
76677
- const hgContent = fs65.readFileSync(hgPath, "utf-8");
77630
+ const hgContent = fs66.readFileSync(hgPath, "utf-8");
76678
77631
  const hgBundle = JSON.parse(hgContent);
76679
77632
  for (const entry of hgBundle.entries ?? []) {
76680
77633
  if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
@@ -76742,11 +77695,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76742
77695
  const overrides = session2?.qaGateSessionOverrides ?? {};
76743
77696
  const effective = getEffectiveGates(profile, overrides);
76744
77697
  if (effective.mutation_test === true) {
76745
- const mgPath = path79.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
77698
+ const mgPath = path80.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
76746
77699
  let mgVerdictFound = false;
76747
77700
  let mgVerdict;
76748
77701
  try {
76749
- const mgContent = fs65.readFileSync(mgPath, "utf-8");
77702
+ const mgContent = fs66.readFileSync(mgPath, "utf-8");
76750
77703
  const mgBundle = JSON.parse(mgContent);
76751
77704
  for (const entry of mgBundle.entries ?? []) {
76752
77705
  if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
@@ -76814,7 +77767,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76814
77767
  }
76815
77768
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
76816
77769
  try {
76817
- const projectName = path79.basename(dir);
77770
+ const projectName = path80.basename(dir);
76818
77771
  const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
76819
77772
  if (curationResult) {
76820
77773
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -76894,7 +77847,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76894
77847
  let phaseRequiredAgents;
76895
77848
  try {
76896
77849
  const planPath = validateSwarmPath(dir, "plan.json");
76897
- const planRaw = fs65.readFileSync(planPath, "utf-8");
77850
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
76898
77851
  const plan = JSON.parse(planRaw);
76899
77852
  const phaseObj = plan.phases.find((p) => p.id === phase);
76900
77853
  phaseRequiredAgents = phaseObj?.required_agents;
@@ -76909,7 +77862,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76909
77862
  if (agentsMissing.length > 0) {
76910
77863
  try {
76911
77864
  const planPath = validateSwarmPath(dir, "plan.json");
76912
- const planRaw = fs65.readFileSync(planPath, "utf-8");
77865
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
76913
77866
  const plan = JSON.parse(planRaw);
76914
77867
  const targetPhase = plan.phases.find((p) => p.id === phase);
76915
77868
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -76949,7 +77902,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
76949
77902
  if (phaseCompleteConfig.regression_sweep?.enforce) {
76950
77903
  try {
76951
77904
  const planPath = validateSwarmPath(dir, "plan.json");
76952
- const planRaw = fs65.readFileSync(planPath, "utf-8");
77905
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
76953
77906
  const plan = JSON.parse(planRaw);
76954
77907
  const targetPhase = plan.phases.find((p) => p.id === phase);
76955
77908
  if (targetPhase) {
@@ -77003,7 +77956,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
77003
77956
  }
77004
77957
  try {
77005
77958
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
77006
- fs65.appendFileSync(eventsPath, `${JSON.stringify(event)}
77959
+ fs66.appendFileSync(eventsPath, `${JSON.stringify(event)}
77007
77960
  `, "utf-8");
77008
77961
  } catch (writeError) {
77009
77962
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -77078,12 +78031,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
77078
78031
  warnings.push(`Warning: failed to update plan.json phase status`);
77079
78032
  try {
77080
78033
  const planPath = validateSwarmPath(dir, "plan.json");
77081
- const planRaw = fs65.readFileSync(planPath, "utf-8");
78034
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
77082
78035
  const plan2 = JSON.parse(planRaw);
77083
78036
  const phaseObj = plan2.phases.find((p) => p.id === phase);
77084
78037
  if (phaseObj) {
77085
78038
  phaseObj.status = "complete";
77086
- fs65.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
78039
+ fs66.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
77087
78040
  }
77088
78041
  } catch {}
77089
78042
  } else if (plan) {
@@ -77120,12 +78073,12 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
77120
78073
  warnings.push(`Warning: failed to update plan.json phase status`);
77121
78074
  try {
77122
78075
  const planPath = validateSwarmPath(dir, "plan.json");
77123
- const planRaw = fs65.readFileSync(planPath, "utf-8");
78076
+ const planRaw = fs66.readFileSync(planPath, "utf-8");
77124
78077
  const plan = JSON.parse(planRaw);
77125
78078
  const phaseObj = plan.phases.find((p) => p.id === phase);
77126
78079
  if (phaseObj) {
77127
78080
  phaseObj.status = "complete";
77128
- fs65.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
78081
+ fs66.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
77129
78082
  }
77130
78083
  } catch {}
77131
78084
  }
@@ -77182,8 +78135,8 @@ init_dist();
77182
78135
  init_discovery();
77183
78136
  init_utils();
77184
78137
  init_create_tool();
77185
- import * as fs66 from "fs";
77186
- import * as path80 from "path";
78138
+ import * as fs67 from "fs";
78139
+ import * as path81 from "path";
77187
78140
  var MAX_OUTPUT_BYTES5 = 52428800;
77188
78141
  var AUDIT_TIMEOUT_MS = 120000;
77189
78142
  function isValidEcosystem(value) {
@@ -77211,31 +78164,31 @@ function validateArgs3(args2) {
77211
78164
  function detectEcosystems(directory) {
77212
78165
  const ecosystems = [];
77213
78166
  const cwd = directory;
77214
- if (fs66.existsSync(path80.join(cwd, "package.json"))) {
78167
+ if (fs67.existsSync(path81.join(cwd, "package.json"))) {
77215
78168
  ecosystems.push("npm");
77216
78169
  }
77217
- if (fs66.existsSync(path80.join(cwd, "pyproject.toml")) || fs66.existsSync(path80.join(cwd, "requirements.txt"))) {
78170
+ if (fs67.existsSync(path81.join(cwd, "pyproject.toml")) || fs67.existsSync(path81.join(cwd, "requirements.txt"))) {
77218
78171
  ecosystems.push("pip");
77219
78172
  }
77220
- if (fs66.existsSync(path80.join(cwd, "Cargo.toml"))) {
78173
+ if (fs67.existsSync(path81.join(cwd, "Cargo.toml"))) {
77221
78174
  ecosystems.push("cargo");
77222
78175
  }
77223
- if (fs66.existsSync(path80.join(cwd, "go.mod"))) {
78176
+ if (fs67.existsSync(path81.join(cwd, "go.mod"))) {
77224
78177
  ecosystems.push("go");
77225
78178
  }
77226
78179
  try {
77227
- const files = fs66.readdirSync(cwd);
78180
+ const files = fs67.readdirSync(cwd);
77228
78181
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
77229
78182
  ecosystems.push("dotnet");
77230
78183
  }
77231
78184
  } catch {}
77232
- if (fs66.existsSync(path80.join(cwd, "Gemfile")) || fs66.existsSync(path80.join(cwd, "Gemfile.lock"))) {
78185
+ if (fs67.existsSync(path81.join(cwd, "Gemfile")) || fs67.existsSync(path81.join(cwd, "Gemfile.lock"))) {
77233
78186
  ecosystems.push("ruby");
77234
78187
  }
77235
- if (fs66.existsSync(path80.join(cwd, "pubspec.yaml"))) {
78188
+ if (fs67.existsSync(path81.join(cwd, "pubspec.yaml"))) {
77236
78189
  ecosystems.push("dart");
77237
78190
  }
77238
- if (fs66.existsSync(path80.join(cwd, "composer.lock"))) {
78191
+ if (fs67.existsSync(path81.join(cwd, "composer.lock"))) {
77239
78192
  ecosystems.push("composer");
77240
78193
  }
77241
78194
  return ecosystems;
@@ -78394,8 +79347,8 @@ var pkg_audit = createSwarmTool({
78394
79347
  // src/tools/placeholder-scan.ts
78395
79348
  init_dist();
78396
79349
  init_manager2();
78397
- import * as fs67 from "fs";
78398
- import * as path81 from "path";
79350
+ import * as fs68 from "fs";
79351
+ import * as path82 from "path";
78399
79352
  init_utils();
78400
79353
  init_create_tool();
78401
79354
  var MAX_FILE_SIZE = 1024 * 1024;
@@ -78518,7 +79471,7 @@ function isScaffoldFile(filePath) {
78518
79471
  if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
78519
79472
  return true;
78520
79473
  }
78521
- const filename = path81.basename(filePath);
79474
+ const filename = path82.basename(filePath);
78522
79475
  if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
78523
79476
  return true;
78524
79477
  }
@@ -78535,7 +79488,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
78535
79488
  if (regex.test(normalizedPath)) {
78536
79489
  return true;
78537
79490
  }
78538
- const filename = path81.basename(filePath);
79491
+ const filename = path82.basename(filePath);
78539
79492
  const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
78540
79493
  if (filenameRegex.test(filename)) {
78541
79494
  return true;
@@ -78544,7 +79497,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
78544
79497
  return false;
78545
79498
  }
78546
79499
  function isParserSupported(filePath) {
78547
- const ext = path81.extname(filePath).toLowerCase();
79500
+ const ext = path82.extname(filePath).toLowerCase();
78548
79501
  return SUPPORTED_PARSER_EXTENSIONS.has(ext);
78549
79502
  }
78550
79503
  function isPlanFile(filePath) {
@@ -78791,28 +79744,28 @@ async function placeholderScan(input, directory) {
78791
79744
  let filesScanned = 0;
78792
79745
  const filesWithFindings = new Set;
78793
79746
  for (const filePath of changed_files) {
78794
- const fullPath = path81.isAbsolute(filePath) ? filePath : path81.resolve(directory, filePath);
78795
- const resolvedDirectory = path81.resolve(directory);
78796
- if (!fullPath.startsWith(resolvedDirectory + path81.sep) && fullPath !== resolvedDirectory) {
79747
+ const fullPath = path82.isAbsolute(filePath) ? filePath : path82.resolve(directory, filePath);
79748
+ const resolvedDirectory = path82.resolve(directory);
79749
+ if (!fullPath.startsWith(resolvedDirectory + path82.sep) && fullPath !== resolvedDirectory) {
78797
79750
  continue;
78798
79751
  }
78799
- if (!fs67.existsSync(fullPath)) {
79752
+ if (!fs68.existsSync(fullPath)) {
78800
79753
  continue;
78801
79754
  }
78802
79755
  if (isAllowedByGlobs(filePath, allow_globs)) {
78803
79756
  continue;
78804
79757
  }
78805
- const relativeFilePath = path81.relative(directory, fullPath).replace(/\\/g, "/");
79758
+ const relativeFilePath = path82.relative(directory, fullPath).replace(/\\/g, "/");
78806
79759
  if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
78807
79760
  continue;
78808
79761
  }
78809
79762
  let content;
78810
79763
  try {
78811
- const stat4 = fs67.statSync(fullPath);
79764
+ const stat4 = fs68.statSync(fullPath);
78812
79765
  if (stat4.size > MAX_FILE_SIZE) {
78813
79766
  continue;
78814
79767
  }
78815
- content = fs67.readFileSync(fullPath, "utf-8");
79768
+ content = fs68.readFileSync(fullPath, "utf-8");
78816
79769
  } catch {
78817
79770
  continue;
78818
79771
  }
@@ -78874,8 +79827,8 @@ var placeholder_scan = createSwarmTool({
78874
79827
  });
78875
79828
  // src/tools/pre-check-batch.ts
78876
79829
  init_dist();
78877
- import * as fs70 from "fs";
78878
- import * as path84 from "path";
79830
+ import * as fs71 from "fs";
79831
+ import * as path85 from "path";
78879
79832
  init_manager2();
78880
79833
  init_utils();
78881
79834
  init_create_tool();
@@ -79010,8 +79963,8 @@ var quality_budget = createSwarmTool({
79010
79963
  init_dist();
79011
79964
  init_manager2();
79012
79965
  init_detector();
79013
- import * as fs69 from "fs";
79014
- import * as path83 from "path";
79966
+ import * as fs70 from "fs";
79967
+ import * as path84 from "path";
79015
79968
  import { extname as extname18 } from "path";
79016
79969
 
79017
79970
  // src/sast/rules/c.ts
@@ -79904,25 +80857,25 @@ init_create_tool();
79904
80857
  // src/tools/sast-baseline.ts
79905
80858
  init_utils2();
79906
80859
  import * as crypto8 from "crypto";
79907
- import * as fs68 from "fs";
79908
- import * as path82 from "path";
80860
+ import * as fs69 from "fs";
80861
+ import * as path83 from "path";
79909
80862
  var BASELINE_SCHEMA_VERSION = "1.0.0";
79910
80863
  var MAX_BASELINE_FINDINGS = 2000;
79911
80864
  var MAX_BASELINE_BYTES = 2 * 1048576;
79912
80865
  var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
79913
80866
  function normalizeFindingPath(directory, file3) {
79914
- const resolved = path82.isAbsolute(file3) ? file3 : path82.resolve(directory, file3);
79915
- const rel = path82.relative(path82.resolve(directory), resolved);
80867
+ const resolved = path83.isAbsolute(file3) ? file3 : path83.resolve(directory, file3);
80868
+ const rel = path83.relative(path83.resolve(directory), resolved);
79916
80869
  return rel.replace(/\\/g, "/");
79917
80870
  }
79918
80871
  function baselineRelPath(phase) {
79919
- return path82.join("evidence", String(phase), "sast-baseline.json");
80872
+ return path83.join("evidence", String(phase), "sast-baseline.json");
79920
80873
  }
79921
80874
  function tempRelPath(phase) {
79922
- return path82.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
80875
+ return path83.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
79923
80876
  }
79924
80877
  function lockRelPath(phase) {
79925
- return path82.join("evidence", String(phase), "sast-baseline.json.lock");
80878
+ return path83.join("evidence", String(phase), "sast-baseline.json.lock");
79926
80879
  }
79927
80880
  function getLine(lines, idx) {
79928
80881
  if (idx < 0 || idx >= lines.length)
@@ -79939,7 +80892,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
79939
80892
  }
79940
80893
  const lineNum = finding.location.line;
79941
80894
  try {
79942
- const content = fs68.readFileSync(finding.location.file, "utf-8");
80895
+ const content = fs69.readFileSync(finding.location.file, "utf-8");
79943
80896
  const lines = content.split(`
79944
80897
  `);
79945
80898
  const idx = lineNum - 1;
@@ -79970,7 +80923,7 @@ function assignOccurrenceIndices(findings, directory) {
79970
80923
  try {
79971
80924
  if (relFile.startsWith(".."))
79972
80925
  throw new Error("escapes workspace");
79973
- const content = fs68.readFileSync(finding.location.file, "utf-8");
80926
+ const content = fs69.readFileSync(finding.location.file, "utf-8");
79974
80927
  const lines = content.split(`
79975
80928
  `);
79976
80929
  const idx = lineNum - 1;
@@ -79999,11 +80952,11 @@ function assignOccurrenceIndices(findings, directory) {
79999
80952
  async function acquireLock(lockPath) {
80000
80953
  for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
80001
80954
  try {
80002
- const fd = fs68.openSync(lockPath, "wx");
80003
- fs68.closeSync(fd);
80955
+ const fd = fs69.openSync(lockPath, "wx");
80956
+ fs69.closeSync(fd);
80004
80957
  return () => {
80005
80958
  try {
80006
- fs68.unlinkSync(lockPath);
80959
+ fs69.unlinkSync(lockPath);
80007
80960
  } catch {}
80008
80961
  };
80009
80962
  } catch {
@@ -80043,12 +80996,12 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
80043
80996
  message: e instanceof Error ? e.message : "Path validation failed"
80044
80997
  };
80045
80998
  }
80046
- fs68.mkdirSync(path82.dirname(baselinePath), { recursive: true });
80999
+ fs69.mkdirSync(path83.dirname(baselinePath), { recursive: true });
80047
81000
  const releaseLock = await acquireLock(lockPath);
80048
81001
  try {
80049
81002
  let existing = null;
80050
81003
  try {
80051
- const raw = fs68.readFileSync(baselinePath, "utf-8");
81004
+ const raw = fs69.readFileSync(baselinePath, "utf-8");
80052
81005
  const parsed = JSON.parse(raw);
80053
81006
  if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
80054
81007
  existing = parsed;
@@ -80108,8 +81061,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
80108
81061
  message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
80109
81062
  };
80110
81063
  }
80111
- fs68.writeFileSync(tempPath, json4, "utf-8");
80112
- fs68.renameSync(tempPath, baselinePath);
81064
+ fs69.writeFileSync(tempPath, json4, "utf-8");
81065
+ fs69.renameSync(tempPath, baselinePath);
80113
81066
  return {
80114
81067
  status: "merged",
80115
81068
  path: baselinePath,
@@ -80140,8 +81093,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
80140
81093
  message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
80141
81094
  };
80142
81095
  }
80143
- fs68.writeFileSync(tempPath, json3, "utf-8");
80144
- fs68.renameSync(tempPath, baselinePath);
81096
+ fs69.writeFileSync(tempPath, json3, "utf-8");
81097
+ fs69.renameSync(tempPath, baselinePath);
80145
81098
  return {
80146
81099
  status: "written",
80147
81100
  path: baselinePath,
@@ -80166,7 +81119,7 @@ function loadBaseline(directory, phase) {
80166
81119
  };
80167
81120
  }
80168
81121
  try {
80169
- const raw = fs68.readFileSync(baselinePath, "utf-8");
81122
+ const raw = fs69.readFileSync(baselinePath, "utf-8");
80170
81123
  const parsed = JSON.parse(raw);
80171
81124
  if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
80172
81125
  return {
@@ -80208,17 +81161,17 @@ var SEVERITY_ORDER = {
80208
81161
  };
80209
81162
  function shouldSkipFile(filePath) {
80210
81163
  try {
80211
- const stats = fs69.statSync(filePath);
81164
+ const stats = fs70.statSync(filePath);
80212
81165
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
80213
81166
  return { skip: true, reason: "file too large" };
80214
81167
  }
80215
81168
  if (stats.size === 0) {
80216
81169
  return { skip: true, reason: "empty file" };
80217
81170
  }
80218
- const fd = fs69.openSync(filePath, "r");
81171
+ const fd = fs70.openSync(filePath, "r");
80219
81172
  const buffer = Buffer.alloc(8192);
80220
- const bytesRead = fs69.readSync(fd, buffer, 0, 8192, 0);
80221
- fs69.closeSync(fd);
81173
+ const bytesRead = fs70.readSync(fd, buffer, 0, 8192, 0);
81174
+ fs70.closeSync(fd);
80222
81175
  if (bytesRead > 0) {
80223
81176
  let nullCount = 0;
80224
81177
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -80257,7 +81210,7 @@ function countBySeverity(findings) {
80257
81210
  }
80258
81211
  function scanFileWithTierA(filePath, language) {
80259
81212
  try {
80260
- const content = fs69.readFileSync(filePath, "utf-8");
81213
+ const content = fs70.readFileSync(filePath, "utf-8");
80261
81214
  const findings = executeRulesSync(filePath, content, language);
80262
81215
  return findings.map((f) => ({
80263
81216
  rule_id: f.rule_id,
@@ -80310,13 +81263,13 @@ async function sastScan(input, directory, config3) {
80310
81263
  _filesSkipped++;
80311
81264
  continue;
80312
81265
  }
80313
- const resolvedPath = path83.isAbsolute(filePath) ? filePath : path83.resolve(directory, filePath);
80314
- const resolvedDirectory = path83.resolve(directory);
80315
- if (!resolvedPath.startsWith(resolvedDirectory + path83.sep) && resolvedPath !== resolvedDirectory) {
81266
+ const resolvedPath = path84.isAbsolute(filePath) ? filePath : path84.resolve(directory, filePath);
81267
+ const resolvedDirectory = path84.resolve(directory);
81268
+ if (!resolvedPath.startsWith(resolvedDirectory + path84.sep) && resolvedPath !== resolvedDirectory) {
80316
81269
  _filesSkipped++;
80317
81270
  continue;
80318
81271
  }
80319
- if (!fs69.existsSync(resolvedPath)) {
81272
+ if (!fs70.existsSync(resolvedPath)) {
80320
81273
  _filesSkipped++;
80321
81274
  continue;
80322
81275
  }
@@ -80623,18 +81576,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
80623
81576
  let resolved;
80624
81577
  const isWinAbs = isWindowsAbsolutePath(inputPath);
80625
81578
  if (isWinAbs) {
80626
- resolved = path84.win32.resolve(inputPath);
80627
- } else if (path84.isAbsolute(inputPath)) {
80628
- resolved = path84.resolve(inputPath);
81579
+ resolved = path85.win32.resolve(inputPath);
81580
+ } else if (path85.isAbsolute(inputPath)) {
81581
+ resolved = path85.resolve(inputPath);
80629
81582
  } else {
80630
- resolved = path84.resolve(baseDir, inputPath);
81583
+ resolved = path85.resolve(baseDir, inputPath);
80631
81584
  }
80632
- const workspaceResolved = path84.resolve(workspaceDir);
81585
+ const workspaceResolved = path85.resolve(workspaceDir);
80633
81586
  let relative20;
80634
81587
  if (isWinAbs) {
80635
- relative20 = path84.win32.relative(workspaceResolved, resolved);
81588
+ relative20 = path85.win32.relative(workspaceResolved, resolved);
80636
81589
  } else {
80637
- relative20 = path84.relative(workspaceResolved, resolved);
81590
+ relative20 = path85.relative(workspaceResolved, resolved);
80638
81591
  }
80639
81592
  if (relative20.startsWith("..")) {
80640
81593
  return "path traversal detected";
@@ -80699,7 +81652,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
80699
81652
  if (typeof file3 !== "string") {
80700
81653
  continue;
80701
81654
  }
80702
- const resolvedPath = path84.resolve(file3);
81655
+ const resolvedPath = path85.resolve(file3);
80703
81656
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
80704
81657
  if (validationError) {
80705
81658
  continue;
@@ -80856,7 +81809,7 @@ async function runSecretscanWithFiles(files, directory) {
80856
81809
  skippedFiles++;
80857
81810
  continue;
80858
81811
  }
80859
- const resolvedPath = path84.resolve(file3);
81812
+ const resolvedPath = path85.resolve(file3);
80860
81813
  const validationError = validatePath(resolvedPath, directory, directory);
80861
81814
  if (validationError) {
80862
81815
  skippedFiles++;
@@ -80874,14 +81827,14 @@ async function runSecretscanWithFiles(files, directory) {
80874
81827
  };
80875
81828
  }
80876
81829
  for (const file3 of validatedFiles) {
80877
- const ext = path84.extname(file3).toLowerCase();
81830
+ const ext = path85.extname(file3).toLowerCase();
80878
81831
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
80879
81832
  skippedFiles++;
80880
81833
  continue;
80881
81834
  }
80882
81835
  let stat4;
80883
81836
  try {
80884
- stat4 = fs70.statSync(file3);
81837
+ stat4 = fs71.statSync(file3);
80885
81838
  } catch {
80886
81839
  skippedFiles++;
80887
81840
  continue;
@@ -80892,7 +81845,7 @@ async function runSecretscanWithFiles(files, directory) {
80892
81845
  }
80893
81846
  let content;
80894
81847
  try {
80895
- const buffer = fs70.readFileSync(file3);
81848
+ const buffer = fs71.readFileSync(file3);
80896
81849
  if (buffer.includes(0)) {
80897
81850
  skippedFiles++;
80898
81851
  continue;
@@ -81093,7 +82046,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
81093
82046
  const preexistingFindings = [];
81094
82047
  for (const finding of findings) {
81095
82048
  const filePath = finding.location.file;
81096
- const normalised = path84.relative(directory, filePath).replace(/\\/g, "/");
82049
+ const normalised = path85.relative(directory, filePath).replace(/\\/g, "/");
81097
82050
  const changedLines = changedLineRanges.get(normalised);
81098
82051
  if (changedLines?.has(finding.location.line)) {
81099
82052
  newFindings.push(finding);
@@ -81144,7 +82097,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
81144
82097
  warn(`pre_check_batch: Invalid file path: ${file3}`);
81145
82098
  continue;
81146
82099
  }
81147
- changedFiles.push(path84.resolve(directory, file3));
82100
+ changedFiles.push(path85.resolve(directory, file3));
81148
82101
  }
81149
82102
  if (changedFiles.length === 0) {
81150
82103
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -81345,7 +82298,7 @@ var pre_check_batch = createSwarmTool({
81345
82298
  };
81346
82299
  return JSON.stringify(errorResult, null, 2);
81347
82300
  }
81348
- const resolvedDirectory = path84.resolve(typedArgs.directory);
82301
+ const resolvedDirectory = path85.resolve(typedArgs.directory);
81349
82302
  const workspaceAnchor = resolvedDirectory;
81350
82303
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
81351
82304
  if (dirError) {
@@ -81386,7 +82339,7 @@ var pre_check_batch = createSwarmTool({
81386
82339
  });
81387
82340
  // src/tools/repo-map.ts
81388
82341
  init_dist();
81389
- import * as path85 from "path";
82342
+ import * as path86 from "path";
81390
82343
  init_path_security();
81391
82344
  init_create_tool();
81392
82345
  var VALID_ACTIONS = [
@@ -81411,7 +82364,7 @@ function validateFile(p) {
81411
82364
  return "file contains control characters";
81412
82365
  if (containsPathTraversal(p))
81413
82366
  return "file contains path traversal";
81414
- if (path85.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
82367
+ if (path86.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
81415
82368
  return "file must be a workspace-relative path, not absolute";
81416
82369
  }
81417
82370
  return null;
@@ -81434,8 +82387,8 @@ function ok(action, payload) {
81434
82387
  }
81435
82388
  function toRelativeGraphPath(input, workspaceRoot) {
81436
82389
  const normalized = input.replace(/\\/g, "/");
81437
- if (path85.isAbsolute(normalized)) {
81438
- const rel = path85.relative(workspaceRoot, normalized).replace(/\\/g, "/");
82390
+ if (path86.isAbsolute(normalized)) {
82391
+ const rel = path86.relative(workspaceRoot, normalized).replace(/\\/g, "/");
81439
82392
  return normalizeGraphPath2(rel);
81440
82393
  }
81441
82394
  return normalizeGraphPath2(normalized);
@@ -81579,8 +82532,8 @@ var repo_map = createSwarmTool({
81579
82532
  // src/tools/req-coverage.ts
81580
82533
  init_dist();
81581
82534
  init_create_tool();
81582
- import * as fs71 from "fs";
81583
- import * as path86 from "path";
82535
+ import * as fs72 from "fs";
82536
+ import * as path87 from "path";
81584
82537
  var SPEC_FILE = ".swarm/spec.md";
81585
82538
  var EVIDENCE_DIR4 = ".swarm/evidence";
81586
82539
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -81639,19 +82592,19 @@ function extractObligationAndText(id, lineText) {
81639
82592
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
81640
82593
  function readTouchedFiles(evidenceDir, phase, cwd) {
81641
82594
  const touchedFiles = new Set;
81642
- if (!fs71.existsSync(evidenceDir) || !fs71.statSync(evidenceDir).isDirectory()) {
82595
+ if (!fs72.existsSync(evidenceDir) || !fs72.statSync(evidenceDir).isDirectory()) {
81643
82596
  return [];
81644
82597
  }
81645
82598
  let entries;
81646
82599
  try {
81647
- entries = fs71.readdirSync(evidenceDir);
82600
+ entries = fs72.readdirSync(evidenceDir);
81648
82601
  } catch {
81649
82602
  return [];
81650
82603
  }
81651
82604
  for (const entry of entries) {
81652
- const entryPath = path86.join(evidenceDir, entry);
82605
+ const entryPath = path87.join(evidenceDir, entry);
81653
82606
  try {
81654
- const stat4 = fs71.statSync(entryPath);
82607
+ const stat4 = fs72.statSync(entryPath);
81655
82608
  if (!stat4.isDirectory()) {
81656
82609
  continue;
81657
82610
  }
@@ -81665,14 +82618,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
81665
82618
  if (entryPhase !== String(phase)) {
81666
82619
  continue;
81667
82620
  }
81668
- const evidenceFilePath = path86.join(entryPath, "evidence.json");
82621
+ const evidenceFilePath = path87.join(entryPath, "evidence.json");
81669
82622
  try {
81670
- const resolvedPath = path86.resolve(evidenceFilePath);
81671
- const evidenceDirResolved = path86.resolve(evidenceDir);
81672
- if (!resolvedPath.startsWith(evidenceDirResolved + path86.sep)) {
82623
+ const resolvedPath = path87.resolve(evidenceFilePath);
82624
+ const evidenceDirResolved = path87.resolve(evidenceDir);
82625
+ if (!resolvedPath.startsWith(evidenceDirResolved + path87.sep)) {
81673
82626
  continue;
81674
82627
  }
81675
- const stat4 = fs71.lstatSync(evidenceFilePath);
82628
+ const stat4 = fs72.lstatSync(evidenceFilePath);
81676
82629
  if (!stat4.isFile()) {
81677
82630
  continue;
81678
82631
  }
@@ -81684,7 +82637,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
81684
82637
  }
81685
82638
  let content;
81686
82639
  try {
81687
- content = fs71.readFileSync(evidenceFilePath, "utf-8");
82640
+ content = fs72.readFileSync(evidenceFilePath, "utf-8");
81688
82641
  } catch {
81689
82642
  continue;
81690
82643
  }
@@ -81703,7 +82656,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
81703
82656
  if (Array.isArray(diffEntry.files_changed)) {
81704
82657
  for (const file3 of diffEntry.files_changed) {
81705
82658
  if (typeof file3 === "string") {
81706
- touchedFiles.add(path86.resolve(cwd, file3));
82659
+ touchedFiles.add(path87.resolve(cwd, file3));
81707
82660
  }
81708
82661
  }
81709
82662
  }
@@ -81716,12 +82669,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
81716
82669
  }
81717
82670
  function searchFileForKeywords(filePath, keywords, cwd) {
81718
82671
  try {
81719
- const resolvedPath = path86.resolve(filePath);
81720
- const cwdResolved = path86.resolve(cwd);
82672
+ const resolvedPath = path87.resolve(filePath);
82673
+ const cwdResolved = path87.resolve(cwd);
81721
82674
  if (!resolvedPath.startsWith(cwdResolved)) {
81722
82675
  return false;
81723
82676
  }
81724
- const content = fs71.readFileSync(resolvedPath, "utf-8");
82677
+ const content = fs72.readFileSync(resolvedPath, "utf-8");
81725
82678
  for (const keyword of keywords) {
81726
82679
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
81727
82680
  if (regex.test(content)) {
@@ -81851,10 +82804,10 @@ var req_coverage = createSwarmTool({
81851
82804
  }, null, 2);
81852
82805
  }
81853
82806
  const cwd = inputDirectory || directory;
81854
- const specPath = path86.join(cwd, SPEC_FILE);
82807
+ const specPath = path87.join(cwd, SPEC_FILE);
81855
82808
  let specContent;
81856
82809
  try {
81857
- specContent = fs71.readFileSync(specPath, "utf-8");
82810
+ specContent = fs72.readFileSync(specPath, "utf-8");
81858
82811
  } catch (readError) {
81859
82812
  return JSON.stringify({
81860
82813
  success: false,
@@ -81878,7 +82831,7 @@ var req_coverage = createSwarmTool({
81878
82831
  message: "No FR requirements found in spec.md"
81879
82832
  }, null, 2);
81880
82833
  }
81881
- const evidenceDir = path86.join(cwd, EVIDENCE_DIR4);
82834
+ const evidenceDir = path87.join(cwd, EVIDENCE_DIR4);
81882
82835
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
81883
82836
  const analyzedRequirements = [];
81884
82837
  let coveredCount = 0;
@@ -81904,12 +82857,12 @@ var req_coverage = createSwarmTool({
81904
82857
  requirements: analyzedRequirements
81905
82858
  };
81906
82859
  const reportFilename = `req-coverage-phase-${phase}.json`;
81907
- const reportPath = path86.join(evidenceDir, reportFilename);
82860
+ const reportPath = path87.join(evidenceDir, reportFilename);
81908
82861
  try {
81909
- if (!fs71.existsSync(evidenceDir)) {
81910
- fs71.mkdirSync(evidenceDir, { recursive: true });
82862
+ if (!fs72.existsSync(evidenceDir)) {
82863
+ fs72.mkdirSync(evidenceDir, { recursive: true });
81911
82864
  }
81912
- fs71.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
82865
+ fs72.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
81913
82866
  } catch (writeError) {
81914
82867
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
81915
82868
  }
@@ -81988,6 +82941,7 @@ ${paginatedContent}`;
81988
82941
  // src/tools/save-plan.ts
81989
82942
  init_tool();
81990
82943
  init_plan_schema();
82944
+ init_qa_gate_profile();
81991
82945
  init_file_locks();
81992
82946
  init_checkpoint3();
81993
82947
  init_ledger();
@@ -81995,8 +82949,8 @@ init_manager();
81995
82949
  init_state();
81996
82950
  init_create_tool();
81997
82951
  import * as crypto9 from "crypto";
81998
- import * as fs72 from "fs";
81999
- import * as path87 from "path";
82952
+ import * as fs73 from "fs";
82953
+ import * as path88 from "path";
82000
82954
  function detectPlaceholderContent(args2) {
82001
82955
  const issues = [];
82002
82956
  const placeholderPattern = /^\[\w[\w\s]*\]$/;
@@ -82070,17 +83024,17 @@ async function executeSavePlan(args2, fallbackDir) {
82070
83024
  };
82071
83025
  }
82072
83026
  if (args2.working_directory && fallbackDir) {
82073
- const resolvedTarget = path87.resolve(args2.working_directory);
82074
- const resolvedRoot = path87.resolve(fallbackDir);
83027
+ const resolvedTarget = path88.resolve(args2.working_directory);
83028
+ const resolvedRoot = path88.resolve(fallbackDir);
82075
83029
  let fallbackExists = false;
82076
83030
  try {
82077
- fs72.accessSync(resolvedRoot, fs72.constants.F_OK);
83031
+ fs73.accessSync(resolvedRoot, fs73.constants.F_OK);
82078
83032
  fallbackExists = true;
82079
83033
  } catch {
82080
83034
  fallbackExists = false;
82081
83035
  }
82082
83036
  if (fallbackExists) {
82083
- const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path87.sep);
83037
+ const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path88.sep);
82084
83038
  if (isSubdirectory) {
82085
83039
  return {
82086
83040
  success: false,
@@ -82096,11 +83050,11 @@ async function executeSavePlan(args2, fallbackDir) {
82096
83050
  let specMtime;
82097
83051
  let specHash;
82098
83052
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
82099
- const specPath = path87.join(targetWorkspace, ".swarm", "spec.md");
83053
+ const specPath = path88.join(targetWorkspace, ".swarm", "spec.md");
82100
83054
  try {
82101
- const stat4 = await fs72.promises.stat(specPath);
83055
+ const stat4 = await fs73.promises.stat(specPath);
82102
83056
  specMtime = stat4.mtime.toISOString();
82103
- const content = await fs72.promises.readFile(specPath, "utf8");
83057
+ const content = await fs73.promises.readFile(specPath, "utf8");
82104
83058
  specHash = crypto9.createHash("sha256").update(content).digest("hex");
82105
83059
  } catch {
82106
83060
  return {
@@ -82111,6 +83065,32 @@ async function executeSavePlan(args2, fallbackDir) {
82111
83065
  };
82112
83066
  }
82113
83067
  }
83068
+ if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
83069
+ const contextPath = path88.join(targetWorkspace, ".swarm", "context.md");
83070
+ let contextContent = "";
83071
+ try {
83072
+ contextContent = await fs73.promises.readFile(contextPath, "utf8");
83073
+ } catch {}
83074
+ const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
83075
+ if (!hasPendingSection) {
83076
+ const candidatePlanId = `${args2.swarm_id}-${args2.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
83077
+ let existingProfile = null;
83078
+ try {
83079
+ existingProfile = getProfile(targetWorkspace, candidatePlanId);
83080
+ } catch {}
83081
+ if (!existingProfile) {
83082
+ return {
83083
+ success: false,
83084
+ message: "QA_GATE_SELECTION_REQUIRED: QA gate selection has not been completed. " + "Present the gate selection question to the user (step 5b of MODE: SPECIFY " + "or Phase 6 of MODE: BRAINSTORM), write their response to .swarm/context.md " + 'under "## Pending QA Gate Selection", then retry save_plan.',
83085
+ errors: [
83086
+ "Missing ## Pending QA Gate Selection in .swarm/context.md",
83087
+ "No existing QaGateProfile found for this plan"
83088
+ ],
83089
+ recovery_guidance: "Do not call set_qa_gates with assumed values. Present the gate dialogue, " + "wait for the user's answer, write it to context.md, then retry save_plan."
83090
+ };
83091
+ }
83092
+ }
83093
+ }
82114
83094
  const dir = targetWorkspace;
82115
83095
  const existingStatusMap = new Map;
82116
83096
  let preservedExecutionProfile;
@@ -82236,14 +83216,14 @@ async function executeSavePlan(args2, fallbackDir) {
82236
83216
  }
82237
83217
  await writeCheckpoint(dir).catch(() => {});
82238
83218
  try {
82239
- const markerPath = path87.join(dir, ".swarm", ".plan-write-marker");
83219
+ const markerPath = path88.join(dir, ".swarm", ".plan-write-marker");
82240
83220
  const marker = JSON.stringify({
82241
83221
  source: "save_plan",
82242
83222
  timestamp: new Date().toISOString(),
82243
83223
  phases_count: plan.phases.length,
82244
83224
  tasks_count: tasksCount
82245
83225
  });
82246
- await fs72.promises.writeFile(markerPath, marker, "utf8");
83226
+ await fs73.promises.writeFile(markerPath, marker, "utf8");
82247
83227
  } catch {}
82248
83228
  const warnings = [];
82249
83229
  let criticReviewFound = false;
@@ -82259,7 +83239,7 @@ async function executeSavePlan(args2, fallbackDir) {
82259
83239
  return {
82260
83240
  success: true,
82261
83241
  message: "Plan saved successfully",
82262
- plan_path: path87.join(dir, ".swarm", "plan.json"),
83242
+ plan_path: path88.join(dir, ".swarm", "plan.json"),
82263
83243
  phases_count: plan.phases.length,
82264
83244
  tasks_count: tasksCount,
82265
83245
  ...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
@@ -82311,8 +83291,8 @@ var save_plan = createSwarmTool({
82311
83291
  // src/tools/sbom-generate.ts
82312
83292
  init_dist();
82313
83293
  init_manager2();
82314
- import * as fs73 from "fs";
82315
- import * as path88 from "path";
83294
+ import * as fs74 from "fs";
83295
+ import * as path89 from "path";
82316
83296
 
82317
83297
  // src/sbom/detectors/index.ts
82318
83298
  init_utils();
@@ -83160,9 +84140,9 @@ function findManifestFiles(rootDir) {
83160
84140
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
83161
84141
  function searchDir(dir) {
83162
84142
  try {
83163
- const entries = fs73.readdirSync(dir, { withFileTypes: true });
84143
+ const entries = fs74.readdirSync(dir, { withFileTypes: true });
83164
84144
  for (const entry of entries) {
83165
- const fullPath = path88.join(dir, entry.name);
84145
+ const fullPath = path89.join(dir, entry.name);
83166
84146
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
83167
84147
  continue;
83168
84148
  }
@@ -83171,7 +84151,7 @@ function findManifestFiles(rootDir) {
83171
84151
  } else if (entry.isFile()) {
83172
84152
  for (const pattern of patterns) {
83173
84153
  if (simpleGlobToRegex(pattern).test(entry.name)) {
83174
- manifestFiles.push(path88.relative(rootDir, fullPath));
84154
+ manifestFiles.push(path89.relative(rootDir, fullPath));
83175
84155
  break;
83176
84156
  }
83177
84157
  }
@@ -83187,13 +84167,13 @@ function findManifestFilesInDirs(directories, workingDir) {
83187
84167
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
83188
84168
  for (const dir of directories) {
83189
84169
  try {
83190
- const entries = fs73.readdirSync(dir, { withFileTypes: true });
84170
+ const entries = fs74.readdirSync(dir, { withFileTypes: true });
83191
84171
  for (const entry of entries) {
83192
- const fullPath = path88.join(dir, entry.name);
84172
+ const fullPath = path89.join(dir, entry.name);
83193
84173
  if (entry.isFile()) {
83194
84174
  for (const pattern of patterns) {
83195
84175
  if (simpleGlobToRegex(pattern).test(entry.name)) {
83196
- found.push(path88.relative(workingDir, fullPath));
84176
+ found.push(path89.relative(workingDir, fullPath));
83197
84177
  break;
83198
84178
  }
83199
84179
  }
@@ -83206,11 +84186,11 @@ function findManifestFilesInDirs(directories, workingDir) {
83206
84186
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
83207
84187
  const dirs = new Set;
83208
84188
  for (const file3 of changedFiles) {
83209
- let currentDir = path88.dirname(file3);
84189
+ let currentDir = path89.dirname(file3);
83210
84190
  while (true) {
83211
- if (currentDir && currentDir !== "." && currentDir !== path88.sep) {
83212
- dirs.add(path88.join(workingDir, currentDir));
83213
- const parent = path88.dirname(currentDir);
84191
+ if (currentDir && currentDir !== "." && currentDir !== path89.sep) {
84192
+ dirs.add(path89.join(workingDir, currentDir));
84193
+ const parent = path89.dirname(currentDir);
83214
84194
  if (parent === currentDir)
83215
84195
  break;
83216
84196
  currentDir = parent;
@@ -83224,7 +84204,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
83224
84204
  }
83225
84205
  function ensureOutputDir(outputDir) {
83226
84206
  try {
83227
- fs73.mkdirSync(outputDir, { recursive: true });
84207
+ fs74.mkdirSync(outputDir, { recursive: true });
83228
84208
  } catch (error93) {
83229
84209
  if (!error93 || error93.code !== "EEXIST") {
83230
84210
  throw error93;
@@ -83294,7 +84274,7 @@ var sbom_generate = createSwarmTool({
83294
84274
  const changedFiles = obj.changed_files;
83295
84275
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
83296
84276
  const workingDir = directory;
83297
- const outputDir = path88.isAbsolute(relativeOutputDir) ? relativeOutputDir : path88.join(workingDir, relativeOutputDir);
84277
+ const outputDir = path89.isAbsolute(relativeOutputDir) ? relativeOutputDir : path89.join(workingDir, relativeOutputDir);
83298
84278
  let manifestFiles = [];
83299
84279
  if (scope === "all") {
83300
84280
  manifestFiles = findManifestFiles(workingDir);
@@ -83317,11 +84297,11 @@ var sbom_generate = createSwarmTool({
83317
84297
  const processedFiles = [];
83318
84298
  for (const manifestFile of manifestFiles) {
83319
84299
  try {
83320
- const fullPath = path88.isAbsolute(manifestFile) ? manifestFile : path88.join(workingDir, manifestFile);
83321
- if (!fs73.existsSync(fullPath)) {
84300
+ const fullPath = path89.isAbsolute(manifestFile) ? manifestFile : path89.join(workingDir, manifestFile);
84301
+ if (!fs74.existsSync(fullPath)) {
83322
84302
  continue;
83323
84303
  }
83324
- const content = fs73.readFileSync(fullPath, "utf-8");
84304
+ const content = fs74.readFileSync(fullPath, "utf-8");
83325
84305
  const components = detectComponents(manifestFile, content);
83326
84306
  processedFiles.push(manifestFile);
83327
84307
  if (components.length > 0) {
@@ -83334,8 +84314,8 @@ var sbom_generate = createSwarmTool({
83334
84314
  const bom = generateCycloneDX(allComponents);
83335
84315
  const bomJson = serializeCycloneDX(bom);
83336
84316
  const filename = generateSbomFilename();
83337
- const outputPath = path88.join(outputDir, filename);
83338
- fs73.writeFileSync(outputPath, bomJson, "utf-8");
84317
+ const outputPath = path89.join(outputDir, filename);
84318
+ fs74.writeFileSync(outputPath, bomJson, "utf-8");
83339
84319
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
83340
84320
  try {
83341
84321
  const timestamp = new Date().toISOString();
@@ -83377,8 +84357,8 @@ var sbom_generate = createSwarmTool({
83377
84357
  // src/tools/schema-drift.ts
83378
84358
  init_dist();
83379
84359
  init_create_tool();
83380
- import * as fs74 from "fs";
83381
- import * as path89 from "path";
84360
+ import * as fs75 from "fs";
84361
+ import * as path90 from "path";
83382
84362
  var SPEC_CANDIDATES = [
83383
84363
  "openapi.json",
83384
84364
  "openapi.yaml",
@@ -83410,28 +84390,28 @@ function normalizePath3(p) {
83410
84390
  }
83411
84391
  function discoverSpecFile(cwd, specFileArg) {
83412
84392
  if (specFileArg) {
83413
- const resolvedPath = path89.resolve(cwd, specFileArg);
83414
- const normalizedCwd = cwd.endsWith(path89.sep) ? cwd : cwd + path89.sep;
84393
+ const resolvedPath = path90.resolve(cwd, specFileArg);
84394
+ const normalizedCwd = cwd.endsWith(path90.sep) ? cwd : cwd + path90.sep;
83415
84395
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
83416
84396
  throw new Error("Invalid spec_file: path traversal detected");
83417
84397
  }
83418
- const ext = path89.extname(resolvedPath).toLowerCase();
84398
+ const ext = path90.extname(resolvedPath).toLowerCase();
83419
84399
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
83420
84400
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
83421
84401
  }
83422
- const stats = fs74.statSync(resolvedPath);
84402
+ const stats = fs75.statSync(resolvedPath);
83423
84403
  if (stats.size > MAX_SPEC_SIZE) {
83424
84404
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
83425
84405
  }
83426
- if (!fs74.existsSync(resolvedPath)) {
84406
+ if (!fs75.existsSync(resolvedPath)) {
83427
84407
  throw new Error(`Spec file not found: ${resolvedPath}`);
83428
84408
  }
83429
84409
  return resolvedPath;
83430
84410
  }
83431
84411
  for (const candidate of SPEC_CANDIDATES) {
83432
- const candidatePath = path89.resolve(cwd, candidate);
83433
- if (fs74.existsSync(candidatePath)) {
83434
- const stats = fs74.statSync(candidatePath);
84412
+ const candidatePath = path90.resolve(cwd, candidate);
84413
+ if (fs75.existsSync(candidatePath)) {
84414
+ const stats = fs75.statSync(candidatePath);
83435
84415
  if (stats.size <= MAX_SPEC_SIZE) {
83436
84416
  return candidatePath;
83437
84417
  }
@@ -83440,8 +84420,8 @@ function discoverSpecFile(cwd, specFileArg) {
83440
84420
  return null;
83441
84421
  }
83442
84422
  function parseSpec(specFile) {
83443
- const content = fs74.readFileSync(specFile, "utf-8");
83444
- const ext = path89.extname(specFile).toLowerCase();
84423
+ const content = fs75.readFileSync(specFile, "utf-8");
84424
+ const ext = path90.extname(specFile).toLowerCase();
83445
84425
  if (ext === ".json") {
83446
84426
  return parseJsonSpec(content);
83447
84427
  }
@@ -83512,12 +84492,12 @@ function extractRoutes(cwd) {
83512
84492
  function walkDir(dir) {
83513
84493
  let entries;
83514
84494
  try {
83515
- entries = fs74.readdirSync(dir, { withFileTypes: true });
84495
+ entries = fs75.readdirSync(dir, { withFileTypes: true });
83516
84496
  } catch {
83517
84497
  return;
83518
84498
  }
83519
84499
  for (const entry of entries) {
83520
- const fullPath = path89.join(dir, entry.name);
84500
+ const fullPath = path90.join(dir, entry.name);
83521
84501
  if (entry.isSymbolicLink()) {
83522
84502
  continue;
83523
84503
  }
@@ -83527,7 +84507,7 @@ function extractRoutes(cwd) {
83527
84507
  }
83528
84508
  walkDir(fullPath);
83529
84509
  } else if (entry.isFile()) {
83530
- const ext = path89.extname(entry.name).toLowerCase();
84510
+ const ext = path90.extname(entry.name).toLowerCase();
83531
84511
  const baseName = entry.name.toLowerCase();
83532
84512
  if (![".ts", ".js", ".mjs"].includes(ext)) {
83533
84513
  continue;
@@ -83545,7 +84525,7 @@ function extractRoutes(cwd) {
83545
84525
  }
83546
84526
  function extractRoutesFromFile(filePath) {
83547
84527
  const routes = [];
83548
- const content = fs74.readFileSync(filePath, "utf-8");
84528
+ const content = fs75.readFileSync(filePath, "utf-8");
83549
84529
  const lines = content.split(/\r?\n/);
83550
84530
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
83551
84531
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -83693,8 +84673,8 @@ var schema_drift = createSwarmTool({
83693
84673
  init_tool();
83694
84674
  init_path_security();
83695
84675
  init_create_tool();
83696
- import * as fs75 from "fs";
83697
- import * as path90 from "path";
84676
+ import * as fs76 from "fs";
84677
+ import * as path91 from "path";
83698
84678
  var DEFAULT_MAX_RESULTS = 100;
83699
84679
  var DEFAULT_MAX_LINES = 200;
83700
84680
  var REGEX_TIMEOUT_MS = 5000;
@@ -83730,11 +84710,11 @@ function containsWindowsAttacks3(str) {
83730
84710
  }
83731
84711
  function isPathInWorkspace3(filePath, workspace) {
83732
84712
  try {
83733
- const resolvedPath = path90.resolve(workspace, filePath);
83734
- const realWorkspace = fs75.realpathSync(workspace);
83735
- const realResolvedPath = fs75.realpathSync(resolvedPath);
83736
- const relativePath = path90.relative(realWorkspace, realResolvedPath);
83737
- if (relativePath.startsWith("..") || path90.isAbsolute(relativePath)) {
84713
+ const resolvedPath = path91.resolve(workspace, filePath);
84714
+ const realWorkspace = fs76.realpathSync(workspace);
84715
+ const realResolvedPath = fs76.realpathSync(resolvedPath);
84716
+ const relativePath = path91.relative(realWorkspace, realResolvedPath);
84717
+ if (relativePath.startsWith("..") || path91.isAbsolute(relativePath)) {
83738
84718
  return false;
83739
84719
  }
83740
84720
  return true;
@@ -83747,12 +84727,12 @@ function validatePathForRead2(filePath, workspace) {
83747
84727
  }
83748
84728
  function findRgInEnvPath() {
83749
84729
  const searchPath = process.env.PATH ?? "";
83750
- for (const dir of searchPath.split(path90.delimiter)) {
84730
+ for (const dir of searchPath.split(path91.delimiter)) {
83751
84731
  if (!dir)
83752
84732
  continue;
83753
84733
  const isWindows = process.platform === "win32";
83754
- const candidate = path90.join(dir, isWindows ? "rg.exe" : "rg");
83755
- if (fs75.existsSync(candidate))
84734
+ const candidate = path91.join(dir, isWindows ? "rg.exe" : "rg");
84735
+ if (fs76.existsSync(candidate))
83756
84736
  return candidate;
83757
84737
  }
83758
84738
  return null;
@@ -83879,10 +84859,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
83879
84859
  return files;
83880
84860
  }
83881
84861
  try {
83882
- const entries = fs75.readdirSync(dir, { withFileTypes: true });
84862
+ const entries = fs76.readdirSync(dir, { withFileTypes: true });
83883
84863
  for (const entry of entries) {
83884
- const fullPath = path90.join(dir, entry.name);
83885
- const relativePath = path90.relative(workspace, fullPath);
84864
+ const fullPath = path91.join(dir, entry.name);
84865
+ const relativePath = path91.relative(workspace, fullPath);
83886
84866
  if (!validatePathForRead2(fullPath, workspace)) {
83887
84867
  continue;
83888
84868
  }
@@ -83923,13 +84903,13 @@ async function fallbackSearch(opts) {
83923
84903
  const matches = [];
83924
84904
  let total = 0;
83925
84905
  for (const file3 of files) {
83926
- const fullPath = path90.join(opts.workspace, file3);
84906
+ const fullPath = path91.join(opts.workspace, file3);
83927
84907
  if (!validatePathForRead2(fullPath, opts.workspace)) {
83928
84908
  continue;
83929
84909
  }
83930
84910
  let stats;
83931
84911
  try {
83932
- stats = fs75.statSync(fullPath);
84912
+ stats = fs76.statSync(fullPath);
83933
84913
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
83934
84914
  continue;
83935
84915
  }
@@ -83938,7 +84918,7 @@ async function fallbackSearch(opts) {
83938
84918
  }
83939
84919
  let content;
83940
84920
  try {
83941
- content = fs75.readFileSync(fullPath, "utf-8");
84921
+ content = fs76.readFileSync(fullPath, "utf-8");
83942
84922
  } catch {
83943
84923
  continue;
83944
84924
  }
@@ -84050,7 +85030,7 @@ var search = createSwarmTool({
84050
85030
  message: "Exclude pattern contains invalid Windows-specific sequence"
84051
85031
  }, null, 2);
84052
85032
  }
84053
- if (!fs75.existsSync(directory)) {
85033
+ if (!fs76.existsSync(directory)) {
84054
85034
  return JSON.stringify({
84055
85035
  error: true,
84056
85036
  type: "unknown",
@@ -84118,7 +85098,8 @@ async function executeSetQaGates(args2, directory) {
84118
85098
  "critic_pre_plan",
84119
85099
  "hallucination_guard",
84120
85100
  "sast_enabled",
84121
- "mutation_test"
85101
+ "mutation_test",
85102
+ "council_general_review"
84122
85103
  ]) {
84123
85104
  if (args2[key] !== undefined)
84124
85105
  partial3[key] = args2[key];
@@ -84164,6 +85145,7 @@ var set_qa_gates = createSwarmTool({
84164
85145
  hallucination_guard: tool.schema.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
84165
85146
  sast_enabled: tool.schema.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
84166
85147
  mutation_test: tool.schema.boolean().optional().describe("Enable the mutation-testing gate (default: off). Requires mutation " + "tests to achieve a passing kill rate before phase completion; " + "WARN verdict allows advancement, FAIL blocks."),
85148
+ council_general_review: tool.schema.boolean().optional().describe("Enable the council_general_review gate (default: off). When on, " + "MODE: SPECIFY runs convene_general_council on the draft spec " + "before the critic-gate, folding multi-model deliberation into " + "the spec. Requires council.general.enabled and a search API key."),
84167
85149
  project_type: tool.schema.string().optional().describe('Project type label (e.g. "ts", "python"). Only applied when the profile is being created for the first time.')
84168
85150
  },
84169
85151
  execute: async (args2, directory) => {
@@ -84175,8 +85157,8 @@ var set_qa_gates = createSwarmTool({
84175
85157
  init_tool();
84176
85158
  init_path_security();
84177
85159
  init_create_tool();
84178
- import * as fs76 from "fs";
84179
- import * as path91 from "path";
85160
+ import * as fs77 from "fs";
85161
+ import * as path92 from "path";
84180
85162
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
84181
85163
  function containsWindowsAttacks4(str) {
84182
85164
  if (/:[^\\/]/.test(str))
@@ -84190,14 +85172,14 @@ function containsWindowsAttacks4(str) {
84190
85172
  }
84191
85173
  function isPathInWorkspace4(filePath, workspace) {
84192
85174
  try {
84193
- const resolvedPath = path91.resolve(workspace, filePath);
84194
- if (!fs76.existsSync(resolvedPath)) {
85175
+ const resolvedPath = path92.resolve(workspace, filePath);
85176
+ if (!fs77.existsSync(resolvedPath)) {
84195
85177
  return true;
84196
85178
  }
84197
- const realWorkspace = fs76.realpathSync(workspace);
84198
- const realResolvedPath = fs76.realpathSync(resolvedPath);
84199
- const relativePath = path91.relative(realWorkspace, realResolvedPath);
84200
- if (relativePath.startsWith("..") || path91.isAbsolute(relativePath)) {
85179
+ const realWorkspace = fs77.realpathSync(workspace);
85180
+ const realResolvedPath = fs77.realpathSync(resolvedPath);
85181
+ const relativePath = path92.relative(realWorkspace, realResolvedPath);
85182
+ if (relativePath.startsWith("..") || path92.isAbsolute(relativePath)) {
84201
85183
  return false;
84202
85184
  }
84203
85185
  return true;
@@ -84369,7 +85351,7 @@ var suggestPatch = createSwarmTool({
84369
85351
  message: "changes cannot be empty"
84370
85352
  }, null, 2);
84371
85353
  }
84372
- if (!fs76.existsSync(directory)) {
85354
+ if (!fs77.existsSync(directory)) {
84373
85355
  return JSON.stringify({
84374
85356
  success: false,
84375
85357
  error: true,
@@ -84405,8 +85387,8 @@ var suggestPatch = createSwarmTool({
84405
85387
  });
84406
85388
  continue;
84407
85389
  }
84408
- const fullPath = path91.resolve(directory, change.file);
84409
- if (!fs76.existsSync(fullPath)) {
85390
+ const fullPath = path92.resolve(directory, change.file);
85391
+ if (!fs77.existsSync(fullPath)) {
84410
85392
  errors5.push({
84411
85393
  success: false,
84412
85394
  error: true,
@@ -84420,7 +85402,7 @@ var suggestPatch = createSwarmTool({
84420
85402
  }
84421
85403
  let content;
84422
85404
  try {
84423
- content = fs76.readFileSync(fullPath, "utf-8");
85405
+ content = fs77.readFileSync(fullPath, "utf-8");
84424
85406
  } catch (err3) {
84425
85407
  errors5.push({
84426
85408
  success: false,
@@ -84654,8 +85636,8 @@ var generate_mutants = createSwarmTool({
84654
85636
  // src/tools/lint-spec.ts
84655
85637
  init_spec_schema();
84656
85638
  init_create_tool();
84657
- import * as fs77 from "fs";
84658
- import * as path92 from "path";
85639
+ import * as fs78 from "fs";
85640
+ import * as path93 from "path";
84659
85641
  var SPEC_FILE_NAME = "spec.md";
84660
85642
  var SWARM_DIR2 = ".swarm";
84661
85643
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -84708,8 +85690,8 @@ var lint_spec = createSwarmTool({
84708
85690
  async execute(_args, directory) {
84709
85691
  const errors5 = [];
84710
85692
  const warnings = [];
84711
- const specPath = path92.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
84712
- if (!fs77.existsSync(specPath)) {
85693
+ const specPath = path93.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
85694
+ if (!fs78.existsSync(specPath)) {
84713
85695
  const result2 = {
84714
85696
  valid: false,
84715
85697
  specMtime: null,
@@ -84728,12 +85710,12 @@ var lint_spec = createSwarmTool({
84728
85710
  }
84729
85711
  let specMtime = null;
84730
85712
  try {
84731
- const stats = fs77.statSync(specPath);
85713
+ const stats = fs78.statSync(specPath);
84732
85714
  specMtime = stats.mtime.toISOString();
84733
85715
  } catch {}
84734
85716
  let content;
84735
85717
  try {
84736
- content = fs77.readFileSync(specPath, "utf-8");
85718
+ content = fs78.readFileSync(specPath, "utf-8");
84737
85719
  } catch (e) {
84738
85720
  const result2 = {
84739
85721
  valid: false,
@@ -84778,13 +85760,13 @@ var lint_spec = createSwarmTool({
84778
85760
  });
84779
85761
  // src/tools/mutation-test.ts
84780
85762
  init_dist();
84781
- import * as fs78 from "fs";
84782
- import * as path94 from "path";
85763
+ import * as fs79 from "fs";
85764
+ import * as path95 from "path";
84783
85765
 
84784
85766
  // src/mutation/engine.ts
84785
85767
  import { spawnSync as spawnSync3 } from "child_process";
84786
85768
  import { unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "fs";
84787
- import * as path93 from "path";
85769
+ import * as path94 from "path";
84788
85770
 
84789
85771
  // src/mutation/equivalence.ts
84790
85772
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -84919,7 +85901,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
84919
85901
  let patchFile;
84920
85902
  try {
84921
85903
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
84922
- patchFile = path93.join(workingDir, `.mutation_patch_${safeId2}.diff`);
85904
+ patchFile = path94.join(workingDir, `.mutation_patch_${safeId2}.diff`);
84923
85905
  try {
84924
85906
  writeFileSync18(patchFile, patch.patch);
84925
85907
  } catch (writeErr) {
@@ -85313,8 +86295,8 @@ var mutation_test = createSwarmTool({
85313
86295
  ];
85314
86296
  for (const filePath of uniquePaths) {
85315
86297
  try {
85316
- const resolvedPath = path94.resolve(cwd, filePath);
85317
- sourceFiles.set(filePath, fs78.readFileSync(resolvedPath, "utf-8"));
86298
+ const resolvedPath = path95.resolve(cwd, filePath);
86299
+ sourceFiles.set(filePath, fs79.readFileSync(resolvedPath, "utf-8"));
85318
86300
  } catch {}
85319
86301
  }
85320
86302
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -85332,8 +86314,8 @@ var mutation_test = createSwarmTool({
85332
86314
  init_dist();
85333
86315
  init_manager2();
85334
86316
  init_detector();
85335
- import * as fs79 from "fs";
85336
- import * as path95 from "path";
86317
+ import * as fs80 from "fs";
86318
+ import * as path96 from "path";
85337
86319
  init_create_tool();
85338
86320
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
85339
86321
  var BINARY_CHECK_BYTES = 8192;
@@ -85399,7 +86381,7 @@ async function syntaxCheck(input, directory, config3) {
85399
86381
  if (languages?.length) {
85400
86382
  const lowerLangs = languages.map((l) => l.toLowerCase());
85401
86383
  filesToCheck = filesToCheck.filter((file3) => {
85402
- const ext = path95.extname(file3.path).toLowerCase();
86384
+ const ext = path96.extname(file3.path).toLowerCase();
85403
86385
  const langDef = getLanguageForExtension(ext);
85404
86386
  const fileProfile = getProfileForFile(file3.path);
85405
86387
  const langId = fileProfile?.id || langDef?.id;
@@ -85412,7 +86394,7 @@ async function syntaxCheck(input, directory, config3) {
85412
86394
  let skippedCount = 0;
85413
86395
  for (const fileInfo of filesToCheck) {
85414
86396
  const { path: filePath } = fileInfo;
85415
- const fullPath = path95.isAbsolute(filePath) ? filePath : path95.join(directory, filePath);
86397
+ const fullPath = path96.isAbsolute(filePath) ? filePath : path96.join(directory, filePath);
85416
86398
  const result = {
85417
86399
  path: filePath,
85418
86400
  language: "",
@@ -85442,7 +86424,7 @@ async function syntaxCheck(input, directory, config3) {
85442
86424
  }
85443
86425
  let content;
85444
86426
  try {
85445
- content = fs79.readFileSync(fullPath, "utf8");
86427
+ content = fs80.readFileSync(fullPath, "utf8");
85446
86428
  } catch {
85447
86429
  result.skipped_reason = "file_read_error";
85448
86430
  skippedCount++;
@@ -85461,7 +86443,7 @@ async function syntaxCheck(input, directory, config3) {
85461
86443
  results.push(result);
85462
86444
  continue;
85463
86445
  }
85464
- const ext = path95.extname(filePath).toLowerCase();
86446
+ const ext = path96.extname(filePath).toLowerCase();
85465
86447
  const langDef = getLanguageForExtension(ext);
85466
86448
  result.language = profile?.id || langDef?.id || "unknown";
85467
86449
  const errors5 = extractSyntaxErrors(parser, content);
@@ -85553,8 +86535,8 @@ init_dist();
85553
86535
  init_utils();
85554
86536
  init_create_tool();
85555
86537
  init_path_security();
85556
- import * as fs80 from "fs";
85557
- import * as path96 from "path";
86538
+ import * as fs81 from "fs";
86539
+ import * as path97 from "path";
85558
86540
  var MAX_TEXT_LENGTH = 200;
85559
86541
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
85560
86542
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -85620,9 +86602,9 @@ function validatePathsInput(paths, cwd) {
85620
86602
  return { error: "paths contains path traversal", resolvedPath: null };
85621
86603
  }
85622
86604
  try {
85623
- const resolvedPath = path96.resolve(paths);
85624
- const normalizedCwd = path96.resolve(cwd);
85625
- const normalizedResolved = path96.resolve(resolvedPath);
86605
+ const resolvedPath = path97.resolve(paths);
86606
+ const normalizedCwd = path97.resolve(cwd);
86607
+ const normalizedResolved = path97.resolve(resolvedPath);
85626
86608
  if (!normalizedResolved.startsWith(normalizedCwd)) {
85627
86609
  return {
85628
86610
  error: "paths must be within the current working directory",
@@ -85638,13 +86620,13 @@ function validatePathsInput(paths, cwd) {
85638
86620
  }
85639
86621
  }
85640
86622
  function isSupportedExtension(filePath) {
85641
- const ext = path96.extname(filePath).toLowerCase();
86623
+ const ext = path97.extname(filePath).toLowerCase();
85642
86624
  return SUPPORTED_EXTENSIONS4.has(ext);
85643
86625
  }
85644
86626
  function findSourceFiles4(dir, files = []) {
85645
86627
  let entries;
85646
86628
  try {
85647
- entries = fs80.readdirSync(dir);
86629
+ entries = fs81.readdirSync(dir);
85648
86630
  } catch {
85649
86631
  return files;
85650
86632
  }
@@ -85653,10 +86635,10 @@ function findSourceFiles4(dir, files = []) {
85653
86635
  if (SKIP_DIRECTORIES5.has(entry)) {
85654
86636
  continue;
85655
86637
  }
85656
- const fullPath = path96.join(dir, entry);
86638
+ const fullPath = path97.join(dir, entry);
85657
86639
  let stat4;
85658
86640
  try {
85659
- stat4 = fs80.statSync(fullPath);
86641
+ stat4 = fs81.statSync(fullPath);
85660
86642
  } catch {
85661
86643
  continue;
85662
86644
  }
@@ -85749,7 +86731,7 @@ var todo_extract = createSwarmTool({
85749
86731
  return JSON.stringify(errorResult, null, 2);
85750
86732
  }
85751
86733
  const scanPath = resolvedPath;
85752
- if (!fs80.existsSync(scanPath)) {
86734
+ if (!fs81.existsSync(scanPath)) {
85753
86735
  const errorResult = {
85754
86736
  error: `path not found: ${pathsInput}`,
85755
86737
  total: 0,
@@ -85759,13 +86741,13 @@ var todo_extract = createSwarmTool({
85759
86741
  return JSON.stringify(errorResult, null, 2);
85760
86742
  }
85761
86743
  const filesToScan = [];
85762
- const stat4 = fs80.statSync(scanPath);
86744
+ const stat4 = fs81.statSync(scanPath);
85763
86745
  if (stat4.isFile()) {
85764
86746
  if (isSupportedExtension(scanPath)) {
85765
86747
  filesToScan.push(scanPath);
85766
86748
  } else {
85767
86749
  const errorResult = {
85768
- error: `unsupported file extension: ${path96.extname(scanPath)}`,
86750
+ error: `unsupported file extension: ${path97.extname(scanPath)}`,
85769
86751
  total: 0,
85770
86752
  byPriority: { high: 0, medium: 0, low: 0 },
85771
86753
  entries: []
@@ -85778,11 +86760,11 @@ var todo_extract = createSwarmTool({
85778
86760
  const allEntries = [];
85779
86761
  for (const filePath of filesToScan) {
85780
86762
  try {
85781
- const fileStat = fs80.statSync(filePath);
86763
+ const fileStat = fs81.statSync(filePath);
85782
86764
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
85783
86765
  continue;
85784
86766
  }
85785
- const content = fs80.readFileSync(filePath, "utf-8");
86767
+ const content = fs81.readFileSync(filePath, "utf-8");
85786
86768
  const entries = parseTodoComments(content, filePath, tagsSet);
85787
86769
  allEntries.push(...entries);
85788
86770
  } catch {}
@@ -85812,18 +86794,18 @@ init_tool();
85812
86794
  init_loader();
85813
86795
  init_schema();
85814
86796
  init_gate_evidence();
85815
- import * as fs82 from "fs";
85816
- import * as path98 from "path";
86797
+ import * as fs83 from "fs";
86798
+ import * as path99 from "path";
85817
86799
 
85818
86800
  // src/hooks/diff-scope.ts
85819
- import * as fs81 from "fs";
85820
- import * as path97 from "path";
86801
+ import * as fs82 from "fs";
86802
+ import * as path98 from "path";
85821
86803
  function getDeclaredScope(taskId, directory) {
85822
86804
  try {
85823
- const planPath = path97.join(directory, ".swarm", "plan.json");
85824
- if (!fs81.existsSync(planPath))
86805
+ const planPath = path98.join(directory, ".swarm", "plan.json");
86806
+ if (!fs82.existsSync(planPath))
85825
86807
  return null;
85826
- const raw = fs81.readFileSync(planPath, "utf-8");
86808
+ const raw = fs82.readFileSync(planPath, "utf-8");
85827
86809
  const plan = JSON.parse(raw);
85828
86810
  for (const phase of plan.phases ?? []) {
85829
86811
  for (const task of phase.tasks ?? []) {
@@ -85939,7 +86921,7 @@ var TIER_3_PATTERNS = [
85939
86921
  ];
85940
86922
  function matchesTier3Pattern(files) {
85941
86923
  for (const file3 of files) {
85942
- const fileName = path98.basename(file3);
86924
+ const fileName = path99.basename(file3);
85943
86925
  for (const pattern of TIER_3_PATTERNS) {
85944
86926
  if (pattern.test(fileName)) {
85945
86927
  return true;
@@ -85953,8 +86935,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
85953
86935
  if (hasActiveTurboMode()) {
85954
86936
  const resolvedDir2 = workingDirectory;
85955
86937
  try {
85956
- const planPath = path98.join(resolvedDir2, ".swarm", "plan.json");
85957
- const planRaw = fs82.readFileSync(planPath, "utf-8");
86938
+ const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
86939
+ const planRaw = fs83.readFileSync(planPath, "utf-8");
85958
86940
  const plan = JSON.parse(planRaw);
85959
86941
  for (const planPhase of plan.phases ?? []) {
85960
86942
  for (const task of planPhase.tasks ?? []) {
@@ -86023,8 +87005,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
86023
87005
  }
86024
87006
  try {
86025
87007
  const resolvedDir2 = workingDirectory;
86026
- const planPath = path98.join(resolvedDir2, ".swarm", "plan.json");
86027
- const planRaw = fs82.readFileSync(planPath, "utf-8");
87008
+ const planPath = path99.join(resolvedDir2, ".swarm", "plan.json");
87009
+ const planRaw = fs83.readFileSync(planPath, "utf-8");
86028
87010
  const plan = JSON.parse(planRaw);
86029
87011
  for (const planPhase of plan.phases ?? []) {
86030
87012
  for (const task of planPhase.tasks ?? []) {
@@ -86248,8 +87230,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
86248
87230
  };
86249
87231
  }
86250
87232
  }
86251
- normalizedDir = path98.normalize(args2.working_directory);
86252
- const pathParts = normalizedDir.split(path98.sep);
87233
+ normalizedDir = path99.normalize(args2.working_directory);
87234
+ const pathParts = normalizedDir.split(path99.sep);
86253
87235
  if (pathParts.includes("..")) {
86254
87236
  return {
86255
87237
  success: false,
@@ -86259,11 +87241,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
86259
87241
  ]
86260
87242
  };
86261
87243
  }
86262
- const resolvedDir = path98.resolve(normalizedDir);
87244
+ const resolvedDir = path99.resolve(normalizedDir);
86263
87245
  try {
86264
- const realPath = fs82.realpathSync(resolvedDir);
86265
- const planPath = path98.join(realPath, ".swarm", "plan.json");
86266
- if (!fs82.existsSync(planPath)) {
87246
+ const realPath = fs83.realpathSync(resolvedDir);
87247
+ const planPath = path99.join(realPath, ".swarm", "plan.json");
87248
+ if (!fs83.existsSync(planPath)) {
86267
87249
  return {
86268
87250
  success: false,
86269
87251
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -86294,22 +87276,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
86294
87276
  }
86295
87277
  if (args2.status === "in_progress") {
86296
87278
  try {
86297
- const evidencePath = path98.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
86298
- fs82.mkdirSync(path98.dirname(evidencePath), { recursive: true });
86299
- const fd = fs82.openSync(evidencePath, "wx");
87279
+ const evidencePath = path99.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
87280
+ fs83.mkdirSync(path99.dirname(evidencePath), { recursive: true });
87281
+ const fd = fs83.openSync(evidencePath, "wx");
86300
87282
  let writeOk = false;
86301
87283
  try {
86302
- fs82.writeSync(fd, JSON.stringify({
87284
+ fs83.writeSync(fd, JSON.stringify({
86303
87285
  taskId: args2.task_id,
86304
87286
  required_gates: ["reviewer", "test_engineer"],
86305
87287
  gates: {}
86306
87288
  }, null, 2));
86307
87289
  writeOk = true;
86308
87290
  } finally {
86309
- fs82.closeSync(fd);
87291
+ fs83.closeSync(fd);
86310
87292
  if (!writeOk) {
86311
87293
  try {
86312
- fs82.unlinkSync(evidencePath);
87294
+ fs83.unlinkSync(evidencePath);
86313
87295
  } catch {}
86314
87296
  }
86315
87297
  }
@@ -86319,8 +87301,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
86319
87301
  recoverTaskStateFromDelegations(args2.task_id);
86320
87302
  let phaseRequiresReviewer = true;
86321
87303
  try {
86322
- const planPath = path98.join(directory, ".swarm", "plan.json");
86323
- const planRaw = fs82.readFileSync(planPath, "utf-8");
87304
+ const planPath = path99.join(directory, ".swarm", "plan.json");
87305
+ const planRaw = fs83.readFileSync(planPath, "utf-8");
86324
87306
  const plan = JSON.parse(planRaw);
86325
87307
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
86326
87308
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -86422,6 +87404,216 @@ var update_task_status = createSwarmTool({
86422
87404
  return JSON.stringify(await executeUpdateTaskStatus(args2, _directory), null, 2);
86423
87405
  }
86424
87406
  });
87407
+ // src/tools/web-search.ts
87408
+ init_dist();
87409
+ init_zod();
87410
+ init_loader();
87411
+
87412
+ // src/council/web-search-provider.ts
87413
+ class WebSearchError extends Error {
87414
+ cause;
87415
+ constructor(message, cause) {
87416
+ super(message);
87417
+ this.cause = cause;
87418
+ this.name = "WebSearchError";
87419
+ }
87420
+ }
87421
+
87422
+ class WebSearchConfigError extends Error {
87423
+ constructor(message) {
87424
+ super(message);
87425
+ this.name = "WebSearchConfigError";
87426
+ }
87427
+ }
87428
+
87429
+ class TavilyProvider {
87430
+ apiKey;
87431
+ constructor(apiKey) {
87432
+ this.apiKey = apiKey;
87433
+ }
87434
+ async search(query, maxResults) {
87435
+ let response;
87436
+ try {
87437
+ response = await fetch("https://api.tavily.com/search", {
87438
+ method: "POST",
87439
+ headers: { "Content-Type": "application/json" },
87440
+ body: JSON.stringify({
87441
+ api_key: this.apiKey,
87442
+ query,
87443
+ max_results: maxResults,
87444
+ search_depth: "advanced"
87445
+ })
87446
+ });
87447
+ } catch (err3) {
87448
+ throw new WebSearchError(`Tavily network error for query "${query}"`, err3);
87449
+ }
87450
+ if (!response.ok) {
87451
+ throw new WebSearchError(`Tavily HTTP ${response.status} for query "${query}"`);
87452
+ }
87453
+ let body2;
87454
+ try {
87455
+ body2 = await response.json();
87456
+ } catch (err3) {
87457
+ throw new WebSearchError("Tavily returned non-JSON response", err3);
87458
+ }
87459
+ const results = body2?.results;
87460
+ if (!Array.isArray(results)) {
87461
+ return [];
87462
+ }
87463
+ return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.content === "string").map((r) => ({
87464
+ title: r.title,
87465
+ url: r.url,
87466
+ snippet: r.content,
87467
+ query
87468
+ }));
87469
+ }
87470
+ }
87471
+
87472
+ class BraveProvider {
87473
+ apiKey;
87474
+ constructor(apiKey) {
87475
+ this.apiKey = apiKey;
87476
+ }
87477
+ async search(query, maxResults) {
87478
+ const url3 = new URL("https://api.search.brave.com/res/v1/web/search");
87479
+ url3.searchParams.set("q", query);
87480
+ url3.searchParams.set("count", String(maxResults));
87481
+ let response;
87482
+ try {
87483
+ response = await fetch(url3.toString(), {
87484
+ method: "GET",
87485
+ headers: {
87486
+ "X-Subscription-Token": this.apiKey,
87487
+ Accept: "application/json"
87488
+ }
87489
+ });
87490
+ } catch (err3) {
87491
+ throw new WebSearchError(`Brave network error for query "${query}"`, err3);
87492
+ }
87493
+ if (!response.ok) {
87494
+ throw new WebSearchError(`Brave HTTP ${response.status} for query "${query}"`);
87495
+ }
87496
+ let body2;
87497
+ try {
87498
+ body2 = await response.json();
87499
+ } catch (err3) {
87500
+ throw new WebSearchError("Brave returned non-JSON response", err3);
87501
+ }
87502
+ const results = body2?.web?.results;
87503
+ if (!Array.isArray(results)) {
87504
+ return [];
87505
+ }
87506
+ return results.filter((r) => typeof r?.title === "string" && typeof r?.url === "string" && typeof r?.description === "string").map((r) => ({
87507
+ title: r.title,
87508
+ url: r.url,
87509
+ snippet: r.description,
87510
+ query
87511
+ }));
87512
+ }
87513
+ }
87514
+ function resolveApiKey(provider, configKey) {
87515
+ if (configKey && configKey.length > 0) {
87516
+ return configKey;
87517
+ }
87518
+ const envName = provider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
87519
+ const fromEnv = process.env[envName];
87520
+ return fromEnv && fromEnv.length > 0 ? fromEnv : undefined;
87521
+ }
87522
+ function createWebSearchProvider(config3) {
87523
+ const apiKey = resolveApiKey(config3.searchProvider, config3.searchApiKey);
87524
+ if (!apiKey) {
87525
+ const envName = config3.searchProvider === "tavily" ? "TAVILY_API_KEY" : "BRAVE_SEARCH_API_KEY";
87526
+ throw new WebSearchConfigError(`No API key for search provider "${config3.searchProvider}". Set ` + `council.general.searchApiKey in opencode-swarm.json or export ${envName}.`);
87527
+ }
87528
+ switch (config3.searchProvider) {
87529
+ case "tavily":
87530
+ return new TavilyProvider(apiKey);
87531
+ case "brave":
87532
+ return new BraveProvider(apiKey);
87533
+ }
87534
+ }
87535
+
87536
+ // src/tools/web-search.ts
87537
+ init_create_tool();
87538
+ init_resolve_working_directory();
87539
+ var MAX_RESULTS_HARD_CAP = 10;
87540
+ var ArgsSchema4 = exports_external.object({
87541
+ query: exports_external.string().min(1).max(500),
87542
+ max_results: exports_external.number().int().min(1).max(20).optional(),
87543
+ working_directory: exports_external.string().optional()
87544
+ });
87545
+ var web_search = createSwarmTool({
87546
+ description: "External web search for council member agents. Returns titled results with snippets and URLs. " + "Restricted to council_member agents via AGENT_TOOL_MAP. Requires council.general.enabled and a " + "configured search API key (Tavily or Brave). max_results is capped at 10 with default from council.general.maxSourcesPerMember.",
87547
+ args: {
87548
+ query: tool.schema.string().min(1).max(500).describe("Search query string (1\u2013500 characters)."),
87549
+ max_results: tool.schema.number().int().min(1).max(20).optional().describe(`Number of results to request (1\u201320). Hard-capped at ${MAX_RESULTS_HARD_CAP}. Defaults to council.general.maxSourcesPerMember.`),
87550
+ working_directory: tool.schema.string().optional().describe("Project root for config resolution. Optional.")
87551
+ },
87552
+ execute: async (args2, directory) => {
87553
+ const parsed = ArgsSchema4.safeParse(args2);
87554
+ if (!parsed.success) {
87555
+ const fail = {
87556
+ success: false,
87557
+ reason: "invalid_args",
87558
+ message: parsed.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`).join("; ")
87559
+ };
87560
+ return JSON.stringify(fail, null, 2);
87561
+ }
87562
+ const dirResult = resolveWorkingDirectory(parsed.data.working_directory, directory);
87563
+ if (!dirResult.success) {
87564
+ const fail = {
87565
+ success: false,
87566
+ reason: "invalid_working_directory",
87567
+ message: dirResult.message
87568
+ };
87569
+ return JSON.stringify(fail, null, 2);
87570
+ }
87571
+ const config3 = loadPluginConfig(dirResult.directory);
87572
+ const generalConfig = config3.council?.general;
87573
+ if (!generalConfig || generalConfig.enabled !== true) {
87574
+ const fail = {
87575
+ success: false,
87576
+ reason: "council_general_disabled",
87577
+ message: "web_search is disabled \u2014 set council.general.enabled: true in opencode-swarm.json."
87578
+ };
87579
+ return JSON.stringify(fail, null, 2);
87580
+ }
87581
+ const requested = parsed.data.max_results ?? generalConfig.maxSourcesPerMember;
87582
+ const maxResults = Math.min(requested, MAX_RESULTS_HARD_CAP);
87583
+ let provider;
87584
+ try {
87585
+ provider = createWebSearchProvider(generalConfig);
87586
+ } catch (err3) {
87587
+ const fail = {
87588
+ success: false,
87589
+ reason: err3 instanceof WebSearchConfigError ? "missing_api_key" : "provider_init_failed",
87590
+ message: err3 instanceof Error ? err3.message : String(err3)
87591
+ };
87592
+ return JSON.stringify(fail, null, 2);
87593
+ }
87594
+ try {
87595
+ const results = await provider.search(parsed.data.query, maxResults);
87596
+ const ok2 = {
87597
+ success: true,
87598
+ query: parsed.data.query,
87599
+ totalResults: results.length,
87600
+ results: results.map(({ title, url: url3, snippet }) => ({
87601
+ title,
87602
+ url: url3,
87603
+ snippet
87604
+ }))
87605
+ };
87606
+ return JSON.stringify(ok2, null, 2);
87607
+ } catch (err3) {
87608
+ const fail = {
87609
+ success: false,
87610
+ reason: err3 instanceof WebSearchError ? "search_failed" : "unknown",
87611
+ message: err3 instanceof Error ? err3.message : String(err3)
87612
+ };
87613
+ return JSON.stringify(fail, null, 2);
87614
+ }
87615
+ }
87616
+ });
86425
87617
  // src/tools/write-drift-evidence.ts
86426
87618
  init_tool();
86427
87619
  init_qa_gate_profile();
@@ -86429,8 +87621,8 @@ init_utils2();
86429
87621
  init_ledger();
86430
87622
  init_manager();
86431
87623
  init_create_tool();
86432
- import fs83 from "fs";
86433
- import path99 from "path";
87624
+ import fs84 from "fs";
87625
+ import path100 from "path";
86434
87626
  function derivePlanId5(plan) {
86435
87627
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
86436
87628
  }
@@ -86481,7 +87673,7 @@ async function executeWriteDriftEvidence(args2, directory) {
86481
87673
  entries: [evidenceEntry]
86482
87674
  };
86483
87675
  const filename = "drift-verifier.json";
86484
- const relativePath = path99.join("evidence", String(phase), filename);
87676
+ const relativePath = path100.join("evidence", String(phase), filename);
86485
87677
  let validatedPath;
86486
87678
  try {
86487
87679
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -86492,12 +87684,12 @@ async function executeWriteDriftEvidence(args2, directory) {
86492
87684
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
86493
87685
  }, null, 2);
86494
87686
  }
86495
- const evidenceDir = path99.dirname(validatedPath);
87687
+ const evidenceDir = path100.dirname(validatedPath);
86496
87688
  try {
86497
- await fs83.promises.mkdir(evidenceDir, { recursive: true });
86498
- const tempPath = path99.join(evidenceDir, `.${filename}.tmp`);
86499
- await fs83.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
86500
- await fs83.promises.rename(tempPath, validatedPath);
87689
+ await fs84.promises.mkdir(evidenceDir, { recursive: true });
87690
+ const tempPath = path100.join(evidenceDir, `.${filename}.tmp`);
87691
+ await fs84.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
87692
+ await fs84.promises.rename(tempPath, validatedPath);
86501
87693
  let snapshotInfo;
86502
87694
  let snapshotError;
86503
87695
  let qaProfileLocked;
@@ -86591,8 +87783,8 @@ var write_drift_evidence = createSwarmTool({
86591
87783
  init_tool();
86592
87784
  init_utils2();
86593
87785
  init_create_tool();
86594
- import fs84 from "fs";
86595
- import path100 from "path";
87786
+ import fs85 from "fs";
87787
+ import path101 from "path";
86596
87788
  function normalizeVerdict2(verdict) {
86597
87789
  switch (verdict) {
86598
87790
  case "APPROVED":
@@ -86640,7 +87832,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
86640
87832
  entries: [evidenceEntry]
86641
87833
  };
86642
87834
  const filename = "hallucination-guard.json";
86643
- const relativePath = path100.join("evidence", String(phase), filename);
87835
+ const relativePath = path101.join("evidence", String(phase), filename);
86644
87836
  let validatedPath;
86645
87837
  try {
86646
87838
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -86651,12 +87843,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
86651
87843
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
86652
87844
  }, null, 2);
86653
87845
  }
86654
- const evidenceDir = path100.dirname(validatedPath);
87846
+ const evidenceDir = path101.dirname(validatedPath);
86655
87847
  try {
86656
- await fs84.promises.mkdir(evidenceDir, { recursive: true });
86657
- const tempPath = path100.join(evidenceDir, `.${filename}.tmp`);
86658
- await fs84.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
86659
- await fs84.promises.rename(tempPath, validatedPath);
87848
+ await fs85.promises.mkdir(evidenceDir, { recursive: true });
87849
+ const tempPath = path101.join(evidenceDir, `.${filename}.tmp`);
87850
+ await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
87851
+ await fs85.promises.rename(tempPath, validatedPath);
86660
87852
  return JSON.stringify({
86661
87853
  success: true,
86662
87854
  phase,
@@ -86702,8 +87894,8 @@ var write_hallucination_evidence = createSwarmTool({
86702
87894
  init_tool();
86703
87895
  init_utils2();
86704
87896
  init_create_tool();
86705
- import fs85 from "fs";
86706
- import path101 from "path";
87897
+ import fs86 from "fs";
87898
+ import path102 from "path";
86707
87899
  function normalizeVerdict3(verdict) {
86708
87900
  switch (verdict) {
86709
87901
  case "PASS":
@@ -86777,7 +87969,7 @@ async function executeWriteMutationEvidence(args2, directory) {
86777
87969
  entries: [evidenceEntry]
86778
87970
  };
86779
87971
  const filename = "mutation-gate.json";
86780
- const relativePath = path101.join("evidence", String(phase), filename);
87972
+ const relativePath = path102.join("evidence", String(phase), filename);
86781
87973
  let validatedPath;
86782
87974
  try {
86783
87975
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -86788,12 +87980,12 @@ async function executeWriteMutationEvidence(args2, directory) {
86788
87980
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
86789
87981
  }, null, 2);
86790
87982
  }
86791
- const evidenceDir = path101.dirname(validatedPath);
87983
+ const evidenceDir = path102.dirname(validatedPath);
86792
87984
  try {
86793
- await fs85.promises.mkdir(evidenceDir, { recursive: true });
86794
- const tempPath = path101.join(evidenceDir, `.${filename}.tmp`);
86795
- await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
86796
- await fs85.promises.rename(tempPath, validatedPath);
87985
+ await fs86.promises.mkdir(evidenceDir, { recursive: true });
87986
+ const tempPath = path102.join(evidenceDir, `.${filename}.tmp`);
87987
+ await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
87988
+ await fs86.promises.rename(tempPath, validatedPath);
86797
87989
  return JSON.stringify({
86798
87990
  success: true,
86799
87991
  phase,
@@ -87011,7 +88203,7 @@ var OpenCodeSwarm = async (ctx) => {
87011
88203
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
87012
88204
  preflightTriggerManager = new PTM(automationConfig);
87013
88205
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
87014
- const swarmDir = path102.resolve(ctx.directory, ".swarm");
88206
+ const swarmDir = path103.resolve(ctx.directory, ".swarm");
87015
88207
  statusArtifact = new ASA(swarmDir);
87016
88208
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
87017
88209
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -87117,6 +88309,7 @@ var OpenCodeSwarm = async (ctx) => {
87117
88309
  completion_verify,
87118
88310
  complexity_hotspots,
87119
88311
  convene_council,
88312
+ convene_general_council,
87120
88313
  curator_analyze,
87121
88314
  declare_council_criteria,
87122
88315
  knowledge_add,
@@ -87163,6 +88356,7 @@ var OpenCodeSwarm = async (ctx) => {
87163
88356
  build_check,
87164
88357
  suggest_patch: suggestPatch,
87165
88358
  update_task_status,
88359
+ web_search,
87166
88360
  write_retro,
87167
88361
  write_drift_evidence,
87168
88362
  write_hallucination_evidence,